fix(algebra-8 ch2): шаговые решатели — теперь действительно по одному шагу

Три «пошаговых» решателя дампили все шаги сразу при первом клике.
Переписаны на прогрессивное раскрытие:
- § 8 INT 5 «Пошаговый решатель» (квадратное)
- § 10 INT 2 «Пошаговый разлагатель»
- § 12 INT 1 «Решатель биквадратного»

Паттерн: Старт → шаги собираются в массив, idx=0 → Дальше (1/N) →
каждый шаг — отдельный блок с border-left и fadeIn. По окончании —
кнопка «Готово», начисление достижения и confetti. Кнопка «Сначала»
сбрасывает к Старту.

Ещё: § 8 INT 4 — $D = b^2 - 4ac$ показывался буквально с долларами,
потому что использовался textContent + renderMath на чужом элементе.
Заменено на innerHTML + renderMath на правильный узел.
This commit is contained in:
Maxim Dolgolyov
2026-05-27 15:21:45 +03:00
parent 7a85007777
commit 75792c93aa
+156 -55
View File
@@ -1037,12 +1037,14 @@ function buildP10(){
<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$ — система разложит трёхчлен по шагам.',`
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>
<button class="btn primary" id="p10s-go">Старт</button>
<button class="btn" id="p10s-next" style="display:none">Дальше</button>
<button class="btn" id="p10s-reset" style="display:none">Сначала</button>
</div>
<div id="p10s-stage" style="padding:14px;background:var(--card-soft);border-radius:10px;min-height:80px"></div>`);
@@ -1121,24 +1123,51 @@ function buildP10(){
refresh();
})();
/* INIT 2 */
/* 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 stage = document.getElementById('p10s-stage');
const goBtn = document.getElementById('p10s-go');
const nextBtn = document.getElementById('p10s-next');
const resetBtn = document.getElementById('p10s-reset');
let steps = [], idx = 0, awarded = false;
function build(a, b, c){
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 arr = [];
arr.push('<b>Шаг 1.</b> Исходный трёхчлен: $' + a + 'x^2 ' + (b >= 0 ? '+ ' + b : '- ' + Math.abs(b)) + 'x ' + (c >= 0 ? '+ ' + c : '- ' + Math.abs(c)) + '$');
arr.push('<b>Шаг 2.</b> $D = b^2 - 4ac = ' + (b*b) + ' - ' + (4*a*c) + ' = ' + D + '$');
if(D < 0){
arr.push('<b>Шаг 3.</b> $D < 0$ — на множители первой степени с действительными коэффициентами не раскладывается.');
} 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>';
arr.push('<b>Шаг 3.</b> Корни: $x_1 = ' + fmt(x1) + ',\\ x_2 = ' + fmt(x2) + '$');
arr.push('<b>Шаг 4.</b> Подставляем в формулу $a(x-x_1)(x-x_2)$:');
arr.push('<b>Ответ:</b> $' + (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);
return arr;
}
function render(){
stage.innerHTML = steps.slice(0, idx + 1)
.map(s => `<div style="margin:8px 0;padding:10px 12px;background:var(--card);border-left:3px solid var(--sec-acc);border-radius:7px;animation:fadeIn .3s ease">${s}</div>`)
.join('');
renderMath(stage);
if(idx >= steps.length - 1){
nextBtn.disabled = true; nextBtn.textContent = 'Готово';
if(!awarded){ awarded = true; achievement('p10_steps'); bumpProgress('p10', 14); confetti(); }
} else {
nextBtn.disabled = false; nextBtn.textContent = 'Дальше (' + (idx + 1) + '/' + steps.length + ')';
}
}
goBtn.addEventListener('click', ()=>{
const a = +document.getElementById('p10s-a').value, b = +document.getElementById('p10s-b').value, c = +document.getElementById('p10s-c').value;
if(!a){ stage.innerHTML = '<p>$a$ не может быть нулём.</p>'; renderMath(stage); return; }
steps = build(a, b, c); idx = 0; awarded = false;
goBtn.style.display = 'none'; nextBtn.style.display = ''; resetBtn.style.display = '';
render();
});
nextBtn.addEventListener('click', ()=>{ if(idx < steps.length - 1){ idx++; render(); } });
resetBtn.addEventListener('click', ()=>{
idx = 0; steps = []; awarded = false; stage.innerHTML = '';
goBtn.style.display = ''; nextBtn.style.display = 'none'; resetBtn.style.display = 'none';
});
})();
@@ -1568,12 +1597,14 @@ function buildP12(){
<p style="margin-top:6px"><b>3)</b> $\\dfrac{x}{x-2} - \\dfrac{2}{x+2} = 1$. ОДЗ: $x \\neq \\pm 2$. Умножим на $(x-2)(x+2)$: $x(x+2) - 2(x-2) = (x-2)(x+2) \\Rightarrow x^2 + 2x - 2x + 4 = x^2 - 4 \\Rightarrow 4 = -4$ — противоречие, корней нет.</p>`);
/* INT 1 — Биквадратное пошагово */
html += widget('Решатель биквадратного','INTERACT 1','Введите $a$, $b$, $c$ — система применит замену $t=x^2$ и доведёт до конца.',`
html += widget('Решатель биквадратного','INTERACT 1','Введите $a$, $b$, $c$ и нажимайте «Дальше» — шаги замены и решения.',`
<div style="display:flex;gap:10px;flex-wrap:wrap;align-items:center;margin-bottom:10px">
<label>$a$ = <input type="number" id="p12b-a" value="1" style="width:70px;padding:6px;border:1.5px solid var(--border);border-radius:6px"></label>
<label>$b$ = <input type="number" id="p12b-b" value="-5" style="width:70px;padding:6px;border:1.5px solid var(--border);border-radius:6px"></label>
<label>$c$ = <input type="number" id="p12b-c" value="4" style="width:70px;padding:6px;border:1.5px solid var(--border);border-radius:6px"></label>
<button class="btn primary" id="p12b-go">Решить</button>
<button class="btn primary" id="p12b-go">Старт</button>
<button class="btn" id="p12b-next" style="display:none">Дальше</button>
<button class="btn" id="p12b-reset" style="display:none">Сначала</button>
</div>
<div id="p12b-out" style="padding:14px;background:var(--sec-acc-soft);border-radius:10px;line-height:1.7;min-height:60px"></div>`);
@@ -1626,33 +1657,59 @@ function buildP12(){
box.innerHTML = html;
if(window.renderMathInElement) setTimeout(()=>renderMath(box), 0);
/* INIT 1 — Биквадратное */
/* INIT 1 — Биквадратное пошагово */
(function(){
document.getElementById('p12b-go').addEventListener('click', ()=>{
const a = +document.getElementById('p12b-a').value;
const b = +document.getElementById('p12b-b').value;
const c = +document.getElementById('p12b-c').value;
const out = document.getElementById('p12b-out');
if(!a){ out.innerHTML = '$a \\neq 0$'; renderMath(out); return; }
let html = '<div><b>Дано:</b> $' + a + 'x^4 ' + (b >= 0 ? '+ ' + b : '- ' + Math.abs(b)) + 'x^2 ' + (c >= 0 ? '+ ' + c : '- ' + Math.abs(c)) + ' = 0$</div>';
html += '<div><b>Замена</b> $t = x^2,\\ t \\geq 0$: $' + a + 't^2 ' + (b >= 0 ? '+ ' + b : '- ' + Math.abs(b)) + 't ' + (c >= 0 ? '+ ' + c : '- ' + Math.abs(c)) + ' = 0$</div>';
const out = document.getElementById('p12b-out');
const goBtn = document.getElementById('p12b-go');
const nextBtn = document.getElementById('p12b-next');
const resetBtn = document.getElementById('p12b-reset');
let steps = [], idx = 0, awarded = false;
function build(a, b, c){
const arr = [];
arr.push('<b>Дано:</b> $' + a + 'x^4 ' + (b >= 0 ? '+ ' + b : '- ' + Math.abs(b)) + 'x^2 ' + (c >= 0 ? '+ ' + c : '- ' + Math.abs(c)) + ' = 0$');
arr.push('<b>Замена.</b> Пусть $t = x^2,\\ t \\geq 0$. Получаем $' + a + 't^2 ' + (b >= 0 ? '+ ' + b : '- ' + Math.abs(b)) + 't ' + (c >= 0 ? '+ ' + c : '- ' + Math.abs(c)) + ' = 0$');
const D = b*b - 4*a*c;
html += '<div><b>D</b> = ' + (b*b) + ' ' + (4*a*c) + ' = ' + D + '</div>';
if(D < 0){ html += '<div>$D < 0$ &rarr; нет $t$, значит нет $x$.</div>'; }
else {
arr.push('<b>Дискриминант.</b> $D = ' + (b*b) + ' - ' + (4*a*c) + ' = ' + D + '$');
if(D < 0){
arr.push('<b>Анализ.</b> $D < 0 \\Rightarrow$ нет $t$, значит нет $x$.');
arr.push('<b>Ответ:</b> корней нет.');
} else {
const t1 = (-b - Math.sqrt(D))/(2*a), t2 = (-b + Math.sqrt(D))/(2*a);
html += '<div>$t_1 = ' + fmt(t1) + ',\\ t_2 = ' + fmt(t2) + '$</div>';
arr.push('<b>Корни вспомогательного.</b> $t_1 = ' + fmt(t1) + ',\\ t_2 = ' + fmt(t2) + '$');
const roots = [];
[t1, t2].forEach((t, k)=>{
if(t > 0){ const r = Math.sqrt(t); roots.push(r, -r); html += '<div>$t_' + (k+1) + ' = ' + fmt(t) + ' > 0 \\Rightarrow x = \\pm' + fmt(r) + '$</div>'; }
else if(t === 0){ roots.push(0); html += '<div>$t_' + (k+1) + ' = 0 \\Rightarrow x = 0$</div>'; }
else html += '<div>$t_' + (k+1) + ' = ' + fmt(t) + ' < 0$ — не подходит</div>';
if(t > 0){ const r = Math.sqrt(t); roots.push(r, -r); arr.push('<b>Возврат.</b> $t_' + (k+1) + ' = ' + fmt(t) + ' > 0 \\Rightarrow x = \\pm\\sqrt{' + fmt(t) + '} = \\pm' + fmt(r) + '$'); }
else if(t === 0){ roots.push(0); arr.push('<b>Возврат.</b> $t_' + (k+1) + ' = 0 \\Rightarrow x = 0$'); }
else arr.push('<b>Возврат.</b> $t_' + (k+1) + ' = ' + fmt(t) + ' < 0$ — не подходит, ведь $t \\geq 0$.');
});
const u = [...new Set(roots)].sort((a,b)=>a-b);
html += '<div style="margin-top:8px;font-size:1.05rem"><b>Ответ:</b> ' + (u.length ? '$\\{' + u.map(fmt).join(';\\ ') + '\\}$' : 'корней нет') + '</div>';
arr.push('<b>Ответ:</b> ' + (u.length ? '$\\{' + u.map(fmt).join(';\\ ') + '\\}$' : 'корней нет'));
}
out.innerHTML = html; renderMath(out);
achievement('p12_bi'); bumpProgress('p12', 14);
return arr;
}
function render(){
out.innerHTML = steps.slice(0, idx + 1)
.map(s => `<div style="margin:6px 0;padding:9px 12px;background:var(--card);border-left:3px solid var(--sec-acc);border-radius:7px;animation:fadeIn .3s ease">${s}</div>`)
.join('');
renderMath(out);
if(idx >= steps.length - 1){
nextBtn.disabled = true; nextBtn.textContent = 'Готово';
if(!awarded){ awarded = true; achievement('p12_bi'); bumpProgress('p12', 14); confetti(); }
} else {
nextBtn.disabled = false; nextBtn.textContent = 'Дальше (' + (idx + 1) + '/' + steps.length + ')';
}
}
goBtn.addEventListener('click', ()=>{
const a = +document.getElementById('p12b-a').value, b = +document.getElementById('p12b-b').value, c = +document.getElementById('p12b-c').value;
if(!a){ out.innerHTML = '$a \\neq 0$'; renderMath(out); return; }
steps = build(a, b, c); idx = 0; awarded = false;
goBtn.style.display = 'none'; nextBtn.style.display = ''; resetBtn.style.display = '';
render();
});
nextBtn.addEventListener('click', ()=>{ if(idx < steps.length - 1){ idx++; render(); } });
resetBtn.addEventListener('click', ()=>{
idx = 0; steps = []; awarded = false; out.innerHTML = '';
goBtn.style.display = ''; nextBtn.style.display = 'none'; resetBtn.style.display = 'none';
});
})();
@@ -2585,12 +2642,14 @@ function buildP8(){
<button class="btn primary" id="p8t-start" style="margin-top:10px">Начать</button>`);
/* INTERACTIVE 5: пошаговый */
html += widget('Пошаговый решатель','INTERACT 5','Введите $a$, $b$, $c$ — система разложит решение по шагам.',`
html += widget('Пошаговый решатель','INTERACT 5','Введите $a$, $b$, $c$ и нажимайте «Дальше» — каждый шаг появляется отдельно.',`
<div style="display:flex;gap:10px;flex-wrap:wrap;align-items:center;margin-bottom:10px">
<label>$a$ = <input type="number" id="p8s-a" value="1" style="width:70px;padding:6px;border:1.5px solid var(--border);border-radius:6px"></label>
<label>$b$ = <input type="number" id="p8s-b" value="-7" style="width:70px;padding:6px;border:1.5px solid var(--border);border-radius:6px"></label>
<label>$c$ = <input type="number" id="p8s-c" value="12" style="width:70px;padding:6px;border:1.5px solid var(--border);border-radius:6px"></label>
<button class="btn primary" id="p8s-go">Решить пошагово</button>
<button class="btn primary" id="p8s-go">Старт</button>
<button class="btn" id="p8s-next" style="display:none">Дальше</button>
<button class="btn" id="p8s-reset" style="display:none">Сначала</button>
</div>
<div id="p8s-stage" style="padding:14px;background:var(--card-soft);border-radius:10px;min-height:100px"></div>`);
@@ -2755,8 +2814,9 @@ function buildP8(){
const txt = 'x^2 ' + (b >= 0 ? '+ ' + b : '- ' + Math.abs(b)) + 'x ' + (c >= 0 ? '+ ' + c : '- ' + Math.abs(c)) + ' = 0';
document.getElementById('p8t-task').innerHTML = '$' + txt + '$';
document.getElementById('p8t-i').textContent = i;
document.getElementById('p8t-d-hint').textContent = 'Подскажу: $D = b^2 - 4ac$';
document.getElementById('p8t-d-hint').innerHTML = 'Подскажу: $D = b^2 - 4ac$';
renderMath(document.getElementById('p8t-task'));
renderMath(document.getElementById('p8t-d-hint'));
document.getElementById('p8t-fb').style.display = 'none';
}
function answer(n){
@@ -2775,25 +2835,66 @@ function buildP8(){
document.getElementById('p8t-0').addEventListener('click', ()=>answer(0));
})();
/* INIT INTERACTIVE 5 */
/* INIT INTERACTIVE 5 — пошаговый по одному шагу */
(function initSteps(){
document.getElementById('p8s-go').addEventListener('click', ()=>{
const stage = document.getElementById('p8s-stage');
const goBtn = document.getElementById('p8s-go');
const nextBtn = document.getElementById('p8s-next');
const resetBtn = document.getElementById('p8s-reset');
let steps = [], idx = 0, awarded = false;
function buildSteps(a, b, c){
const D = b*b - 4*a*c;
const arr = [];
arr.push('<b>Шаг 1.</b> Выпишем коэффициенты: $a = ' + a + ',\\ b = ' + b + ',\\ c = ' + c + '$');
arr.push('<b>Шаг 2.</b> $D = b^2 - 4ac = (' + b + ')^2 - 4 \\cdot (' + a + ') \\cdot (' + c + ') = ' + (b*b) + ' - ' + (4*a*c) + ' = ' + D + '$');
arr.push('<b>Шаг 3.</b> ' + (D > 0 ? '$D > 0 \\Rightarrow$ два корня' : D === 0 ? '$D = 0 \\Rightarrow$ один корень' : '$D < 0 \\Rightarrow$ корней нет'));
if(D >= 0){
arr.push('<b>Шаг 4.</b> $\\sqrt{D} = \\sqrt{' + D + '} = ' + fmt(Math.sqrt(D)) + '$');
if(D > 0){
const r1 = (-b - Math.sqrt(D))/(2*a), r2 = (-b + Math.sqrt(D))/(2*a);
arr.push('<b>Шаг 5.</b> $x_{1,2} = \\dfrac{-b \\pm \\sqrt{D}}{2a} = \\dfrac{' + (-b) + ' \\pm ' + fmt(Math.sqrt(D)) + '}{' + (2*a) + '}$');
arr.push('<b>Шаг 6.</b> $x_1 = ' + fmt(r1) + ',\\ x_2 = ' + fmt(r2) + '$');
} else {
arr.push('<b>Шаг 5.</b> $x = \\dfrac{-b}{2a} = \\dfrac{' + (-b) + '}{' + (2*a) + '} = ' + fmt(-b/(2*a)) + '$');
}
}
arr.push('<b>Ответ:</b> ' + (D > 0 ? 'два корня' : D === 0 ? 'один корень' : 'корней нет'));
return arr;
}
function render(){
const html = steps.slice(0, idx + 1)
.map((s, k)=>`<div class="step-line" style="margin:8px 0;padding:10px 12px;background:var(--card);border-left:3px solid var(--sec-acc);border-radius:7px;animation:fadeIn .3s ease">${s}</div>`)
.join('');
stage.innerHTML = html;
renderMath(stage);
if(idx >= steps.length - 1){
nextBtn.disabled = true;
nextBtn.textContent = 'Готово';
if(!awarded){ awarded = true; achievement('p8_steps'); bumpProgress('p8', 14); confetti(); }
} else {
nextBtn.disabled = false;
nextBtn.textContent = 'Дальше (' + (idx + 1) + '/' + steps.length + ')';
}
}
goBtn.addEventListener('click', ()=>{
const a = +document.getElementById('p8s-a').value;
const b = +document.getElementById('p8s-b').value;
const c = +document.getElementById('p8s-c').value;
const stage = document.getElementById('p8s-stage');
if(!a){ stage.innerHTML = '<p>$a$ не может быть нулём.</p>'; return; }
const D = b*b - 4*a*c;
let html = '<p><b>Шаг 1:</b> $a = ' + a + ',\\ b = ' + b + ',\\ c = ' + c + '$</p>';
html += '<p><b>Шаг 2:</b> $D = b^2 - 4ac = ' + b + '^2 - 4 \\cdot ' + a + ' \\cdot ' + c + ' = ' + (b*b) + ' - ' + (4*a*c) + ' = ' + D + '$</p>';
html += '<p><b>Шаг 3:</b> ' + (D > 0 ? '$D > 0$ &rarr; два корня' : D === 0 ? '$D = 0$ &rarr; один корень' : '$D < 0$ &rarr; корней нет') + '</p>';
if(D >= 0){
html += '<p><b>Шаг 4:</b> $\\sqrt{D} = ' + fmt(Math.sqrt(D)) + '$</p>';
if(D > 0){ const r1 = (-b - Math.sqrt(D))/(2*a), r2 = (-b + Math.sqrt(D))/(2*a); html += '<p><b>Шаг 5:</b> $x_1 = \\dfrac{' + (-b) + ' - ' + fmt(Math.sqrt(D)) + '}{' + (2*a) + '} = ' + fmt(r1) + ',\\ x_2 = ' + fmt(r2) + '$</p>'; }
else html += '<p><b>Шаг 5:</b> $x = \\dfrac{' + (-b) + '}{' + (2*a) + '} = ' + fmt(-b/(2*a)) + '$</p>';
}
stage.innerHTML = html; renderMath(stage);
achievement('p8_steps'); bumpProgress('p8', 12);
if(!a){ stage.innerHTML = '<p>$a$ не может быть нулём.</p>'; renderMath(stage); return; }
steps = buildSteps(a, b, c);
idx = 0; awarded = false;
goBtn.style.display = 'none';
nextBtn.style.display = ''; resetBtn.style.display = '';
nextBtn.disabled = false;
render();
});
nextBtn.addEventListener('click', ()=>{
if(idx < steps.length - 1){ idx++; render(); }
});
resetBtn.addEventListener('click', ()=>{
idx = 0; steps = []; awarded = false;
stage.innerHTML = '';
goBtn.style.display = ''; nextBtn.style.display = 'none'; resetBtn.style.display = 'none';
});
})();