diff --git a/frontend/textbooks/algebra_9_ch2.html b/frontend/textbooks/algebra_9_ch2.html
index e2ef4e1..05592e5 100644
--- a/frontend/textbooks/algebra_9_ch2.html
+++ b/frontend/textbooks/algebra_9_ch2.html
@@ -109,6 +109,46 @@ a{color:inherit;text-decoration:none}
.feedback.ok{display:block;background:var(--ok-bg);color:#065f46;border-left:4px solid var(--ok)}
.feedback.fail{display:block;background:var(--fail-bg);color:#7f1d1d;border-left:4px solid var(--fail)}
+.wg{background:linear-gradient(135deg,var(--card),var(--sec-acc-soft,var(--pri-soft)));border:1.5px solid var(--sec-acc,var(--pri));border-radius:14px;padding:18px 20px;margin-bottom:18px;box-shadow:var(--sh2);position:relative;z-index:1}
+.wg-header{display:flex;align-items:center;gap:8px;margin-bottom:14px}
+.wg-badge{padding:4px 9px;background:var(--sec-acc,var(--pri));color:#fff;border-radius:6px;font-family:'Unbounded',sans-serif;font-size:.68rem;font-weight:800;text-transform:uppercase;letter-spacing:.06em}
+.wg-title{font-family:'Unbounded',sans-serif;font-size:1.05rem;font-weight:800;color:var(--sec-acc-d,var(--pri2));flex:1}
+.wg-help{font-size:.88rem;color:var(--text);margin-bottom:12px;line-height:1.55;background:linear-gradient(135deg,var(--warn-bg,#fef3c7),var(--sec-acc-soft,var(--pri-soft)));border-left:4px solid var(--warn,#f59e0b);padding:9px 14px;border-radius:9px}
+.tinp{padding:8px 12px;border:1.5px solid var(--border);border-radius:8px;background:var(--card);color:var(--text);transition:border-color .15s;font-family:'JetBrains Mono',monospace}
+.tinp:focus{outline:0;border-color:var(--sec-acc,var(--pri));box-shadow:0 0 0 3px var(--sec-acc-soft,var(--pri-soft))}
+.actions{display:flex;gap:8px;flex-wrap:wrap;margin-top:10px}
+.sliders{display:grid;grid-template-columns:repeat(auto-fit,minmax(180px,1fr));gap:10px;margin-bottom:10px}
+.sliders label{display:block;font-size:.92rem;color:var(--muted);background:var(--card);padding:8px 12px;border-radius:8px;border:1px solid var(--border);line-height:1.5}
+.sliders label b{font-family:'JetBrains Mono',monospace;font-size:1.05rem;color:var(--sec-acc-d,var(--pri2));margin-left:4px}
+.sliders label input[type="range"]{display:block;width:100%;margin-top:6px;accent-color:var(--sec-acc,var(--pri))}
+.score-display{display:flex;gap:14px;flex-wrap:wrap;align-items:center;padding:10px 14px;background:var(--sec-acc-soft,var(--pri-soft));border-radius:10px;margin-bottom:12px}
+.score-display b{color:var(--sec-acc-d,var(--pri2));font-size:1.15rem}
+.spoiler{border:1px solid var(--border);border-radius:10px;background:var(--card);margin:10px 0;overflow:hidden}
+.spoiler summary{padding:8px 14px;background:var(--sec-acc-soft,var(--pri-soft));font-weight:700;cursor:pointer;font-size:.88rem;color:var(--sec-acc-d,var(--pri2));list-style:none;display:flex;align-items:center;gap:8px}
+.spoiler summary::-webkit-details-marker{display:none}
+.spoiler summary::before{content:'+';font-size:1.2rem;font-weight:900;color:var(--sec-acc,var(--pri));width:18px}
+.spoiler[open] summary::before{content:'\2212'}
+.spoiler-body{padding:10px 14px;font-size:.92rem;line-height:1.6}
+.dnd-pool{display:flex;flex-wrap:wrap;gap:8px;margin-bottom:14px;padding:10px;border:1.5px dashed var(--border);border-radius:10px;min-height:54px;transition:border-color .18s,background .18s}
+.dnd-pool.over{border-color:var(--sec-acc,var(--pri));background:var(--sec-acc-soft,var(--pri-soft));border-style:solid}
+.dnd-pool.col{flex-direction:column;align-items:stretch}
+.dnd-pool.col .dnd-chip{width:auto}
+.dnd-chip{display:inline-flex;align-items:center;gap:6px;padding:6px 12px;background:var(--card);border:1.5px solid var(--border);border-radius:10px;cursor:grab;user-select:none;font-size:.92rem;line-height:1.4;transition:transform .12s,box-shadow .12s,border-color .12s;touch-action:none;max-width:100%}
+.dnd-chip:hover{transform:translateY(-1px);border-color:var(--sec-acc,var(--pri));box-shadow:var(--sh)}
+.dnd-chip:active{cursor:grabbing}
+.dnd-chip.armed{border-color:var(--sec-acc,var(--pri));background:var(--sec-acc-soft,var(--pri-soft));box-shadow:0 0 0 3px rgba(217,119,6,.22);transform:translateY(-1px)}
+.dnd-chip.dragging{opacity:.28}
+.dnd-chip.placed{background:var(--sec-acc-soft,var(--pri-soft));border-color:var(--sec-acc,var(--pri))}
+.dnd-chip .dnd-x{padding:0 5px;color:var(--muted);font-weight:700;font-size:1.05rem;border-radius:4px;cursor:pointer}
+.dnd-chip .dnd-x:hover{color:var(--bad,var(--fail));background:var(--fail-bg)}
+.drop-box{background:var(--card);border:1.5px dashed var(--border);border-radius:10px;padding:10px;min-height:90px;transition:border-color .15s,background .15s}
+.drop-box:hover{border-color:var(--sec-acc,var(--pri));background:var(--sec-acc-soft,var(--pri-soft))}
+.drop-box h5{font-family:'Unbounded',sans-serif;font-size:.78rem;color:var(--sec-acc-d,var(--pri2));margin-bottom:8px;text-transform:uppercase;letter-spacing:.05em}
+.drop-box.over{border-color:var(--sec-acc,var(--pri));background:var(--sec-acc-soft,var(--pri-soft));border-style:solid;transform:scale(1.015)}
+.drop-items{display:flex;flex-wrap:wrap;gap:6px;min-height:32px}
+.dnd-hint{font-size:.78rem;color:var(--muted);margin-bottom:8px;display:flex;align-items:center;gap:6px}
+.dnd-hint svg{width:14px;height:14px;flex-shrink:0}
+
.col-side{position:sticky;top:14px;align-self:start;height:fit-content;max-height:calc(100vh - 28px);overflow-y:auto}
.sidecard{background:var(--card);border:1px solid var(--border);border-radius:14px;padding:16px;margin-bottom:14px;box-shadow:var(--sh)}
.sidecard h4{font-family:'Unbounded',sans-serif;font-size:.74rem;font-weight:800;color:var(--pri2);text-transform:uppercase;letter-spacing:.07em;margin-bottom:10px;padding-bottom:8px;border-bottom:1px solid var(--border)}
@@ -426,47 +466,654 @@ function readButton(paraId){
function wireReadBtn(paraId){
const btn = document.getElementById(paraId+'-read-btn'); if(!btn) return;
btn.addEventListener('click', ()=>{
- addXp(10, paraId+'-read'); bumpProgress(paraId, 100);
+ addXp(10, paraId+'-read'); bumpProgress(paraId, 30);
btn.textContent='Прочитано! +10 XP'; btn.disabled=true; btn.style.opacity=.6;
+ if(paraId==='p7') achievement('p7_done');
+ if(paraId==='p8') achievement('p8_done');
+ if(paraId==='p9') achievement('p9_done');
if(paraId==='final2') achievement('ch2_done');
});
}
+function gcd(a,b){ a=Math.abs(a|0); b=Math.abs(b|0); while(b){ const t=b; b=a%b; a=t; } return a||1; }
+function makeCard(kind, title, num, body){
+ const labels = {repeat:'Повторение',theory:'Теория',algo:'Алгоритм',rule:'Правило',example:'Пример',oral:'Устно'};
+ return '
';
+}
+function setupSorter(cfg){
+ const placed = {}; const pool = document.getElementById(cfg.poolId); const scope = document.querySelector(cfg.scopeSelector);
+ if(!pool||!scope) return {placed,render:()=>{},reset:()=>{}};
+ pool.classList.add('dnd-pool'); if(cfg.columnLayout) pool.classList.add('col');
+ let armed = null;
+ function buildChip(it,isPlaced){ const e=document.createElement('div'); e.className='dnd-chip'+(isPlaced?' placed':''); e.dataset.id=it.id; e.innerHTML=''+it.html+' \xd7 '; attach(e,it.id); return e; }
+ function attach(elm,itId){ let ghost=null,dragging=false,sx=0,sy=0; elm.addEventListener('pointerdown',ev=>{ if(ev.button!==undefined&&ev.button!==0) return;
+ ev.preventDefault(); if(ev.target.classList&&ev.target.classList.contains('dnd-x')){ ev.stopPropagation(); if(placed[itId]){delete placed[itId];render();}else if(armed===itId){armed=null;render();} return; } sx=ev.clientX;sy=ev.clientY; const r=elm.getBoundingClientRect(); const ox=ev.clientX-r.left,oy=ev.clientY-r.top; try{elm.setPointerCapture(ev.pointerId);}catch(e){} function onMove(e){ const dx=e.clientX-sx,dy=e.clientY-sy; if(!dragging&&Math.hypot(dx,dy)>8){ dragging=true; ghost=elm.cloneNode(true); ghost.classList.remove('armed'); ghost.style.cssText='position:fixed;z-index:9999;pointer-events:none;opacity:.9;transform:rotate(-2.5deg);box-shadow:0 14px 36px rgba(0,0,0,.32);width:'+r.width+'px;left:'+(e.clientX-ox)+'px;top:'+(e.clientY-oy)+'px'; document.body.appendChild(ghost); elm.classList.add('dragging'); } if(dragging&&ghost){ ghost.style.left=(e.clientX-ox)+'px';ghost.style.top=(e.clientY-oy)+'px'; const under=document.elementsFromPoint(e.clientX,e.clientY); scope.querySelectorAll('.drop-box.over,.dnd-pool.over').forEach(n=>n.classList.remove('over')); const tgt=under.find(n=>n.classList&&(n.classList.contains('drop-box')||n.classList.contains('dnd-pool'))); if(tgt)tgt.classList.add('over'); } } function onUp(e){ elm.removeEventListener('pointermove',onMove);elm.removeEventListener('pointerup',onUp);elm.removeEventListener('pointercancel',onUp);elm.classList.remove('dragging'); if(ghost){ghost.remove();ghost=null;} scope.querySelectorAll('.drop-box.over,.dnd-pool.over').forEach(n=>n.classList.remove('over')); if(dragging){ const under=document.elementsFromPoint(e.clientX,e.clientY); const box=under.find(n=>n.classList&&n.classList.contains('drop-box')); const pl=under.find(n=>n.classList&&n.classList.contains('dnd-pool')); if(box){const di=box.querySelector('[data-cat]');if(di){placed[itId]=di.dataset.cat;armed=null;render();return;}}else if(pl){delete placed[itId];armed=null;render();return;} }else{ if(placed[itId]){delete placed[itId];armed=null;render();}else{armed=(armed===itId)?null:itId;render();} } dragging=false; } elm.addEventListener('pointermove',onMove);elm.addEventListener('pointerup',onUp);elm.addEventListener('pointercancel',onUp); }); }
+ function attachBoxTaps(){ scope.querySelectorAll('.drop-box').forEach(box=>{ box.addEventListener('click',ev=>{ if(!armed)return; if(ev.target.closest('.dnd-chip'))return; const di=box.querySelector('[data-cat]'); if(di){placed[armed]=di.dataset.cat;armed=null;render();} }); }); }
+ function render(){ pool.innerHTML=''; cfg.items.forEach(it=>{if(placed[it.id])return;const c=buildChip(it,false);if(armed===it.id)c.classList.add('armed');pool.appendChild(c);}); cfg.cats.forEach(cat=>{const box=scope.querySelector('.drop-items[data-cat="'+cat+'"]');if(!box)return;box.innerHTML='';cfg.items.forEach(it=>{if(placed[it.id]!==cat)return;box.appendChild(buildChip(it,true));});}); if(window.renderMathInElement)try{renderMath(scope);}catch(_){} }
+ attachBoxTaps(); render();
+ return {placed,render,reset(){ for(const k in placed)delete placed[k];armed=null;render(); }};
+}
+
+/* Координатная плоскость в SVG */
+function axes2D(W, H, pad, xmin, xmax, ymin, ymax){
+ const ux = (W - 2*pad) / (xmax - xmin);
+ const uy = (H - 2*pad) / (ymax - ymin);
+ const toX = v => pad + (v - xmin) * ux;
+ const toY = v => H - pad - (v - ymin) * uy;
+ let g = '';
+ g += '';
+ for (let x = Math.ceil(xmin); x <= xmax; x++){
+ g += ' ';
+ }
+ for (let y = Math.ceil(ymin); y <= ymax; y++){
+ g += ' ';
+ }
+ g += ' ';
+ const y0 = toY(0), x0 = toX(0);
+ g += ' ';
+ g += ' ';
+ g += 'x ';
+ g += 'y ';
+ g += '';
+ for (let x = Math.ceil(xmin); x <= xmax; x++){
+ if (x !== 0) g += ''+x+' ';
+ }
+ for (let y = Math.ceil(ymin); y <= ymax; y++){
+ if (y !== 0) g += ''+y+' ';
+ }
+ g += '0 ';
+ g += ' ';
+ return { content: g, toX, toY, ux, uy };
+}
+
+/* График функции y=f(x) — возвращает строку */
+function plotFunc(f, xmin, xmax, toX, toY, color, N){
+ N = N || 200;
+ let d = '';
+ let prevValid = false;
+ for (let i = 0; i <= N; i++){
+ const x = xmin + (xmax - xmin) * i / N;
+ let y;
+ try { y = f(x); } catch(e){ y = NaN; }
+ if (!isFinite(y) || isNaN(y) || y < -1e4 || y > 1e4){ prevValid = false; continue; }
+ d += (prevValid ? ' L' : ' M') + toX(x).toFixed(2) + ',' + toY(y).toFixed(2);
+ prevValid = true;
+ }
+ return ' ';
+}
+
/* ===== STUB BUILDERS — наполнение в Phase 1+ ===== */
function buildP6(){
- const root = document.getElementById('p6-body');
- root.innerHTML = `
-
-
-
-
Содержание параграфа «Функция числового аргумента» будет добавлено в следующих обновлениях.
-
Раздел Phase 1.
-
-
` + secNav(null, 'p7') + readButton('p6');
- renderMath(root);
+ const box = document.getElementById('p6-body');
+ let html = '';
+
+ html += makeCard('theory', 'Определение функции', '6.1', `
+ Если каждому значению $x$ из некоторого множества $X$ поставлено в соответствие единственное значение $y$, то говорят, что $y$ есть функция от $x$. Записывают $y = f(x)$.
+ Переменную $x$ называют аргументом (или независимой переменной), а $y$ — значением функции (зависимой переменной).
+
+ Множество $X$ — область определения функции, обозначается $D(f)$.
+ Множество всех значений $y = f(x)$ — область значений , обозначается $E(f)$.
+
+ А что если двум разным $x$ соответствует одно $y$?
+ Это нормально! Главное требование: одному $x$ — ровно одно $y$. А разные $x$ могут давать одно и то же $y$ — например, $f(x) = x^2$: при $x = -2$ и $x = 2$ получаем одно значение $y = 4$.
+
`);
+
+ html += makeCard('rule', 'Три способа задания функции', '6.2', `
+ Функцию можно задать одним из трёх основных способов:
+
+ Формулой (аналитически): $y = 2x + 1$, $y = \\dfrac{1}{x}$, $y = \\sqrt{x}$.
+ Таблицей: две строки — значения $x$ и соответствующие $y$.
+ Графиком: точки $(x; f(x))$ в координатной плоскости.
+
+ Иногда функцию задают словесным описанием : «каждому натуральному числу поставлен в соответствие его последний разряд».
`);
+
+ html += makeCard('example', 'Нахождение области определения', '6.3', `
+ а) $f(x) = \\dfrac{1}{x - 3}$. Знаменатель не равен нулю: $x - 3 \\ne 0 \\Rightarrow x \\ne 3$. $D(f) = (-\\infty; 3) \\cup (3; +\\infty)$.
+ б) $f(x) = \\sqrt{x - 2}$. Подкоренное выражение неотрицательно: $x - 2 \\ge 0 \\Rightarrow x \\ge 2$. $D(f) = [2; +\\infty)$.
+ в) $f(x) = x^2 + 1$. Никаких ограничений нет. $D(f) = \\mathbb{R}$.
+ г) $f(x) = \\dfrac{\\sqrt{x}}{x - 5}$. Сразу два условия: $x \\ge 0$ и $x \\ne 5$. $D(f) = [0; 5) \\cup (5; +\\infty)$.
`);
+
+ /* INTERACTIVE 1 — график + точка */
+ html += `
+
+
Выбери функцию ползунком, а потом — значение $x_0$. На графике появится точка $(x_0; f(x_0))$ и пунктирные линии к осям.
+
+ Функция №1 / 5
+ $x_0$ =0
+
+
+
+
+
+
+
`;
+
+ /* INTERACTIVE 2 — функция или нет */
+ html += `
+
+
Помни главное правило: одному $x$ — ровно одно $y$. Если хотя бы одному $x$ соответствует два разных $y$ — это уже не функция.
+
Задача: 1 / 6 · Очки: 0
+
+
+ Да, функция
+ Нет, не функция
+
+
+
`;
+
+ /* INTERACTIVE 3 — DnD сортер D(f) */
+ html += `
+
+
Перетащи каждую функцию в подходящий ящик с её областью определения.
+
+
+
Проверить Сбросить
+
+
`;
+
+ /* INTERACTIVE 4 — калькулятор f(x) */
+ html += `
+
+
Выбери функцию из списка и значение $x$. Калькулятор проверит принадлежность $D(f)$ и посчитает $y$.
+
+
+ y = x²
+ y = 2x + 5
+ y = 1/x
+ y = √x
+ y = |x|
+ y = (x+1)/(x-3)
+
+ $x$ =
+
+ Вычислить
+
+
+
`;
+
+ box.innerHTML = html + secNav(null, 'p7') + readButton('p6');
+ renderMath(box);
+
+ /* ===== IV1 wiring ===== */
+ (function(){
+ const fns = [
+ { label: 'y = 2x - 1', f: x=>2*x-1, ok: x=>true },
+ { label: 'y = x²', f: x=>x*x, ok: x=>true },
+ { label: 'y = -x + 3', f: x=>-x+3, ok: x=>true },
+ { label: 'y = 6/x', f: x=>6/x, ok: x=>x!==0 },
+ { label: 'y = √x', f: x=>Math.sqrt(x), ok: x=>x>=0 }
+ ];
+ const svg = document.getElementById('p6-iv1-svg');
+ const fnSl = document.getElementById('p6-iv1-fn');
+ const xSl = document.getElementById('p6-iv1-xs');
+ const fi = document.getElementById('p6-iv1-fi');
+ const xv = document.getElementById('p6-iv1-xv');
+ const formula = document.getElementById('p6-iv1-formula');
+ const out = document.getElementById('p6-iv1-out');
+ let bumped = false;
+
+ function redraw(){
+ const idx = (+fnSl.value)-1;
+ const x0 = +xSl.value;
+ const fobj = fns[idx];
+ fi.textContent = (idx+1);
+ xv.textContent = fmt(x0);
+
+ const ax = axes2D(360, 280, 28, -6, 6, -6, 6);
+ let g = ax.content;
+ g += plotFunc(fobj.f, -6, 6, ax.toX, ax.toY, '#059669', 300);
+
+ let outHtml = '';
+ if (fobj.ok(x0)){
+ const y0 = fobj.f(x0);
+ if (isFinite(y0) && y0>=-6 && y0<=6){
+ const px = ax.toX(x0), py = ax.toY(y0);
+ g += ' ';
+ g += ' ';
+ g += ' ';
+ }
+ outHtml = '$f(' + fmt(x0) + ') = ' + fmt(+y0.toFixed(4)) + '$';
+ } else {
+ outHtml = '$f(' + fmt(x0) + ')$ — не определена';
+ }
+
+ svg.innerHTML = g;
+ formula.innerHTML = '$' + fobj.label.replace('y =','y =').replace('²','^2').replace('√','\\sqrt') + '$';
+ // отрисовать формулу из набора: подменим на корректные KaTeX-варианты
+ const tex = ['y = 2x - 1','y = x^2','y = -x + 3','y = \\dfrac{6}{x}','y = \\sqrt{x}'];
+ formula.innerHTML = '$' + tex[idx] + '$';
+ out.innerHTML = outHtml;
+ renderMath(formula); renderMath(out);
+ if (!bumped){ bumped = true; bumpProgress('p6', 15); addXp(10,'p6-iv1'); }
+ }
+ fnSl.addEventListener('input', redraw);
+ xSl.addEventListener('input', redraw);
+ redraw();
+ })();
+
+ /* ===== IV2 wiring ===== */
+ (function(){
+ const items = [
+ { q: 'Соответствие $\\{(1,2),\\ (2,4),\\ (3,6)\\}$', ans: true, hint: 'Каждому $x$ — один $y$. Это функция.' },
+ { q: 'Соответствие $\\{(1,2),\\ (1,3),\\ (2,4)\\}$', ans: false, hint: 'При $x = 1$ получаем сразу 2 и 3. Не функция.' },
+ { q: '$y = x^2$', ans: true, hint: 'Каждому $x$ соответствует ровно одно $y$.' },
+ { q: '$y^2 = x$', ans: false, hint: 'При $x = 4$ получаем $y = 2$ и $y = -2$. Не функция.' },
+ { q: '$y = |x|$', ans: true, hint: 'Один $x$ → один $y$.' },
+ { q: 'Окружность $x^2 + y^2 = 9$', ans: false, hint: 'При $x = 0$ есть $y = 3$ и $y = -3$. Не функция.' }
+ ];
+ let i = 0, sc = 0;
+ const idxEl = document.getElementById('p6-iv2-idx');
+ const scEl = document.getElementById('p6-iv2-sc');
+ const qEl = document.getElementById('p6-iv2-q');
+ const fb = document.getElementById('p6-iv2-fb');
+ const yBtn = document.getElementById('p6-iv2-y');
+ const nBtn = document.getElementById('p6-iv2-n');
+ let bumped = false;
+ function render(){
+ idxEl.textContent = Math.min(i+1, items.length);
+ scEl.textContent = sc;
+ if (i >= items.length){
+ qEl.innerHTML = 'Готово! Результат: ' + sc + ' / ' + items.length;
+ yBtn.disabled = true; nBtn.disabled = true;
+ yBtn.style.opacity = .5; nBtn.style.opacity = .5;
+ if (!bumped){ bumped = true; bumpProgress('p6', 15); addXp(10,'p6-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?'✓ Верно. ':'✗ Неверно. ') + it.hint);
+ i++;
+ setTimeout(render, 900);
+ }
+ yBtn.addEventListener('click', ()=>answer(true));
+ nBtn.addEventListener('click', ()=>answer(false));
+ render();
+ })();
+
+ /* ===== IV3 wiring — DnD sorter ===== */
+ (function(){
+ const items = [
+ { id:'a', html:'$f(x) = x^2 + 1$', cat:'R' },
+ { id:'b', html:'$f(x) = \\dfrac{1}{x}$', cat:'hole' },
+ { id:'c', html:'$f(x) = \\sqrt{x}$', cat:'ray' },
+ { id:'d', html:'$f(x) = \\dfrac{1}{x-2}$', cat:'hole' },
+ { id:'e', html:'$f(x) = \\sqrt{x-3}$', cat:'ray' },
+ { id:'f', html:'$f(x) = 5x - 7$', cat:'R' }
+ ];
+ const sorter = setupSorter({
+ poolId: 'p6-iv3-pool',
+ scopeSelector: '#p6-iv3',
+ cats: ['R','hole','ray'],
+ items: items
+ });
+ let bumped = false;
+ document.getElementById('p6-iv3-check').addEventListener('click', ()=>{
+ const fb = document.getElementById('p6-iv3-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 ? '✓ Все верно! ' + correct + ' / ' + total : '✗ Правильно: ' + correct + ' / ' + total);
+ if (ok && !bumped){ bumped = true; bumpProgress('p6', 25); addXp(15,'p6-iv3'); }
+ });
+ document.getElementById('p6-iv3-reset').addEventListener('click', ()=>{
+ sorter.reset();
+ const fb = document.getElementById('p6-iv3-fb'); fb.style.display='none';
+ });
+ })();
+
+ /* ===== IV4 wiring — calculator ===== */
+ (function(){
+ const fns = [
+ { tex:'y = x^2', f:x=>x*x, ok:x=>true, bad:'' },
+ { tex:'y = 2x + 5', f:x=>2*x+5, ok:x=>true, bad:'' },
+ { tex:'y = \\dfrac{1}{x}', f:x=>1/x, ok:x=>x!==0, bad:'$x = 0$' },
+ { tex:'y = \\sqrt{x}', f:x=>Math.sqrt(x), ok:x=>x>=0, bad:'$x < 0$' },
+ { tex:'y = |x|', f:x=>Math.abs(x), ok:x=>true, bad:'' },
+ { tex:'y = \\dfrac{x+1}{x-3}', f:x=>(x+1)/(x-3), ok:x=>x!==3, bad:'$x = 3$' }
+ ];
+ const out = document.getElementById('p6-iv4-out');
+ let bumped = false;
+ document.getElementById('p6-iv4-go').addEventListener('click', ()=>{
+ const idx = +document.getElementById('p6-iv4-fn').value;
+ const x = +document.getElementById('p6-iv4-x').value;
+ const fn = fns[idx];
+ if (!fn.ok(x)){
+ out.innerHTML = 'Функция $' + fn.tex + '$ не определена при ' + fn.bad + '.';
+ } else {
+ const y = fn.f(x);
+ out.innerHTML = '$' + fn.tex + '$, при $x = ' + fmt(x) + '$: $y = ' + fmt(+y.toFixed(6)) + '$.';
+ }
+ renderMath(out);
+ if (!bumped){ bumped = true; bumpProgress('p6', 25); addXp(15,'p6-iv4'); }
+ });
+ })();
+
wireReadBtn('p6');
}
function buildP7(){
- const root = document.getElementById('p7-body');
- root.innerHTML = `
-
-
-
-
Содержание параграфа «Свойства функции» будет добавлено в следующих обновлениях.
-
Раздел Phase 1.
-
-
` + secNav('p6', 'p8') + readButton('p7');
- renderMath(root);
+ const box = document.getElementById('p7-body');
+ let html = '';
+
+ html += makeCard('theory', 'Возрастание и убывание', '7.1', `
+ Функция $f$ возрастает на промежутке $I$, если для любых $x_1, x_2 \\in I$ из $x_1 < x_2$ следует $f(x_1) < f(x_2)$ (бо́льшему аргументу — бо́льшее значение).
+ Функция $f$ убывает на промежутке $I$, если для любых $x_1, x_2 \\in I$ из $x_1 < x_2$ следует $f(x_1) > f(x_2)$ (бо́льшему аргументу — меньшее значение).
+ Функция называется монотонной на $I$, если она возрастает или убывает на $I$.
+ Пример
+ $y = x^2$ убывает на $(-\\infty; 0]$ и возрастает на $[0; +\\infty)$. На всём $\\mathbb{R}$ — не монотонна.
+
`);
+
+ html += makeCard('rule', 'Нули и знакопостоянство', '7.2', `
+ Нуль функции — такое значение $x_0$, при котором $f(x_0) = 0$. Графически — точка пересечения графика с осью $Ox$.
+ Чтобы найти нули, решают уравнение $f(x) = 0$.
+ Промежутки знакопостоянства — те, на которых функция сохраняет знак:
+
+ $f(x) > 0$ — график над осью $Ox$;
+ $f(x) < 0$ — график под осью $Ox$.
+
+ Пример. Для $f(x) = x^2 - 4$: нули $x = \\pm 2$; $f(x) > 0$ на $(-\\infty;-2) \\cup (2;+\\infty)$; $f(x) < 0$ на $(-2; 2)$.
`);
+
+ html += makeCard('example', 'Экстремумы функции', '7.3', `
+ Точка $x_0$ — точка максимума , если в некоторой её окрестности $f(x_0) \\ge f(x)$ для всех $x$. Значение $f(x_0)$ называют максимумом и пишут $f_{\\max}$.
+ Аналогично, $x_0$ — точка минимума , если $f(x_0) \\le f(x)$ в окрестности. Значение — $f_{\\min}$.
+ Примеры:
+
+ $y = x^2$ — точка минимума $x = 0$, $f_{\\min} = 0$.
+ $y = -x^2 + 1$ — точка максимума $x = 0$, $f_{\\max} = 1$.
+ $y = x^3$ — экстремумов нет (функция возрастает всюду).
+ `);
+
+ /* INTERACTIVE 1 — анализ графика */
+ html += `
+
+
Выбери функцию ползунком. На графике видны нули (красные точки) и экстремум (синяя звёздочка), а под графиком — все свойства функции.
+
+ Функция №1 / 5
+
+
+
+
+
+
+
`;
+
+ /* INTERACTIVE 2 — нули функции */
+ html += `
+
+
Введи сумму всех нулей функции. Если нулей нет — введи -999 . Если бесконечно много — введи 999 .
+
Задача: 1 / 6 · Очки: 0
+
+
+ Сумма нулей =
+
+ Проверить
+
+
+
`;
+
+ /* INTERACTIVE 3 — возрастает или убывает */
+ html += `
+
+
Для каждой функции и промежутка выбери: возрастает она или убывает.
+
Задача: 1 / 6 · Очки: 0
+
+
+ Возрастает
+ Убывает
+
+
+
`;
+
+ /* INTERACTIVE 4 — DnD сортер */
+ html += `
+
+
Перетащи каждую карточку с функцией и промежутком в нужный ящик.
+
+
+
Проверить Сбросить
+
+
`;
+
+ box.innerHTML = html + secNav('p6', 'p8') + readButton('p7');
+ renderMath(box);
+
+ /* ===== IV1 wiring — анализ графика ===== */
+ (function(){
+ const fns = [
+ {
+ tex: 'y = x^2 - 4',
+ f: x=>x*x - 4,
+ zeros: [-2, 2],
+ ext: {x:0, y:-4, kind:'min'},
+ desc: 'Нули: $x = -2,\\ x = 2$. Точка минимума $x = 0$, $f_{\\min} = -4$. Убывает на $(-\\infty; 0]$, возрастает на $[0; +\\infty)$.'
+ },
+ {
+ tex: 'y = -x^2 + 3',
+ f: x=>-x*x + 3,
+ zeros: [-Math.sqrt(3), Math.sqrt(3)],
+ ext: {x:0, y:3, kind:'max'},
+ desc: 'Нули: $x = \\pm\\sqrt{3}$. Точка максимума $x = 0$, $f_{\\max} = 3$. Возрастает на $(-\\infty; 0]$, убывает на $[0; +\\infty)$.'
+ },
+ {
+ tex: 'y = x^3',
+ f: x=>x*x*x,
+ zeros: [0],
+ ext: null,
+ desc: 'Нуль: $x = 0$. Экстремумов нет. Возрастает на всей числовой прямой $\\mathbb{R}$.'
+ },
+ {
+ tex: 'y = (x - 2)^2 - 1',
+ f: x=>(x-2)*(x-2) - 1,
+ zeros: [1, 3],
+ ext: {x:2, y:-1, kind:'min'},
+ desc: 'Нули: $x = 1,\\ x = 3$. Точка минимума $x = 2$, $f_{\\min} = -1$. Убывает на $(-\\infty; 2]$, возрастает на $[2; +\\infty)$.'
+ },
+ {
+ tex: 'y = -|x| + 2',
+ f: x=>-Math.abs(x) + 2,
+ zeros: [-2, 2],
+ ext: {x:0, y:2, kind:'max'},
+ desc: 'Нули: $x = \\pm 2$. Точка максимума $x = 0$, $f_{\\max} = 2$. Возрастает на $(-\\infty; 0]$, убывает на $[0; +\\infty)$.'
+ }
+ ];
+ const sl = document.getElementById('p7-iv1-fn');
+ const fi = document.getElementById('p7-iv1-fi');
+ const svg = document.getElementById('p7-iv1-svg');
+ const formula = document.getElementById('p7-iv1-formula');
+ const out = document.getElementById('p7-iv1-out');
+ let bumped = false;
+
+ function redraw(){
+ const idx = (+sl.value)-1;
+ const fobj = fns[idx];
+ fi.textContent = (idx+1);
+ const ax = axes2D(400, 280, 30, -5, 5, -4, 6);
+ let g = ax.content;
+ g += plotFunc(fobj.f, -5, 5, ax.toX, ax.toY, '#10b981', 300);
+ // нули
+ fobj.zeros.forEach(z=>{
+ if (z>=-5 && z<=5){
+ const cx = ax.toX(z), cy = ax.toY(0);
+ g += ' ';
+ }
+ });
+ // экстремум — звёздочка
+ if (fobj.ext){
+ const ex = fobj.ext;
+ if (ex.x>=-5 && ex.x<=5 && ex.y>=-4 && ex.y<=6){
+ const cx = ax.toX(ex.x), cy = ax.toY(ex.y);
+ // 5-конечная звезда
+ let pts = '';
+ for (let i=0;i<10;i++){
+ const r = (i%2===0) ? 7 : 3.2;
+ const a = -Math.PI/2 + i*Math.PI/5;
+ pts += (cx + r*Math.cos(a)).toFixed(2) + ',' + (cy + r*Math.sin(a)).toFixed(2) + ' ';
+ }
+ g += ' ';
+ }
+ }
+ svg.innerHTML = g;
+ formula.innerHTML = '$' + fobj.tex + '$';
+ out.innerHTML = fobj.desc;
+ renderMath(formula); renderMath(out);
+ if (!bumped){ bumped = true; bumpProgress('p7', 15); addXp(10,'p7-iv1'); }
+ }
+ sl.addEventListener('input', redraw);
+ redraw();
+ })();
+
+ /* ===== IV2 wiring — нули ===== */
+ (function(){
+ const items = [
+ { q: '$f(x) = x - 5$', ans: 5 },
+ { q: '$f(x) = x^2 - 9$', ans: 0 },
+ { q: '$f(x) = (x+1)(x-4)$', ans: 3 },
+ { q: '$f(x) = x^2 + 1$', ans: -999 },
+ { q: '$f(x) = x^2 - 6x + 9$', ans: 3 },
+ { q: '$f(x) = x^3 - 8$', ans: 2 }
+ ];
+ let i = 0, sc = 0;
+ const idxEl = document.getElementById('p7-iv2-idx');
+ const scEl = document.getElementById('p7-iv2-sc');
+ const qEl = document.getElementById('p7-iv2-q');
+ const inEl = document.getElementById('p7-iv2-in');
+ const fb = document.getElementById('p7-iv2-fb');
+ const goBtn = document.getElementById('p7-iv2-go');
+ let bumped = false;
+ function render(){
+ idxEl.textContent = Math.min(i+1, items.length);
+ scEl.textContent = sc;
+ if (i >= items.length){
+ qEl.innerHTML = 'Готово! Результат: ' + sc + ' / ' + items.length;
+ inEl.disabled = true; goBtn.disabled = true; goBtn.style.opacity = .5;
+ if (!bumped){ bumped = true; bumpProgress('p7', 15); addXp(10,'p7-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++;
+ let hint = '';
+ if (it.ans === -999) hint = 'Нулей нет.';
+ else if (it.ans === 999) hint = 'Нулей бесконечно много.';
+ else hint = 'Сумма нулей = ' + it.ans + '.';
+ feedback(fb, ok, (ok?'✓ Верно. ':'✗ Неверно. ') + 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 = 2x + 1$ на $\\mathbb{R}$', up: true, hint: 'Линейная с положительным $k$. Возрастает.' },
+ { q: '$y = -3x + 5$ на $\\mathbb{R}$', up: false, hint: 'Линейная с отрицательным $k$. Убывает.' },
+ { q: '$y = x^2$ на $(-\\infty; 0]$', up: false, hint: 'Левая ветвь параболы. Убывает.' },
+ { q: '$y = x^2$ на $[0; +\\infty)$', up: true, hint: 'Правая ветвь параболы. Возрастает.' },
+ { q: '$y = \\dfrac{1}{x}$ на $(0; +\\infty)$', up: false, hint: 'Гипербола в I четверти. Убывает.' },
+ { q: '$y = x^3$ на $\\mathbb{R}$', up: true, hint: 'Кубическая. Возрастает всюду.' }
+ ];
+ let i = 0, sc = 0;
+ const idxEl = document.getElementById('p7-iv3-idx');
+ const scEl = document.getElementById('p7-iv3-sc');
+ const qEl = document.getElementById('p7-iv3-q');
+ const fb = document.getElementById('p7-iv3-fb');
+ const uBtn = document.getElementById('p7-iv3-u');
+ const dBtn = document.getElementById('p7-iv3-d');
+ let bumped = false;
+ function render(){
+ idxEl.textContent = Math.min(i+1, items.length);
+ scEl.textContent = sc;
+ if (i >= items.length){
+ qEl.innerHTML = 'Готово! Результат: ' + sc + ' / ' + items.length;
+ uBtn.disabled = true; dBtn.disabled = true;
+ uBtn.style.opacity = .5; dBtn.style.opacity = .5;
+ if (!bumped){ bumped = true; bumpProgress('p7', 25); addXp(15,'p7-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.up);
+ if (ok) sc++;
+ feedback(fb, ok, (ok?'✓ Верно. ':'✗ Неверно. ') + it.hint);
+ i++;
+ setTimeout(render, 900);
+ }
+ uBtn.addEventListener('click', ()=>answer(true));
+ dBtn.addEventListener('click', ()=>answer(false));
+ render();
+ })();
+
+ /* ===== IV4 wiring — DnD sorter ===== */
+ (function(){
+ const items = [
+ { id:'a', html:'$y = 5x$ на $\\mathbb{R}$', cat:'up' },
+ { id:'b', html:'$y = -2x$ на $\\mathbb{R}$', cat:'down' },
+ { id:'c', html:'$y = x^2$ на $[0;\\,3]$', cat:'up' },
+ { id:'d', html:'$y = x^2$ на $[-3;\\,0]$', cat:'down' },
+ { id:'e', html:'$y = x^3$ на $\\mathbb{R}$', cat:'up' },
+ { id:'f', html:'$y = \\dfrac{1}{x}$ на $(0;\\,+\\infty)$', cat:'down' }
+ ];
+ const sorter = setupSorter({
+ poolId: 'p7-iv4-pool',
+ scopeSelector: '#p7-iv4',
+ cats: ['up','down'],
+ items: items
+ });
+ let bumped = false;
+ document.getElementById('p7-iv4-check').addEventListener('click', ()=>{
+ const fb = document.getElementById('p7-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 ? '✓ Все верно! ' + correct + ' / ' + total : '✗ Правильно: ' + correct + ' / ' + total);
+ if (ok && !bumped){ bumped = true; bumpProgress('p7', 25); addXp(15,'p7-iv4'); }
+ });
+ document.getElementById('p7-iv4-reset').addEventListener('click', ()=>{
+ sorter.reset();
+ const fb = document.getElementById('p7-iv4-fb'); fb.style.display='none';
+ });
+ })();
+
wireReadBtn('p7');
}