From 8ecb8409ebb7d9f5a7f9f1c4b45561dcb8f12fe0 Mon Sep 17 00:00:00 2001 From: Maxim Dolgolyov Date: Fri, 29 May 2026 08:42:33 +0300 Subject: [PATCH] =?UTF-8?q?feat(alg9=20ch3=20wave2):=20=C2=A712=20=C2=AB?= =?UTF-8?q?=D0=9E=D0=BA=D1=80=D1=83=D0=B6=D0=BD=D0=BE=D1=81=D1=82=D1=8C?= =?UTF-8?q?=C2=BB=20+=20=C2=A713=20=C2=AB=D0=9C=D0=B5=D1=82=D0=BE=D0=B4=20?= =?UTF-8?q?=D0=B8=D0=BD=D1=82=D0=B5=D1=80=D0=B2=D0=B0=D0=BB=D0=BE=D0=B2?= =?UTF-8?q?=C2=BB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit §12 «Длина отрезка. Уравнение окружности»: - 3 теорет. карточки (формула расстояния, уравнение окружности, особые случаи). - IV1 «Окружность-конструктор»: 3 слайдера a/b/R, SVG-окружность + центр + радиус-линия + динамическое уравнение. - IV2 «Калькулятор расстояния»: 4 input + пошаговый разбор + мини-SVG с отрезком AB. - IV3 «Точка на окружности?»: 6 квикфайр-задач (да/нет). - IV4 «Тренажёр радиуса/центра/длины»: 6 задач на ввод числа. §13 «Дробно-рациональные неравенства. Метод интервалов»: - 3 теорет. карточки (метод интервалов, правило знаков, пример). - IV1 «Числовая прямая знаков»: 5 неравенств, SVG-прямая с точками (закрашенными/выколотыми) и цветными знаками интервалов. - IV2 «Закрашена или выколота?»: DnD-сортер 6 карточек по 2 категориям. - IV3 «Сколько целых решений в [-5;5]»: 6 задач. - IV4 «Сумма концов интервалов»: 6 задач на ввод числа. Добавлены setupSorter() + DnD CSS (.dnd-pool/.dnd-chip/.drop-box/.drop-items). --- frontend/textbooks/algebra_9_ch3.html | 649 ++++++++++++++++++++++++-- 1 file changed, 621 insertions(+), 28 deletions(-) diff --git a/frontend/textbooks/algebra_9_ch3.html b/frontend/textbooks/algebra_9_ch3.html index a3855e8..c77bb51 100644 --- a/frontend/textbooks/algebra_9_ch3.html +++ b/frontend/textbooks/algebra_9_ch3.html @@ -179,6 +179,24 @@ a{color:inherit;text-decoration:none} .spoiler-body{padding:10px 14px;font-size:.92rem;line-height:1.6} .step-box{background:var(--card);border:1.5px solid var(--border);border-radius:10px;padding:12px 14px;margin-top:10px;font-size:.94rem;line-height:1.55} .step-box.show{border-color:var(--sec-acc,var(--pri));background:var(--sec-acc-soft,var(--pri-soft))} + +.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(--sec-acc,var(--pri));background:var(--sec-acc-soft,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(--sec-acc,var(--pri));box-shadow:var(--sh)} +.dnd-chip:active{cursor:grabbing} +.dnd-chip.armed{border-color:var(--sec-acc,var(--pri));background:var(--sec-acc-soft,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(--sec-acc-soft,var(--pri-soft));border-color:var(--sec-acc,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,var(--fail));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(--sec-acc,var(--pri));background:var(--sec-acc-soft,var(--pri-soft))} +.drop-box h5{font-family:'Unbounded',sans-serif;font-size:.78rem;color:var(--sec-acc-d,var(--pri2));margin-bottom:8px;text-transform:uppercase;letter-spacing:.05em} +.drop-box.over{border-color:var(--sec-acc,var(--pri));background:var(--sec-acc-soft,var(--pri-soft));border-style:solid;transform:scale(1.015)} +.drop-items{display:flex;flex-wrap:wrap;gap:6px;min-height:32px} @@ -461,6 +479,21 @@ function makeCard(kind, title, num, body){ return '
'+ICONS[kind]+'
'+(labels[kind]||'')+(title&&title!==labels[kind]?' \xb7 '+title:'')+'
'+(num?'
'+num+'
':'')+'
'+body+'
'; } +/* 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(); }}; +} + /* Координатная плоскость в SVG */ function axes2D(W, H, pad, xmin, xmax, ymin, ymax){ const ux = (W - 2*pad) / (xmax - xmin); @@ -1112,38 +1145,598 @@ function buildP11(){ } function buildP12(){ - const root = document.getElementById('p12-body'); - root.innerHTML = ` -
-
- ${ICONS.theory} - В разработке - § 12 -
-
-

Содержание параграфа «Уравнение окружности» будет добавлено в следующих обновлениях.

-

Раздел Phase 1.

-
-
` + secNav('p11', 'p13') + readButton('p12'); - renderMath(root); + const box = document.getElementById('p12-body'); + let html = ''; + + html += makeCard('theory', 'Длина отрезка (расстояние между точками)', '12.1', ` +

Для двух точек $A(x_1;\\ y_1)$ и $B(x_2;\\ y_2)$ длина отрезка $AB$ (= расстояние между точками) равна:

+

$|AB| = \\sqrt{(x_2 - x_1)^2 + (y_2 - y_1)^2}$

+

Это прямое следствие теоремы Пифагора: разности координат — катеты прямоугольного треугольника, а отрезок $AB$ — его гипотенуза.

+

Пример. $A(1;\\ 2),\\ B(4;\\ 6)$: + $|AB| = \\sqrt{(4-1)^2 + (6-2)^2} = \\sqrt{9 + 16} = \\sqrt{25} = 5$.

+
Почему именно квадраты?
+ Если построить из $A$ горизонтальный, а из $B$ вертикальный отрезок до их пересечения, получится прямоугольный треугольник с катетами $|x_2 - x_1|$ и $|y_2 - y_1|$. По Пифагору квадрат гипотенузы $|AB|^2 = (x_2-x_1)^2 + (y_2-y_1)^2$, откуда и формула. +
`); + + html += makeCard('rule', 'Уравнение окружности', '12.2', ` +

Окружность с центром $C(a;\\ b)$ и радиусом $R$ задаётся уравнением:

+

$(x - a)^2 + (y - b)^2 = R^2$

+

Геометрический смысл: все точки $(x;\\ y)$ плоскости, удовлетворяющие этому равенству, лежат на окружности, и наоборот. Это запись свойства «расстояние от $(x;y)$ до центра $C$ равно $R$»: + $\\sqrt{(x - a)^2 + (y - b)^2} = R$, после возведения в квадрат получаем уравнение.

+

Числа $a$ и $b$ — координаты центра, $R > 0$ — радиус.

`); + + html += makeCard('example', 'Особые случаи и примеры', '12.3', ` +

1. Центр в начале координат. При $a = 0,\\ b = 0$ уравнение упрощается: + $x^2 + y^2 = R^2$. Например, $x^2 + y^2 = 9$ — окружность с центром $(0;0)$ и радиусом $R = 3$.

+

2. Через центр и точку на окружности. Радиус — расстояние от центра до этой точки. + Пример. Центр $C(1;\\ 2)$, точка $M(4;\\ 6)$ лежит на окружности. Тогда + $R = \\sqrt{(4-1)^2 + (6-2)^2} = 5$, и уравнение: $(x - 1)^2 + (y - 2)^2 = 25$.

+

3. По заданным центру и радиусу. Уравнение окружности с центром $(2;\\ -1)$ и радиусом $R = 4$: + $(x - 2)^2 + (y + 1)^2 = 16$.

`); + + /* INTERACTIVE 1 — Окружность-конструктор */ + html += `
+
ИНТЕРАКТИВ 1
Окружность-конструктор
+
Двигай ползунки центра $a, b$ и радиуса $R$ — окружность перерисуется, а уравнение под графиком подстроится.
+
+ + + +
+
+ +
+
+
`; + + /* INTERACTIVE 2 — Калькулятор расстояния */ + html += `
+
ИНТЕРАКТИВ 2
Калькулятор расстояния $|AB|$
+
Введи целые координаты двух точек $A(x_1;y_1)$ и $B(x_2;y_2)$ — калькулятор покажет подробное вычисление и нарисует отрезок.
+
+ $A(\\ $ + + ; + + $\\ )$ + $B(\\ $ + + ; + + $\\ )$ + +
+
+
+ +
+
`; + + /* INTERACTIVE 3 — Принадлежит ли точка окружности? */ + html += `
+
ИНТЕРАКТИВ 3
Точка на окружности?
+
Для каждой пары «окружность + точка» определи: точка лежит на окружности или нет?
+
Задача: 1 / 6 · Очки: 0
+
+
+ + +
+ +
`; + + /* INTERACTIVE 4 — Найди радиус или координату */ + html += `
+
ИНТЕРАКТИВ 4
Тренажёр: радиус / центр / длина
+
Прочитай уравнение или условие и введи число (радиус, координату, длину).
+
Задача: 1 / 6 · Очки: 0
+
+
+ Ответ = + + + +
+ +
`; + + box.innerHTML = html + secNav('p11', 'p13') + readButton('p12'); + renderMath(box); + + /* ===== IV1 wiring — Окружность-конструктор ===== */ + (function(){ + const aIn = document.getElementById('p12-iv1-a'); + const bIn = document.getElementById('p12-iv1-b'); + const rIn = document.getElementById('p12-iv1-r'); + const aL = document.getElementById('p12-iv1-aL'); + const bL = document.getElementById('p12-iv1-bL'); + const rL = document.getElementById('p12-iv1-rL'); + const svg = document.getElementById('p12-iv1-svg'); + const eqEl = document.getElementById('p12-iv1-eq'); + let bumped = false; + function redraw(){ + const a = +aIn.value, b = +bIn.value, R = +rIn.value; + aL.textContent = a; bL.textContent = b; rL.textContent = R; + const ax = axes2D(400, 400, 30, -7, 7, -7, 7); + let g = ax.content; + const cx = ax.toX(a), cy = ax.toY(b), rPx = R * ax.ux; + g += ''; + const ex = ax.toX(a + R), ey = cy; + g += ''; + g += 'R = '+R+''; + g += ''; + g += 'C('+a+'; '+b+')'; + svg.innerHTML = g; + const aS = a >= 0 ? '- ' + a : '+ ' + (-a); + const bS = b >= 0 ? '- ' + b : '+ ' + (-b); + eqEl.innerHTML = '$(x \\, ' + aS + ')^2 + (y \\, ' + bS + ')^2 = ' + (R*R) + '$'; + renderMath(eqEl); + if (!bumped){ bumped = true; bumpProgress('p12', 15); addXp(10, 'p12-iv1'); } + } + [aIn, bIn, rIn].forEach(el => el.addEventListener('input', redraw)); + redraw(); + })(); + + /* ===== IV2 wiring — Калькулятор расстояния ===== */ + (function(){ + const x1In = document.getElementById('p12-iv2-x1'); + const y1In = document.getElementById('p12-iv2-y1'); + const x2In = document.getElementById('p12-iv2-x2'); + const y2In = document.getElementById('p12-iv2-y2'); + const out = document.getElementById('p12-iv2-out'); + const svg = document.getElementById('p12-iv2-svg'); + let bumped = false; + function compute(){ + const x1 = +x1In.value, y1 = +y1In.value, x2 = +x2In.value, y2 = +y2In.value; + const dx = x2 - x1, dy = y2 - y1; + const sq = dx*dx + dy*dy; + const d = Math.sqrt(sq); + const dStr = Number.isInteger(d) ? String(d) : d.toFixed(2); + let h = '
$|AB| = \\sqrt{(' + x2 + ' - ' + x1 + ')^2 + (' + y2 + ' - ' + y1 + ')^2}$
'; + h += '
$= \\sqrt{(' + dx + ')^2 + (' + dy + ')^2} = \\sqrt{' + (dx*dx) + ' + ' + (dy*dy) + '} = \\sqrt{' + sq + '}$
'; + h += '
$|AB| = ' + dStr + '$
'; + out.innerHTML = h; + renderMath(out); + const lo = Math.min(x1, y1, x2, y2, 0) - 1; + const hi = Math.max(x1, y1, x2, y2, 0) + 1; + const ax = axes2D(360, 280, 26, Math.floor(lo), Math.ceil(hi), Math.floor(lo), Math.ceil(hi)); + let g = ax.content; + const ax1 = ax.toX(x1), ay1 = ax.toY(y1), ax2 = ax.toX(x2), ay2 = ax.toY(y2); + g += ''; + g += ''; + g += 'A('+x1+';'+y1+')'; + g += ''; + g += 'B('+x2+';'+y2+')'; + svg.innerHTML = g; + if (!bumped){ bumped = true; bumpProgress('p12', 15); addXp(10, 'p12-iv2'); } + } + document.getElementById('p12-iv2-go').addEventListener('click', compute); + compute(); + })(); + + /* ===== IV3 wiring — Точка на окружности? ===== */ + (function(){ + const items = [ + { q: 'Окружность $x^2 + y^2 = 25$, точка $(3;\\ 4)$.', ans: true, hint: '$3^2 + 4^2 = 9 + 16 = 25$ — равно $R^2$. Точка лежит.' }, + { q: 'Окружность $x^2 + y^2 = 25$, точка $(0;\\ 5)$.', ans: true, hint: '$0 + 25 = 25$. Точка лежит.' }, + { q: 'Окружность $x^2 + y^2 = 25$, точка $(2;\\ 3)$.', ans: false, hint: '$4 + 9 = 13 \\ne 25$. Точка не лежит.' }, + { q: 'Окружность $(x - 1)^2 + y^2 = 4$, точка $(3;\\ 0)$.', ans: true, hint: '$(3-1)^2 + 0 = 4$. Точка лежит.' }, + { q: 'Окружность $(x - 2)^2 + (y + 1)^2 = 9$, точка $(2;\\ 2)$.', ans: true, hint: '$0 + (2+1)^2 = 9$. Точка лежит.' }, + { q: 'Окружность $x^2 + (y - 3)^2 = 16$, точка $(4;\\ 3)$.', ans: true, hint: '$16 + 0 = 16$. Точка лежит.' } + ]; + let i = 0, sc = 0, bumped = false; + const idxEl = document.getElementById('p12-iv3-idx'); + const scEl = document.getElementById('p12-iv3-sc'); + const qEl = document.getElementById('p12-iv3-q'); + const fb = document.getElementById('p12-iv3-fb'); + const okBtn = document.getElementById('p12-iv3-ok'); + const badBtn = document.getElementById('p12-iv3-bad'); + function render(){ + idxEl.textContent = Math.min(i + 1, items.length); + scEl.textContent = sc; + if (i >= items.length){ + qEl.innerHTML = 'Готово! Результат: ' + sc + ' / ' + items.length; + okBtn.disabled = true; badBtn.disabled = true; + okBtn.style.opacity = .5; badBtn.style.opacity = .5; + if (!bumped){ bumped = true; bumpProgress('p12', 25); addXp(15, 'p12-iv3'); } + return; + } + qEl.innerHTML = items[i].q; + fb.style.display = 'none'; + renderMath(qEl); + } + function answer(v){ + if (i >= items.length) return; + const it = items[i]; + const ok = (v === it.ans); + if (ok) sc++; + feedback(fb, ok, (ok ? '✓ Верно. ' : '✗ Неверно. ') + it.hint); + i++; + setTimeout(render, 1100); + } + okBtn.addEventListener('click', ()=>answer(true)); + badBtn.addEventListener('click', ()=>answer(false)); + render(); + })(); + + /* ===== IV4 wiring — Тренажёр: радиус / центр / длина ===== */ + (function(){ + const items = [ + { q: 'Уравнение окружности: $(x - 3)^2 + (y - 5)^2 = 49$. Найди радиус $R$.', ans: 7, hint: '$R^2 = 49 \\Rightarrow R = 7$.' }, + { q: 'Уравнение окружности: $(x + 2)^2 + (y - 4)^2 = 16$. Найди координату центра $a$.', ans: -2, hint: 'Сравни с $(x - a)^2$: $x + 2 = x - (-2) \\Rightarrow a = -2$.' }, + { q: 'Уравнение окружности: $x^2 + (y + 1)^2 = 100$. Найди радиус $R$.', ans: 10, hint: '$R^2 = 100 \\Rightarrow R = 10$.' }, + { q: 'Найди длину отрезка $AB$, где $A(0;\\ 0),\\ B(6;\\ 8)$.', ans: 10, hint: '$|AB| = \\sqrt{36 + 64} = \\sqrt{100} = 10$.' }, + { q: 'Найди длину отрезка $AB$, где $A(-2;\\ 1),\\ B(4;\\ 1)$.', ans: 6, hint: 'Точки на одной горизонтали: $|AB| = |4 - (-2)| = 6$.' }, + { q: 'Окружность $(x - 3)^2 + y^2 = 25$ пересекает ось $Ox$ в двух точках. Найди меньшую координату $x$ точки пересечения.', ans: -2, hint: 'При $y = 0$: $(x-3)^2 = 25 \\Rightarrow x - 3 = \\pm 5 \\Rightarrow x = 8$ или $x = -2$. Меньшая = $-2$.' } + ]; + let i = 0, sc = 0, bumped = false; + const idxEl = document.getElementById('p12-iv4-idx'); + const scEl = document.getElementById('p12-iv4-sc'); + const qEl = document.getElementById('p12-iv4-q'); + const inp = document.getElementById('p12-iv4-inp'); + const fb = document.getElementById('p12-iv4-fb'); + const goBtn = document.getElementById('p12-iv4-go'); + const skipBtn = document.getElementById('p12-iv4-skip'); + function render(){ + idxEl.textContent = Math.min(i + 1, items.length); + scEl.textContent = sc; + if (i >= items.length){ + qEl.innerHTML = 'Готово! Результат: ' + sc + ' / ' + items.length; + inp.disabled = true; goBtn.disabled = true; skipBtn.disabled = true; + inp.style.opacity = .5; goBtn.style.opacity = .5; skipBtn.style.opacity = .5; + if (!bumped){ bumped = true; bumpProgress('p12', 25); addXp(15, 'p12-iv4'); achievement('p12_done'); } + return; + } + qEl.innerHTML = items[i].q; + inp.value = ''; + fb.style.display = 'none'; + renderMath(qEl); + } + function check(){ + if (i >= items.length) return; + const v = parseFloat(inp.value); + const it = items[i]; + if (isNaN(v)){ feedback(fb, false, 'Введи число.'); return; } + const ok = Math.abs(v - it.ans) < 1e-6; + if (ok) sc++; + feedback(fb, ok, (ok ? '✓ Верно! ' : '✗ Неверно. Ответ: $' + it.ans + '$. ') + it.hint); + i++; + setTimeout(render, 1200); + } + function skip(){ + if (i >= items.length) return; + const it = items[i]; + feedback(fb, false, 'Пропущено. Ответ: $' + it.ans + '$. ' + it.hint); + i++; + setTimeout(render, 1200); + } + goBtn.addEventListener('click', check); + skipBtn.addEventListener('click', skip); + inp.addEventListener('keydown', e => { if (e.key === 'Enter') check(); }); + render(); + })(); + wireReadBtn('p12'); } function buildP13(){ - const root = document.getElementById('p13-body'); - root.innerHTML = ` -
-
- ${ICONS.theory} - В разработке - § 13 -
-
-

Содержание параграфа «Метод интервалов» будет добавлено в следующих обновлениях.

-

Раздел Phase 1.

-
-
` + secNav('p12', 'final3') + readButton('p13'); - renderMath(root); + const box = document.getElementById('p13-body'); + let html = ''; + + html += makeCard('theory', 'Метод интервалов', '13.1', ` +

Для неравенств вида $\\dfrac{P(x)}{Q(x)} \\, [><] \\, 0$ применяют метод интервалов:

+
    +
  1. Найти все корни уравнений $P(x) = 0$ (нули числителя) и $Q(x) = 0$ (нули знаменателя).
  2. +
  3. Отметить корни на числовой прямой. Корни знаменателя — всегда выколоты (в них дробь не определена). Корни числителя — закрашены при нестрогом неравенстве ($\\le,\\ \\ge$) и выколоты при строгом ($<,\\ >$).
  4. +
  5. На каждом интервале определить знак выражения (например, подставив пробное число).
  6. +
  7. Записать ответ — объединение интервалов с нужным знаком, учитывая закрашенные/выколотые точки.
  8. +
`); + + html += makeCard('rule', 'Правило знаков (чередование)', '13.2', ` +

Если все корни простые (степени 1), знаки на интервалах чередуются: $\\ldots\\,+\\,-\\,+\\,-\\,\\ldots$ Достаточно определить знак на одном интервале (обычно крайнем правом) — остальные восстановятся.

+

Если корень с чётной кратностью (например, $(x - 2)^2$) — при переходе через него знак не меняется.

+

На крайнем правом интервале знак удобно проверить, подставив очень большое $x$ — обычно знак совпадает со знаком старшего коэффициента в числителе $\\div$ знаменателя.

`); + + html += makeCard('example', 'Пример пошагово', '13.3', ` +

Решить: $\\dfrac{x - 1}{x + 2} \\le 0$.

+

1. Корни: $x = 1$ (числитель, закрашен — неравенство нестрогое) и $x = -2$ (знаменатель, всегда выколота).

+

2. На числовой прямой: $\\ldots \\;[-]\\; \\circ_{-2} \\;[+]\\; \\bullet_{1} \\;[-]\\; \\ldots$

+

3. Проверка знака при $x = 5$: $\\dfrac{4}{7} > 0$ — крайний правый интервал «$+$». Значит, чередование такое, как показано.

+

4. Нужен $\\le 0$ — берём «$-$»-интервалы. Слева от $-2$ — но $x = -2$ выколота. Справа от $-2$ — «$+$», пропускаем. Между $-2$ и $1$ — это «$+$»-интервал? Перепроверим: при $x = 0$: $\\dfrac{-1}{2} < 0$ — да, «$-$». Берём $(-2;\\ 1]$.

+

Ответ: $x \\in (-2;\\ 1]$.

`); + + /* INTERACTIVE 1 — Числовая прямая знаков */ + html += `
+
ИНТЕРАКТИВ 1
Числовая прямая знаков
+
Выбери неравенство ползунком — на числовой прямой увидишь корни (закрашенные/выколотые) и знаки на интервалах. Внизу — ответ.
+
+ +
+
+
+ +
+
+
`; + + /* INTERACTIVE 2 — Закрашена или выколота? (DnD) */ + html += `
+
ИНТЕРАКТИВ 2
Закрашена или выколота?
+
Перетащи каждую карточку «корень в неравенстве» в нужную колонку. Корни числителя при нестрогом неравенстве — закрашены ($\\bullet$); корни знаменателя и любые корни при строгом неравенстве — выколоты ($\\circ$).
+
+
+
Закрашена ●
+
Выколота ○
+
+
+ + +
+ +
`; + + /* INTERACTIVE 3 — Сколько целых решений? */ + html += `
+
ИНТЕРАКТИВ 3
Сколько целых $x \\in [-5;5]$ — решения?
+
Для каждого неравенства подсчитай, сколько целых $x$ из отрезка $[-5;\\ 5]$ ему удовлетворяют.
+
Задача: 1 / 6 · Очки: 0
+
+
+ Количество = + + + +
+ +
`; + + /* INTERACTIVE 4 — Тренажёр интервалов (сумма концов) */ + html += `
+
ИНТЕРАКТИВ 4
Сумма концов интервалов
+
Реши неравенство, запиши ответ в виде объединения интервалов, и введи сумму всех конечных концов (бесконечности не учитываются).
+
Задача: 1 / 6 · Очки: 0
+
+
+ Сумма = + + + +
+ +
`; + + box.innerHTML = html + secNav('p12', 'final3') + readButton('p13'); + renderMath(box); + + /* ===== IV1 wiring — Числовая прямая ===== */ + (function(){ + const tasks = [ + { + ineq: '$\\dfrac{x}{x - 3} > 0$', + points: [{x:0, full:false}, {x:3, full:false}], + signs: [{from:-8, to:0, s:'+'}, {from:0, to:3, s:'-'}, {from:3, to:8, s:'+'}], + answer: '$(-\\infty;\\ 0) \\cup (3;\\ +\\infty)$' + }, + { + ineq: '$\\dfrac{x - 2}{x + 1} \\le 0$', + points: [{x:-1, full:false}, {x:2, full:true}], + signs: [{from:-8, to:-1, s:'+'}, {from:-1, to:2, s:'-'}, {from:2, to:8, s:'+'}], + answer: '$(-1;\\ 2]$' + }, + { + ineq: '$(x - 1)(x - 3) > 0$', + points: [{x:1, full:false}, {x:3, full:false}], + signs: [{from:-8, to:1, s:'+'}, {from:1, to:3, s:'-'}, {from:3, to:8, s:'+'}], + answer: '$(-\\infty;\\ 1) \\cup (3;\\ +\\infty)$' + }, + { + ineq: '$\\dfrac{1}{x^2 - 4} < 0$', + points: [{x:-2, full:false}, {x:2, full:false}], + signs: [{from:-8, to:-2, s:'+'}, {from:-2, to:2, s:'-'}, {from:2, to:8, s:'+'}], + answer: '$(-2;\\ 2)$' + }, + { + ineq: '$\\dfrac{x + 2}{(x - 1)(x + 1)} \\ge 0$', + points: [{x:-2, full:true}, {x:-1, full:false}, {x:1, full:false}], + signs: [{from:-8, to:-2, s:'-'}, {from:-2, to:-1, s:'+'}, {from:-1, to:1, s:'-'}, {from:1, to:8, s:'+'}], + answer: '$[-2;\\ -1) \\cup (1;\\ +\\infty)$' + } + ]; + const sl = document.getElementById('p13-iv1-slider'); + const idxEl = document.getElementById('p13-iv1-idx'); + const ineqEl = document.getElementById('p13-iv1-ineq'); + const svg = document.getElementById('p13-iv1-svg'); + const out = document.getElementById('p13-iv1-out'); + let bumped = false; + + function draw(){ + const idx = (+sl.value) - 1; + const t = tasks[idx]; + idxEl.textContent = idx + 1; + ineqEl.innerHTML = t.ineq; + const W = 600, H = 120, padX = 30, y0 = 70; + const xmin = -8, xmax = 8; + const toX = v => padX + (v - xmin) * (W - 2*padX) / (xmax - xmin); + let g = ''; + g += ''; + g += ''; + for (let x = xmin + 1; x <= xmax - 1; x++){ + g += ''; + g += ''+x+''; + } + t.signs.forEach(s => { + const cx = (toX(s.from) + toX(s.to)) / 2; + const col = s.s === '+' ? '#10b981' : '#ef4444'; + g += ''+s.s+''; + }); + t.points.forEach(p => { + const px = toX(p.x); + if (p.full){ + g += ''; + } else { + g += ''; + } + g += ''+p.x+''; + }); + svg.innerHTML = g; + out.innerHTML = 'Ответ: $x \\in $ ' + t.answer; + renderMath(ineqEl); renderMath(out); + if (!bumped){ bumped = true; bumpProgress('p13', 15); addXp(10, 'p13-iv1'); } + } + sl.addEventListener('input', draw); + draw(); + })(); + + /* ===== IV2 wiring — DnD: закрашена/выколота ===== */ + (function(){ + const items = [ + { id:'a', html:'$x = 0$ в $\\dfrac{x}{x - 3} \\ge 0$', cat:'full' }, + { id:'b', html:'$x = 3$ в $\\dfrac{x}{x - 3} \\ge 0$', cat:'empty' }, + { id:'c', html:'$x = 1$ в $\\dfrac{x - 1}{x + 2} > 0$', cat:'empty' }, + { id:'d', html:'$x = -2$ в $\\dfrac{x - 1}{x + 2} > 0$', cat:'empty' }, + { id:'e', html:'$x = 5$ в $(x - 5)(x + 1) \\ge 0$', cat:'full' }, + { id:'f', html:'$x = -3$ в $(x + 3)^2 > 0$', cat:'empty' } + ]; + const sorter = setupSorter({ + poolId: 'p13-iv2-pool', + scopeSelector: '#p13-iv2', + cats: ['full', 'empty'], + items: items + }); + let bumped = false; + document.getElementById('p13-iv2-check').addEventListener('click', () => { + const fb = document.getElementById('p13-iv2-fb'); + const total = items.length; + let correct = 0, placed = 0; + items.forEach(it => { if (sorter.placed[it.id]){ placed++; if (sorter.placed[it.id] === it.cat) correct++; } }); + if (placed < total){ + feedback(fb, false, 'Размещены не все: ' + placed + ' / ' + total + '.'); + return; + } + const ok = (correct === total); + feedback(fb, ok, ok ? '✓ Все верно! ' + correct + ' / ' + total : '✗ Правильно: ' + correct + ' / ' + total); + if (ok && !bumped){ bumped = true; bumpProgress('p13', 15); addXp(10, 'p13-iv2'); } + }); + document.getElementById('p13-iv2-reset').addEventListener('click', () => { + sorter.reset(); + const fb = document.getElementById('p13-iv2-fb'); fb.style.display = 'none'; + }); + })(); + + /* ===== IV3 wiring — Сколько целых решений? ===== */ + (function(){ + const items = [ + { q: '$\\dfrac{x}{x - 2} > 0$', ans: 8, hint: 'Решение $(-\\infty;0) \\cup (2;+\\infty)$. Целые из $[-5;5]$: $\\{-5,-4,-3,-2,-1,3,4,5\\}$ — 8 шт.' }, + { q: '$\\dfrac{1}{x + 1} < 0$', ans: 4, hint: 'Решение $x < -1$. Целые из $[-5;5]$: $\\{-5,-4,-3,-2\\}$ — 4 шт.' }, + { q: '$\\dfrac{x - 2}{x + 3} \\le 0$', ans: 5, hint: 'Решение $(-3;\\ 2]$. Целые: $\\{-2,-1,0,1,2\\}$ — 5 шт.' }, + { q: '$(x - 1)(x + 1) > 0$', ans: 8, hint: 'Решение $|x| > 1$. Целые из $[-5;5]$: $\\{-5,-4,-3,-2,2,3,4,5\\}$ — 8 шт.' }, + { q: '$\\dfrac{x}{x^2 + 1} \\ge 0$', ans: 6, hint: 'Знаменатель $> 0$, значит $x \\ge 0$. Целые: $\\{0,1,2,3,4,5\\}$ — 6 шт.' }, + { q: '$\\dfrac{1}{x^2 - 4} > 0$', ans: 6, hint: 'Решение $|x| > 2$. Целые из $[-5;5]$: $\\{-5,-4,-3,3,4,5\\}$ — 6 шт.' } + ]; + let i = 0, sc = 0, bumped = false; + const idxEl = document.getElementById('p13-iv3-idx'); + const scEl = document.getElementById('p13-iv3-sc'); + const qEl = document.getElementById('p13-iv3-q'); + const inp = document.getElementById('p13-iv3-inp'); + const fb = document.getElementById('p13-iv3-fb'); + const goBtn = document.getElementById('p13-iv3-go'); + const skipBtn = document.getElementById('p13-iv3-skip'); + function render(){ + idxEl.textContent = Math.min(i + 1, items.length); + scEl.textContent = sc; + if (i >= items.length){ + qEl.innerHTML = 'Готово! Результат: ' + sc + ' / ' + items.length; + inp.disabled = true; goBtn.disabled = true; skipBtn.disabled = true; + inp.style.opacity = .5; goBtn.style.opacity = .5; skipBtn.style.opacity = .5; + if (!bumped){ bumped = true; bumpProgress('p13', 25); addXp(15, 'p13-iv3'); } + return; + } + qEl.innerHTML = items[i].q; + inp.value = ''; + fb.style.display = 'none'; + renderMath(qEl); + } + function check(){ + if (i >= items.length) return; + const v = parseFloat(inp.value); + const it = items[i]; + if (isNaN(v)){ feedback(fb, false, 'Введи число.'); return; } + const ok = Math.abs(v - it.ans) < 1e-6; + if (ok) sc++; + feedback(fb, ok, (ok ? '✓ Верно! ' : '✗ Неверно. Ответ: ' + it.ans + '. ') + it.hint); + i++; + setTimeout(render, 1300); + } + function skip(){ + if (i >= items.length) return; + const it = items[i]; + feedback(fb, false, 'Пропущено. Ответ: ' + it.ans + '. ' + it.hint); + i++; + setTimeout(render, 1300); + } + goBtn.addEventListener('click', check); + skipBtn.addEventListener('click', skip); + inp.addEventListener('keydown', e => { if (e.key === 'Enter') check(); }); + render(); + })(); + + /* ===== IV4 wiring — Сумма концов интервалов ===== */ + (function(){ + const items = [ + { q: '$\\dfrac{x}{x - 3} > 0$', ans: 3, hint: 'Ответ $(-\\infty;0) \\cup (3;+\\infty)$. Конечные концы: $0,\\ 3$. Сумма $= 3$.' }, + { q: '$\\dfrac{x - 2}{x + 1} \\le 0$', ans: 1, hint: 'Ответ $(-1;\\ 2]$. Концы $-1,\\ 2$. Сумма $= 1$.' }, + { q: '$(x - 1)(x - 3) > 0$', ans: 4, hint: 'Ответ $(-\\infty;1) \\cup (3;+\\infty)$. Концы $1,\\ 3$. Сумма $= 4$.' }, + { q: '$\\dfrac{x + 2}{x - 4} < 0$', ans: 2, hint: 'Ответ $(-2;\\ 4)$. Концы $-2,\\ 4$. Сумма $= 2$.' }, + { q: '$(x - 5)(x + 5) \\le 0$', ans: 0, hint: 'Ответ $[-5;\\ 5]$. Концы $-5,\\ 5$. Сумма $= 0$.' }, + { q: '$\\dfrac{x}{(x - 1)(x + 1)} \\ge 0$', ans: 0, hint: 'Корни $0$ (закрашен), $\\pm 1$ (выколоты). Знаки $-,+,-,+$. Ответ $(-1;\\ 0] \\cup (1;+\\infty)$. Концы $-1,\\ 0,\\ 1$. Сумма $= 0$.' } + ]; + let i = 0, sc = 0, bumped = false; + const idxEl = document.getElementById('p13-iv4-idx'); + const scEl = document.getElementById('p13-iv4-sc'); + const qEl = document.getElementById('p13-iv4-q'); + const inp = document.getElementById('p13-iv4-inp'); + const fb = document.getElementById('p13-iv4-fb'); + const goBtn = document.getElementById('p13-iv4-go'); + const skipBtn = document.getElementById('p13-iv4-skip'); + function render(){ + idxEl.textContent = Math.min(i + 1, items.length); + scEl.textContent = sc; + if (i >= items.length){ + qEl.innerHTML = 'Готово! Результат: ' + sc + ' / ' + items.length; + inp.disabled = true; goBtn.disabled = true; skipBtn.disabled = true; + inp.style.opacity = .5; goBtn.style.opacity = .5; skipBtn.style.opacity = .5; + if (!bumped){ bumped = true; bumpProgress('p13', 25); addXp(15, 'p13-iv4'); achievement('p13_done'); } + return; + } + qEl.innerHTML = items[i].q; + inp.value = ''; + fb.style.display = 'none'; + renderMath(qEl); + } + function check(){ + if (i >= items.length) return; + const v = parseFloat(inp.value); + const it = items[i]; + if (isNaN(v)){ feedback(fb, false, 'Введи число.'); return; } + const ok = Math.abs(v - it.ans) < 1e-6; + if (ok) sc++; + feedback(fb, ok, (ok ? '✓ Верно! ' : '✗ Неверно. Сумма $= ' + it.ans + '$. ') + it.hint); + i++; + setTimeout(render, 1300); + } + function skip(){ + if (i >= items.length) return; + const it = items[i]; + feedback(fb, false, 'Пропущено. Сумма $= ' + it.ans + '$. ' + it.hint); + i++; + setTimeout(render, 1300); + } + goBtn.addEventListener('click', check); + skipBtn.addEventListener('click', skip); + inp.addEventListener('keydown', e => { if (e.key === 'Enter') check(); }); + render(); + })(); + wireReadBtn('p13'); }