feat(algebra-8 ch2): Wave 2 — §9 (Виета) + §10 (Разложение)

§ 9 «Теорема Виета»:
- Теория, обратная теорема, общий случай (a≠1), знаки корней
- INTERACT 1: Тренажёр устного подбора (10 уравнений)
- INTERACT 2: Конструктор «корни → уравнение»
- INTERACT 3: Знаки корней (8 раундов: оба+, оба-, разные, нет)
- INTERACT 4: Быстрая проверка корней через Виета
- INTERACT 5: Виета для непривед. (сумма −b/a, произведение c/a)

§ 10 «Квадратный трёхчлен. Разложение»:
- Теория, алгоритм, формула ax²+bx+c = a(x−x₁)(x−x₂)
- INTERACT 1: Конструктор разложения (a, x1, x2 → трёхчлен)
- INTERACT 2: Пошаговый разлагатель (D, корни, разложение)
- INTERACT 3: Тренажёр (8 трёхчленов → корни)
- INTERACT 4: Сокращение дробей (5 задач, выбор из 4 вариантов)
- INTERACT 5: Разложимо или нет (8 трёхчленов по D)

ACH_LABELS добавлены для p9_* и p10_*.
Сайдбары для §9 и §10 заполнены формулами.
This commit is contained in:
Maxim Dolgolyov
2026-05-27 14:46:03 +03:00
parent 2c8eb84c65
commit 90d0c41fd0
+561 -4
View File
@@ -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 = `<div class="card"><div class="card-body"><p style="text-align:center;padding:20px"><b>§ 9 — Теорема Виета</b><br><br>Этот параграф будет реализован в следующей волне обновлений.<br><br><small>Wave 2: § 9 + § 10</small></p></div></div>${secNav('p8','p10')}`; }
function buildP10stub(){ document.getElementById('p10-body').innerHTML = `<div class="card"><div class="card-body"><p style="text-align:center;padding:20px"><b>§ 10 — Квадратный трёхчлен. Разложение на множители</b><br><br>Скоро в Wave 2.</p></div></div>${secNav('p9','p11')}`; }
function buildP9stub(){ buildP9(); }
function buildP9(){
const box = document.getElementById('p9-body');
let html = '';
html += makeCard('repeat','Повторение',null,`
<ul style="margin-left:18px;line-height:1.7">
<li><b>Приведённое</b> уравнение: $x^2 + px + q = 0$ — старший коэффициент равен $1$.</li>
<li>Полное: $ax^2 + bx + c = 0$ &rarr; делим на $a$, получим $x^2 + \\dfrac{b}{a}x + \\dfrac{c}{a} = 0$.</li>
<li>Формулы корней через дискриминант — из § 8.</li>
</ul>`);
html += makeCard('theory','Теорема Виета','9.1',`
<p>Пусть $x_1$ и $x_2$ — корни приведённого уравнения $x^2 + px + q = 0$. Тогда:</p>
<div style="background:var(--sec-acc-soft);border-radius:10px;padding:14px;margin:10px 0;text-align:center;font-size:1.15rem">$$x_1 + x_2 = -p,\\qquad x_1 \\cdot x_2 = q$$</div>
<p><b>Обратная теорема.</b> Если для чисел $x_1, x_2$ выполнено $x_1+x_2=-p$ и $x_1 x_2=q$, то они — корни $x^2+px+q=0$.</p>`);
html += makeCard('rule','Общий случай (a ≠ 1)','9.2',`
<p>Для уравнения $ax^2 + bx + c = 0$:</p>
<div style="background:var(--sec-acc-soft);border-radius:10px;padding:14px;margin:10px 0;text-align:center">$$x_1 + x_2 = -\\dfrac{b}{a},\\qquad x_1 \\cdot x_2 = \\dfrac{c}{a}$$</div>
<p><b>Анализ знаков</b> (если корни вещественные):</p>
<ul style="margin-left:18px;line-height:1.7">
<li>$q > 0$ &rarr; корни одного знака; если ещё $-p > 0$ — оба положительные.</li>
<li>$q < 0$ &rarr; корни разных знаков.</li>
<li>$q = 0$ &rarr; один из корней равен нулю.</li>
</ul>`);
html += makeCard('example','Пример',null,`
<p><b>Подбор:</b> $x^2 - 7x + 12 = 0$. Ищем $x_1, x_2$: сумма $7$, произведение $12$. Это $3$ и $4$. Ответ: $\\{3;\\ 4\\}$.</p>
<p style="margin-top:6px"><b>Составление:</b> найти уравнение с корнями $-2$ и $5$. Сумма $= 3$, произведение $= -10$. Уравнение: $x^2 - 3x - 10 = 0$.</p>`);
/* ===== INTERACTIVE 1: Подбор корней ===== */
html += widget('Тренажёр Виета: устный подбор','INTERACT 1','Дано приведённое уравнение. Подберите корни в уме и введите через точку с запятой.',`
<div class="score-display"><span>Задача <b id="p9v-i">1</b> / 10</span><span>Очки: <b id="p9v-score">0</b></span></div>
<div id="p9v-task" style="font-size:1.4rem;text-align:center;padding:18px;background:var(--sec-acc-soft);border-radius:10px;margin-bottom:10px"></div>
<div style="display:flex;gap:8px;justify-content:center;flex-wrap:wrap">
<input type="text" id="p9v-inp" placeholder="например: 3; 4" style="width:200px;padding:8px;border:1.5px solid var(--border);border-radius:8px">
<button class="btn primary" id="p9v-go">Ответ</button>
<button class="btn" id="p9v-skip">Пропустить</button>
</div>
<div class="feedback" id="p9v-fb" style="display:none;margin-top:10px"></div>
<button class="btn primary" id="p9v-start" style="margin-top:10px">Начать</button>`);
/* ===== INTERACTIVE 2: Составить уравнение ===== */
html += widget('Конструктор: корни → уравнение','INTERACT 2','Выберите корни — система соберёт приведённое уравнение по Виета.',`
<div style="display:flex;gap:10px;flex-wrap:wrap;align-items:center;justify-content:center;margin-bottom:10px">
<label>$x_1$ = <input type="number" id="p9c-r1" value="2" style="width:80px;padding:6px;border:1.5px solid var(--border);border-radius:6px"></label>
<label>$x_2$ = <input type="number" id="p9c-r2" value="-3" style="width:80px;padding:6px;border:1.5px solid var(--border);border-radius:6px"></label>
</div>
<div id="p9c-out" style="padding:14px;background:var(--sec-acc-soft);border-radius:10px;font-size:1.05rem;line-height:1.8"></div>`);
/* ===== INTERACTIVE 3: Знаки корней ===== */
html += widget('Знаки корней по Виета','INTERACT 3','По уравнению определите знаки корней (если они существуют).',`
<div class="score-display"><span>Раунд <b id="p9z-i">1</b> / 8</span><span>Правильно: <b id="p9z-score">0</b></span></div>
<div id="p9z-task" style="font-size:1.3rem;text-align:center;padding:16px;background:var(--sec-acc-soft);border-radius:10px;margin-bottom:10px"></div>
<div style="display:flex;gap:6px;flex-wrap:wrap;justify-content:center">
<button class="btn" data-z="pp">Оба «+»</button>
<button class="btn" data-z="nn">Оба «−»</button>
<button class="btn" data-z="pn">Разных знаков</button>
<button class="btn" data-z="none">Корней нет</button>
</div>
<div class="feedback" id="p9z-fb" style="display:none;margin-top:10px"></div>
<button class="btn primary" id="p9z-start" style="margin-top:10px">Начать</button>`);
/* ===== INTERACTIVE 4: Проверка корней по Виета ===== */
html += widget('Быстрая проверка корней','INTERACT 4','Подставьте найденные корни — проверьте через Виета мгновенно.',`
<div style="display:flex;gap:8px;flex-wrap:wrap;align-items:center;margin-bottom:10px">
<label>Уравнение: $x^2$ + <input type="number" id="p9p-p" value="-5" style="width:60px;padding:5px;border:1.5px solid var(--border);border-radius:6px">$x$ + <input type="number" id="p9p-q" value="6" style="width:60px;padding:5px;border:1.5px solid var(--border);border-radius:6px"> = 0</label>
</div>
<div style="display:flex;gap:8px;flex-wrap:wrap;align-items:center">
<label>Ваши $x_1, x_2$: <input type="number" id="p9p-r1" value="2" style="width:70px;padding:5px;border:1.5px solid var(--border);border-radius:6px">, <input type="number" id="p9p-r2" value="3" style="width:70px;padding:5px;border:1.5px solid var(--border);border-radius:6px"></label>
<button class="btn primary" id="p9p-check">Проверить</button>
</div>
<div id="p9p-out" style="margin-top:10px;padding:10px 14px;border-radius:8px"></div>`);
/* ===== INTERACTIVE 5: Виета для непривед. ===== */
html += widget('Виета для $a \\neq 1$','INTERACT 5','Уравнение не приведённое. Введите сумму и произведение корней.',`
<div id="p9n-task" style="font-size:1.2rem;text-align:center;padding:14px;background:var(--sec-acc-soft);border-radius:10px;margin-bottom:10px"></div>
<div style="display:flex;gap:10px;flex-wrap:wrap;align-items:center;justify-content:center">
<label>$x_1+x_2$ = <input type="text" id="p9n-s" style="width:80px;padding:6px;border:1.5px solid var(--border);border-radius:6px"></label>
<label>$x_1 x_2$ = <input type="text" id="p9n-p" style="width:80px;padding:6px;border:1.5px solid var(--border);border-radius:6px"></label>
<button class="btn primary" id="p9n-go">Проверить</button>
<button class="btn" id="p9n-next">Следующее</button>
</div>
<div class="feedback" id="p9n-fb" style="display:none;margin-top:10px"></div>`);
html += makeCard('oral','Устные вопросы',null,`
<ol style="margin-left:18px;line-height:1.8">
<li>Сформулируйте теорему Виета для приведённого уравнения.</li>
<li>Что говорит обратная теорема?</li>
<li>Корни уравнения $x^2 - 9x + 20 = 0$ — целые? Найдите устно.</li>
<li>Какого знака произведение корней уравнения $x^2 + 3x - 10 = 0$?</li>
</ol>`);
html += makeCard('class','Класс — решите по Виета',null,`
<ol style="margin-left:18px;line-height:1.8">
<li>$x^2 - 11x + 28 = 0$</li>
<li>$x^2 + x - 12 = 0$</li>
<li>$x^2 + 10x + 21 = 0$</li>
<li>Составьте приведённое уравнение с корнями $-4$ и $7$.</li>
</ol>`);
html += makeCard('home','Домашка',null,`
<ol style="margin-left:18px;line-height:1.8">
<li>$x^2 - 8x + 15 = 0$</li>
<li>$x^2 + 2x - 35 = 0$</li>
<li>Составьте уравнение с корнями $\\dfrac{1}{2}$ и $-3$.</li>
<li>Найдите $p$, если корни $x^2 + px - 6 = 0$ — это $2$ и $-3$.</li>
</ol>`);
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, '&#10003; Верно: ' + 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 = '<div><b>Сумма:</b> $x_1 + x_2 = ' + r1 + ' + (' + r2 + ') = ' + (r1+r2) + ' \\Rightarrow -p = ' + (r1+r2) + ' \\Rightarrow p = ' + p + '$</div>';
s += '<div><b>Произведение:</b> $x_1 \\cdot x_2 = ' + r1 + ' \\cdot (' + r2 + ') = ' + q + ' \\Rightarrow q = ' + q + '$</div>';
s += '<div style="margin-top:8px;font-size:1.15rem"><b>Уравнение:</b> $x^2 ' + (p >= 0 ? '+ ' + p : '- ' + Math.abs(p)) + 'x ' + (q >= 0 ? '+ ' + q : '- ' + Math.abs(q)) + ' = 0$</div>';
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, '&#10003; Верно'); }
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 = '<div>$x_1 + x_2 = ' + sum + '$ vs $-p = ' + (-p) + '$ &mdash; ' + (okS ? '<span style="color:var(--ok)">&#10003;</span>' : '<span style="color:var(--bad)">&#10007;</span>') + '</div>';
html += '<div>$x_1 \\cdot x_2 = ' + prod + '$ vs $q = ' + q + '$ &mdash; ' + (okP ? '<span style="color:var(--ok)">&#10003;</span>' : '<span style="color:var(--bad)">&#10007;</span>') + '</div>';
if(okS && okP){ html += '<div style="margin-top:8px;color:var(--ok);font-weight:700">Корни верные!</div>'; achievement('p9_check'); bumpProgress('p9', 12); }
else html += '<div style="margin-top:8px;color:var(--bad);font-weight:700">Корни ошибочны.</div>';
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, '&#10003; Верно! $\\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,`
<ul style="margin-left:18px;line-height:1.7">
<li>Дискриминант $D = b^2 - 4ac$. Корни через формулу или Виета.</li>
<li><b>Формулы сокр. умножения:</b> $(a-b)(a+b) = a^2 - b^2$, $a^2 \\pm 2ab + b^2 = (a \\pm b)^2$.</li>
<li>Сокращение дробей: сокращаются равные множители числителя и знаменателя.</li>
</ul>`);
html += makeCard('theory','Что такое квадратный трёхчлен','10.1',`
<p><b>Квадратный трёхчлен</b> — это многочлен $ax^2 + bx + c$, где $a \\neq 0$. Числа $x_1, x_2$, при которых он равен нулю, называются <b>корнями</b> трёхчлена.</p>
<p style="margin-top:6px">Если $D \\geq 0$, трёхчлен раскладывается:</p>
<div style="background:var(--sec-acc-soft);border-radius:10px;padding:14px;margin:10px 0;text-align:center;font-size:1.15rem">$$ax^2 + bx + c = a(x - x_1)(x - x_2)$$</div>
<p>Если $D < 0$ — на множители первой степени с действительными коэффициентами не раскладывается.</p>`);
html += makeCard('algo','Алгоритм разложения',null,`
<ol style="margin-left:18px;line-height:1.8">
<li>Найти $D$ и корни $x_1, x_2$.</li>
<li>Если $D \\geq 0$, записать $a(x - x_1)(x - x_2)$.</li>
<li>Раскрыть скобки и проверить.</li>
</ol>`);
html += makeCard('example','Примеры',null,`
<p><b>1)</b> $x^2 - 5x + 6 = (x-2)(x-3)$.</p>
<p><b>2)</b> $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)$.</p>
<p><b>3)</b> $x^2 + 1$: $D = -4 < 0$ &rarr; не раскладывается.</p>
<p><b>4)</b> Сокращение: $\\dfrac{x^2 - 9}{x^2 - 5x + 6} = \\dfrac{(x-3)(x+3)}{(x-3)(x-2)} = \\dfrac{x+3}{x-2}$.</p>`);
/* INT 1 — Конструктор разложения */
html += widget('Конструктор разложения','INTERACT 1','Введите $a$, $x_1$, $x_2$ — получите трёхчлен и его разложение.',`
<div style="display:flex;gap:10px;flex-wrap:wrap;align-items:center;justify-content:center;margin-bottom:10px">
<label>$a$ = <input type="number" id="p10c-a" value="1" style="width:70px;padding:6px;border:1.5px solid var(--border);border-radius:6px"></label>
<label>$x_1$ = <input type="number" id="p10c-x1" value="2" style="width:70px;padding:6px;border:1.5px solid var(--border);border-radius:6px"></label>
<label>$x_2$ = <input type="number" id="p10c-x2" value="-3" style="width:70px;padding:6px;border:1.5px solid var(--border);border-radius:6px"></label>
</div>
<div id="p10c-out" style="padding:14px;background:var(--sec-acc-soft);border-radius:10px;font-size:1.05rem;line-height:1.8"></div>`);
/* INT 2 — Шаговый разлагатель */
html += widget('Пошаговый разлагатель','INTERACT 2','Введите $a$, $b$, $c$ — система разложит трёхчлен по шагам.',`
<div style="display:flex;gap:10px;flex-wrap:wrap;align-items:center;margin-bottom:10px">
<label>$a$ = <input type="number" id="p10s-a" value="1" style="width:70px;padding:6px;border:1.5px solid var(--border);border-radius:6px"></label>
<label>$b$ = <input type="number" id="p10s-b" value="-7" style="width:70px;padding:6px;border:1.5px solid var(--border);border-radius:6px"></label>
<label>$c$ = <input type="number" id="p10s-c" value="12" style="width:70px;padding:6px;border:1.5px solid var(--border);border-radius:6px"></label>
<button class="btn primary" id="p10s-go">Разложить</button>
</div>
<div id="p10s-stage" style="padding:14px;background:var(--card-soft);border-radius:10px;min-height:80px"></div>`);
/* INT 3 — Тренажёр разложения */
html += widget('Тренажёр разложения','INTERACT 3','Введите разложение в формате $(x-?)(x-?)$ — корни через точку с запятой, со знаками.',`
<div class="score-display"><span>Задача <b id="p10t-i">1</b> / 8</span><span>Очки: <b id="p10t-score">0</b></span></div>
<div id="p10t-task" style="font-size:1.3rem;text-align:center;padding:16px;background:var(--sec-acc-soft);border-radius:10px;margin-bottom:10px"></div>
<div style="display:flex;gap:8px;justify-content:center;flex-wrap:wrap">
<input type="text" id="p10t-inp" placeholder="корни: например 2; -3" style="width:200px;padding:8px;border:1.5px solid var(--border);border-radius:8px">
<button class="btn primary" id="p10t-go">Ответ</button>
</div>
<div class="feedback" id="p10t-fb" style="display:none;margin-top:10px"></div>
<button class="btn primary" id="p10t-start" style="margin-top:10px">Начать</button>`);
/* INT 4 — Сокращение дробей */
html += widget('Сокращение дробей','INTERACT 4','Дробь содержит квадратные трёхчлены. Разложите устно и введите сокращённую форму.',`
<div class="score-display"><span>Задача <b id="p10f-i">1</b> / 5</span><span>Очки: <b id="p10f-score">0</b></span></div>
<div id="p10f-task" style="font-size:1.2rem;text-align:center;padding:16px;background:var(--sec-acc-soft);border-radius:10px;margin-bottom:10px"></div>
<div id="p10f-opts" style="display:flex;flex-direction:column;gap:6px"></div>
<div class="feedback" id="p10f-fb" style="display:none;margin-top:10px"></div>
<button class="btn primary" id="p10f-start" style="margin-top:10px">Начать</button>`);
/* INT 5 — Drag: разложимо или нет */
html += widget('Разложимо или нет?','INTERACT 5','По знаку дискриминанта разнесите трёхчлены: раскладываются на множители или нет.',`
<div id="p10z-pool" style="display:flex;flex-wrap:wrap;gap:8px;margin-bottom:14px"></div>
<div class="drop-row" style="display:grid;grid-template-columns:1fr 1fr;gap:10px">
<div class="drop-box"><h5>Раскладывается ($D \\geq 0$)</h5><div class="drop-items" data-cat="yes"></div></div>
<div class="drop-box"><h5>Не раскладывается ($D < 0$)</h5><div class="drop-items" data-cat="no"></div></div>
</div>
<div class="actions"><button class="btn primary" id="p10z-check">Проверить</button><button class="btn" id="p10z-reset">Сначала</button></div>
<div class="feedback" id="p10z-fb" style="display:none"></div>`);
html += makeCard('oral','Устно',null,`
<ol style="margin-left:18px;line-height:1.8">
<li>При каком условии трёхчлен раскладывается?</li>
<li>Запишите разложение $x^2 - 4x + 3$.</li>
<li>Разложим ли $x^2 + 9$?</li>
</ol>`);
html += makeCard('class','Класс — разложите',null,`
<ol style="margin-left:18px;line-height:1.8">
<li>$x^2 - 8x + 15$</li>
<li>$2x^2 + 5x - 3$</li>
<li>$x^2 + 6x + 9$</li>
<li>Сократите: $\\dfrac{x^2 - 4}{x^2 + x - 6}$.</li>
</ol>`);
html += makeCard('home','Домашка',null,`
<ol style="margin-left:18px;line-height:1.8">
<li>$x^2 - 9x + 20$</li>
<li>$3x^2 - 8x + 4$</li>
<li>$x^2 + 4x + 7$ — раскладывается ли?</li>
<li>Сократите: $\\dfrac{x^2 - 25}{x^2 - 7x + 10}$.</li>
</ol>`);
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 = '<div><b>Трёхчлен:</b> $' + eq + '$</div><div><b>Разложение:</b> $' + fact + '$</div>';
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 = '<p>$a$ не может быть нулём.</p>'; return; }
const D = b*b - 4*a*c;
let html = '<p><b>Шаг 1:</b> $D = ' + (b*b) + ' - ' + (4*a*c) + ' = ' + D + '$</p>';
if(D < 0){ html += '<p><b>Шаг 2:</b> $D < 0$ &rarr; трёхчлен не раскладывается на множители с действительными коэффициентами.</p>'; }
else {
const x1 = (-b - Math.sqrt(D))/(2*a), x2 = (-b + Math.sqrt(D))/(2*a);
html += '<p><b>Шаг 2:</b> $x_1 = ' + fmt(x1) + ',\\ x_2 = ' + fmt(x2) + '$</p>';
html += '<p><b>Шаг 3:</b> $' + 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)) + ')$</p>';
}
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, '&#10003; $(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, '&#10003;'); }
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, '&#9888; Разложите все ' + items.length + ' трёхчленов.'); return; }
let ok = 0; items.forEach(it=>{ if(placed[it.id] === it.cat) ok++; });
if(ok === items.length){ feedback(fb, true, '&#10003; Все ' + 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 = `<div class="card"><div class="card-body"><p style="text-align:center;padding:20px"><b>§ 11 — Текстовые задачи</b><br><br>Скоро в Wave 3.</p></div></div>${secNav('p10','p12')}`; }
function buildP12stub(){ document.getElementById('p12-body').innerHTML = `<div class="card"><div class="card-body"><p style="text-align:center;padding:20px"><b>§ 12 — Сводящиеся к квадратным</b><br><br>Скоро в Wave 3.</p></div></div>${secNav('p11','final2')}`; }
function buildFinal2stub(){ document.getElementById('final2-body').innerHTML = `<div class="card"><div class="card-body"><p style="text-align:center;padding:20px"><b>Финал главы</b><br><br>Итоговая самооценка, практика, увлекательная математика и финальный босс — в Wave 4.</p></div></div>${secNav('p12',null)}`; }