From bb40776fa8409ea50d0f621c7bc8151b658d21d9 Mon Sep 17 00:00:00 2001 From: Maxim Dolgolyov Date: Fri, 29 May 2026 08:21:14 +0300 Subject: [PATCH] =?UTF-8?q?feat(alg9=20ch2=20wave1):=20=C2=A76=20=C2=AB?= =?UTF-8?q?=D0=A4=D1=83=D0=BD=D0=BA=D1=86=D0=B8=D1=8F,=20D(f),=20E(f)?= =?UTF-8?q?=C2=BB=20+=20=C2=A77=20=C2=AB=D0=A1=D0=B2=D0=BE=D0=B9=D1=81?= =?UTF-8?q?=D1=82=D0=B2=D0=B0=C2=BB=20+=20axes2D/plotFunc?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/textbooks/algebra_9_ch2.html | 705 ++++++++++++++++++++++++-- 1 file changed, 676 insertions(+), 29 deletions(-) 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 '
'+ICONS[kind]+'
'+(labels[kind]||'')+(title&&title!==labels[kind]?' \xb7 '+title:'')+'
'+(num?'
'+num+'
':'')+'
'+body+'
'; +} +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 = ` -
-
- ${ICONS.theory} - В разработке - § 6 -
-
-

Содержание параграфа «Функция числового аргумента» будет добавлено в следующих обновлениях.

-

Раздел 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', ` +

Функцию можно задать одним из трёх основных способов:

+
    +
  1. Формулой (аналитически): $y = 2x + 1$, $y = \\dfrac{1}{x}$, $y = \\sqrt{x}$.
  2. +
  3. Таблицей: две строки — значения $x$ и соответствующие $y$.
  4. +
  5. Графиком: точки $(x; f(x))$ в координатной плоскости.
  6. +
+

Иногда функцию задают словесным описанием: «каждому натуральному числу поставлен в соответствие его последний разряд».

`); + + 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 += `
+
ИНТЕРАКТИВ 1
График и точка $(x_0; f(x_0))$
+
Выбери функцию ползунком, а потом — значение $x_0$. На графике появится точка $(x_0; f(x_0))$ и пунктирные линии к осям.
+
+ + +
+
+
+ +
+
+
`; + + /* INTERACTIVE 2 — функция или нет */ + html += `
+
ИНТЕРАКТИВ 2
Функция или нет?
+
Помни главное правило: одному $x$ — ровно одно $y$. Если хотя бы одному $x$ соответствует два разных $y$ — это уже не функция.
+
Задача: 1 / 6 · Очки: 0
+
+
+ + +
+ +
`; + + /* INTERACTIVE 3 — DnD сортер D(f) */ + html += `
+
ИНТЕРАКТИВ 3
Найди $D(f)$
+
Перетащи каждую функцию в подходящий ящик с её областью определения.
+
+
+
$\\mathbb{R}$
+
Все, кроме точки
+
Луч $[a; +\\infty)$
+
+
+ +
`; + + /* INTERACTIVE 4 — калькулятор f(x) */ + html += `
+
ИНТЕРАКТИВ 4
Калькулятор $f(x)$
+
Выбери функцию из списка и значение $x$. Калькулятор проверит принадлежность $D(f)$ и посчитает $y$.
+
+ + $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 = ` -
-
- ${ICONS.theory} - В разработке - § 7 -
-
-

Содержание параграфа «Свойства функции» будет добавлено в следующих обновлениях.

-

Раздел 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
Анализ графика
+
Выбери функцию ползунком. На графике видны нули (красные точки) и экстремум (синяя звёздочка), а под графиком — все свойства функции.
+
+ +
+
+
+ +
+
+
`; + + /* INTERACTIVE 2 — нули функции */ + html += `
+
ИНТЕРАКТИВ 2
Найди нули функции
+
Введи сумму всех нулей функции. Если нулей нет — введи -999. Если бесконечно много — введи 999.
+
Задача: 1 / 6 · Очки: 0
+
+
+ Сумма нулей = + + +
+ +
`; + + /* INTERACTIVE 3 — возрастает или убывает */ + html += `
+
ИНТЕРАКТИВ 3
Возрастает или убывает?
+
Для каждой функции и промежутка выбери: возрастает она или убывает.
+
Задача: 1 / 6 · Очки: 0
+
+
+ + +
+ +
`; + + /* INTERACTIVE 4 — DnD сортер */ + html += `
+
ИНТЕРАКТИВ 4
Свойства: возрастает / убывает
+
Перетащи каждую карточку с функцией и промежутком в нужный ящик.
+
+
+
Возрастает
+
Убывает
+
+
+ +
`; + + 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'); }