feat(alg9 ch2 wave2): §8 «Чётные/нечётные» + §9 «Сдвиги графиков»

This commit is contained in:
Maxim Dolgolyov
2026-05-29 08:26:11 +03:00
parent bb40776fa8
commit 70c5641452
+676 -28
View File
@@ -1118,38 +1118,686 @@ function buildP7(){
}
function buildP8(){
const root = document.getElementById('p8-body');
root.innerHTML = `
<div class="card">
<div class="card-header">
<span class="card-icon theory">${ICONS.theory}</span>
<span class="card-title">В разработке</span>
<span class="card-num">§ 8</span>
</div>
<div class="card-body">
<p>Содержание параграфа <b>«Чётные и нечётные функции»</b> будет добавлено в следующих обновлениях.</p>
<p style="color:var(--muted);font-size:.9rem">Раздел Phase 1.</p>
</div>
</div>` + secNav('p7', 'p9') + readButton('p8');
renderMath(root);
const box = document.getElementById('p8-body');
let html = '';
html += makeCard('theory', 'Определения', '8.1', `
<p>Функция $y = f(x)$ называется <b>чётной</b>, если её область определения $D(f)$ симметрична относительно нуля и для всех $x \\in D(f)$ выполняется равенство:</p>
<p style="text-align:center;font-size:1.05rem">$f(-x) = f(x)$</p>
<p>Функция $y = f(x)$ называется <b>нечётной</b>, если её область определения $D(f)$ симметрична относительно нуля и для всех $x \\in D(f)$ выполняется равенство:</p>
<p style="text-align:center;font-size:1.05rem">$f(-x) = -f(x)$</p>
<p>Если ни одно из этих условий не выполняется — функцию называют <b>функцией общего вида</b>.</p>
<details class="spoiler"><summary>Что значит «$D(f)$ симметрична относительно нуля»?</summary><div class="spoiler-body">
Это значит: если $x \\in D(f)$, то и $-x \\in D(f)$. Например, $D(f) = [-3; 3]$ — симметрична, а $D(f) = [0; +\\infty)$ — нет.
</div></details>`);
html += makeCard('rule', 'Графическая симметрия', '8.2', `
<p>Свойство чётности или нечётности имеет наглядный геометрический смысл:</p>
<ul style="padding-left:22px;line-height:1.9">
<li>График <b>чётной</b> функции симметричен относительно <b>оси $Oy$</b> (зеркальное отражение).</li>
<li>График <b>нечётной</b> функции симметричен относительно <b>начала координат</b> (центральная симметрия — точка $O$).</li>
</ul>
<p>Поэтому достаточно построить график на $[0; +\\infty)$а на $(-\\infty; 0]$ его можно восстановить отражением.</p>`);
html += makeCard('example', 'Примеры функций', '8.3', `
<p><b>Чётные:</b> $y = x^2$, $y = x^4$, $y = |x|$, $y = \\cos x$, $y = x^2 + 1$.</p>
<p><b>Нечётные:</b> $y = x$, $y = x^3$, $y = \\dfrac{1}{x}$, $y = \\sin x$, $y = x^5 - x$.</p>
<p><b>Общего вида:</b></p>
<ul style="padding-left:22px;line-height:1.9">
<li>$y = x + 1$ — линейная без чётности;</li>
<li>$y = x^2 + x$ — смесь чётной и нечётной части;</li>
<li>$y = \\sqrt{x}$$D(f) = [0; +\\infty)$ не симметрична относительно нуля.</li>
</ul>`);
/* INTERACTIVE 1 — симметрия графика */
html += `<div class="wg" id="p8-iv1">
<div class="wg-header"><span class="wg-badge">ИНТЕРАКТИВ 1</span><div class="wg-title">Симметрия графика</div></div>
<div class="wg-help">Выбери функцию ползунком. На графике появятся линии симметрии: для чётной — относительно оси $Oy$, для нечётной — относительно точки $O$.</div>
<div class="sliders">
<label>Функция №<b id="p8-iv1-fi">1</b> / 6<input type="range" id="p8-iv1-fn" min="1" max="6" step="1" value="1"></label>
</div>
<div id="p8-iv1-formula" style="text-align:center;font-size:1.1rem;padding:10px;background:var(--card);border-radius:9px;margin-bottom:10px"></div>
<div style="background:var(--card);border-radius:10px;padding:10px;overflow-x:auto">
<svg id="p8-iv1-svg" viewBox="0 0 380 280" style="width:100%;max-width:500px;height:auto;display:block;margin:0 auto"></svg>
</div>
<div id="p8-iv1-out" style="margin-top:10px;padding:12px 14px;background:var(--sec-acc-soft);border-radius:9px;font-size:.95rem;line-height:1.7"></div>
</div>`;
/* INTERACTIVE 2 — квикфайр */
html += `<div class="wg" id="p8-iv2">
<div class="wg-header"><span class="wg-badge">ИНТЕРАКТИВ 2</span><div class="wg-title">Чётная, нечётная или общая?</div></div>
<div class="wg-help">Определи тип функции, выбрав один из трёх вариантов.</div>
<div class="score-display">Задача: <b id="p8-iv2-idx">1</b> / 8 &middot; Очки: <b id="p8-iv2-sc">0</b></div>
<div id="p8-iv2-q" style="text-align:center;font-size:1.15rem;padding:14px;background:var(--card);border-radius:9px;margin-bottom:12px;min-height:60px"></div>
<div class="actions" style="justify-content:center;flex-wrap:wrap">
<button class="btn primary" id="p8-iv2-e">Чётная</button>
<button class="btn" id="p8-iv2-o">Нечётная</button>
<button class="btn" id="p8-iv2-g">Общая</button>
</div>
<div class="feedback" id="p8-iv2-fb"></div>
</div>`;
/* INTERACTIVE 3 — подставь -x */
html += `<div class="wg" id="p8-iv3">
<div class="wg-header"><span class="wg-badge">ИНТЕРАКТИВ 3</span><div class="wg-title">Подставь $-x$</div></div>
<div class="wg-help">Подставь в формулу $-x$ вместо $x$. Сравни с исходной: получилась $f(x)$? — чётная. Получилась $-f(x)$? — нечётная. Что-то другое? — общая.</div>
<div class="score-display">Задача: <b id="p8-iv3-idx">1</b> / 6 &middot; Очки: <b id="p8-iv3-sc">0</b></div>
<div id="p8-iv3-q" style="text-align:center;font-size:1.05rem;padding:14px;background:var(--card);border-radius:9px;margin-bottom:12px;min-height:60px"></div>
<div class="actions" style="justify-content:center;flex-wrap:wrap">
<button class="btn primary" id="p8-iv3-a">$f(-x) = f(x)$</button>
<button class="btn" id="p8-iv3-b">$f(-x) = -f(x)$</button>
<button class="btn" id="p8-iv3-c">Другое</button>
</div>
<div class="feedback" id="p8-iv3-fb"></div>
</div>`;
/* INTERACTIVE 4 — DnD сортер */
html += `<div class="wg" id="p8-iv4">
<div class="wg-header"><span class="wg-badge">ИНТЕРАКТИВ 4</span><div class="wg-title">Сортер: какой тип?</div></div>
<div class="wg-help">Перетащи каждую функцию в подходящий ящик.</div>
<div id="p8-iv4-pool"></div>
<div style="display:grid;grid-template-columns:repeat(auto-fit,minmax(160px,1fr));gap:10px">
<div class="drop-box"><h5>Чётная</h5><div class="drop-items" data-cat="even"></div></div>
<div class="drop-box"><h5>Нечётная</h5><div class="drop-items" data-cat="odd"></div></div>
<div class="drop-box"><h5>Общая</h5><div class="drop-items" data-cat="gen"></div></div>
</div>
<div class="actions"><button class="btn primary" id="p8-iv4-check">Проверить</button><button class="btn" id="p8-iv4-reset">Сбросить</button></div>
<div class="feedback" id="p8-iv4-fb"></div>
</div>`;
box.innerHTML = html + secNav('p7', 'p9') + readButton('p8');
renderMath(box);
/* ===== IV1 wiring — симметрия графика ===== */
(function(){
const fns = [
{ tex:'y = x^2', f:x=>x*x, type:'even', xmin:-4, xmax:4 },
{ tex:'y = x^3', f:x=>x*x*x, type:'odd', xmin:-4, xmax:4 },
{ tex:'y = |x|', f:x=>Math.abs(x),type:'even', xmin:-4, xmax:4 },
{ tex:'y = x', f:x=>x, type:'odd', xmin:-4, xmax:4 },
{ tex:'y = x^2 + x', f:x=>x*x+x, type:'gen', xmin:-4, xmax:4 },
{ tex:'y = \\dfrac{1}{x}', f:x=>1/x, type:'odd', xmin:-4, xmax:4 }
];
const sl = document.getElementById('p8-iv1-fn');
const fi = document.getElementById('p8-iv1-fi');
const svg = document.getElementById('p8-iv1-svg');
const formula = document.getElementById('p8-iv1-formula');
const out = document.getElementById('p8-iv1-out');
let bumped = false;
function redraw(){
const idx = (+sl.value)-1;
const fobj = fns[idx];
fi.textContent = (idx+1);
const ax = axes2D(380, 280, 28, -4, 4, -4, 4);
let g = ax.content;
// линии симметрии под графиком
if (fobj.type === 'even'){
// подсветим ось Oy
const x0 = ax.toX(0);
g += '<line x1="'+x0+'" y1="'+ax.toY(-4)+'" x2="'+x0+'" y2="'+ax.toY(4)+'" stroke="#a855f7" stroke-width="2.5" stroke-dasharray="6 4" opacity=".7"/>';
} else if (fobj.type === 'odd'){
// выделим точку O маркером
const cx = ax.toX(0), cy = ax.toY(0);
g += '<circle cx="'+cx+'" cy="'+cy+'" r="10" fill="none" stroke="#a855f7" stroke-width="2.5" stroke-dasharray="4 3"/>';
// диагонали через O
g += '<line x1="'+ax.toX(-4)+'" y1="'+ax.toY(4)+'" x2="'+ax.toX(4)+'" y2="'+ax.toY(-4)+'" stroke="#a855f7" stroke-width="1" stroke-dasharray="3 5" opacity=".4"/>';
g += '<line x1="'+ax.toX(-4)+'" y1="'+ax.toY(-4)+'" x2="'+ax.toX(4)+'" y2="'+ax.toY(4)+'" stroke="#a855f7" stroke-width="1" stroke-dasharray="3 5" opacity=".4"/>';
}
// график
if (fobj.type === 'odd' && fobj.tex.indexOf('1') !== -1 && fobj.tex.indexOf('x') !== -1 && fobj.tex.indexOf('dfrac') !== -1){
// гипербола: рисуем две ветви
g += plotFunc(fobj.f, -4, -0.05, ax.toX, ax.toY, '#059669', 200);
g += plotFunc(fobj.f, 0.05, 4, ax.toX, ax.toY, '#059669', 200);
} else {
g += plotFunc(fobj.f, fobj.xmin, fobj.xmax, ax.toX, ax.toY, '#059669', 300);
}
svg.innerHTML = g;
formula.innerHTML = '$' + fobj.tex + '$';
// подсчёт f(-x)
const xprobe = 1.5;
let fnegX_tex = '';
if (idx === 0) fnegX_tex = '(-x)^2 = x^2 = f(x)';
else if (idx === 1) fnegX_tex = '(-x)^3 = -x^3 = -f(x)';
else if (idx === 2) fnegX_tex = '|-x| = |x| = f(x)';
else if (idx === 3) fnegX_tex = '-x = -f(x)';
else if (idx === 4) fnegX_tex = '(-x)^2 + (-x) = x^2 - x \\ne \\pm f(x)';
else if (idx === 5) fnegX_tex = '\\dfrac{1}{-x} = -\\dfrac{1}{x} = -f(x)';
const typeLabel = (fobj.type==='even')?'<b style="color:#059669">чётная</b>':(fobj.type==='odd')?'<b style="color:#2563eb">нечётная</b>':'<b style="color:#ef4444">общего вида</b>';
const symLabel = (fobj.type==='even')?'ось $Oy$':(fobj.type==='odd')?'точка $O$ (начало координат)':'нет симметрии';
out.innerHTML =
'<div>$f(-x) = ' + fnegX_tex + '$</div>' +
'<div style="margin-top:6px">Тип: ' + typeLabel + '</div>' +
'<div style="margin-top:4px">Симметрия: ' + symLabel + '</div>';
renderMath(formula); renderMath(out);
if (!bumped){ bumped = true; bumpProgress('p8', 15); addXp(10,'p8-iv1'); }
}
sl.addEventListener('input', redraw);
redraw();
})();
/* ===== IV2 wiring — квикфайр 8 ===== */
(function(){
const items = [
{ q:'$f(x) = x^4$', ans:'even', hint:'$(-x)^4 = x^4$ — чётная.' },
{ q:'$f(x) = x^5$', ans:'odd', hint:'$(-x)^5 = -x^5$ — нечётная.' },
{ q:'$f(x) = x^2 + 3$', ans:'even', hint:'$(-x)^2 + 3 = x^2 + 3 = f(x)$ — чётная.' },
{ q:'$f(x) = x^3 + x$', ans:'odd', hint:'$(-x)^3 + (-x) = -x^3 - x = -(x^3+x) = -f(x)$ — нечётная.' },
{ q:'$f(x) = x^3 + 1$', ans:'gen', hint:'$(-x)^3 + 1 = -x^3 + 1$ — не равно ни $f(x)$, ни $-f(x)$. Общая.' },
{ q:'$f(x) = |x| - 5$', ans:'even', hint:'$|-x| - 5 = |x| - 5 = f(x)$ — чётная.' },
{ q:'$f(x) = \\dfrac{1}{x^2}$', ans:'even', hint:'$\\dfrac{1}{(-x)^2} = \\dfrac{1}{x^2} = f(x)$ — чётная.' },
{ q:'$f(x) = \\dfrac{1}{x^3}$', ans:'odd', hint:'$\\dfrac{1}{(-x)^3} = -\\dfrac{1}{x^3} = -f(x)$ — нечётная.' }
];
let i = 0, sc = 0;
const idxEl = document.getElementById('p8-iv2-idx');
const scEl = document.getElementById('p8-iv2-sc');
const qEl = document.getElementById('p8-iv2-q');
const fb = document.getElementById('p8-iv2-fb');
const eBtn = document.getElementById('p8-iv2-e');
const oBtn = document.getElementById('p8-iv2-o');
const gBtn = document.getElementById('p8-iv2-g');
let bumped = false;
function render(){
idxEl.textContent = Math.min(i+1, items.length);
scEl.textContent = sc;
if (i >= items.length){
qEl.innerHTML = '<b>Готово!</b> Результат: ' + sc + ' / ' + items.length;
eBtn.disabled = true; oBtn.disabled = true; gBtn.disabled = true;
eBtn.style.opacity = .5; oBtn.style.opacity = .5; gBtn.style.opacity = .5;
if (!bumped){ bumped = true; bumpProgress('p8', 15); addXp(10,'p8-iv2'); }
return;
}
qEl.innerHTML = items[i].q;
fb.style.display = 'none';
renderMath(qEl);
}
function answer(v){
if (i >= items.length) return;
const it = items[i];
const ok = (v === it.ans);
if (ok) sc++;
feedback(fb, ok, (ok?'&#10003; Верно. ':'&#10007; Неверно. ') + it.hint);
i++;
setTimeout(render, 1000);
}
eBtn.addEventListener('click', ()=>answer('even'));
oBtn.addEventListener('click', ()=>answer('odd'));
gBtn.addEventListener('click', ()=>answer('gen'));
render();
})();
/* ===== IV3 wiring — подставь -x ===== */
(function(){
const items = [
{ q:'$f(x) = 2x^2 - 7$', ans:'a', hint:'$f(-x) = 2(-x)^2 - 7 = 2x^2 - 7 = f(x)$. Чётная.' },
{ q:'$f(x) = x^5 - 2x$', ans:'b', hint:'$f(-x) = -x^5 + 2x = -(x^5 - 2x) = -f(x)$. Нечётная.' },
{ q:'$f(x) = x + 1$', ans:'c', hint:'$f(-x) = -x + 1$ — не равно ни $f(x)$, ни $-f(x)$. Общая.' },
{ q:'$f(x) = -3x^2 + |x|$', ans:'a', hint:'$f(-x) = -3x^2 + |x| = f(x)$. Чётная.' },
{ q:'$f(x) = x^3 - 3x$', ans:'b', hint:'$f(-x) = -x^3 + 3x = -(x^3 - 3x) = -f(x)$. Нечётная.' },
{ q:'$f(x) = (x + 2)^2$', ans:'c', hint:'$f(-x) = (-x + 2)^2 = (x - 2)^2$ — не равно ни $f(x)$, ни $-f(x)$. Общая.' }
];
let i = 0, sc = 0;
const idxEl = document.getElementById('p8-iv3-idx');
const scEl = document.getElementById('p8-iv3-sc');
const qEl = document.getElementById('p8-iv3-q');
const fb = document.getElementById('p8-iv3-fb');
const aBtn = document.getElementById('p8-iv3-a');
const bBtn = document.getElementById('p8-iv3-b');
const cBtn = document.getElementById('p8-iv3-c');
let bumped = false;
function render(){
idxEl.textContent = Math.min(i+1, items.length);
scEl.textContent = sc;
if (i >= items.length){
qEl.innerHTML = '<b>Готово!</b> Результат: ' + sc + ' / ' + items.length;
aBtn.disabled = true; bBtn.disabled = true; cBtn.disabled = true;
aBtn.style.opacity = .5; bBtn.style.opacity = .5; cBtn.style.opacity = .5;
if (!bumped){ bumped = true; bumpProgress('p8', 25); addXp(15,'p8-iv3'); }
return;
}
qEl.innerHTML = items[i].q;
fb.style.display = 'none';
renderMath(qEl);
}
function answer(v){
if (i >= items.length) return;
const it = items[i];
const ok = (v === it.ans);
if (ok) sc++;
feedback(fb, ok, (ok?'&#10003; Верно. ':'&#10007; Неверно. ') + it.hint);
i++;
setTimeout(render, 1100);
}
aBtn.addEventListener('click', ()=>answer('a'));
bBtn.addEventListener('click', ()=>answer('b'));
cBtn.addEventListener('click', ()=>answer('c'));
render();
})();
/* ===== IV4 wiring — DnD сортер ===== */
(function(){
const items = [
{ id:'a', html:'$y = 5x^2$', cat:'even' },
{ id:'b', html:'$y = 7x^3$', cat:'odd' },
{ id:'c', html:'$y = 4x + 1$', cat:'gen' },
{ id:'d', html:'$y = x^2 - 9$', cat:'even' },
{ id:'e', html:'$y = x^3 - 2x$', cat:'odd' },
{ id:'f', html:'$y = \\sqrt{x}$', cat:'gen' }
];
const sorter = setupSorter({
poolId: 'p8-iv4-pool',
scopeSelector: '#p8-iv4',
cats: ['even','odd','gen'],
items: items
});
let bumped = false;
document.getElementById('p8-iv4-check').addEventListener('click', ()=>{
const fb = document.getElementById('p8-iv4-fb');
const total = items.length;
let correct = 0, placed = 0;
items.forEach(it=>{ if(sorter.placed[it.id]){ placed++; if(sorter.placed[it.id]===it.cat) correct++; } });
if (placed < total){ feedback(fb, false, 'Размещены не все: ' + placed + ' / ' + total + '.'); return; }
const ok = (correct === total);
feedback(fb, ok, ok ? '&#10003; Все верно! ' + correct + ' / ' + total : '&#10007; Правильно: ' + correct + ' / ' + total);
if (ok && !bumped){ bumped = true; bumpProgress('p8', 25); addXp(15,'p8-iv4'); }
});
document.getElementById('p8-iv4-reset').addEventListener('click', ()=>{
sorter.reset();
const fb = document.getElementById('p8-iv4-fb'); fb.style.display='none';
});
})();
wireReadBtn('p8');
}
function buildP9(){
const root = document.getElementById('p9-body');
root.innerHTML = `
<div class="card">
<div class="card-header">
<span class="card-icon theory">${ICONS.theory}</span>
<span class="card-title">В разработке</span>
<span class="card-num">§ 9</span>
</div>
<div class="card-body">
<p>Содержание параграфа <b>«Сдвиги графиков»</b> будет добавлено в следующих обновлениях.</p>
<p style="color:var(--muted);font-size:.9rem">Раздел Phase 1.</p>
</div>
</div>` + secNav('p8', 'final2') + readButton('p9');
renderMath(root);
const box = document.getElementById('p9-body');
let html = '';
html += makeCard('theory', 'Вертикальный сдвиг $y = f(x) + b$', '9.1', `
<p>График функции $y = f(x) + b$ получается из графика $y = f(x)$ <b>параллельным переносом</b> вдоль оси $Oy$:</p>
<ul style="padding-left:22px;line-height:1.9">
<li>если $b > 0$ — <b>вверх</b> на $b$ единиц;</li>
<li>если $b < 0$ — <b>вниз</b> на $|b|$ единиц.</li>
</ul>
<p><b>Пример.</b> График $y = x^2 + 3$ — это парабола $y = x^2$, сдвинутая <b>вверх</b> на 3 единицы. Её вершина: $(0;\\ 3)$.</p>
<details class="spoiler"><summary>Почему так?</summary><div class="spoiler-body">
При том же $x$ значение $y$ увеличивается на $b$ — то есть каждая точка поднимается на $b$ вверх. Это и есть вертикальный перенос.
</div></details>`);
html += makeCard('rule', 'Горизонтальный сдвиг $y = f(x \\pm a)$', '9.2', `
<p>График функции $y = f(x - a)$ получается из графика $y = f(x)$ параллельным переносом вдоль оси $Ox$:</p>
<ul style="padding-left:22px;line-height:1.9">
<li>если $a > 0$ — <b>вправо</b> на $a$ единиц;</li>
<li>если $a < 0$ — <b>влево</b> на $|a|$ единиц.</li>
</ul>
<p><b>Внимание!</b> Знак <b>минус</b> внутри аргумента — сдвиг <b>вправо</b>. Знак <b>плюс</b> (то есть $f(x + a)$) — сдвиг <b>влево</b>. Это самая частая ошибка!</p>
<p><b>Пример.</b> График $y = (x - 2)^2$ — парабола $y = x^2$, сдвинутая <b>вправо</b> на 2. Её вершина: $(2;\\ 0)$.</p>
<p>А $y = (x + 5)^2$ — сдвиг <b>влево</b> на 5. Вершина: $(-5;\\ 0)$.</p>`);
html += makeCard('example', 'Комбинированный сдвиг', '9.3', `
<p>График $y = f(x - a) + b$ получается комбинацией двух переносов: сдвиг на $a$ по оси $Ox$ и на $b$ по оси $Oy$.</p>
<ul style="padding-left:22px;line-height:1.9">
<li>$y = (x - 1)^2 + 3$ — парабола с вершиной в $(1;\\ 3)$.</li>
<li>$y = (x + 2)^2 - 4$ — парабола с вершиной в $(-2;\\ -4)$.</li>
<li>$y = \\sqrt{x - 3} + 1$ — график $y = \\sqrt{x}$, сдвинутый вправо на 3 и вверх на 1.</li>
<li>$y = |x + 1| - 2$ — график $y = |x|$, сдвинутый влево на 1 и вниз на 2.</li>
</ul>
<p><b>Лайфхак.</b> Для параболы $y = (x - a)^2 + b$ вершина всегда в точке $(a;\\ b)$.</p>`);
/* INTERACTIVE 1 — слайдер сдвигов */
html += `<div class="wg" id="p9-iv1">
<div class="wg-header"><span class="wg-badge">ИНТЕРАКТИВ 1</span><div class="wg-title">Слайдер сдвигов</div></div>
<div class="wg-help">Выбери базовую функцию и крути ползунки $a$ и $b$. Синяя кривая — сдвинутый график $y = f(x - a) + b$, серая — исходный.</div>
<div class="sliders">
<label>Функция №<b id="p9-iv1-fi">1</b> / 4<input type="range" id="p9-iv1-fn" min="1" max="4" step="1" value="1"></label>
<label>$a$ =<b id="p9-iv1-av">0</b><input type="range" id="p9-iv1-a" min="-4" max="4" step="1" value="0"></label>
<label>$b$ =<b id="p9-iv1-bv">0</b><input type="range" id="p9-iv1-b" min="-4" max="4" step="1" value="0"></label>
</div>
<div id="p9-iv1-formula" style="text-align:center;font-size:1.1rem;padding:10px;background:var(--card);border-radius:9px;margin-bottom:10px"></div>
<div style="background:var(--card);border-radius:10px;padding:10px;overflow-x:auto">
<svg id="p9-iv1-svg" viewBox="0 0 420 320" style="width:100%;max-width:560px;height:auto;display:block;margin:0 auto"></svg>
</div>
<div id="p9-iv1-out" style="margin-top:10px;padding:12px 14px;background:var(--sec-acc-soft);border-radius:9px;font-size:.95rem;line-height:1.7;text-align:center"></div>
</div>`;
/* INTERACTIVE 2 — вершина параболы */
html += `<div class="wg" id="p9-iv2">
<div class="wg-header"><span class="wg-badge">ИНТЕРАКТИВ 2</span><div class="wg-title">Сдвиг и вершина параболы</div></div>
<div class="wg-help">Для параболы $y = (x - a)^2 + b$ вершина — в точке $(a;\\ b)$. Введи <b>сумму</b> координат вершины: $a + b$.</div>
<div class="score-display">Задача: <b id="p9-iv2-idx">1</b> / 6 &middot; Очки: <b id="p9-iv2-sc">0</b></div>
<div id="p9-iv2-q" style="text-align:center;font-size:1.1rem;padding:14px;background:var(--card);border-radius:9px;margin-bottom:12px"></div>
<div style="display:flex;gap:10px;justify-content:center;flex-wrap:wrap;align-items:center">
<span>$a + b$ =</span>
<input type="number" id="p9-iv2-in" class="tinp" style="width:110px;text-align:center" step="1">
<button class="btn primary" id="p9-iv2-go">Проверить</button>
</div>
<div class="feedback" id="p9-iv2-fb"></div>
</div>`;
/* INTERACTIVE 3 — куда сдвиг */
html += `<div class="wg" id="p9-iv3">
<div class="wg-header"><span class="wg-badge">ИНТЕРАКТИВ 3</span><div class="wg-title">В какую сторону сдвиг?</div></div>
<div class="wg-help">Определи направление сдвига графика относительно исходной функции.</div>
<div class="score-display">Задача: <b id="p9-iv3-idx">1</b> / 8 &middot; Очки: <b id="p9-iv3-sc">0</b></div>
<div id="p9-iv3-q" style="text-align:center;font-size:1.15rem;padding:14px;background:var(--card);border-radius:9px;margin-bottom:12px;min-height:60px"></div>
<div class="actions" style="justify-content:center;flex-wrap:wrap">
<button class="btn primary" id="p9-iv3-u">Вверх</button>
<button class="btn" id="p9-iv3-d">Вниз</button>
<button class="btn" id="p9-iv3-r">Вправо</button>
<button class="btn" id="p9-iv3-l">Влево</button>
</div>
<div class="feedback" id="p9-iv3-fb"></div>
</div>`;
/* INTERACTIVE 4 — конструктор формулы */
html += `<div class="wg" id="p9-iv4">
<div class="wg-header"><span class="wg-badge">ИНТЕРАКТИВ 4</span><div class="wg-title">Конструктор формулы по графику</div></div>
<div class="wg-help">Для каждого описания выбери верную формулу из выпадающего списка. Нажми «Проверить» в конце.</div>
<div id="p9-iv4-rows" style="display:flex;flex-direction:column;gap:12px"></div>
<div class="actions"><button class="btn primary" id="p9-iv4-check">Проверить</button><button class="btn" id="p9-iv4-reset">Сбросить</button></div>
<div class="feedback" id="p9-iv4-fb"></div>
</div>`;
box.innerHTML = html + secNav('p8', 'final2') + readButton('p9');
renderMath(box);
/* ===== IV1 wiring — слайдер сдвигов ===== */
(function(){
const fns = [
{ tex:'x^2', f:x=>x*x, dom:[-6,6] },
{ tex:'|x|', f:x=>Math.abs(x), dom:[-6,6] },
{ tex:'\\sqrt{x}', f:x=>Math.sqrt(x), dom:[0,6], shiftDom:true },
{ tex:'x^3', f:x=>x*x*x, dom:[-6,6] }
];
const fnSl = document.getElementById('p9-iv1-fn');
const aSl = document.getElementById('p9-iv1-a');
const bSl = document.getElementById('p9-iv1-b');
const fi = document.getElementById('p9-iv1-fi');
const av = document.getElementById('p9-iv1-av');
const bv = document.getElementById('p9-iv1-bv');
const svg = document.getElementById('p9-iv1-svg');
const formula = document.getElementById('p9-iv1-formula');
const out = document.getElementById('p9-iv1-out');
let bumped = false;
function redraw(){
const idx = (+fnSl.value)-1;
const a = +aSl.value, b = +bSl.value;
const fobj = fns[idx];
fi.textContent = (idx+1);
av.textContent = a;
bv.textContent = b;
const ax = axes2D(420, 320, 30, -6, 6, -6, 6);
let g = ax.content;
// исходная (серая)
g += plotFunc(fobj.f, fobj.dom[0], fobj.dom[1], ax.toX, ax.toY, '#94a3b8', 250);
// сдвинутая (синяя) — y = f(x - a) + b
const shifted = x => fobj.f(x - a) + b;
const xmin2 = fobj.dom[0] + a, xmax2 = fobj.dom[1] + a;
g += plotFunc(shifted, Math.max(xmin2,-6), Math.min(xmax2,6), ax.toX, ax.toY, '#2563eb', 280);
// маркеры начальных точек
// исходная: для x^2, |x|, x^3 — это (0;0); для sqrt — тоже (0;0)
const ox = 0, oy = fobj.f(0);
if (oy>=-6 && oy<=6){
const cx = ax.toX(ox), cy = ax.toY(oy);
g += '<circle cx="'+cx+'" cy="'+cy+'" r="4" fill="#94a3b8" stroke="#fff" stroke-width="1.5"/>';
}
// сдвинутая
const sx = ox + a, sy = oy + b;
if (sx>=-6 && sx<=6 && sy>=-6 && sy<=6){
const cx = ax.toX(sx), cy = ax.toY(sy);
g += '<circle cx="'+cx+'" cy="'+cy+'" r="5" fill="#2563eb" stroke="#fff" stroke-width="2"/>';
// стрелка от (0;0) до (a;b)
if (a !== 0 || b !== 0){
const sx0 = ax.toX(ox), sy0 = ax.toY(oy);
g += '<line x1="'+sx0+'" y1="'+sy0+'" x2="'+cx+'" y2="'+cy+'" stroke="#a855f7" stroke-width="1.6" stroke-dasharray="5 3" marker-end=""/>';
}
}
svg.innerHTML = g;
// формула
const aStr = (a >= 0) ? ('- ' + a) : ('+ ' + (-a));
const bStr = (b >= 0) ? ('+ ' + b) : ('- ' + (-b));
let inner;
if (a === 0) inner = fobj.tex;
else if (fobj.tex === '\\sqrt{x}') inner = '\\sqrt{x ' + aStr + '}';
else if (fobj.tex === '|x|') inner = '|x ' + aStr + '|';
else if (fobj.tex === 'x^2') inner = '(x ' + aStr + ')^2';
else if (fobj.tex === 'x^3') inner = '(x ' + aStr + ')^3';
else inner = fobj.tex;
let full = 'y = ' + inner;
if (b !== 0) full += ' ' + bStr;
formula.innerHTML = '$' + full + '$';
// направление
let dir = [];
if (a > 0) dir.push('вправо на ' + a);
else if (a < 0) dir.push('влево на ' + (-a));
if (b > 0) dir.push('вверх на ' + b);
else if (b < 0) dir.push('вниз на ' + (-b));
const dirText = dir.length ? 'Сдвиг: ' + dir.join(', ') + '.' : 'Сдвига нет — графики совпадают.';
out.innerHTML = dirText;
renderMath(formula); renderMath(out);
if (!bumped){ bumped = true; bumpProgress('p9', 15); addXp(10,'p9-iv1'); }
}
fnSl.addEventListener('input', redraw);
aSl.addEventListener('input', redraw);
bSl.addEventListener('input', redraw);
redraw();
})();
/* ===== IV2 wiring — вершина параболы ===== */
(function(){
const items = [
{ q:'$y = (x - 2)^2 + 3$', ans:5, hint:'Вершина $(2;\\ 3)$, $a + b = 5$.' },
{ q:'$y = (x + 1)^2 - 4$', ans:-5, hint:'Вершина $(-1;\\ -4)$, $a + b = -5$.' },
{ q:'$y = (x - 5)^2$', ans:5, hint:'Вершина $(5;\\ 0)$, $a + b = 5$.' },
{ q:'$y = x^2 + 7$', ans:7, hint:'Вершина $(0;\\ 7)$, $a + b = 7$.' },
{ q:'$y = (x + 3)^2 + 2$', ans:-1, hint:'Вершина $(-3;\\ 2)$, $a + b = -1$.' },
{ q:'$y = (x - 4)^2 - 6$', ans:-2, hint:'Вершина $(4;\\ -6)$, $a + b = -2$.' }
];
let i = 0, sc = 0;
const idxEl = document.getElementById('p9-iv2-idx');
const scEl = document.getElementById('p9-iv2-sc');
const qEl = document.getElementById('p9-iv2-q');
const inEl = document.getElementById('p9-iv2-in');
const fb = document.getElementById('p9-iv2-fb');
const goBtn = document.getElementById('p9-iv2-go');
let bumped = false;
function render(){
idxEl.textContent = Math.min(i+1, items.length);
scEl.textContent = sc;
if (i >= items.length){
qEl.innerHTML = '<b>Готово!</b> Результат: ' + sc + ' / ' + items.length;
inEl.disabled = true; goBtn.disabled = true; goBtn.style.opacity = .5;
if (!bumped){ bumped = true; bumpProgress('p9', 15); addXp(10,'p9-iv2'); }
return;
}
qEl.innerHTML = items[i].q;
inEl.value = '';
fb.style.display = 'none';
renderMath(qEl);
inEl.focus();
}
function check(){
if (i >= items.length) return;
const v = +inEl.value;
if (!Number.isFinite(v) || inEl.value === ''){ feedback(fb, false, 'Введи число.'); return; }
const it = items[i];
const ok = (v === it.ans);
if (ok) sc++;
feedback(fb, ok, (ok?'&#10003; Верно. ':'&#10007; Неверно. ') + it.hint);
i++;
setTimeout(render, 1100);
}
goBtn.addEventListener('click', check);
inEl.addEventListener('keydown', e=>{ if (e.key === 'Enter') check(); });
render();
})();
/* ===== IV3 wiring — направление сдвига ===== */
(function(){
const items = [
{ q:'$y = x^2 + 5$', ans:'u', hint:'$b = 5 > 0$ — вверх.' },
{ q:'$y = x^2 - 3$', ans:'d', hint:'$b = -3 < 0$ — вниз.' },
{ q:'$y = (x - 2)^2$', ans:'r', hint:'$x - 2$ → сдвиг вправо на 2.' },
{ q:'$y = (x + 4)^2$', ans:'l', hint:'$x + 4$ → сдвиг влево на 4.' },
{ q:'$y = |x| + 1$', ans:'u', hint:'$b = 1 > 0$ — вверх.' },
{ q:'$y = |x - 7|$', ans:'r', hint:'$x - 7$ → сдвиг вправо на 7.' },
{ q:'$y = \\sqrt{x + 2}$', ans:'l', hint:'$x + 2$ → сдвиг влево на 2.' },
{ q:'$y = x^3 - 1$', ans:'d', hint:'$b = -1 < 0$ — вниз.' }
];
let i = 0, sc = 0;
const idxEl = document.getElementById('p9-iv3-idx');
const scEl = document.getElementById('p9-iv3-sc');
const qEl = document.getElementById('p9-iv3-q');
const fb = document.getElementById('p9-iv3-fb');
const uBtn = document.getElementById('p9-iv3-u');
const dBtn = document.getElementById('p9-iv3-d');
const rBtn = document.getElementById('p9-iv3-r');
const lBtn = document.getElementById('p9-iv3-l');
let bumped = false;
function render(){
idxEl.textContent = Math.min(i+1, items.length);
scEl.textContent = sc;
if (i >= items.length){
qEl.innerHTML = '<b>Готово!</b> Результат: ' + sc + ' / ' + items.length;
[uBtn,dBtn,rBtn,lBtn].forEach(b=>{ b.disabled=true; b.style.opacity=.5; });
if (!bumped){ bumped = true; bumpProgress('p9', 25); addXp(15,'p9-iv3'); }
return;
}
qEl.innerHTML = items[i].q;
fb.style.display = 'none';
renderMath(qEl);
}
function answer(v){
if (i >= items.length) return;
const it = items[i];
const ok = (v === it.ans);
if (ok) sc++;
feedback(fb, ok, (ok?'&#10003; Верно. ':'&#10007; Неверно. ') + it.hint);
i++;
setTimeout(render, 900);
}
uBtn.addEventListener('click', ()=>answer('u'));
dBtn.addEventListener('click', ()=>answer('d'));
rBtn.addEventListener('click', ()=>answer('r'));
lBtn.addEventListener('click', ()=>answer('l'));
render();
})();
/* ===== IV4 wiring — конструктор формулы ===== */
(function(){
const tasks = [
{
desc: 'Парабола $y = x^2$ сдвинута на 2 <b>влево</b>',
opts: [
{ v:'a', tex:'y = (x - 2)^2' },
{ v:'b', tex:'y = (x + 2)^2' },
{ v:'c', tex:'y = x^2 + 2' },
{ v:'d', tex:'y = x^2 - 2' }
],
ans: 'b'
},
{
desc: '$y = |x|$ сдвинута на 4 <b>вверх</b>',
opts: [
{ v:'a', tex:'y = |x| + 4' },
{ v:'b', tex:'y = |x| - 4' },
{ v:'c', tex:'y = |x - 4|' },
{ v:'d', tex:'y = |x + 4|' }
],
ans: 'a'
},
{
desc: '$y = \\sqrt{x}$ сдвинута на 1 <b>вниз</b>',
opts: [
{ v:'a', tex:'y = \\sqrt{x} + 1' },
{ v:'b', tex:'y = \\sqrt{x} - 1' },
{ v:'c', tex:'y = \\sqrt{x - 1}' },
{ v:'d', tex:'y = \\sqrt{x + 1}' }
],
ans: 'b'
},
{
desc: '$y = x^2$ сдвинута на 3 <b>вправо</b> и 2 <b>вверх</b>',
opts: [
{ v:'a', tex:'y = (x + 3)^2 + 2' },
{ v:'b', tex:'y = (x - 3)^2 - 2' },
{ v:'c', tex:'y = (x - 3)^2 + 2' },
{ v:'d', tex:'y = (x + 3)^2 - 2' }
],
ans: 'c'
},
{
desc: '$y = x^3$ сдвинута на 5 <b>влево</b>',
opts: [
{ v:'a', tex:'y = (x - 5)^3' },
{ v:'b', tex:'y = (x + 5)^3' },
{ v:'c', tex:'y = x^3 + 5' },
{ v:'d', tex:'y = x^3 - 5' }
],
ans: 'b'
},
{
desc: '$y = |x|$ сдвинута на 1 <b>вправо</b> и 4 <b>вниз</b>',
opts: [
{ v:'a', tex:'y = |x - 1| - 4' },
{ v:'b', tex:'y = |x + 1| - 4' },
{ v:'c', tex:'y = |x - 1| + 4' },
{ v:'d', tex:'y = |x + 1| + 4' }
],
ans: 'a'
}
];
const rowsBox = document.getElementById('p9-iv4-rows');
let rowsHtml = '';
tasks.forEach((t, i)=>{
let optsHtml = '<option value="">— выбери —</option>';
t.opts.forEach(o=>{ optsHtml += '<option value="'+o.v+'">'+o.tex+'</option>'; });
rowsHtml +=
'<div style="background:var(--card);padding:10px 12px;border-radius:9px;display:flex;flex-direction:column;gap:8px">' +
'<div style="font-size:.98rem"><b>'+(i+1)+'.</b> '+t.desc+'</div>' +
'<select id="p9-iv4-s'+i+'" class="tinp" style="min-width:200px">'+optsHtml+'</select>' +
'</div>';
});
rowsBox.innerHTML = rowsHtml;
renderMath(rowsBox);
let bumped = false;
document.getElementById('p9-iv4-check').addEventListener('click', ()=>{
const fb = document.getElementById('p9-iv4-fb');
let correct = 0, answered = 0;
tasks.forEach((t,i)=>{
const v = document.getElementById('p9-iv4-s'+i).value;
if (v) answered++;
if (v === t.ans) correct++;
});
if (answered < tasks.length){
feedback(fb, false, 'Отвечены не все: ' + answered + ' / ' + tasks.length + '.');
return;
}
const ok = (correct === tasks.length);
feedback(fb, ok, ok ? '&#10003; Все верно! ' + correct + ' / ' + tasks.length : '&#10007; Правильно: ' + correct + ' / ' + tasks.length);
if (ok && !bumped){ bumped = true; bumpProgress('p9', 25); addXp(15,'p9-iv4'); }
});
document.getElementById('p9-iv4-reset').addEventListener('click', ()=>{
tasks.forEach((t,i)=>{ document.getElementById('p9-iv4-s'+i).value = ''; });
const fb = document.getElementById('p9-iv4-fb'); fb.style.display='none';
});
})();
wireReadBtn('p9');
}