From 140f711e3c8b60d9233111ed1c76c492ba3ecbc4 Mon Sep 17 00:00:00 2001 From: Maxim Dolgolyov Date: Fri, 29 May 2026 08:03:28 +0300 Subject: [PATCH] =?UTF-8?q?feat(alg9=20ch1=20wave1):=20=C2=A71=20=C2=AB?= =?UTF-8?q?=D0=A0=D0=B0=D1=86=D0=B8=D0=BE=D0=BD=D0=B0=D0=BB=D1=8C=D0=BD?= =?UTF-8?q?=D0=B0=D1=8F=20=D0=B4=D1=80=D0=BE=D0=B1=D1=8C=C2=BB=20+=20?= =?UTF-8?q?=C2=A72=20=C2=AB=D0=9E=D1=81=D0=BD=D0=BE=D0=B2=D0=BD=D0=BE?= =?UTF-8?q?=D0=B5=20=D1=81=D0=B2=D0=BE=D0=B9=D1=81=D1=82=D0=B2=D0=BE,=20?= =?UTF-8?q?=D1=81=D0=BE=D0=BA=D1=80=D0=B0=D1=89=D0=B5=D0=BD=D0=B8=D0=B5?= =?UTF-8?q?=C2=BB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/textbooks/algebra_9_ch1.html | 575 ++++++++++++++++++++++++-- 1 file changed, 546 insertions(+), 29 deletions(-) diff --git a/frontend/textbooks/algebra_9_ch1.html b/frontend/textbooks/algebra_9_ch1.html index c5a3e8d..b1531c6 100644 --- a/frontend/textbooks/algebra_9_ch1.html +++ b/frontend/textbooks/algebra_9_ch1.html @@ -110,6 +110,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)} @@ -403,6 +443,25 @@ function initTheme(){ function renderMath(root){ if(window.renderMathInElement){ try{ renderMathInElement(root, {delimiters:[{left:'$$',right:'$$',display:true},{left:'$',right:'$',display:false},{left:'\\[',right:'\\]',display:true},{left:'\\(',right:'\\)',display:false}],throwOnError:false}); }catch(e){} } } function feedback(elm, ok, text){ if(!elm) return; elm.className='feedback '+(ok?'ok':'fail'); elm.innerHTML=text||(ok?'✓ Верно!':'✗ Неверно'); elm.style.display='block'; try{renderMath(elm);}catch(e){} } function fmt(n){ if(!isFinite(n)) return '?'; if(Number.isInteger(n)) return String(n); return Math.abs(n-Math.round(n))<1e-9?String(Math.round(n)):(+n.toFixed(6)).toString(); } +function ipow(base, exp){ let r=1; for(let i=0;i
'+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(); }}; +} const ICONS = { repeat:'', @@ -431,8 +490,11 @@ 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==='p2') achievement('p2_done'); + if(paraId==='p4') achievement('p4_done'); + if(paraId==='p5') achievement('p5_done'); if(paraId==='final1') achievement('ch1_done'); }); } @@ -440,38 +502,493 @@ function wireReadBtn(paraId){ /* ===== STUB BUILDERS — наполнение в Phase 1+ ===== */ function buildP1(){ - const root = document.getElementById('p1-body'); - root.innerHTML = ` -
-
- ${ICONS.theory} - В разработке - § 1 -
-
-

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

-

Раздел Phase 1.

-
-
` + secNav(null, 'p2') + readButton('p1'); - renderMath(root); + const box = document.getElementById('p1-body'); + let html = ''; + + html += makeCard('theory', 'Определение рациональной дроби', '1.1', ` +

Рациональной дробью называется выражение вида $\\dfrac{P(x)}{Q(x)}$, где $P(x)$ и $Q(x)$ — многочлены, причём $Q(x) \\ne 0$.

+

Числитель $P(x)$ может быть любым многочленом (даже нулевым), а знаменатель $Q(x)$ — не равен нулю.

+

Примеры рациональных дробей:

+ +
А целые выражения — это дроби?
+ Да! Любой многочлен $P(x)$ — это частный случай рациональной дроби: $P(x) = \\dfrac{P(x)}{1}$. Поэтому рациональные дроби обобщают и целые выражения. +
`); + + html += makeCard('rule', 'Область допустимых значений (ОДЗ)', '1.2', ` +

ОДЗ рациональной дроби $\\dfrac{P(x)}{Q(x)}$ — это все значения переменной, при которых знаменатель не обращается в ноль.

+

Алгоритм нахождения ОДЗ:

+
    +
  1. Записать уравнение $Q(x) = 0$.
  2. +
  3. Найти все его корни.
  4. +
  5. Исключить эти корни из множества действительных чисел.
  6. +
+

Запись: «$x \\ne$ ...» или «$x \\in \\mathbb{R}, x \\ne $...».

+

Если уравнение $Q(x) = 0$ не имеет корней (например, $x^2 + 1 = 0$), то ОДЗ — все действительные числа.

`); + + html += makeCard('example', 'Примеры нахождения ОДЗ', '1.3', ` +

а) $\\dfrac{1}{x-3}$. Знаменатель: $x - 3 = 0 \\Rightarrow x = 3$. ОДЗ: $x \\ne 3$.

+

б) $\\dfrac{x+5}{x^2-4}$. Знаменатель: $x^2 - 4 = 0 \\Rightarrow (x-2)(x+2) = 0 \\Rightarrow x = \\pm 2$. ОДЗ: $x \\ne 2, x \\ne -2$.

+

в) $\\dfrac{2x}{(x-1)(x+7)}$. Знаменатель: $(x-1)(x+7) = 0 \\Rightarrow x = 1$ или $x = -7$. ОДЗ: $x \\ne 1, x \\ne -7$.

