From a61b1e3c20d52865685cab2a8aa159fb394a83d9 Mon Sep 17 00:00:00 2001 From: Maxim Dolgolyov Date: Fri, 29 May 2026 08:14:15 +0300 Subject: [PATCH] =?UTF-8?q?feat(alg9=20ch1=20wave3=20+=20final):=20=C2=A75?= =?UTF-8?q?=20=C2=AB=D0=9F=D1=80=D0=B5=D0=BE=D0=B1=D1=80=D0=B0=D0=B7=D0=BE?= =?UTF-8?q?=D0=B2=D0=B0=D0=BD=D0=B8=D0=B5=C2=BB=20+=20=D0=A4=D0=B8=D0=BD?= =?UTF-8?q?=D0=B0=D0=BB=20=D0=B3=D0=BB=D0=B0=D0=B2=D1=8B=201=20(5=20=D0=B1?= =?UTF-8?q?=D0=BE=D1=81=D1=81=D0=BE=D0=B2=20+=20=D0=B0=D1=87=D0=B8=D0=B2?= =?UTF-8?q?=D0=BA=D0=B0)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/textbooks/algebra_9_ch1.html | 540 ++++++++++++++++++++++++-- 1 file changed, 512 insertions(+), 28 deletions(-) diff --git a/frontend/textbooks/algebra_9_ch1.html b/frontend/textbooks/algebra_9_ch1.html index 1b12907..48dce55 100644 --- a/frontend/textbooks/algebra_9_ch1.html +++ b/frontend/textbooks/algebra_9_ch1.html @@ -1503,39 +1503,523 @@ function buildP4(){ } function buildP5(){ - const root = document.getElementById('p5-body'); - root.innerHTML = ` -
-
- ${ICONS.theory} - В разработке - § 5 -
-
-

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

-

Раздел Phase 1.

-
-
` + secNav('p4', 'final1') + readButton('p5'); - renderMath(root); + const box = document.getElementById('p5-body'); + let html = ''; + + html += makeCard('theory', 'Алгоритм многошагового упрощения', '5.1', ` +

Сложное рациональное выражение упрощается по чёткому порядку действий:

+
    +
  1. Разложи все числители и знаменатели на множители.
  2. +
  3. Выполни действия в скобках (сначала $+$ и $-$).
  4. +
  5. Выполни умножение и деление в порядке слева направо.
  6. +
  7. Сократи результат.
  8. +
+

Главное: на каждом шаге следи за ОДЗ — никакой знаменатель не должен обращаться в ноль.

+

Помни также правила приоритета: сначала всё, что в скобках; затем умножение/деление; затем сложение/вычитание.

`); + + html += makeCard('example', 'Пример: вся цепочка от начала до конца', '5.2', ` +

Упростим выражение:

+ \\[\\left( \\dfrac{1}{x-1} + \\dfrac{1}{x+1} \\right) \\cdot \\dfrac{x^2-1}{2x}.\\] +

Шаг 1 (скобка). Общий знаменатель — $(x-1)(x+1)$:

+ \\[\\dfrac{1}{x-1} + \\dfrac{1}{x+1} = \\dfrac{(x+1)+(x-1)}{(x-1)(x+1)} = \\dfrac{2x}{(x-1)(x+1)}.\\] +

Шаг 2 (умножение). Разложим $x^2-1 = (x-1)(x+1)$:

+ \\[\\dfrac{2x}{(x-1)(x+1)} \\cdot \\dfrac{(x-1)(x+1)}{2x}.\\] +

Шаг 3 (сокращение). Сокращаем $2x$ и $(x-1)(x+1)$:

+ \\[= 1.\\] +

ОДЗ: $x \\ne 0,\\ x \\ne 1,\\ x \\ne -1$. На ОДЗ результат равен $1$.

`); + + html += makeCard('rule', 'Полезные тождества для сокращений', '5.3', ` +

Без этих формул многие дроби не упростить:

+ +

Перед сокращением всегда попробуй применить одно из этих тождеств — часто это единственный путь.

+
Подсказка: алгоритм поиска множителя
+ Если в числителе или знаменателе видишь два слагаемых — проверь: это разность квадратов? Если три слагаемых — может, полный квадрат? Если есть общий буквенный множитель — вынеси его. И только после этого сокращай. +
`); + + /* INTERACTIVE 1 — Конвейер пошагового упрощения */ + html += `
+
ИНТЕРАКТИВ 1
Конвейер упрощения
+
Выбери задачу и раскрывай шаги один за другим. После последнего шага — «Готово!». 5 задач.
+
+ +
+
+
+
+ + +
+ +
`; + + /* INTERACTIVE 2 — DnD: разложи числитель и знаменатель */ + html += `
+
ИНТЕРАКТИВ 2
Сборщик: разложи и сократи
+
Дана дробь $\\dfrac{x^2-4}{x^2+4x+4}$. Перетащи правильные разложения в ящики «Числитель» и «Знаменатель». Лишние карточки оставь в пуле.
+
Подсказка: $a^2-b^2$ и $(a+b)^2$
+
+
+
Числитель: $x^2-4$
+
Знаменатель: $x^2+4x+4$
+
+
+ + +
`; + + /* INTERACTIVE 3 — Найди ошибку (квикфайр) */ + html += `
+
ИНТЕРАКТИВ 3
Найди ошибку в упрощении
+
6 цепочек преобразований. Реши: верно или ошибка?
+
Задача 1 / 6Очки: 0 / 6
+
+
+ + +
+ +
`; + + /* INTERACTIVE 4 — Тренажёр многошагового упрощения */ + html += `
+
ИНТЕРАКТИВ 4
Тренажёр многошагового упрощения
+
Упрости каждое выражение и введи число, которое спрашивают в подсказке.
+
Задача 1 / 5Очки: 0 / 5
+
+
+ ответ = + + + +
+ +
`; + + html += secNav('p4', 'final1'); + html += readButton('p5'); + + box.innerHTML = html; + renderMath(box); + + /* IV1 — Конвейер */ + (function(){ + const T = [ + { + title:'\\dfrac{x^2-9}{x+3} : \\dfrac{x-3}{2}', + steps:[ + '\\dfrac{x^2-9}{x+3} : \\dfrac{x-3}{2} = \\dfrac{(x-3)(x+3)}{x+3} \\cdot \\dfrac{2}{x-3}', + '= \\dfrac{(x-3)(x+3) \\cdot 2}{(x+3)(x-3)}', + '= 2' + ], + notes:['разность квадратов и деление = умножение на обратную','перемножили','сократили $(x-3)(x+3)$'] + }, + { + title:'\\dfrac{1}{a} + \\dfrac{1}{a^2}', + steps:[ + '\\dfrac{1}{a} + \\dfrac{1}{a^2} = \\dfrac{a}{a^2} + \\dfrac{1}{a^2}', + '= \\dfrac{a+1}{a^2}' + ], + notes:['общий знаменатель $a^2$','сложили числители'] + }, + { + title:'\\dfrac{x^2-4}{x} \\cdot \\dfrac{1}{x+2}', + steps:[ + '\\dfrac{x^2-4}{x} \\cdot \\dfrac{1}{x+2} = \\dfrac{(x-2)(x+2)}{x} \\cdot \\dfrac{1}{x+2}', + '= \\dfrac{(x-2)(x+2)}{x(x+2)}', + '= \\dfrac{x-2}{x}' + ], + notes:['разложили $x^2-4$','перемножили','сократили $(x+2)$'] + }, + { + title:'\\dfrac{a}{a-b} - \\dfrac{b}{a-b}', + steps:[ + '\\dfrac{a}{a-b} - \\dfrac{b}{a-b} = \\dfrac{a-b}{a-b}', + '= 1' + ], + notes:['одинаковые знаменатели — вычли числители','сократили'] + }, + { + title:'\\dfrac{x+1}{x-1} : \\dfrac{x^2-1}{x-1}', + steps:[ + '\\dfrac{x+1}{x-1} : \\dfrac{x^2-1}{x-1} = \\dfrac{x+1}{x-1} \\cdot \\dfrac{x-1}{(x-1)(x+1)}', + '= \\dfrac{(x+1)(x-1)}{(x-1)(x-1)(x+1)}', + '= \\dfrac{1}{x-1}' + ], + notes:['обратная дробь + разложение $x^2-1$','перемножили','сократили $(x+1)(x-1)$'] + }, + ]; + const sl = document.getElementById('p5-iv1-sl'); + const idx = document.getElementById('p5-iv1-idx'); + const bEl = document.getElementById('p5-iv1-before'); + const stEl = document.getElementById('p5-iv1-steps'); + const next = document.getElementById('p5-iv1-next'); + const rst = document.getElementById('p5-iv1-reset'); + const fb = document.getElementById('p5-iv1-fb'); + let step = 0; + const seen = new Set(); + function show(){ + const k = +sl.value; idx.textContent = k; + const t = T[k-1]; + bEl.innerHTML = '$' + t.title + '$'; + let out = ''; + for(let i=0;i
$'+t.steps[i]+'$
'; + } + stEl.innerHTML = out; + renderMath(bEl); renderMath(stEl); + if(step >= t.steps.length){ + feedback(fb, true, '✓ Готово! Все шаги пройдены.'); + seen.add(k); + if(seen.size === T.length && !seen.has('done')){ addXp(10,'p5-iv1'); bumpProgress('p5', 15); seen.add('done'); } + } else { + fb.style.display = 'none'; + } + } + sl.addEventListener('input', ()=>{ step = 0; show(); }); + next.addEventListener('click', ()=>{ const t = T[(+sl.value)-1]; if(step < t.steps.length){ step++; show(); } }); + rst.addEventListener('click', ()=>{ step = 0; show(); }); + show(); + })(); + + /* IV2 — DnD: разложи числитель и знаменатель */ + (function(){ + const items = [ + { id:'s1', cat:'num', html:'$(x-2)(x+2)$' }, + { id:'s2', cat:'den', html:'$(x+2)^2$' }, + { id:'s3', cat:'trash', html:'$(x-2)^2$' }, + { id:'s4', cat:'trash', html:'$x^2-2$' }, + { id:'s5', cat:'num', html:'$(x+2)(x-2)$' }, + { id:'s6', cat:'trash', html:'$(x-4)(x+1)$' }, + ]; + const sorter = setupSorter({ + poolId:'p5-iv2-pool', + scopeSelector:'#p5-iv2', + items: items, + cats:['num','den'], + columnLayout:true, + }); + let solved = false; + document.getElementById('p5-iv2-check').addEventListener('click', ()=>{ + const fb = document.getElementById('p5-iv2-fb'); + const result = document.getElementById('p5-iv2-result'); + const placed = sorter.placed; + const inNum = items.filter(it => placed[it.id] === 'num'); + const inDen = items.filter(it => placed[it.id] === 'den'); + const trashPlaced = items.filter(it => it.cat === 'trash' && placed[it.id]); + if(inNum.length === 0 || inDen.length === 0){ feedback(fb, false, '✗ В каждый ящик нужно что-то положить.'); return; } + const numOk = inNum.every(it => it.cat === 'num'); + const denOk = inDen.every(it => it.cat === 'den') && inDen.some(it => it.id === 's2'); + if(numOk && denOk && trashPlaced.length === 0){ + feedback(fb, true, '✓ Верно! Разложили правильно. +10 XP'); + result.style.display = 'block'; + result.innerHTML = 'После сокращения: $\\dfrac{x^2-4}{x^2+4x+4} = \\dfrac{(x-2)(x+2)}{(x+2)^2} = \\dfrac{x-2}{x+2}$'; + renderMath(result); + if(!solved){ addXp(10,'p5-iv2'); bumpProgress('p5', 15); solved = true; } + } else if(trashPlaced.length > 0){ + feedback(fb, false, '✗ Лишние карточки в ящиках — убери их в пул.'); + } else { + feedback(fb, false, '✗ Не все разложения подходят. Подсказка: $x^2-4$ — разность квадратов, $x^2+4x+4 = (x+2)^2$.'); + } + }); + document.getElementById('p5-iv2-reset').addEventListener('click', ()=>{ + sorter.reset(); + document.getElementById('p5-iv2-fb').style.display = 'none'; + document.getElementById('p5-iv2-result').style.display = 'none'; + }); + })(); + + /* IV3 — Найди ошибку */ + (function(){ + const Q = [ + { expr:'$\\dfrac{x+2}{x+3} = \\dfrac{2}{3}$', ok:false, why:'нельзя сокращать слагаемые — только общий множитель' }, + { expr:'$\\dfrac{(x+2)(x-1)}{(x+2)(x+3)} = \\dfrac{x-1}{x+3}$', ok:true, why:'сократили общий множитель $(x+2)$' }, + { expr:'$\\dfrac{x^2-9}{x-3} = x+3$', ok:true, why:'$\\dfrac{(x-3)(x+3)}{x-3} = x+3$' }, + { expr:'$\\dfrac{a^2+a}{a^2-a} = -1$', ok:false, why:'правильно $\\dfrac{a(a+1)}{a(a-1)} = \\dfrac{a+1}{a-1}$' }, + { expr:'$\\dfrac{2x-2}{x-1} = 2$', ok:true, why:'$\\dfrac{2(x-1)}{x-1} = 2$' }, + { expr:'$\\dfrac{x}{x+y} = \\dfrac{1}{1+y}$', ok:false, why:'нельзя сокращать $x$ со слагаемым $x$ в сумме $x+y$' }, + ]; + let i = 0, score = 0; + function show(){ + if(i >= Q.length){ + document.getElementById('p5-iv3-q').innerHTML = 'Готово! Результат: ' + score + ' / ' + Q.length; + if(score === Q.length){ addXp(15,'p5-iv3'); bumpProgress('p5', 25); } + else if(score >= Q.length - 1){ addXp(8,'p5-iv3'); bumpProgress('p5', 15); } + return; + } + document.getElementById('p5-iv3-i').textContent = (i+1); + document.getElementById('p5-iv3-s').textContent = score; + document.getElementById('p5-iv3-q').innerHTML = Q[i].expr; + renderMath(document.getElementById('p5-iv3-q')); + document.getElementById('p5-iv3-fb').style.display = 'none'; + } + function answer(isOk){ + if(i >= Q.length) return; + const fb = document.getElementById('p5-iv3-fb'); + if(isOk === Q[i].ok){ score++; feedback(fb, true, '✓ Верно! '+Q[i].why+'. Дальше ▶'); } + else feedback(fb, false, '✗ Неверно. '+Q[i].why+'. Дальше ▶'); + document.getElementById('p5-iv3-s').textContent = score; + i++; + setTimeout(show, 1500); + } + document.getElementById('p5-iv3-ok').addEventListener('click', ()=>answer(true)); + document.getElementById('p5-iv3-err').addEventListener('click', ()=>answer(false)); + show(); + })(); + + /* IV4 — Тренажёр многошагового упрощения */ + (function(){ + const Q = [ + { q:'$\\dfrac{a-b}{a+b} + \\dfrac{2b}{a+b}$', ans:1, prompt:'значение =', hint:'$=\\dfrac{a-b+2b}{a+b} = \\dfrac{a+b}{a+b} = 1$', res:'1' }, + { q:'$\\dfrac{x^2-25}{x+5}$', ans:-5, prompt:'свободный член результата =', hint:'$=\\dfrac{(x-5)(x+5)}{x+5} = x-5$ — свободный член $-5$', res:'x-5' }, + { q:'$\\dfrac{2}{x-3} - \\dfrac{2}{x+3}$', ans:12, prompt:'числитель результата =', hint:'$=\\dfrac{2(x+3)-2(x-3)}{(x-3)(x+3)} = \\dfrac{12}{x^2-9}$', res:'\\dfrac{12}{x^2-9}' }, + { q:'$\\dfrac{a^2-1}{a+1} \\cdot \\dfrac{1}{a-1}$', ans:1, prompt:'значение =', hint:'$=\\dfrac{(a-1)(a+1)}{a+1} \\cdot \\dfrac{1}{a-1} = 1$', res:'1' }, + { q:'$\\dfrac{x}{x^2-1} : \\dfrac{x^2}{x-1}$', ans:-1, prompt:'сумма корней знаменателя $x(x+1)$ =', hint:'$=\\dfrac{x}{(x-1)(x+1)} \\cdot \\dfrac{x-1}{x^2} = \\dfrac{1}{x(x+1)}$. Корни $0$ и $-1$, сумма $-1$.', res:'\\dfrac{1}{x(x+1)}' }, + ]; + let i = 0, score = 0; + function show(){ + if(i >= Q.length){ + document.getElementById('p5-iv4-q').innerHTML = 'Готово! Результат: ' + score + ' / ' + Q.length; + document.getElementById('p5-iv4-prompt').textContent = 'ответ ='; + if(score === Q.length){ addXp(15,'p5-iv4'); bumpProgress('p5', 25); } + else if(score >= 4){ addXp(8,'p5-iv4'); bumpProgress('p5', 15); } + return; + } + document.getElementById('p5-iv4-i').textContent = (i+1); + document.getElementById('p5-iv4-s').textContent = score; + document.getElementById('p5-iv4-q').innerHTML = 'Упрости: ' + Q[i].q; + document.getElementById('p5-iv4-prompt').textContent = Q[i].prompt; + document.getElementById('p5-iv4-ans').value = ''; + renderMath(document.getElementById('p5-iv4-q')); + document.getElementById('p5-iv4-fb').style.display = 'none'; + } + function go(){ + if(i >= Q.length) return; + const fb = document.getElementById('p5-iv4-fb'); + const ans = parseInt(document.getElementById('p5-iv4-ans').value, 10); + if(isNaN(ans)){ feedback(fb, false, '✗ Введи целое число.'); return; } + if(ans === Q[i].ans){ score++; feedback(fb, true, '✓ Верно! Ответ: $' + Q[i].res + '$ ('+Q[i].hint+'). Дальше ▶'); } + else feedback(fb, false, '✗ Неверно. Должно быть $' + Q[i].ans + '$. Ответ: $' + Q[i].res + '$ ('+Q[i].hint+'). Дальше ▶'); + document.getElementById('p5-iv4-s').textContent = score; + i++; + setTimeout(show, 1600); + } + document.getElementById('p5-iv4-go').addEventListener('click', go); + document.getElementById('p5-iv4-ans').addEventListener('keydown', e=>{ if(e.key === 'Enter') go(); }); + document.getElementById('p5-iv4-start').addEventListener('click', ()=>{ i=0; score=0; show(); }); + show(); + })(); + wireReadBtn('p5'); } function buildFinal1(){ - const root = document.getElementById('final1-body'); - root.innerHTML = ` -
-
- ${ICONS.theory} - В разработке - + const box = document.getElementById('final1-body'); + let html = ''; + + /* Часть А — Шпаргалка главы (5 mini-карточек) */ + html += `
+
+ ${ICONS.theory} + Шпаргалка главы 1 + Итог +
+
+

Все ключевые правила главы — в одном месте. Просмотри перед боссами!

+
+
+
§ 1 · ОДЗ
+
$\\dfrac{P(x)}{Q(x)},\\ Q(x) \\ne 0$. Решаем $Q(x)=0$ и исключаем корни.
+
+
+
§ 2 · Сокращение
+
$\\dfrac{AC}{BC} = \\dfrac{A}{B}$. Только общий множитель, не слагаемое!
+
+
+
§ 3 · Сложение
+
Одинаковые знам. — числители $\\pm$. Разные — приводим к НОЗ.
+
+
+
§ 4 · Умножение и деление
+
$\\dfrac{A}{B} \\cdot \\dfrac{C}{D} = \\dfrac{AC}{BD}$. Деление — умножение на обратную.
+
+
+
§ 5 · Преобразование
+
Разложи → скобки → $\\cdot$ и $:$ слева направо → сократи.
+
-
-

Содержание финала главы Финал главы — в разработке будет добавлено в следующих обновлениях.

-

Боссы и итоговые задания будут добавлены в Phase 1.

-
-
` + secNav('p5', null) + readButton('final1'); - renderMath(root); - wireReadBtn('final1'); +
+
`; + + /* Часть Б — 5 боссов */ + html += `
+
+ ${ICONS.rule} + Боссы главы 1 + 5 +
+
+

5 интегрированных задач. Каждая комбинирует несколько тем. За каждого побеждённого босса — +10 XP. Победишь всех — +50 XP бонус и ачивка «Магистр рациональных дробей»!

+
+
`; + + html += '
'; + + html += `
+
Прогресс по боссам
+
0 / 5 боссов побеждено
+
+
+
+ +
`; + + html += secNav('p5', null); + + box.innerHTML = html; + renderMath(box); + + /* Боссы */ + const BOSSES = [ + { + n:1, color:'#10b981', + title:'Огр Сокращения', + tag:'§ 1 + § 2', + q:'Найди ОДЗ и сократи: $\\dfrac{x^2-16}{x^2-x-12}$. После сокращения получится $\\dfrac{x+4}{x+?}$. Введи число в знаменателе.', + ans:3, + hint:'$x^2-16 = (x-4)(x+4)$; $x^2-x-12 = (x-4)(x+3)$. Сократи $(x-4)$ — останется $\\dfrac{x+4}{x+3}$.' + }, + { + n:2, color:'#0891b2', + title:'Дракон Сложения', + tag:'§ 3 + § 2', + q:'Упрости: $\\dfrac{1}{x-2} + \\dfrac{1}{x+2} - \\dfrac{4}{x^2-4}$. Результат имеет вид $\\dfrac{?}{x+2}$. Введи числитель.', + ans:2, + hint:'Общий знаменатель $x^2-4 = (x-2)(x+2)$. Числитель: $(x+2)+(x-2)-4 = 2x-4 = 2(x-2)$. После сокращения $(x-2)$: $\\dfrac{2}{x+2}$.' + }, + { + n:3, color:'#7c3aed', + title:'Гидра Умножения', + tag:'§ 4 + § 5', + q:'Упрости: $\\dfrac{x^2-9}{x+5} \\cdot \\dfrac{x^2+10x+25}{x-3}$. Результат — квадратный трёхчлен $x^2+\\Box x+15$. Введи коэффициент при $x$.', + ans:8, + hint:'$(x^2-9)(x+5)^2 / ((x+5)(x-3)) = (x-3)(x+3) \\cdot (x+5)/(x-3) = (x+3)(x+5) = x^2+8x+15$.' + }, + { + n:4, color:'#dc2626', + title:'Тёмный Лорд Деления', + tag:'§ 4 + § 1', + q:'Реши: $\\dfrac{x+1}{x^2-9} : \\dfrac{x+1}{x-3}$. Результат имеет вид $\\dfrac{1}{x+?}$. Введи число.', + ans:3, + hint:'ОДЗ: $x \\ne \\pm 3,\\ x \\ne -1$. $\\dfrac{x+1}{(x-3)(x+3)} \\cdot \\dfrac{x-3}{x+1} = \\dfrac{1}{x+3}$.' + }, + { + n:5, color:'#f59e0b', + title:'Мастер Синтеза', + tag:'§ 5 — итог', + q:'Найди значение: $\\left(\\dfrac{1}{a-b} - \\dfrac{1}{a+b}\\right) : \\dfrac{2b}{a^2-b^2}$ при $a=5,\\ b=3$. Введи число.', + ans:1, + hint:'$\\dfrac{(a+b)-(a-b)}{(a-b)(a+b)} : \\dfrac{2b}{a^2-b^2} = \\dfrac{2b}{a^2-b^2} \\cdot \\dfrac{a^2-b^2}{2b} = 1$ — независимо от $a, b$.' + }, + ]; + + const cont = document.getElementById('ch1-bosses-container'); + const STATE_KEY = 'algebra9_ch1_bosses'; + const BOSS_STATE = (function(){ + try{ const s = localStorage.getItem(STATE_KEY); if(s) return JSON.parse(s); }catch(e){} + return BOSSES.map(()=>({defeated:false})); + })(); + function saveBosses(){ try{ localStorage.setItem(STATE_KEY, JSON.stringify(BOSS_STATE)); }catch(e){} } + + cont.innerHTML = BOSSES.map((b, idx)=>{ + return '
' + +'
' + +'' + +'
Босс '+b.n+': '+b.title+'
' + +'
'+b.tag+'
' + +'
' + +'
'+b.q+'
' + +'
' + +'ответ =' + +'' + +'' + +'' + +'
' + +'' + +'
'; + }).join(''); + renderMath(cont); + + function refreshOverall(){ + const won = BOSS_STATE.filter(s => s.defeated).length; + const txt = document.getElementById('ch1-boss-overall'); + const fill = document.getElementById('ch1-boss-overall-fill'); + if(txt) txt.textContent = won + ' / ' + BOSSES.length + ' боссов побеждено'; + if(fill) fill.style.width = (won * 100 / BOSSES.length) + '%'; + if(won >= BOSSES.length){ + const reward = document.getElementById('ch1-final-reward'); + if(reward && reward.style.display === 'none'){ + reward.style.display = 'block'; + if(!STATE.achievements.has('ch1_done')){ + achievement('ch1_done','Магистр рациональных дробей'); + addXp(50, 'ch1-bonus'); + bumpProgress('final1', 30); + if(window.confetti){ try{ confetti(); }catch(e){} } + } + } + } + } + + BOSSES.forEach((b, idx)=>{ + const card = document.getElementById('boss-'+b.n+'-card'); + const goBtn = document.getElementById('boss-'+b.n+'-go'); + const hintBtn = document.getElementById('boss-'+b.n+'-hint'); + const ansInp = document.getElementById('boss-'+b.n+'-ans'); + if(BOSS_STATE[idx].defeated){ + card.style.background = 'linear-gradient(135deg,var(--sec-acc-soft),var(--pri-soft))'; + goBtn.disabled = true; goBtn.style.opacity = .55; goBtn.textContent = '✓ Повержен'; + ansInp.disabled = true; + } + goBtn.addEventListener('click', ()=>{ + if(BOSS_STATE[idx].defeated) return; + const fb = document.getElementById('boss-'+b.n+'-fb'); + const val = parseInt(ansInp.value, 10); + if(isNaN(val)){ feedback(fb, false, '✗ Введи целое число.'); return; } + if(val === b.ans){ + BOSS_STATE[idx].defeated = true; saveBosses(); + feedback(fb, true, '✓ Босс '+b.n+' повержен! +10 XP. '+b.hint); + addXp(10, 'boss-ch1-'+b.n); + bumpProgress('final1', 18); + goBtn.disabled = true; goBtn.style.opacity = .55; goBtn.textContent = '✓ Повержен'; + ansInp.disabled = true; + card.style.background = 'linear-gradient(135deg,var(--sec-acc-soft),var(--pri-soft))'; + refreshOverall(); + } else { + feedback(fb, false, '✗ Промах. Попробуй ещё. Подсказка доступна.'); + } + }); + hintBtn.addEventListener('click', ()=>{ + const fb = document.getElementById('boss-'+b.n+'-fb'); + fb.className = 'feedback ok'; + fb.innerHTML = 'Подсказка: '+b.hint; + fb.style.display = 'block'; + fb.style.background = 'var(--warn-bg)'; + fb.style.color = '#92400e'; + fb.style.borderLeftColor = 'var(--warn)'; + renderMath(fb); + }); + ansInp.addEventListener('keydown', e=>{ if(e.key === 'Enter') goBtn.click(); }); + }); + + refreshOverall(); } /* ===== Search ===== */