'+body+'
';
+}
+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(); }};
+}
const ICONS = {
repeat:' ',
@@ -431,8 +490,11 @@ function readButton(paraId){
function wireReadBtn(paraId){
const btn = document.getElementById(paraId+'-read-btn'); if(!btn) return;
btn.addEventListener('click', ()=>{
- addXp(10, paraId+'-read'); bumpProgress(paraId, 100);
+ addXp(10, paraId+'-read'); bumpProgress(paraId, 30);
btn.textContent='Прочитано! +10 XP'; btn.disabled=true; btn.style.opacity=.6;
+ if(paraId==='p2') achievement('p2_done');
+ if(paraId==='p4') achievement('p4_done');
+ if(paraId==='p5') achievement('p5_done');
if(paraId==='final1') achievement('ch1_done');
});
}
@@ -440,38 +502,493 @@ function wireReadBtn(paraId){
/* ===== STUB BUILDERS — наполнение в Phase 1+ ===== */
function buildP1(){
- const root = document.getElementById('p1-body');
- root.innerHTML = `
-
-
-
-
Содержание параграфа «Рациональная дробь» будет добавлено в следующих обновлениях.
-
Раздел Phase 1.
-
-
` + secNav(null, 'p2') + readButton('p1');
- renderMath(root);
+ const box = document.getElementById('p1-body');
+ let html = '';
+
+ html += makeCard('theory', 'Определение рациональной дроби', '1.1', `
+ Рациональной дробью называется выражение вида $\\dfrac{P(x)}{Q(x)}$, где $P(x)$ и $Q(x)$ — многочлены, причём $Q(x) \\ne 0$.
+ Числитель $P(x)$ может быть любым многочленом (даже нулевым), а знаменатель $Q(x)$ — не равен нулю .
+ Примеры рациональных дробей:
+
+ $\\dfrac{x+1}{x-2}$ — простейшая дробь с линейными многочленами
+ $\\dfrac{3}{x^2+1}$ — числитель константа, знаменатель квадратный
+ $\\dfrac{2a^2-1}{a^2-9}$ — обе части квадратные многочлены
+
+ А целые выражения — это дроби?
+ Да! Любой многочлен $P(x)$ — это частный случай рациональной дроби: $P(x) = \\dfrac{P(x)}{1}$. Поэтому рациональные дроби обобщают и целые выражения.
+
`);
+
+ html += makeCard('rule', 'Область допустимых значений (ОДЗ)', '1.2', `
+ ОДЗ рациональной дроби $\\dfrac{P(x)}{Q(x)}$ — это все значения переменной, при которых знаменатель не обращается в ноль .
+ Алгоритм нахождения ОДЗ:
+
+ Записать уравнение $Q(x) = 0$.
+ Найти все его корни.
+ Исключить эти корни из множества действительных чисел.
+
+ Запись: «$x \\ne$ ...» или «$x \\in \\mathbb{R}, x \\ne $...».
+ Если уравнение $Q(x) = 0$ не имеет корней (например, $x^2 + 1 = 0$), то ОДЗ — все действительные числа .
`);
+
+ html += makeCard('example', 'Примеры нахождения ОДЗ', '1.3', `
+ а) $\\dfrac{1}{x-3}$. Знаменатель: $x - 3 = 0 \\Rightarrow x = 3$. ОДЗ: $x \\ne 3$.
+ б) $\\dfrac{x+5}{x^2-4}$. Знаменатель: $x^2 - 4 = 0 \\Rightarrow (x-2)(x+2) = 0 \\Rightarrow x = \\pm 2$. ОДЗ: $x \\ne 2, x \\ne -2$.
+ в) $\\dfrac{2x}{(x-1)(x+7)}$. Знаменатель: $(x-1)(x+7) = 0 \\Rightarrow x = 1$ или $x = -7$. ОДЗ: $x \\ne 1, x \\ne -7$.
`);
+
+ /* INTERACTIVE 1 — slider + SVG number line */
+ html += `
+
+
Выбери дробь ползунком — увидишь её ОДЗ на числовой прямой и в текстовой форме.
+
+ Дробь №1 / 5
+
+
+
+
+
+
+
`;
+
+ /* INTERACTIVE 2 — calc ОДЗ */
+ html += `
+
+
Введи целые коэффициенты $a$ и $b$, нажми «Найти ОДЗ» — посчитаем точку, исключённую из ОДЗ.
+
+ $a$ =
+
+ $b$ =
+
+ Найти ОДЗ
+
+
+
+
`;
+
+ /* INTERACTIVE 3 — quickfire quiz */
+ html += `
+
+
Дробь и значение $x_0$. Решай: входит ли $x_0$ в ОДЗ (т.е. не обращает ли знаменатель в 0)?
+
Задача 1 / 8 Очки: 0 / 8
+
+
+ Да, входит
+ Нет, не входит
+
+
+
`;
+
+ /* INTERACTIVE 4 — trainer */
+ html += `
+
+
Для каждой дроби введи сумму всех значений , при которых знаменатель $= 0$ (т.е. сумму точек, исключённых из ОДЗ).
+
Задача 1 / 6 Очки: 0 / 6
+
+
+ сумма корней =
+
+ Проверить
+ Заново
+
+
+
`;
+
+ html += secNav(null, 'p2');
+ html += readButton('p1');
+
+ box.innerHTML = html;
+ renderMath(box);
+
+ /* IV1 — slider + SVG */
+ (function(){
+ const FRACS = [
+ { fr:'\\dfrac{1}{x-2}', roots:[2], text:'$x \\ne 2$' },
+ { fr:'\\dfrac{x}{x^2-9}', roots:[-3,3], text:'$x \\ne -3,\\ x \\ne 3$' },
+ { fr:'\\dfrac{3}{(x+1)(x-4)}', roots:[-1,4], text:'$x \\ne -1,\\ x \\ne 4$' },
+ { fr:'\\dfrac{2x+1}{x^2-25}', roots:[-5,5], text:'$x \\ne -5,\\ x \\ne 5$' },
+ { fr:'\\dfrac{1}{x^2+1}', roots:[], text:'$x \\in \\mathbb{R}$ — все действительные' },
+ ];
+ const sl = document.getElementById('p1-iv1-sl');
+ const idx = document.getElementById('p1-iv1-idx');
+ const fEl = document.getElementById('p1-iv1-formula');
+ const svg = document.getElementById('p1-iv1-svg');
+ const out = document.getElementById('p1-iv1-out');
+ const seen = new Set();
+ function draw(){
+ const k = +sl.value; idx.textContent = k;
+ const cur = FRACS[k-1];
+ fEl.innerHTML = '$' + cur.fr + '$';
+ let s = '';
+ // grid
+ s += ' ';
+ for(let v=-10; v<=10; v++){
+ const x = 30 + (v+10) * 27;
+ s += ' ';
+ if(v%2===0){
+ s += ''+v+' ';
+ }
+ }
+ // axis
+ s += ' ';
+ s += ' ';
+ s += 'x ';
+ // excluded points
+ cur.roots.forEach(r=>{
+ const x = 30 + (r+10) * 27;
+ s += ' ';
+ s += ''+r+' ';
+ });
+ svg.innerHTML = s;
+ out.innerHTML = 'ОДЗ: ' + cur.text;
+ renderMath(fEl); renderMath(out);
+ seen.add(k);
+ if(seen.size === FRACS.length){ addXp(10,'p1-iv1'); bumpProgress('p1', 15); seen.clear(); seen.add('done'); }
+ }
+ sl.addEventListener('input', draw);
+ draw();
+ })();
+
+ /* IV2 — ОДЗ калькулятор */
+ (function(){
+ const go = document.getElementById('p1-iv2-go');
+ const aI = document.getElementById('p1-iv2-a');
+ const bI = document.getElementById('p1-iv2-b');
+ const out = document.getElementById('p1-iv2-out');
+ const fb = document.getElementById('p1-iv2-fb');
+ let solved = 0;
+ function showFormula(){ out.innerHTML = 'Дробь: $\\dfrac{1}{('+(aI.value||'a')+')x + ('+(bI.value||'b')+')}$'; renderMath(out); }
+ function calc(){
+ const a = parseInt(aI.value, 10), b = parseInt(bI.value, 10);
+ if(isNaN(a) || isNaN(b)){ feedback(fb, false, '✗ Введи целые числа $a$ и $b$.'); return; }
+ if(a === 0){
+ out.innerHTML = '$a = 0$ — выражение не является рациональной дробью с переменной (знаменатель — константа $'+b+'$). Если ещё и $b = 0$, дробь не определена.';
+ feedback(fb, true, '✓ При $a = 0$ переменная в знаменателе исчезает.');
+ return;
+ }
+ // root: ax + b = 0 → x = -b/a
+ const num = -b, den = a;
+ const d = gcd(Math.abs(num), Math.abs(den));
+ let nn = num/d, dd = den/d;
+ if(dd < 0){ nn = -nn; dd = -dd; }
+ let xstr;
+ if(dd === 1) xstr = String(nn);
+ else xstr = '\\dfrac{'+nn+'}{'+dd+'}';
+ out.innerHTML = 'Знаменатель: $'+a+'x + ('+b+') = 0 \\Rightarrow x = '+xstr+'$.ОДЗ: $x \\ne '+xstr+'$. ';
+ renderMath(out);
+ feedback(fb, true, '✓ ОДЗ найдена! +10 XP');
+ solved++;
+ if(solved === 1){ addXp(10,'p1-iv2'); bumpProgress('p1', 15); }
+ }
+ aI.addEventListener('input', showFormula); bI.addEventListener('input', showFormula);
+ go.addEventListener('click', calc);
+ showFormula();
+ })();
+
+ /* IV3 — quickfire */
+ (function(){
+ const Q = [
+ { expr:'$\\dfrac{1}{x-3}, \\ x_0 = 5$', yes:true, why:'$5 - 3 = 2 \\ne 0$' },
+ { expr:'$\\dfrac{1}{x-3}, \\ x_0 = 3$', yes:false, why:'$3 - 3 = 0$ — знаменатель обнуляется' },
+ { expr:'$\\dfrac{x}{x^2-4}, \\ x_0 = 2$', yes:false, why:'$2^2 - 4 = 0$' },
+ { expr:'$\\dfrac{x}{x^2-4}, \\ x_0 = 0$', yes:true, why:'$0^2 - 4 = -4 \\ne 0$' },
+ { expr:'$\\dfrac{1}{(x+1)(x-5)}, \\ x_0 = -1$', yes:false, why:'$(-1+1)(-1-5) = 0$' },
+ { expr:'$\\dfrac{2}{x^2+1}, \\ x_0 = -7$', yes:true, why:'$(-7)^2 + 1 = 50 \\ne 0$ (знаменатель всегда $>0$)' },
+ { expr:'$\\dfrac{x+1}{x-x}, \\ x_0 = 2$', yes:false, why:'$x - x = 0$ всегда! Дробь не определена ни при каком $x$' },
+ { expr:'$\\dfrac{3}{x^2-9}, \\ x_0 = 3$', yes:false, why:'$3^2 - 9 = 0$' },
+ ];
+ let i = 0, score = 0;
+ function show(){
+ if(i >= Q.length){
+ document.getElementById('p1-iv3-q').innerHTML = 'Готово! Результат: ' + score + ' / ' + Q.length;
+ if(score === Q.length){ addXp(15,'p1-iv3'); bumpProgress('p1', 25); }
+ else if(score >= Q.length - 2){ addXp(8,'p1-iv3'); bumpProgress('p1', 15); }
+ return;
+ }
+ document.getElementById('p1-iv3-i').textContent = (i+1);
+ document.getElementById('p1-iv3-s').textContent = score;
+ document.getElementById('p1-iv3-q').innerHTML = Q[i].expr;
+ renderMath(document.getElementById('p1-iv3-q'));
+ document.getElementById('p1-iv3-fb').style.display = 'none';
+ }
+ function answer(isYes){
+ if(i >= Q.length) return;
+ const fb = document.getElementById('p1-iv3-fb');
+ if(isYes === Q[i].yes){ score++; feedback(fb, true, '✓ Верно! '+Q[i].why+'. Дальше ▶'); }
+ else feedback(fb, false, '✗ Неверно. '+Q[i].why+'. Дальше ▶');
+ document.getElementById('p1-iv3-s').textContent = score;
+ i++;
+ setTimeout(show, 1000);
+ }
+ document.getElementById('p1-iv3-yes').addEventListener('click', ()=>answer(true));
+ document.getElementById('p1-iv3-no').addEventListener('click', ()=>answer(false));
+ show();
+ })();
+
+ /* IV4 — trainer */
+ (function(){
+ const Q = [
+ { q:'$\\dfrac{1}{x-7}$', sum:7, hint:'корень $x = 7$' },
+ { q:'$\\dfrac{x}{x^2-16}$', sum:0, hint:'корни $x = \\pm 4$, сумма $= 0$' },
+ { q:'$\\dfrac{1}{(x-1)(x+9)}$', sum:-8, hint:'корни $x = 1, -9$, сумма $= -8$' },
+ { q:'$\\dfrac{x^2}{x^2-25}$', sum:0, hint:'корни $x = \\pm 5$, сумма $= 0$' },
+ { q:'$\\dfrac{2}{(x+3)^2}$', sum:-3, hint:'один корень $x = -3$' },
+ { q:'$\\dfrac{1}{x^2-2x-15}$', sum:2, hint:'корни $x = 5, -3$, сумма $= 2$' },
+ ];
+ let i = 0, score = 0;
+ function show(){
+ if(i >= Q.length){
+ document.getElementById('p1-iv4-q').innerHTML = 'Готово! Результат: ' + score + ' / ' + Q.length;
+ if(score === Q.length){ addXp(15,'p1-iv4'); bumpProgress('p1', 25); }
+ else if(score >= 4){ addXp(8,'p1-iv4'); bumpProgress('p1', 15); }
+ return;
+ }
+ document.getElementById('p1-iv4-i').textContent = (i+1);
+ document.getElementById('p1-iv4-s').textContent = score;
+ document.getElementById('p1-iv4-q').innerHTML = 'Дробь: ' + Q[i].q;
+ document.getElementById('p1-iv4-ans').value = '';
+ renderMath(document.getElementById('p1-iv4-q'));
+ document.getElementById('p1-iv4-fb').style.display = 'none';
+ }
+ function go(){
+ if(i >= Q.length) return;
+ const fb = document.getElementById('p1-iv4-fb');
+ const ans = parseInt(document.getElementById('p1-iv4-ans').value, 10);
+ if(isNaN(ans)){ feedback(fb, false, '✗ Введи целое число.'); return; }
+ if(ans === Q[i].sum){ score++; feedback(fb, true, '✓ Верно! '+Q[i].hint+'. Дальше ▶'); }
+ else feedback(fb, false, '✗ Неверно. Сумма $= '+Q[i].sum+'$ ('+Q[i].hint+'). Дальше ▶');
+ document.getElementById('p1-iv4-s').textContent = score;
+ i++;
+ setTimeout(show, 1200);
+ }
+ 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');
}
function buildP2(){
- const root = document.getElementById('p2-body');
- root.innerHTML = `
-
-
-
-
Содержание параграфа «Основное свойство дроби» будет добавлено в следующих обновлениях.
-
Раздел Phase 1.
-
-
` + secNav('p1', 'p3') + readButton('p2');
- renderMath(root);
+ const box = document.getElementById('p2-body');
+ let html = '';
+
+ html += makeCard('theory', 'Основное свойство дроби', '2.1', `
+ Основное свойство рациональной дроби: если числитель и знаменатель дроби умножить или разделить на одно и то же отличное от нуля выражение, то значение дроби не изменится:
+ \\[\\dfrac{A}{B} = \\dfrac{A \\cdot C}{B \\cdot C}, \\qquad \\dfrac{A \\cdot C}{B \\cdot C} = \\dfrac{A}{B}, \\quad B \\ne 0,\\ C \\ne 0.\\]
+ Слева направо — это умножение на $C$ (приведение к общему знаменателю), справа налево — это сокращение .
+ Правило знаков
+ $\\dfrac{-a}{-b} = \\dfrac{a}{b}$ (минусы сокращаются), $\\dfrac{-a}{b} = -\\dfrac{a}{b} = \\dfrac{a}{-b}$ — минус можно «перекидывать».
+
`);
+
+ html += makeCard('rule', 'Как сокращать дробь', '2.2', `
+ Алгоритм сокращения:
+
+ Разложить числитель и знаменатель на множители (вынести общий множитель, применить формулы сокращённого умножения).
+ Найти общий множитель числителя и знаменателя.
+ Разделить на него (вычеркнуть).
+
+ Пример: $\\dfrac{12x^2}{18x} = \\dfrac{6x \\cdot 2x}{6x \\cdot 3} = \\dfrac{2x}{3}$ — общий множитель $6x$.
+ Внимание: сокращать можно только множители , а не слагаемые! $\\dfrac{a+b}{a} \\ne 1 + b$ — это ошибка.
`);
+
+ html += makeCard('example', 'Примеры сокращения', '2.3', `
+ а) $\\dfrac{a^2-b^2}{a+b} = \\dfrac{(a-b)(a+b)}{a+b} = a - b$ — применили формулу разности квадратов.
+ б) $\\dfrac{x^2-9}{x^2+3x} = \\dfrac{(x-3)(x+3)}{x(x+3)} = \\dfrac{x-3}{x}$ — общий множитель $(x+3)$.
+ в) $\\dfrac{6a^2b}{15ab^2} = \\dfrac{3ab \\cdot 2a}{3ab \\cdot 5b} = \\dfrac{2a}{5b}$ — общий множитель $3ab$.
`);
+
+ /* INTERACTIVE 1 — slider visualizer */
+ html += `
+
+
Выбери задачу — увидишь дробь с подсвеченным общим множителем. Нажми «Показать сокращение», чтобы увидеть результат.
+
+ Задача №1 / 5
+
+
+
+ Показать сокращение
+ Скрыть
+
+
+
`;
+
+ /* INTERACTIVE 2 — number GCD calc */
+ html += `
+
+
Введи целые числитель и знаменатель — посчитаем НОД и покажем сокращённую дробь.
+
+ числитель =
+
+ знаменатель =
+
+ Сократить
+
+
+
+
`;
+
+ /* INTERACTIVE 3 — DnD sorter "Можно сократить / Уже не сокращается" */
+ html += `
+
+
Перетащи каждую дробь в нужный ящик. Шесть дробей — два варианта.
+
+
+
+
Проверить Сначала
+
+
`;
+
+ /* INTERACTIVE 4 — trainer */
+ html += `
+
+
Сократи дробь и введи числитель результата (число или числовой коэффициент).
+
Задача 1 / 6 Очки: 0 / 6
+
+
+ числитель =
+
+ Проверить
+ Заново
+
+
+
`;
+
+ html += secNav('p1', 'p3');
+ html += readButton('p2');
+
+ box.innerHTML = html;
+ renderMath(box);
+
+ /* IV1 */
+ (function(){
+ const TASKS = [
+ { before:'\\dfrac{6x}{9}', after:'\\dfrac{2x}{3}', common:'общий множитель $3$' },
+ { before:'\\dfrac{a^2-1}{a+1}', after:'a - 1', common:'разность квадратов: $a^2 - 1 = (a-1)(a+1)$' },
+ { before:'\\dfrac{x^2-4}{x-2}', after:'x + 2', common:'разность квадратов: $x^2 - 4 = (x-2)(x+2)$' },
+ { before:'\\dfrac{4ab^2}{6a^2 b}', after:'\\dfrac{2b}{3a}', common:'общий множитель $2ab$' },
+ { before:'\\dfrac{x^2+5x}{x^2-25}', after:'\\dfrac{x}{x-5}', common:'$x^2+5x = x(x+5)$, $x^2-25 = (x-5)(x+5)$, сокращаем $(x+5)$' },
+ ];
+ const sl = document.getElementById('p2-iv1-sl');
+ const idx = document.getElementById('p2-iv1-idx');
+ const bEl = document.getElementById('p2-iv1-before');
+ const aEl = document.getElementById('p2-iv1-after');
+ const go = document.getElementById('p2-iv1-go');
+ const hide = document.getElementById('p2-iv1-hide');
+ const seen = new Set();
+ function show(){
+ const k = +sl.value; idx.textContent = k;
+ const t = TASKS[k-1];
+ bEl.innerHTML = '$' + t.before + '$';
+ aEl.innerHTML = '' + t.common + '
$' + t.before + ' \\;=\\; ' + t.after + '$';
+ aEl.style.display = 'none';
+ renderMath(bEl);
+ seen.add(k);
+ if(seen.size === TASKS.length && !seen.has('done')){ addXp(10,'p2-iv1'); bumpProgress('p2', 15); seen.add('done'); }
+ }
+ sl.addEventListener('input', show);
+ go.addEventListener('click', ()=>{ aEl.style.display = 'block'; renderMath(aEl); });
+ hide.addEventListener('click', ()=>{ aEl.style.display = 'none'; });
+ show();
+ })();
+
+ /* IV2 */
+ (function(){
+ const go = document.getElementById('p2-iv2-go');
+ const nI = document.getElementById('p2-iv2-num');
+ const dI = document.getElementById('p2-iv2-den');
+ const out = document.getElementById('p2-iv2-out');
+ const fb = document.getElementById('p2-iv2-fb');
+ let solved = 0;
+ function calc(){
+ const n = parseInt(nI.value, 10), d = parseInt(dI.value, 10);
+ if(isNaN(n) || isNaN(d)){ feedback(fb, false, '✗ Введи целые числа.'); return; }
+ if(d === 0){ feedback(fb, false, '✗ Знаменатель не может быть 0.'); out.innerHTML = ''; return; }
+ const g = gcd(n, d);
+ const n2 = n/g, d2 = d/g;
+ if(g === 1){
+ out.innerHTML = '$\\dfrac{'+n+'}{'+d+'}$ — НОД $= 1$, дробь уже несократима.';
+ } else {
+ out.innerHTML = '$\\dfrac{'+n+'}{'+d+'} = \\dfrac{'+n+':'+g+'}{'+d+':'+g+'} = \\dfrac{'+n2+'}{'+d2+'}$, где НОД $= '+g+'$.';
+ }
+ renderMath(out);
+ feedback(fb, true, '✓ Готово! +10 XP');
+ solved++;
+ if(solved === 1){ addXp(10,'p2-iv2'); bumpProgress('p2', 15); }
+ }
+ go.addEventListener('click', calc);
+ calc();
+ })();
+
+ /* IV3 — DnD sorter */
+ (function(){
+ const items = [
+ { id:'i1', cat:'yes', html:'$\\dfrac{12}{18}$' },
+ { id:'i2', cat:'no', html:'$\\dfrac{5}{7}$' },
+ { id:'i3', cat:'yes', html:'$\\dfrac{a^2-b^2}{a-b}$' },
+ { id:'i4', cat:'no', html:'$\\dfrac{x+1}{x-1}$' },
+ { id:'i5', cat:'yes', html:'$\\dfrac{4xy}{6xz}$' },
+ { id:'i6', cat:'no', html:'$\\dfrac{1}{x^2+1}$' },
+ ];
+ const sorter = setupSorter({
+ poolId:'p2-iv3-pool',
+ scopeSelector:'#p2-iv3',
+ items: items,
+ cats:['yes','no'],
+ columnLayout:true,
+ });
+ document.getElementById('p2-iv3-check').addEventListener('click', ()=>{
+ const fb = document.getElementById('p2-iv3-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 на месте! +15 XP'); addXp(15,'p2-iv3'); bumpProgress('p2', 25); }
+ else feedback(fb, false, '✗ Правильно ' + correct + ' из 6. Попробуй ещё.');
+ });
+ document.getElementById('p2-iv3-reset').addEventListener('click', ()=>{ sorter.reset(); document.getElementById('p2-iv3-fb').style.display = 'none'; });
+ })();
+
+ /* IV4 — trainer */
+ (function(){
+ const Q = [
+ { q:'$\\dfrac{8a}{12}$', n:2, res:'\\dfrac{2a}{3}', hint:'НОД(8,12) $= 4$' },
+ { q:'$\\dfrac{15x^2}{25x}$', n:3, res:'\\dfrac{3x}{5}', hint:'НОД(15,25) $= 5$, сокращаем $x$' },
+ { q:'$\\dfrac{x^2-25}{x-5}$', n:1, res:'x+5', hint:'$(x-5)(x+5) / (x-5) = x+5$' },
+ { q:'$\\dfrac{a^2-9}{a+3}$', n:1, res:'a-3', hint:'$(a-3)(a+3) / (a+3) = a-3$' },
+ { q:'$\\dfrac{14m^2 n}{21mn^2}$', n:2, res:'\\dfrac{2m}{3n}', hint:'НОД(14,21) $= 7$, сокращаем $mn$' },
+ { q:'$\\dfrac{6(x-1)}{8(x-1)^2}$', n:3, res:'\\dfrac{3}{4(x-1)}', hint:'НОД(6,8) $= 2$, сокращаем $(x-1)$' },
+ ];
+ let i = 0, score = 0;
+ function show(){
+ if(i >= Q.length){
+ document.getElementById('p2-iv4-q').innerHTML = 'Готово! Результат: ' + score + ' / ' + Q.length;
+ if(score === Q.length){ addXp(15,'p2-iv4'); bumpProgress('p2', 25); }
+ else if(score >= 4){ 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 = parseInt(document.getElementById('p2-iv4-ans').value, 10);
+ if(isNaN(ans)){ feedback(fb, false, '✗ Введи число.'); return; }
+ if(ans === Q[i].n){ score++; feedback(fb, true, '✓ Верно! $' + Q[i].res + '$ ('+Q[i].hint+'). Дальше ▶'); }
+ else feedback(fb, false, '✗ Неверно. Должно быть $' + Q[i].n + '$, ответ $' + Q[i].res + '$ ('+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');
}