diff --git a/frontend/textbooks/algebra_8_ch2.html b/frontend/textbooks/algebra_8_ch2.html
index b981aaa..7825be0 100644
--- a/frontend/textbooks/algebra_8_ch2.html
+++ b/frontend/textbooks/algebra_8_ch2.html
@@ -381,6 +381,16 @@ const ACH_LABELS = {
p8_disc_train: 'Тренажёр дискриминанта',
p8_steps: 'Пошаговое решение',
p8_graph: 'Знак D по графику',
+ p9_vieta: 'Подбор по Виета',
+ p9_constr: 'Корни → уравнение',
+ p9_signs: 'Знаки корней',
+ p9_check: 'Проверка по Виета',
+ p9_nonpriv: 'Виета для a≠1',
+ p10_constr: 'Разложение на множители',
+ p10_steps: 'Шаговое разложение',
+ p10_train: 'Тренажёр разложения',
+ p10_fraction: 'Сокращение дробей',
+ p10_sort: 'Разложимо или нет',
};
function loadProgress(){
@@ -505,8 +515,19 @@ const SIDEBARS = {
['$D = 0$','один корень: $x = \\dfrac{-b}{2a}$'],
['$D < 0$','корней нет'],
]},
- p9: { title:'§ 9 — скоро', rows:[['Теорема Виета','будет в Wave 2']]},
- p10:{ title:'§ 10 — скоро', rows:[['Разложение','будет в Wave 2']]},
+ p9: { title:'Шпаргалка § 9', rows:[
+ ['Виета','$x_1+x_2=-p,\\ x_1 x_2 = q$ для $x^2+px+q=0$'],
+ ['Общий','$x_1+x_2 = -b/a,\\ x_1 x_2 = c/a$'],
+ ['$q > 0$','корни одного знака'],
+ ['$q < 0$','корни разных знаков'],
+ ['Обратная','если $x_1+x_2=-p$ и $x_1 x_2=q$ — это корни'],
+ ]},
+ p10:{ title:'Шпаргалка § 10', rows:[
+ ['Разложение','$ax^2+bx+c = a(x-x_1)(x-x_2)$'],
+ ['Условие','$D \\geq 0$ (иначе нельзя)'],
+ ['Корни','через дискриминант или Виета'],
+ ['Сокращение','через разложение числителя и знаменателя'],
+ ]},
p11:{ title:'§ 11 — скоро', rows:[['Текстовые задачи','будет в Wave 3']]},
p12:{ title:'§ 12 — скоро', rows:[['Сводящиеся к квадратным','будет в Wave 3']]},
final2:{ title:'Финал', rows:[['Итоги главы','будет в Wave 4']]},
@@ -657,8 +678,544 @@ function init(){
document.addEventListener('DOMContentLoaded', init);
/* STUBS for paragraphs not yet implemented */
-function buildP9stub(){ document.getElementById('p9-body').innerHTML = `
§ 9 — Теорема Виета Этот параграф будет реализован в следующей волне обновлений.Wave 2: § 9 + § 10
${secNav('p8','p10')}`; }
-function buildP10stub(){ document.getElementById('p10-body').innerHTML = `§ 10 — Квадратный трёхчлен. Разложение на множители Скоро в Wave 2.
${secNav('p9','p11')}`; }
+function buildP9stub(){ buildP9(); }
+function buildP9(){
+ const box = document.getElementById('p9-body');
+ let html = '';
+
+ html += makeCard('repeat','Повторение',null,`
+
+ Приведённое уравнение: $x^2 + px + q = 0$ — старший коэффициент равен $1$.
+ Полное: $ax^2 + bx + c = 0$ → делим на $a$, получим $x^2 + \\dfrac{b}{a}x + \\dfrac{c}{a} = 0$.
+ Формулы корней через дискриминант — из § 8.
+ `);
+
+ html += makeCard('theory','Теорема Виета','9.1',`
+ Пусть $x_1$ и $x_2$ — корни приведённого уравнения $x^2 + px + q = 0$. Тогда:
+ $$x_1 + x_2 = -p,\\qquad x_1 \\cdot x_2 = q$$
+ Обратная теорема. Если для чисел $x_1, x_2$ выполнено $x_1+x_2=-p$ и $x_1 x_2=q$, то они — корни $x^2+px+q=0$.
`);
+
+ html += makeCard('rule','Общий случай (a ≠ 1)','9.2',`
+ Для уравнения $ax^2 + bx + c = 0$:
+ $$x_1 + x_2 = -\\dfrac{b}{a},\\qquad x_1 \\cdot x_2 = \\dfrac{c}{a}$$
+ Анализ знаков (если корни вещественные):
+
+ $q > 0$ → корни одного знака; если ещё $-p > 0$ — оба положительные.
+ $q < 0$ → корни разных знаков.
+ $q = 0$ → один из корней равен нулю.
+ `);
+
+ html += makeCard('example','Пример',null,`
+ Подбор: $x^2 - 7x + 12 = 0$. Ищем $x_1, x_2$: сумма $7$, произведение $12$. Это $3$ и $4$. Ответ: $\\{3;\\ 4\\}$.
+ Составление: найти уравнение с корнями $-2$ и $5$. Сумма $= 3$, произведение $= -10$. Уравнение: $x^2 - 3x - 10 = 0$.
`);
+
+ /* ===== INTERACTIVE 1: Подбор корней ===== */
+ html += widget('Тренажёр Виета: устный подбор','INTERACT 1','Дано приведённое уравнение. Подберите корни в уме и введите через точку с запятой.',`
+ Задача 1 / 10 Очки: 0
+
+
+
+ Ответ
+ Пропустить
+
+
+ Начать `);
+
+ /* ===== INTERACTIVE 2: Составить уравнение ===== */
+ html += widget('Конструктор: корни → уравнение','INTERACT 2','Выберите корни — система соберёт приведённое уравнение по Виета.',`
+
+ $x_1$ =
+ $x_2$ =
+
+
`);
+
+ /* ===== INTERACTIVE 3: Знаки корней ===== */
+ html += widget('Знаки корней по Виета','INTERACT 3','По уравнению определите знаки корней (если они существуют).',`
+ Раунд 1 / 8 Правильно: 0
+
+
+ Оба «+»
+ Оба «−»
+ Разных знаков
+ Корней нет
+
+
+ Начать `);
+
+ /* ===== INTERACTIVE 4: Проверка корней по Виета ===== */
+ html += widget('Быстрая проверка корней','INTERACT 4','Подставьте найденные корни — проверьте через Виета мгновенно.',`
+
+ Уравнение: $x^2$ + $x$ + = 0
+
+
+ Ваши $x_1, x_2$: ,
+ Проверить
+
+
`);
+
+ /* ===== INTERACTIVE 5: Виета для непривед. ===== */
+ html += widget('Виета для $a \\neq 1$','INTERACT 5','Уравнение не приведённое. Введите сумму и произведение корней.',`
+
+
+ $x_1+x_2$ =
+ $x_1 x_2$ =
+ Проверить
+ Следующее
+
+
`);
+
+ html += makeCard('oral','Устные вопросы',null,`
+
+ Сформулируйте теорему Виета для приведённого уравнения.
+ Что говорит обратная теорема?
+ Корни уравнения $x^2 - 9x + 20 = 0$ — целые? Найдите устно.
+ Какого знака произведение корней уравнения $x^2 + 3x - 10 = 0$?
+ `);
+
+ html += makeCard('class','Класс — решите по Виета',null,`
+
+ $x^2 - 11x + 28 = 0$
+ $x^2 + x - 12 = 0$
+ $x^2 + 10x + 21 = 0$
+ Составьте приведённое уравнение с корнями $-4$ и $7$.
+ `);
+
+ html += makeCard('home','Домашка',null,`
+
+ $x^2 - 8x + 15 = 0$
+ $x^2 + 2x - 35 = 0$
+ Составьте уравнение с корнями $\\dfrac{1}{2}$ и $-3$.
+ Найдите $p$, если корни $x^2 + px - 6 = 0$ — это $2$ и $-3$.
+ `);
+
+ html += secNav('p8','p10');
+ box.innerHTML = html;
+ if(window.renderMathInElement) setTimeout(()=>renderMath(box), 0);
+
+ /* INIT 1 — Тренажёр Виета */
+ (function(){
+ let cur = null, i = 1, score = 0;
+ function gen(){
+ const r1 = -7 + Math.floor(Math.random()*15);
+ const r2 = -7 + Math.floor(Math.random()*15);
+ if(r1 === r2) return gen();
+ const p = -(r1 + r2), q = r1*r2;
+ return { r1, r2, p, q };
+ }
+ function show(){
+ cur = gen();
+ document.getElementById('p9v-i').textContent = i;
+ const t = 'x^2 ' + (cur.p >= 0 ? '+ ' + cur.p : '- ' + Math.abs(cur.p)) + 'x ' + (cur.q >= 0 ? '+ ' + cur.q : '- ' + Math.abs(cur.q)) + ' = 0';
+ document.getElementById('p9v-task').innerHTML = '$' + t + '$'; renderMath(document.getElementById('p9v-task'));
+ document.getElementById('p9v-inp').value = '';
+ document.getElementById('p9v-fb').style.display = 'none';
+ }
+ function check(){
+ const fb = document.getElementById('p9v-fb');
+ fb.style.display = 'block';
+ const u = document.getElementById('p9v-inp').value.replace(/,/g,';').split(/[;\s]+/).filter(Boolean).map(Number).sort((a,b)=>a-b);
+ const a = [cur.r1, cur.r2].sort((a,b)=>a-b);
+ const ok = u.length === 2 && u[0] === a[0] && u[1] === a[1];
+ if(ok){ score++; feedback(fb, true, '✓ Верно: ' + a.join(' и ')); }
+ else feedback(fb, false, 'Не то. Правильно: ' + a.join(' и '));
+ document.getElementById('p9v-score').textContent = score;
+ if(i >= 10){ setTimeout(()=>{ feedback(fb, score >= 7, 'Итог: ' + score + '/10'); if(score >= 7){ achievement('p9_vieta'); bumpProgress('p9', 16); confetti(); } }, 600); }
+ else { i++; setTimeout(show, 800); }
+ }
+ document.getElementById('p9v-start').addEventListener('click', ()=>{ i=1; score=0; document.getElementById('p9v-score').textContent = 0; show(); });
+ document.getElementById('p9v-go').addEventListener('click', check);
+ document.getElementById('p9v-inp').addEventListener('keyup', e=>{ if(e.key === 'Enter') check(); });
+ document.getElementById('p9v-skip').addEventListener('click', ()=>{ if(i < 10){ i++; show(); } });
+ })();
+
+ /* INIT 2 — Конструктор уравнения */
+ (function(){
+ const r1E = document.getElementById('p9c-r1'), r2E = document.getElementById('p9c-r2');
+ const out = document.getElementById('p9c-out');
+ let done = false;
+ function refresh(){
+ const r1 = +r1E.value, r2 = +r2E.value;
+ const p = -(r1 + r2), q = r1 * r2;
+ let s = 'Сумма: $x_1 + x_2 = ' + r1 + ' + (' + r2 + ') = ' + (r1+r2) + ' \\Rightarrow -p = ' + (r1+r2) + ' \\Rightarrow p = ' + p + '$
';
+ s += 'Произведение: $x_1 \\cdot x_2 = ' + r1 + ' \\cdot (' + r2 + ') = ' + q + ' \\Rightarrow q = ' + q + '$
';
+ s += 'Уравнение: $x^2 ' + (p >= 0 ? '+ ' + p : '- ' + Math.abs(p)) + 'x ' + (q >= 0 ? '+ ' + q : '- ' + Math.abs(q)) + ' = 0$
';
+ out.innerHTML = s; renderMath(out);
+ if(!done){ done = true; setTimeout(()=>{ achievement('p9_constr'); bumpProgress('p9', 14); }, 300); }
+ }
+ r1E.addEventListener('input', refresh); r2E.addEventListener('input', refresh);
+ refresh();
+ })();
+
+ /* INIT 3 — Знаки корней */
+ (function(){
+ let cur = null, i = 1, score = 0;
+ function gen(){
+ const t = Math.floor(Math.random()*4);
+ if(t === 0){
+ const r1 = 1 + Math.floor(Math.random()*5), r2 = 1 + Math.floor(Math.random()*5);
+ return { p: -(r1+r2), q: r1*r2, kind: 'pp' };
+ }
+ if(t === 1){
+ const r1 = -1 - Math.floor(Math.random()*5), r2 = -1 - Math.floor(Math.random()*5);
+ return { p: -(r1+r2), q: r1*r2, kind: 'nn' };
+ }
+ if(t === 2){
+ const r1 = 1 + Math.floor(Math.random()*5), r2 = -1 - Math.floor(Math.random()*5);
+ return { p: -(r1+r2), q: r1*r2, kind: 'pn' };
+ }
+ const p = -5 + Math.floor(Math.random()*11), q = 4 + Math.floor(Math.random()*8);
+ const D = p*p - 4*q;
+ if(D >= 0) return gen();
+ return { p, q, kind:'none' };
+ }
+ function show(){
+ cur = gen();
+ document.getElementById('p9z-i').textContent = i;
+ const t = 'x^2 ' + (cur.p >= 0 ? '+ ' + cur.p : '- ' + Math.abs(cur.p)) + 'x ' + (cur.q >= 0 ? '+ ' + cur.q : '- ' + Math.abs(cur.q)) + ' = 0';
+ document.getElementById('p9z-task').innerHTML = '$' + t + '$'; renderMath(document.getElementById('p9z-task'));
+ document.getElementById('p9z-fb').style.display = 'none';
+ }
+ function ans(k){
+ const fb = document.getElementById('p9z-fb');
+ fb.style.display = 'block';
+ if(k === cur.kind){ score++; feedback(fb, true, '✓ Верно'); }
+ else feedback(fb, false, 'Не то. Правильно: ' + ({pp:'оба «+»',nn:'оба «−»',pn:'разных знаков',none:'корней нет'})[cur.kind]);
+ document.getElementById('p9z-score').textContent = score;
+ if(i >= 8){ setTimeout(()=>{ feedback(fb, score >= 6, 'Итог: ' + score + '/8'); if(score >= 6){ achievement('p9_signs'); bumpProgress('p9', 14); confetti(); } }, 600); }
+ else { i++; setTimeout(show, 800); }
+ }
+ document.getElementById('p9z-start').addEventListener('click', ()=>{ i=1; score=0; document.getElementById('p9z-score').textContent = 0; show(); });
+ document.querySelectorAll('[data-z]').forEach(b => b.addEventListener('click', ()=>ans(b.dataset.z)));
+ })();
+
+ /* INIT 4 — Проверка корней */
+ (function(){
+ document.getElementById('p9p-check').addEventListener('click', ()=>{
+ const p = +document.getElementById('p9p-p').value, q = +document.getElementById('p9p-q').value;
+ const r1 = +document.getElementById('p9p-r1').value, r2 = +document.getElementById('p9p-r2').value;
+ const sum = r1 + r2, prod = r1 * r2;
+ const out = document.getElementById('p9p-out');
+ const okS = sum === -p, okP = prod === q;
+ let html = '$x_1 + x_2 = ' + sum + '$ vs $-p = ' + (-p) + '$ — ' + (okS ? '✓ ' : '✗ ') + '
';
+ html += '$x_1 \\cdot x_2 = ' + prod + '$ vs $q = ' + q + '$ — ' + (okP ? '✓ ' : '✗ ') + '
';
+ if(okS && okP){ html += 'Корни верные!
'; achievement('p9_check'); bumpProgress('p9', 12); }
+ else html += 'Корни ошибочны.
';
+ out.style.background = (okS && okP) ? 'var(--ok-bg)' : 'var(--fail-bg)';
+ out.innerHTML = html; renderMath(out);
+ });
+ })();
+
+ /* INIT 5 — Виета для непривед. */
+ (function(){
+ let cur = null;
+ function gen(){
+ const a = 2 + Math.floor(Math.random()*3);
+ const r1 = -3 + Math.floor(Math.random()*7), r2 = -3 + Math.floor(Math.random()*7);
+ if(r1 === r2) return gen();
+ const b = -a*(r1+r2), c = a*r1*r2;
+ return { a, b, c, sum:-b/a, prod:c/a };
+ }
+ function show(){
+ cur = gen();
+ document.getElementById('p9n-task').innerHTML = '$' + cur.a + 'x^2 ' + (cur.b >= 0 ? '+ ' + cur.b : '- ' + Math.abs(cur.b)) + 'x ' + (cur.c >= 0 ? '+ ' + cur.c : '- ' + Math.abs(cur.c)) + ' = 0$';
+ renderMath(document.getElementById('p9n-task'));
+ document.getElementById('p9n-s').value = '';
+ document.getElementById('p9n-p').value = '';
+ document.getElementById('p9n-fb').style.display = 'none';
+ }
+ function parse(s){ s = s.trim().replace(/,/g,'.'); if(s.includes('/')){ const [a,b] = s.split('/').map(Number); return a/b; } return +s; }
+ document.getElementById('p9n-go').addEventListener('click', ()=>{
+ const us = parse(document.getElementById('p9n-s').value), up = parse(document.getElementById('p9n-p').value);
+ const fb = document.getElementById('p9n-fb'); fb.style.display = 'block';
+ const okS = Math.abs(us - cur.sum) < 1e-6, okP = Math.abs(up - cur.prod) < 1e-6;
+ if(okS && okP){ feedback(fb, true, '✓ Верно! $\\dfrac{-b}{a}=' + fmt(cur.sum) + ',\\ \\dfrac{c}{a}=' + fmt(cur.prod) + '$'); renderMath(fb); achievement('p9_nonpriv'); bumpProgress('p9', 14); }
+ else feedback(fb, false, 'Правильно: сумма = ' + fmt(cur.sum) + ', произведение = ' + fmt(cur.prod));
+ });
+ document.getElementById('p9n-next').addEventListener('click', show);
+ show();
+ })();
+}
+function buildP10stub(){ buildP10(); }
+function buildP10(){
+ const box = document.getElementById('p10-body');
+ let html = '';
+
+ html += makeCard('repeat','Повторение',null,`
+
+ Дискриминант $D = b^2 - 4ac$. Корни через формулу или Виета.
+ Формулы сокр. умножения: $(a-b)(a+b) = a^2 - b^2$, $a^2 \\pm 2ab + b^2 = (a \\pm b)^2$.
+ Сокращение дробей: сокращаются равные множители числителя и знаменателя.
+ `);
+
+ html += makeCard('theory','Что такое квадратный трёхчлен','10.1',`
+ Квадратный трёхчлен — это многочлен $ax^2 + bx + c$, где $a \\neq 0$. Числа $x_1, x_2$, при которых он равен нулю, называются корнями трёхчлена.
+ Если $D \\geq 0$, трёхчлен раскладывается:
+ $$ax^2 + bx + c = a(x - x_1)(x - x_2)$$
+ Если $D < 0$ — на множители первой степени с действительными коэффициентами не раскладывается.
`);
+
+ html += makeCard('algo','Алгоритм разложения',null,`
+
+ Найти $D$ и корни $x_1, x_2$.
+ Если $D \\geq 0$, записать $a(x - x_1)(x - x_2)$.
+ Раскрыть скобки и проверить.
+ `);
+
+ html += makeCard('example','Примеры',null,`
+ 1) $x^2 - 5x + 6 = (x-2)(x-3)$.
+ 2) $2x^2 - 7x + 3$: $D = 49 - 24 = 25$, $x_1 = 3$, $x_2 = 0{,}5$. Разложение: $2(x-3)(x-0{,}5) = (x-3)(2x-1)$.
+ 3) $x^2 + 1$: $D = -4 < 0$ → не раскладывается.
+ 4) Сокращение: $\\dfrac{x^2 - 9}{x^2 - 5x + 6} = \\dfrac{(x-3)(x+3)}{(x-3)(x-2)} = \\dfrac{x+3}{x-2}$.
`);
+
+ /* INT 1 — Конструктор разложения */
+ html += widget('Конструктор разложения','INTERACT 1','Введите $a$, $x_1$, $x_2$ — получите трёхчлен и его разложение.',`
+
+ $a$ =
+ $x_1$ =
+ $x_2$ =
+
+
`);
+
+ /* INT 2 — Шаговый разлагатель */
+ html += widget('Пошаговый разлагатель','INTERACT 2','Введите $a$, $b$, $c$ — система разложит трёхчлен по шагам.',`
+
+ $a$ =
+ $b$ =
+ $c$ =
+ Разложить
+
+
`);
+
+ /* INT 3 — Тренажёр разложения */
+ html += widget('Тренажёр разложения','INTERACT 3','Введите разложение в формате $(x-?)(x-?)$ — корни через точку с запятой, со знаками.',`
+ Задача 1 / 8 Очки: 0
+
+
+
+ Ответ
+
+
+ Начать `);
+
+ /* INT 4 — Сокращение дробей */
+ html += widget('Сокращение дробей','INTERACT 4','Дробь содержит квадратные трёхчлены. Разложите устно и введите сокращённую форму.',`
+ Задача 1 / 5 Очки: 0
+
+
+
+ Начать `);
+
+ /* INT 5 — Drag: разложимо или нет */
+ html += widget('Разложимо или нет?','INTERACT 5','По знаку дискриминанта разнесите трёхчлены: раскладываются на множители или нет.',`
+
+
+
Раскладывается ($D \\geq 0$)
+
Не раскладывается ($D < 0$)
+
+ Проверить Сначала
+
`);
+
+ html += makeCard('oral','Устно',null,`
+
+ При каком условии трёхчлен раскладывается?
+ Запишите разложение $x^2 - 4x + 3$.
+ Разложим ли $x^2 + 9$?
+ `);
+
+ html += makeCard('class','Класс — разложите',null,`
+
+ $x^2 - 8x + 15$
+ $2x^2 + 5x - 3$
+ $x^2 + 6x + 9$
+ Сократите: $\\dfrac{x^2 - 4}{x^2 + x - 6}$.
+ `);
+
+ html += makeCard('home','Домашка',null,`
+
+ $x^2 - 9x + 20$
+ $3x^2 - 8x + 4$
+ $x^2 + 4x + 7$ — раскладывается ли?
+ Сократите: $\\dfrac{x^2 - 25}{x^2 - 7x + 10}$.
+ `);
+
+ html += secNav('p9','p11');
+ box.innerHTML = html;
+ if(window.renderMathInElement) setTimeout(()=>renderMath(box), 0);
+
+ /* INIT 1 */
+ (function(){
+ const aE = document.getElementById('p10c-a'), x1E = document.getElementById('p10c-x1'), x2E = document.getElementById('p10c-x2');
+ const out = document.getElementById('p10c-out');
+ let done = false;
+ function refresh(){
+ const a = +aE.value, x1 = +x1E.value, x2 = +x2E.value;
+ if(!a){ out.innerHTML = 'Введите $a \\neq 0$'; renderMath(out); return; }
+ const b = -a*(x1+x2), c = a*x1*x2;
+ const eq = (a === 1 ? '' : (a === -1 ? '-' : a)) + 'x^2 ' + (b >= 0 ? '+ ' + b : '- ' + Math.abs(b)) + 'x ' + (c >= 0 ? '+ ' + c : '- ' + Math.abs(c));
+ const fact = (a === 1 ? '' : (a === -1 ? '-' : a)) + '(x ' + (x1 >= 0 ? '- ' + x1 : '+ ' + Math.abs(x1)) + ')(x ' + (x2 >= 0 ? '- ' + x2 : '+ ' + Math.abs(x2)) + ')';
+ out.innerHTML = 'Трёхчлен: $' + eq + '$
Разложение: $' + fact + '$
';
+ renderMath(out);
+ if(!done){ done = true; setTimeout(()=>{ achievement('p10_constr'); bumpProgress('p10', 14); }, 300); }
+ }
+ [aE,x1E,x2E].forEach(e => e.addEventListener('input', refresh));
+ refresh();
+ })();
+
+ /* INIT 2 */
+ (function(){
+ document.getElementById('p10s-go').addEventListener('click', ()=>{
+ const a = +document.getElementById('p10s-a').value;
+ const b = +document.getElementById('p10s-b').value;
+ const c = +document.getElementById('p10s-c').value;
+ const stage = document.getElementById('p10s-stage');
+ if(!a){ stage.innerHTML = '$a$ не может быть нулём.
'; return; }
+ const D = b*b - 4*a*c;
+ let html = 'Шаг 1: $D = ' + (b*b) + ' - ' + (4*a*c) + ' = ' + D + '$
';
+ if(D < 0){ html += 'Шаг 2: $D < 0$ → трёхчлен не раскладывается на множители с действительными коэффициентами.
'; }
+ else {
+ const x1 = (-b - Math.sqrt(D))/(2*a), x2 = (-b + Math.sqrt(D))/(2*a);
+ html += 'Шаг 2: $x_1 = ' + fmt(x1) + ',\\ x_2 = ' + fmt(x2) + '$
';
+ html += 'Шаг 3: $' + a + 'x^2 ' + (b >= 0 ? '+ ' + b : '- ' + Math.abs(b)) + 'x ' + (c >= 0 ? '+ ' + c : '- ' + Math.abs(c)) + ' = ' + (a === 1 ? '' : a) + '(x ' + (x1 >= 0 ? '- ' + fmt(x1) : '+ ' + fmt(-x1)) + ')(x ' + (x2 >= 0 ? '- ' + fmt(x2) : '+ ' + fmt(-x2)) + ')$
';
+ }
+ stage.innerHTML = html; renderMath(stage);
+ achievement('p10_steps'); bumpProgress('p10', 14);
+ });
+ })();
+
+ /* INIT 3 */
+ (function(){
+ let cur = null, i = 1, score = 0;
+ function gen(){
+ const r1 = -5 + Math.floor(Math.random()*11), r2 = -5 + Math.floor(Math.random()*11);
+ if(r1 === r2 || r1 === 0 && r2 === 0) return gen();
+ const b = -(r1+r2), c = r1*r2;
+ return { r1, r2, eq: 'x^2 ' + (b >= 0 ? '+ ' + b : '- ' + Math.abs(b)) + 'x ' + (c >= 0 ? '+ ' + c : '- ' + Math.abs(c)) };
+ }
+ function show(){
+ cur = gen();
+ document.getElementById('p10t-i').textContent = i;
+ document.getElementById('p10t-task').innerHTML = '$' + cur.eq + '$';
+ renderMath(document.getElementById('p10t-task'));
+ document.getElementById('p10t-inp').value = '';
+ document.getElementById('p10t-fb').style.display = 'none';
+ }
+ function check(){
+ const fb = document.getElementById('p10t-fb');
+ fb.style.display = 'block';
+ const u = document.getElementById('p10t-inp').value.replace(/,/g,';').split(/[;\s]+/).filter(Boolean).map(Number).sort((a,b)=>a-b);
+ const a = [cur.r1, cur.r2].sort((a,b)=>a-b);
+ const ok = u.length === 2 && u[0] === a[0] && u[1] === a[1];
+ if(ok){ score++; feedback(fb, true, '✓ $(x ' + (cur.r1 >= 0 ? '- ' + cur.r1 : '+ ' + Math.abs(cur.r1)) + ')(x ' + (cur.r2 >= 0 ? '- ' + cur.r2 : '+ ' + Math.abs(cur.r2)) + ')$'); renderMath(fb); }
+ else feedback(fb, false, 'Правильно: ' + a.join(' и '));
+ document.getElementById('p10t-score').textContent = score;
+ if(i >= 8){ setTimeout(()=>{ feedback(fb, score >= 6, 'Итог: ' + score + '/8'); if(score >= 6){ achievement('p10_train'); bumpProgress('p10', 16); confetti(); } }, 600); }
+ else { i++; setTimeout(show, 800); }
+ }
+ document.getElementById('p10t-start').addEventListener('click', ()=>{ i=1; score=0; document.getElementById('p10t-score').textContent = 0; show(); });
+ document.getElementById('p10t-go').addEventListener('click', check);
+ document.getElementById('p10t-inp').addEventListener('keyup', e=>{ if(e.key === 'Enter') check(); });
+ })();
+
+ /* INIT 4 — Сокращение */
+ (function(){
+ const tasks = [
+ { n:'x^2 - 9', d:'x^2 - 5x + 6', ans:'\\dfrac{x+3}{x-2}', opts:['\\dfrac{x+3}{x-2}','\\dfrac{x-3}{x+2}','\\dfrac{x+3}{x+2}','\\dfrac{1}{x-2}'] },
+ { n:'x^2 - 4', d:'x^2 + x - 6', ans:'\\dfrac{x-2}{x+3}', opts:['\\dfrac{x+2}{x+3}','\\dfrac{x-2}{x+3}','\\dfrac{x-2}{x-3}','\\dfrac{x+2}{x-3}'] },
+ { n:'x^2 - 25', d:'x^2 - 7x + 10', ans:'\\dfrac{x+5}{x-2}', opts:['\\dfrac{x+5}{x-2}','\\dfrac{x-5}{x-2}','\\dfrac{x-5}{x+2}','\\dfrac{x+5}{x+2}'] },
+ { n:'x^2 - 5x + 6', d:'x^2 - 4', ans:'\\dfrac{x-3}{x+2}', opts:['\\dfrac{x-3}{x+2}','\\dfrac{x+3}{x+2}','\\dfrac{x-3}{x-2}','\\dfrac{x+2}{x-3}'] },
+ { n:'x^2 - 1', d:'x^2 + 3x - 4', ans:'\\dfrac{x+1}{x+4}', opts:['\\dfrac{x+1}{x+4}','\\dfrac{x-1}{x+4}','\\dfrac{x-1}{x-4}','\\dfrac{x+1}{x-4}'] },
+ ];
+ let cur = null, i = 1, score = 0, shuffled = [];
+ function show(){
+ cur = shuffled[i-1];
+ document.getElementById('p10f-i').textContent = i;
+ document.getElementById('p10f-task').innerHTML = '$\\dfrac{' + cur.n + '}{' + cur.d + '} = \\ ?$';
+ renderMath(document.getElementById('p10f-task'));
+ const opts = document.getElementById('p10f-opts');
+ opts.innerHTML = '';
+ [...cur.opts].sort(()=>Math.random()-0.5).forEach(o=>{
+ const b = document.createElement('button');
+ b.className = 'btn';
+ b.innerHTML = '$' + o + '$';
+ b.style.cssText = 'text-align:left;padding:10px 14px';
+ b.addEventListener('click', ()=>{
+ const fb = document.getElementById('p10f-fb'); fb.style.display = 'block';
+ if(o === cur.ans){ score++; b.classList.add('ok'); feedback(fb, true, '✓'); }
+ else { b.classList.add('fail'); feedback(fb, false, 'Правильно: $' + cur.ans + '$'); renderMath(fb); }
+ document.getElementById('p10f-score').textContent = score;
+ if(i >= tasks.length){ setTimeout(()=>{ feedback(fb, score >= 4, 'Итог: ' + score + '/' + tasks.length); if(score >= 4){ achievement('p10_fraction'); bumpProgress('p10', 14); confetti(); } }, 600); }
+ else { i++; setTimeout(show, 900); }
+ });
+ opts.appendChild(b);
+ });
+ renderMath(opts);
+ document.getElementById('p10f-fb').style.display = 'none';
+ }
+ document.getElementById('p10f-start').addEventListener('click', ()=>{ i=1; score=0; document.getElementById('p10f-score').textContent = 0; shuffled = [...tasks].sort(()=>Math.random()-0.5); show(); });
+ })();
+
+ /* INIT 5 — Drag */
+ (function(){
+ const items = [
+ { id:1, txt:'$x^2 - 5x + 6$', cat:'yes' },
+ { id:2, txt:'$x^2 + 1$', cat:'no' },
+ { id:3, txt:'$2x^2 + 5x - 3$', cat:'yes' },
+ { id:4, txt:'$x^2 - 2x + 5$', cat:'no' },
+ { id:5, txt:'$x^2 - 9$', cat:'yes' },
+ { id:6, txt:'$3x^2 + x + 1$', cat:'no' },
+ { id:7, txt:'$x^2 + 6x + 9$', cat:'yes' },
+ { id:8, txt:'$x^2 + 4$', cat:'no' },
+ ];
+ const cats = ['yes','no'];
+ const labels = { yes:'Раскл.', no:'Не раскл.' };
+ let placed = {};
+ function makeChip(it, where){
+ const wrap = document.createElement('div');
+ wrap.style.cssText = 'display:inline-flex;align-items:center;gap:4px;background:var(--sec-acc-soft);border-radius:8px;padding:3px 6px;margin:2px';
+ const sp = document.createElement('span');
+ sp.innerHTML = it.txt; sp.style.cssText = 'padding:2px 4px';
+ wrap.appendChild(sp);
+ if(where === 'pool'){
+ cats.forEach(cat=>{
+ const b = document.createElement('button');
+ b.className = 'btn small'; b.textContent = labels[cat];
+ b.style.cssText = 'padding:3px 7px;font-size:.72rem';
+ b.addEventListener('click', ()=>{ placed[it.id] = cat; render(); });
+ wrap.appendChild(b);
+ });
+ } else {
+ const b = document.createElement('button');
+ b.className = 'btn small'; b.textContent = '×';
+ b.style.cssText = 'padding:2px 7px';
+ b.addEventListener('click', ()=>{ delete placed[it.id]; render(); });
+ wrap.appendChild(b);
+ }
+ return wrap;
+ }
+ function render(){
+ const pool = document.getElementById('p10z-pool');
+ pool.innerHTML = '';
+ items.forEach(it=>{ if(!placed[it.id]) pool.appendChild(makeChip(it, 'pool')); });
+ cats.forEach(cat=>{
+ const box = document.querySelector('#p10-body .drop-items[data-cat="' + cat + '"]');
+ if(!box) return;
+ box.innerHTML = '';
+ items.forEach(it=>{ if(placed[it.id] === cat) box.appendChild(makeChip(it, 'placed')); });
+ });
+ if(window.renderMathInElement) renderMath(pool.parentElement);
+ }
+ document.getElementById('p10z-check').addEventListener('click', ()=>{
+ const fb = document.getElementById('p10z-fb');
+ fb.style.display = 'block';
+ const placedCount = Object.keys(placed).length;
+ if(placedCount < items.length){ feedback(fb, false, '⚠ Разложите все ' + items.length + ' трёхчленов.'); return; }
+ let ok = 0; items.forEach(it=>{ if(placed[it.id] === it.cat) ok++; });
+ if(ok === items.length){ feedback(fb, true, '✓ Все ' + items.length + ' верно!'); achievement('p10_sort'); bumpProgress('p10', 14); confetti(); }
+ else feedback(fb, false, 'Верно ' + ok + ' из ' + items.length);
+ });
+ document.getElementById('p10z-reset').addEventListener('click', ()=>{ placed = {}; document.getElementById('p10z-fb').style.display='none'; render(); });
+ render();
+ })();
+}
function buildP11stub(){ document.getElementById('p11-body').innerHTML = `§ 11 — Текстовые задачи Скоро в Wave 3.
${secNav('p10','p12')}`; }
function buildP12stub(){ document.getElementById('p12-body').innerHTML = `§ 12 — Сводящиеся к квадратным Скоро в Wave 3.
${secNav('p11','final2')}`; }
function buildFinal2stub(){ document.getElementById('final2-body').innerHTML = `Финал главы Итоговая самооценка, практика, увлекательная математика и финальный босс — в Wave 4.
${secNav('p12',null)}`; }