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 '';
}
+/* 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 = `
-
-
-
-
Содержание параграфа «Уравнение окружности» будет добавлено в следующих обновлениях.
-
Раздел 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 += `
+
+
Двигай ползунки центра $a, b$ и радиуса $R$ — окружность перерисуется, а уравнение под графиком подстроится.
+
+ центр $a$ =0
+ центр $b$ =0
+ радиус $R$ =3
+
+
+
+
+
+
`;
+
+ /* INTERACTIVE 2 — Калькулятор расстояния */
+ html += ``;
+
+ /* INTERACTIVE 3 — Принадлежит ли точка окружности? */
+ html += `
+
+
Для каждой пары «окружность + точка» определи: точка лежит на окружности или нет?
+
Задача: 1 / 6 · Очки: 0
+
+
+ Лежит на окружности
+ Не лежит
+
+
+
`;
+
+ /* INTERACTIVE 4 — Найди радиус или координату */
+ html += `
+
+
Прочитай уравнение или условие и введи число (радиус, координату, длину).
+
Задача: 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 = `
-
-
-
-
Содержание параграфа «Метод интервалов» будет добавлено в следующих обновлениях.
-
Раздел 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$ применяют метод интервалов :
+
+ Найти все корни уравнений $P(x) = 0$ (нули числителя) и $Q(x) = 0$ (нули знаменателя) .
+ Отметить корни на числовой прямой. Корни знаменателя — всегда выколоты (в них дробь не определена). Корни числителя — закрашены при нестрогом неравенстве ($\\le,\\ \\ge$) и выколоты при строгом ($<,\\ >$).
+ На каждом интервале определить знак выражения (например, подставив пробное число).
+ Записать ответ — объединение интервалов с нужным знаком, учитывая закрашенные/выколотые точки.
+ `);
+
+ 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 / 5
+
+
+
+
+
+
+
`;
+
+ /* INTERACTIVE 2 — Закрашена или выколота? (DnD) */
+ html += `
+
+
Перетащи каждую карточку «корень в неравенстве» в нужную колонку. Корни числителя при нестрогом неравенстве — закрашены ($\\bullet$); корни знаменателя и любые корни при строгом неравенстве — выколоты ($\\circ$).
+
+
+
+ Проверить
+ Сбросить
+
+
+
`;
+
+ /* INTERACTIVE 3 — Сколько целых решений? */
+ html += `
+
+
Для каждого неравенства подсчитай, сколько целых $x$ из отрезка $[-5;\\ 5]$ ему удовлетворяют.
+
Задача: 1 / 6 · Очки: 0
+
+
+ Количество =
+
+ Проверить
+ Пропустить
+
+
+
`;
+
+ /* INTERACTIVE 4 — Тренажёр интервалов (сумма концов) */
+ html += `
+
+
Реши неравенство, запиши ответ в виде объединения интервалов, и введи сумму всех конечных концов (бесконечности не учитываются).
+
Задача: 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');
}