`); + + /* INTERACTIVE 1 — slider + SVG number line */ + html += `
+
ИНТЕРАКТИВ 1
ОДЗ на числовой прямой
+
Выбери дробь ползунком — увидишь её ОДЗ на числовой прямой и в текстовой форме.
+
+ +
+
+
+ +
+
+
`; + + /* INTERACTIVE 2 — calc ОДЗ */ + html += `
+
ИНТЕРАКТИВ 2
Калькулятор ОДЗ дроби $\\dfrac{1}{ax+b}$
+
Введи целые коэффициенты $a$ и $b$, нажми «Найти ОДЗ» — посчитаем точку, исключённую из ОДЗ.
+
+ $a$ = + + $b$ = + + +
+
+ +
`; + + /* INTERACTIVE 3 — quickfire quiz */ + html += `
+
ИНТЕРАКТИВ 3
Входит ли в ОДЗ?
+
Дробь и значение $x_0$. Решай: входит ли $x_0$ в ОДЗ (т.е. не обращает ли знаменатель в 0)?
+
Задача 1 / 8Очки: 0 / 8
+
+
+ + +
+ +
`; + + /* INTERACTIVE 4 — trainer */ + html += `
+
ИНТЕРАКТИВ 4
Тренажёр: сумма корней знаменателя
+
Для каждой дроби введи сумму всех значений, при которых знаменатель $= 0$ (т.е. сумму точек, исключённых из ОДЗ).
+
Задача 1 / 6Очки: 0 / 6
+
+
+ сумма корней = + + + +
+ +
`; + + html += secNav(null, 'p2'); + html += readButton('p1'); + + box.innerHTML = html; + renderMath(box); + + /* IV1 — slider + SVG */ + (function(){ + const FRACS = [ + { fr:'\\dfrac{1}{x-2}', roots:[2], text:'$x \\ne 2$' }, + { fr:'\\dfrac{x}{x^2-9}', roots:[-3,3], text:'$x \\ne -3,\\ x \\ne 3$' }, + { fr:'\\dfrac{3}{(x+1)(x-4)}', roots:[-1,4], text:'$x \\ne -1,\\ x \\ne 4$' }, + { fr:'\\dfrac{2x+1}{x^2-25}', roots:[-5,5], text:'$x \\ne -5,\\ x \\ne 5$' }, + { fr:'\\dfrac{1}{x^2+1}', roots:[], text:'$x \\in \\mathbb{R}$ — все действительные' }, + ]; + const sl = document.getElementById('p1-iv1-sl'); + const idx = document.getElementById('p1-iv1-idx'); + const fEl = document.getElementById('p1-iv1-formula'); + const svg = document.getElementById('p1-iv1-svg'); + const out = document.getElementById('p1-iv1-out'); + const seen = new Set(); + function draw(){ + const k = +sl.value; idx.textContent = k; + const cur = FRACS[k-1]; + fEl.innerHTML = '$' + cur.fr + '$'; + let s = ''; + // grid + s += ''; + for(let v=-10; v<=10; v++){ + const x = 30 + (v+10) * 27; + s += ''; + if(v%2===0){ + s += ''+v+''; + } + } + // axis + s += ''; + s += ''; + s += 'x'; + // excluded points + cur.roots.forEach(r=>{ + const x = 30 + (r+10) * 27; + s += ''; + s += ''+r+''; + }); + svg.innerHTML = s; + out.innerHTML = 'ОДЗ: ' + cur.text; + renderMath(fEl); renderMath(out); + seen.add(k); + if(seen.size === FRACS.length){ addXp(10,'p1-iv1'); bumpProgress('p1', 15); seen.clear(); seen.add('done'); } + } + sl.addEventListener('input', draw); + draw(); + })(); + + /* IV2 — ОДЗ калькулятор */ + (function(){ + const go = document.getElementById('p1-iv2-go'); + const aI = document.getElementById('p1-iv2-a'); + const bI = document.getElementById('p1-iv2-b'); + const out = document.getElementById('p1-iv2-out'); + const fb = document.getElementById('p1-iv2-fb'); + let solved = 0; + function showFormula(){ out.innerHTML = 'Дробь: $\\dfrac{1}{('+(aI.value||'a')+')x + ('+(bI.value||'b')+')}$'; renderMath(out); } + function calc(){ + const a = parseInt(aI.value, 10), b = parseInt(bI.value, 10); + if(isNaN(a) || isNaN(b)){ feedback(fb, false, '✗ Введи целые числа $a$ и $b$.'); return; } + if(a === 0){ + out.innerHTML = '$a = 0$ — выражение не является рациональной дробью с переменной (знаменатель — константа $'+b+'$). Если ещё и $b = 0$, дробь не определена.'; + feedback(fb, true, '✓ При $a = 0$ переменная в знаменателе исчезает.'); + return; + } + // root: ax + b = 0 → x = -b/a + const num = -b, den = a; + const d = gcd(Math.abs(num), Math.abs(den)); + let nn = num/d, dd = den/d; + if(dd < 0){ nn = -nn; dd = -dd; } + let xstr; + if(dd === 1) xstr = String(nn); + else xstr = '\\dfrac{'+nn+'}{'+dd+'}'; + out.innerHTML = 'Знаменатель: $'+a+'x + ('+b+') = 0 \\Rightarrow x = '+xstr+'$.
ОДЗ: $x \\ne '+xstr+'$.'; + renderMath(out); + feedback(fb, true, '✓ ОДЗ найдена! +10 XP'); + solved++; + if(solved === 1){ addXp(10,'p1-iv2'); bumpProgress('p1', 15); } + } + aI.addEventListener('input', showFormula); bI.addEventListener('input', showFormula); + go.addEventListener('click', calc); + showFormula(); + })(); + + /* IV3 — quickfire */ + (function(){ + const Q = [ + { expr:'$\\dfrac{1}{x-3}, \\ x_0 = 5$', yes:true, why:'$5 - 3 = 2 \\ne 0$' }, + { expr:'$\\dfrac{1}{x-3}, \\ x_0 = 3$', yes:false, why:'$3 - 3 = 0$ — знаменатель обнуляется' }, + { expr:'$\\dfrac{x}{x^2-4}, \\ x_0 = 2$', yes:false, why:'$2^2 - 4 = 0$' }, + { expr:'$\\dfrac{x}{x^2-4}, \\ x_0 = 0$', yes:true, why:'$0^2 - 4 = -4 \\ne 0$' }, + { expr:'$\\dfrac{1}{(x+1)(x-5)}, \\ x_0 = -1$', yes:false, why:'$(-1+1)(-1-5) = 0$' }, + { expr:'$\\dfrac{2}{x^2+1}, \\ x_0 = -7$', yes:true, why:'$(-7)^2 + 1 = 50 \\ne 0$ (знаменатель всегда $>0$)' }, + { expr:'$\\dfrac{x+1}{x-x}, \\ x_0 = 2$', yes:false, why:'$x - x = 0$ всегда! Дробь не определена ни при каком $x$' }, + { expr:'$\\dfrac{3}{x^2-9}, \\ x_0 = 3$', yes:false, why:'$3^2 - 9 = 0$' }, + ]; + let i = 0, score = 0; + function show(){ + if(i >= Q.length){ + document.getElementById('p1-iv3-q').innerHTML = 'Готово! Результат: ' + score + ' / ' + Q.length; + if(score === Q.length){ addXp(15,'p1-iv3'); bumpProgress('p1', 25); } + else if(score >= Q.length - 2){ addXp(8,'p1-iv3'); bumpProgress('p1', 15); } + return; + } + document.getElementById('p1-iv3-i').textContent = (i+1); + document.getElementById('p1-iv3-s').textContent = score; + document.getElementById('p1-iv3-q').innerHTML = Q[i].expr; + renderMath(document.getElementById('p1-iv3-q')); + document.getElementById('p1-iv3-fb').style.display = 'none'; + } + function answer(isYes){ + if(i >= Q.length) return; + const fb = document.getElementById('p1-iv3-fb'); + if(isYes === Q[i].yes){ score++; feedback(fb, true, '✓ Верно! '+Q[i].why+'. Дальше ▶'); } + else feedback(fb, false, '✗ Неверно. '+Q[i].why+'. Дальше ▶'); + document.getElementById('p1-iv3-s').textContent = score; + i++; + setTimeout(show, 1000); + } + document.getElementById('p1-iv3-yes').addEventListener('click', ()=>answer(true)); + document.getElementById('p1-iv3-no').addEventListener('click', ()=>answer(false)); + show(); + })(); + + /* IV4 — trainer */ + (function(){ + const Q = [ + { q:'$\\dfrac{1}{x-7}$', sum:7, hint:'корень $x = 7$' }, + { q:'$\\dfrac{x}{x^2-16}$', sum:0, hint:'корни $x = \\pm 4$, сумма $= 0$' }, + { q:'$\\dfrac{1}{(x-1)(x+9)}$', sum:-8, hint:'корни $x = 1, -9$, сумма $= -8$' }, + { q:'$\\dfrac{x^2}{x^2-25}$', sum:0, hint:'корни $x = \\pm 5$, сумма $= 0$' }, + { q:'$\\dfrac{2}{(x+3)^2}$', sum:-3, hint:'один корень $x = -3$' }, + { q:'$\\dfrac{1}{x^2-2x-15}$', sum:2, hint:'корни $x = 5, -3$, сумма $= 2$' }, + ]; + let i = 0, score = 0; + function show(){ + if(i >= Q.length){ + document.getElementById('p1-iv4-q').innerHTML = 'Готово! Результат: ' + score + ' / ' + Q.length; + if(score === Q.length){ addXp(15,'p1-iv4'); bumpProgress('p1', 25); } + else if(score >= 4){ addXp(8,'p1-iv4'); bumpProgress('p1', 15); } + return; + } + document.getElementById('p1-iv4-i').textContent = (i+1); + document.getElementById('p1-iv4-s').textContent = score; + document.getElementById('p1-iv4-q').innerHTML = 'Дробь: ' + Q[i].q; + document.getElementById('p1-iv4-ans').value = ''; + renderMath(document.getElementById('p1-iv4-q')); + document.getElementById('p1-iv4-fb').style.display = 'none'; + } + function go(){ + if(i >= Q.length) return; + const fb = document.getElementById('p1-iv4-fb'); + const ans = parseInt(document.getElementById('p1-iv4-ans').value, 10); + if(isNaN(ans)){ feedback(fb, false, '✗ Введи целое число.'); return; } + if(ans === Q[i].sum){ score++; feedback(fb, true, '✓ Верно! '+Q[i].hint+'. Дальше ▶'); } + else feedback(fb, false, '✗ Неверно. Сумма $= '+Q[i].sum+'$ ('+Q[i].hint+'). Дальше ▶'); + document.getElementById('p1-iv4-s').textContent = score; + i++; + setTimeout(show, 1200); + } + document.getElementById('p1-iv4-go').addEventListener('click', go); + document.getElementById('p1-iv4-ans').addEventListener('keydown', e=>{ if(e.key === 'Enter') go(); }); + document.getElementById('p1-iv4-start').addEventListener('click', ()=>{ i=0; score=0; show(); }); + show(); + })(); + wireReadBtn('p1'); } function buildP2(){ - const root = document.getElementById('p2-body'); - root.innerHTML = ` -
-
- ${ICONS.theory} - В разработке - § 2 -
-
-

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

-

Раздел Phase 1.

-
-
` + secNav('p1', 'p3') + readButton('p2'); - renderMath(root); + const box = document.getElementById('p2-body'); + let html = ''; + + html += makeCard('theory', 'Основное свойство дроби', '2.1', ` +

Основное свойство рациональной дроби: если числитель и знаменатель дроби умножить или разделить на одно и то же отличное от нуля выражение, то значение дроби не изменится:

+ \\[\\dfrac{A}{B} = \\dfrac{A \\cdot C}{B \\cdot C}, \\qquad \\dfrac{A \\cdot C}{B \\cdot C} = \\dfrac{A}{B}, \\quad B \\ne 0,\\ C \\ne 0.\\] +

Слева направо — это умножение на $C$ (приведение к общему знаменателю), справа налево — это сокращение.

+
Правило знаков
+ $\\dfrac{-a}{-b} = \\dfrac{a}{b}$ (минусы сокращаются), $\\dfrac{-a}{b} = -\\dfrac{a}{b} = \\dfrac{a}{-b}$ — минус можно «перекидывать». +
`); + + html += makeCard('rule', 'Как сокращать дробь', '2.2', ` +

Алгоритм сокращения:

+
    +
  1. Разложить числитель и знаменатель на множители (вынести общий множитель, применить формулы сокращённого умножения).
  2. +
  3. Найти общий множитель числителя и знаменателя.
  4. +
  5. Разделить на него (вычеркнуть).
  6. +
+

Пример: $\\dfrac{12x^2}{18x} = \\dfrac{6x \\cdot 2x}{6x \\cdot 3} = \\dfrac{2x}{3}$ — общий множитель $6x$.

+

Внимание: сокращать можно только множители, а не слагаемые! $\\dfrac{a+b}{a} \\ne 1 + b$ — это ошибка.

`); + + html += makeCard('example', 'Примеры сокращения', '2.3', ` +

а) $\\dfrac{a^2-b^2}{a+b} = \\dfrac{(a-b)(a+b)}{a+b} = a - b$ — применили формулу разности квадратов.

+

б) $\\dfrac{x^2-9}{x^2+3x} = \\dfrac{(x-3)(x+3)}{x(x+3)} = \\dfrac{x-3}{x}$ — общий множитель $(x+3)$.

+

в) $\\dfrac{6a^2b}{15ab^2} = \\dfrac{3ab \\cdot 2a}{3ab \\cdot 5b} = \\dfrac{2a}{5b}$ — общий множитель $3ab$.

`); + + /* INTERACTIVE 1 — slider visualizer */ + html += `
+
ИНТЕРАКТИВ 1
Сократить с подсветкой
+
Выбери задачу — увидишь дробь с подсвеченным общим множителем. Нажми «Показать сокращение», чтобы увидеть результат.
+
+ +
+
+
+ + +
+ +
`; + + /* INTERACTIVE 2 — number GCD calc */ + html += `
+
ИНТЕРАКТИВ 2
Калькулятор сокращения числовой дроби
+
Введи целые числитель и знаменатель — посчитаем НОД и покажем сокращённую дробь.
+
+ числитель = + + знаменатель = + + +
+
+ +
`; + + /* INTERACTIVE 3 — DnD sorter "Можно сократить / Уже не сокращается" */ + html += `
+
ИНТЕРАКТИВ 3
Сократимо или нет?
+
Перетащи каждую дробь в нужный ящик. Шесть дробей — два варианта.
+
6 дробей — 2 ящика
+
+
+
Можно сократить
+
Уже не сокращается
+
+
+ +
`; + + /* INTERACTIVE 4 — trainer */ + html += `
+
ИНТЕРАКТИВ 4
Тренажёр сокращения
+
Сократи дробь и введи числитель результата (число или числовой коэффициент).
+
Задача 1 / 6Очки: 0 / 6
+
+
+ числитель = + + + +
+ +
`; + + html += secNav('p1', 'p3'); + html += readButton('p2'); + + box.innerHTML = html; + renderMath(box); + + /* IV1 */ + (function(){ + const TASKS = [ + { before:'\\dfrac{6x}{9}', after:'\\dfrac{2x}{3}', common:'общий множитель $3$' }, + { before:'\\dfrac{a^2-1}{a+1}', after:'a - 1', common:'разность квадратов: $a^2 - 1 = (a-1)(a+1)$' }, + { before:'\\dfrac{x^2-4}{x-2}', after:'x + 2', common:'разность квадратов: $x^2 - 4 = (x-2)(x+2)$' }, + { before:'\\dfrac{4ab^2}{6a^2 b}', after:'\\dfrac{2b}{3a}', common:'общий множитель $2ab$' }, + { before:'\\dfrac{x^2+5x}{x^2-25}', after:'\\dfrac{x}{x-5}', common:'$x^2+5x = x(x+5)$, $x^2-25 = (x-5)(x+5)$, сокращаем $(x+5)$' }, + ]; + const sl = document.getElementById('p2-iv1-sl'); + const idx = document.getElementById('p2-iv1-idx'); + const bEl = document.getElementById('p2-iv1-before'); + const aEl = document.getElementById('p2-iv1-after'); + const go = document.getElementById('p2-iv1-go'); + const hide = document.getElementById('p2-iv1-hide'); + const seen = new Set(); + function show(){ + const k = +sl.value; idx.textContent = k; + const t = TASKS[k-1]; + bEl.innerHTML = '$' + t.before + '$'; + aEl.innerHTML = '
' + t.common + '
$' + t.before + ' \\;=\\; ' + t.after + '$'; + aEl.style.display = 'none'; + renderMath(bEl); + seen.add(k); + if(seen.size === TASKS.length && !seen.has('done')){ addXp(10,'p2-iv1'); bumpProgress('p2', 15); seen.add('done'); } + } + sl.addEventListener('input', show); + go.addEventListener('click', ()=>{ aEl.style.display = 'block'; renderMath(aEl); }); + hide.addEventListener('click', ()=>{ aEl.style.display = 'none'; }); + show(); + })(); + + /* IV2 */ + (function(){ + const go = document.getElementById('p2-iv2-go'); + const nI = document.getElementById('p2-iv2-num'); + const dI = document.getElementById('p2-iv2-den'); + const out = document.getElementById('p2-iv2-out'); + const fb = document.getElementById('p2-iv2-fb'); + let solved = 0; + function calc(){ + const n = parseInt(nI.value, 10), d = parseInt(dI.value, 10); + if(isNaN(n) || isNaN(d)){ feedback(fb, false, '✗ Введи целые числа.'); return; } + if(d === 0){ feedback(fb, false, '✗ Знаменатель не может быть 0.'); out.innerHTML = ''; return; } + const g = gcd(n, d); + const n2 = n/g, d2 = d/g; + if(g === 1){ + out.innerHTML = '$\\dfrac{'+n+'}{'+d+'}$ — НОД $= 1$, дробь уже несократима.'; + } else { + out.innerHTML = '$\\dfrac{'+n+'}{'+d+'} = \\dfrac{'+n+':'+g+'}{'+d+':'+g+'} = \\dfrac{'+n2+'}{'+d2+'}$, где НОД $= '+g+'$.'; + } + renderMath(out); + feedback(fb, true, '✓ Готово! +10 XP'); + solved++; + if(solved === 1){ addXp(10,'p2-iv2'); bumpProgress('p2', 15); } + } + go.addEventListener('click', calc); + calc(); + })(); + + /* IV3 — DnD sorter */ + (function(){ + const items = [ + { id:'i1', cat:'yes', html:'$\\dfrac{12}{18}$' }, + { id:'i2', cat:'no', html:'$\\dfrac{5}{7}$' }, + { id:'i3', cat:'yes', html:'$\\dfrac{a^2-b^2}{a-b}$' }, + { id:'i4', cat:'no', html:'$\\dfrac{x+1}{x-1}$' }, + { id:'i5', cat:'yes', html:'$\\dfrac{4xy}{6xz}$' }, + { id:'i6', cat:'no', html:'$\\dfrac{1}{x^2+1}$' }, + ]; + const sorter = setupSorter({ + poolId:'p2-iv3-pool', + scopeSelector:'#p2-iv3', + items: items, + cats:['yes','no'], + columnLayout:true, + }); + document.getElementById('p2-iv3-check').addEventListener('click', ()=>{ + const fb = document.getElementById('p2-iv3-fb'); + const placedCount = items.filter(it => sorter.placed[it.id]).length; + const correct = items.filter(it => sorter.placed[it.id] === it.cat).length; + if(placedCount < items.length){ feedback(fb, false, '✗ Размести все 6 дробей.'); return; } + if(correct === items.length){ feedback(fb, true, '✓ Все 6 на месте! +15 XP'); addXp(15,'p2-iv3'); bumpProgress('p2', 25); } + else feedback(fb, false, '✗ Правильно ' + correct + ' из 6. Попробуй ещё.'); + }); + document.getElementById('p2-iv3-reset').addEventListener('click', ()=>{ sorter.reset(); document.getElementById('p2-iv3-fb').style.display = 'none'; }); + })(); + + /* IV4 — trainer */ + (function(){ + const Q = [ + { q:'$\\dfrac{8a}{12}$', n:2, res:'\\dfrac{2a}{3}', hint:'НОД(8,12) $= 4$' }, + { q:'$\\dfrac{15x^2}{25x}$', n:3, res:'\\dfrac{3x}{5}', hint:'НОД(15,25) $= 5$, сокращаем $x$' }, + { q:'$\\dfrac{x^2-25}{x-5}$', n:1, res:'x+5', hint:'$(x-5)(x+5) / (x-5) = x+5$' }, + { q:'$\\dfrac{a^2-9}{a+3}$', n:1, res:'a-3', hint:'$(a-3)(a+3) / (a+3) = a-3$' }, + { q:'$\\dfrac{14m^2 n}{21mn^2}$', n:2, res:'\\dfrac{2m}{3n}', hint:'НОД(14,21) $= 7$, сокращаем $mn$' }, + { q:'$\\dfrac{6(x-1)}{8(x-1)^2}$', n:3, res:'\\dfrac{3}{4(x-1)}', hint:'НОД(6,8) $= 2$, сокращаем $(x-1)$' }, + ]; + let i = 0, score = 0; + function show(){ + if(i >= Q.length){ + document.getElementById('p2-iv4-q').innerHTML = 'Готово! Результат: ' + score + ' / ' + Q.length; + if(score === Q.length){ addXp(15,'p2-iv4'); bumpProgress('p2', 25); } + else if(score >= 4){ addXp(8,'p2-iv4'); bumpProgress('p2', 15); } + return; + } + document.getElementById('p2-iv4-i').textContent = (i+1); + document.getElementById('p2-iv4-s').textContent = score; + document.getElementById('p2-iv4-q').innerHTML = 'Сократи: ' + Q[i].q; + document.getElementById('p2-iv4-ans').value = ''; + renderMath(document.getElementById('p2-iv4-q')); + document.getElementById('p2-iv4-fb').style.display = 'none'; + } + function go(){ + if(i >= Q.length) return; + const fb = document.getElementById('p2-iv4-fb'); + const ans = parseInt(document.getElementById('p2-iv4-ans').value, 10); + if(isNaN(ans)){ feedback(fb, false, '✗ Введи число.'); return; } + if(ans === Q[i].n){ score++; feedback(fb, true, '✓ Верно! $' + Q[i].res + '$ ('+Q[i].hint+'). Дальше ▶'); } + else feedback(fb, false, '✗ Неверно. Должно быть $' + Q[i].n + '$, ответ $' + Q[i].res + '$ ('+Q[i].hint+'). Дальше ▶'); + document.getElementById('p2-iv4-s').textContent = score; + i++; + setTimeout(show, 1300); + } + document.getElementById('p2-iv4-go').addEventListener('click', go); + document.getElementById('p2-iv4-ans').addEventListener('keydown', e=>{ if(e.key === 'Enter') go(); }); + document.getElementById('p2-iv4-start').addEventListener('click', ()=>{ i=0; score=0; show(); }); + show(); + })(); + wireReadBtn('p2'); }