diff --git a/backend/src/db/migrations/012_algebra_8_ch2.sql b/backend/src/db/migrations/012_algebra_8_ch2.sql new file mode 100644 index 0000000..aa42185 --- /dev/null +++ b/backend/src/db/migrations/012_algebra_8_ch2.sql @@ -0,0 +1,6 @@ +-- Register Algebra 8 — Chapter 2 «Квадратные уравнения» as a separate textbook entry. +-- Chapter 1 already exists with slug 'algebra-8'. This is a continuation file. +INSERT OR IGNORE INTO textbooks (slug, subject, grade, title, author, description, html_path, para_count, color, sort_order) VALUES + ('algebra-8-ch2', 'math', 8, 'Алгебра — 8 класс · Глава 2', '', + 'Интерактивный учебник: глава 2 «Квадратные уравнения». §§ 7–12 + Финал. 30+ интерактивов: конструкторы уравнений, дискриминант, теорема Виета, разложение, текстовые задачи, биквадратные. 7 боссов-проверок.', + 'algebra_8_ch2.html', 7, 'pink', 4); diff --git a/frontend/textbooks/algebra_8.html b/frontend/textbooks/algebra_8.html index bca69d6..c67e6b9 100644 --- a/frontend/textbooks/algebra_8.html +++ b/frontend/textbooks/algebra_8.html @@ -756,6 +756,10 @@ input,select,textarea{font-family:inherit} + + Глава 2 + + Тёмная diff --git a/frontend/textbooks/algebra_8_ch2.html b/frontend/textbooks/algebra_8_ch2.html index d8a17f8..bc51699 100644 --- a/frontend/textbooks/algebra_8_ch2.html +++ b/frontend/textbooks/algebra_8_ch2.html @@ -401,6 +401,15 @@ const ACH_LABELS = { p12_frac: 'Дробное → квадратное', p12_subst: 'Замена переменной', p12_odz: 'Посторонний корень', + boss_b1: 'Босс §7 повержен', + boss_b2: 'Босс §8 повержен', + boss_b3: 'Босс §9 повержен', + boss_b4: 'Босс §10 повержен', + boss_b5: 'Босс §11 повержен', + boss_b6: 'Босс §12 повержен', + boss_b7: 'Магистр алгебры', + all_bosses: 'Все 7 боссов побеждены!', + prac_streak: 'Серия из 5 верных', }; function loadProgress(){ @@ -1758,7 +1767,287 @@ function buildP12(){ document.getElementById('p12o-start').addEventListener('click', ()=>{ i=1; show(); }); })(); } -function buildFinal2stub(){ document.getElementById('final2-body').innerHTML = `Финал главыИтоговая самооценка, практика, увлекательная математика и финальный босс — в Wave 4.${secNav('p12',null)}`; } +function buildFinal2stub(){ buildFinal2(); } +function buildFinal2(){ + const box = document.getElementById('final2-body'); + let html = ''; + + html += `${ICONS.theory}Поздравляем! + Вы прошли все 6 параграфов главы «Квадратные уравнения». В финале вас ждут 7 боссов — по одному на каждый параграф и один общий. Победите всех — получите титул «Магистр квадратных уравнений». + `; + + /* BOSS ARENA */ + html += widget('Боссы главы 2','BOSS ARENA','Каждый босс — 5 заданий. Победите всех 7, чтобы открыть финальный титул.',` + + + + ★ + + + + + + Выйти + + + + + `); + + /* FUN MATH */ + html += widget('Увлекательная математика','BONUS','3 факта и парадокса про квадратные уравнения.',` + Кто открыл формулу корней? + Первые методы — в Древнем Вавилоне (2000 до н. э.). Полную формулу для произвольных коэффициентов записал Мухаммед аль-Хорезми в IX веке в своём трактате «Краткая книга об исчислении ал-джабра и ал-мукабалы». От слова «ал-джабр» произошло слово «алгебра». + + Почему $a \\neq 0$? + Если $a = 0$, то уравнение $bx + c = 0$ — линейное (степени 1), а не квадратное. Степень уравнения — это степень его старшего члена, поэтому требование $a \\neq 0$ нужно, чтобы старший член был именно $x^2$. + + Парадокс «потерянного корня» + Если уравнение $\\dfrac{x^2-4}{x-2} = 0$ умножить на $x-2$, получим $x^2 - 4 = 0 \\Rightarrow x = \\pm 2$. Но $x = 2$ обнуляет знаменатель! Этот корень — посторонний. ОДЗ всегда выручает. + `); + + /* PRACTICE GENERATOR */ + html += widget('Финальная практика','PRACTICE','Случайные задачи из всей главы. Решайте, сколько хотите.',` + Решено: 0Правильно: 0Серия: 0 + + + + Ответ + Следующая + + `); + + /* CERTIFICATE */ + html += `${ICONS.home}Сертификат прохождения + Как вы оцените своё знание квадратных уравнений? + + `; + + html += secNav('p12', null); + box.innerHTML = html; + if(window.renderMathInElement) setTimeout(()=>renderMath(box), 0); + + /* ===== BOSSES ===== */ + const BOSSES = [ + { id:'b1', name:'Хранитель неполных', sub:'§ 7 · ax²+bx=0 и ax²+c=0', icon:'⚔', hp:5, tasks:[ + { t:'select', q:'Сколько корней у уравнения $5x^2 - 45 = 0$?', opts:['Два','Один','Корней нет'], ok:0 }, + { t:'input', q:'Решите $3x^2 - 12x = 0$. Введите больший корень.', ans:[4] }, + { t:'yesno', q:'Уравнение $2x^2 + 8 = 0$ имеет корни.', ok:false }, + { t:'input', q:'Корни $x^2 - 16 = 0$ — это $\\pm a$. Найдите $a$.', ans:[4] }, + { t:'select', q:'$ax^2 + c = 0$ имеет два корня, если…', opts:['$-c/a > 0$','$c > 0$','$ac > 0$','любое'], ok:0 }, + ]}, + { id:'b2', name:'Дискриминатор', sub:'§ 8 · D = b² − 4ac', icon:'D', hp:5, tasks:[ + { t:'input', q:'Найдите $D$ для $x^2 - 7x + 12 = 0$.', ans:[1] }, + { t:'select', q:'$D < 0$ означает:', opts:['два различных корня','один корень','корней нет'], ok:2 }, + { t:'input', q:'Сумма корней $2x^2 - 10x + 8 = 0$ (через $-b/a$):', ans:[5] }, + { t:'yesno', q:'У $x^2 + x + 1 = 0$ есть действительные корни.', ok:false }, + { t:'input', q:'При каком $m$ уравнение $x^2 - 4x + m = 0$ имеет один корень? $m = ?$', ans:[4] }, + ]}, + { id:'b3', name:'Дух Виета', sub:'§ 9 · сумма и произведение', icon:'∑', hp:5, tasks:[ + { t:'input', q:'Корни $x^2 - 9x + 20 = 0$. Введите больший.', ans:[5] }, + { t:'select', q:'Если $x_1 + x_2 = -p,\\ x_1 x_2 = q$, то корни $\\{2; 7\\}$ дают уравнение:', opts:['$x^2 - 9x + 14 = 0$','$x^2 + 9x + 14 = 0$','$x^2 - 7x + 9 = 0$'], ok:0 }, + { t:'yesno', q:'У уравнения $x^2 + 3x + 10 = 0$ корни одного знака.', ok:true }, + { t:'input', q:'Произведение корней $x^2 + 4x - 21 = 0$.', ans:[-21] }, + { t:'select', q:'Знаки корней $x^2 - 5x + 6 = 0$:', opts:['оба «+»','оба «−»','разные'], ok:0 }, + ]}, + { id:'b4', name:'Разложитель', sub:'§ 10 · a(x−x₁)(x−x₂)', icon:'( )', hp:5, tasks:[ + { t:'select', q:'$x^2 - 4 = ?$', opts:['$(x-2)(x+2)$','$(x-4)(x+1)$','$(x-2)^2$','не раскладывается'], ok:0 }, + { t:'yesno', q:'Трёхчлен $x^2 + 9$ раскладывается на множители первой степени с действительными коэффициентами.', ok:false }, + { t:'input', q:'Корни трёхчлена $x^2 - 7x + 12$. Меньший:', ans:[3] }, + { t:'select', q:'$\\dfrac{x^2-1}{x^2+x-2}$ сокращается до:', opts:['$\\dfrac{x+1}{x+2}$','$\\dfrac{x-1}{x+2}$','$\\dfrac{1}{x+2}$'], ok:0 }, + { t:'input', q:'Найдите $x_1 \\cdot x_2$ для $2x^2 - 7x + 3$ (по Виета $c/a$).', ans:[1.5] }, + ]}, + { id:'b5', name:'Архивариус задач', sub:'§ 11 · текстовые модели', icon:'?', hp:5, tasks:[ + { t:'input', q:'Произведение двух последовательных натуральных чисел = 72. Большее:', ans:[9] }, + { t:'input', q:'Площадь прямоугольника 35 см², сторона на 2 см больше другой. Меньшая сторона:', ans:[5] }, + { t:'select', q:'Двое за 6 ч; первый один на 5 ч быстрее. Уравнение для времени второго $t$:', opts:['$\\dfrac{1}{t-5} + \\dfrac{1}{t} = \\dfrac{1}{6}$','$t + (t-5) = 6$','$6 = t(t-5)$'], ok:0 }, + { t:'yesno', q:'В задаче на площадь оба корня уравнения подходят, если оба положительные.', ok:true }, + { t:'input', q:'Сумма квадратов двух последовательных натуральных = 41. Меньшее:', ans:[4] }, + ]}, + { id:'b6', name:'Мастер замены', sub:'§ 12 · t = x², ОДЗ', icon:'t', hp:5, tasks:[ + { t:'input', q:'Сколько корней у $x^4 - 13x^2 + 36 = 0$?', ans:[4] }, + { t:'yesno', q:'У уравнения $x^4 + 5x^2 + 4 = 0$ есть действительные корни.', ok:false }, + { t:'select', q:'Подходящая замена для $(x^2-1)^2 - 5(x^2-1) + 4 = 0$:', opts:['$t = x^2 - 1$','$t = x^2$','$t = x - 1$'], ok:0 }, + { t:'input', q:'Биквадратное $x^4 - 5x^2 + 4$. Больший положительный корень:', ans:[2] }, + { t:'select', q:'В уравнении $\\dfrac{1}{x-2} = \\dfrac{x-2}{4}$ посторонний корень — это $x = ?$', opts:['$x = 2$','$x = 4$','$x = 0$'], ok:0 }, + ]}, + { id:'b7', name:'Магистр алгебры', sub:'Финал · вся глава', icon:'★', hp:7, tasks:[ + { t:'input', q:'$x^2 + 5x + 6 = 0$. Меньший корень:', ans:[-3] }, + { t:'select', q:'$2x^2 - 8 = 0 \\Rightarrow x = ?$', opts:['$\\pm 2$','$\\pm 4$','$\\pm\\sqrt{2}$','$\\pm 8$'], ok:0 }, + { t:'input', q:'$D$ для $3x^2 + 5x - 2 = 0$:', ans:[49] }, + { t:'input', q:'По Виета: $x_1 + x_2$ для $x^2 - 11x + 28$:', ans:[11] }, + { t:'select', q:'$x^2 - 9$ разложится в:', opts:['$(x-3)(x+3)$','$(x-9)(x+1)$','$(x-3)^2$'], ok:0 }, + { t:'input', q:'$x^4 - 10x^2 + 9 = 0$. Сумма всех корней:', ans:[0] }, + { t:'yesno', q:'Уравнение $x^2 = -4$ имеет корни в множестве действительных чисел.', ok:false }, + ]}, + ]; + + const BOSS_STATE = (function(){ + try { return JSON.parse(localStorage.getItem('algebra8_ch2_bosses') || '{}'); } catch(e){ return {}; } + })(); + function saveBosses(){ try{ localStorage.setItem('algebra8_ch2_bosses', JSON.stringify(BOSS_STATE)); }catch(e){} } + + function renderBossGrid(){ + const g = document.getElementById('boss-grid'); + g.innerHTML = ''; + BOSSES.forEach(b=>{ + const won = BOSS_STATE[b.id]; + const c = document.createElement('div'); + c.style.cssText = 'background:' + (won ? 'linear-gradient(135deg,var(--ok-bg),#d1fae5)' : 'var(--card)') + ';border:1.5px solid ' + (won ? 'var(--ok)' : 'var(--border)') + ';border-radius:10px;padding:10px 12px;cursor:pointer;text-align:center;transition:transform .15s,box-shadow .15s'; + c.innerHTML = '' + b.icon + '' + b.name + '' + b.sub + '' + (won ? '✓ Побеждён' : 'В бой'); + c.addEventListener('click', ()=>startBoss(b.id)); + c.addEventListener('mouseover', ()=>{ c.style.transform='translateY(-2px)'; c.style.boxShadow='var(--sh2)'; }); + c.addEventListener('mouseout', ()=>{ c.style.transform=''; c.style.boxShadow=''; }); + g.appendChild(c); + }); + if(window.renderMathInElement) renderMath(g); + } + + let currentBoss = null, taskIdx = 0, hpLeft = 0; + + function startBoss(id){ + const b = BOSSES.find(x => x.id === id); + if(!b) return; + currentBoss = b; taskIdx = 0; hpLeft = b.hp; + document.getElementById('boss-arena').style.display = 'block'; + document.getElementById('boss-name').textContent = b.name; + document.getElementById('boss-subname').textContent = b.sub; + document.getElementById('boss-emoji').textContent = b.icon; + showTask(); + } + function showTask(){ + const t = currentBoss.tasks[taskIdx]; + if(!t){ winBoss(); return; } + document.getElementById('boss-hp').style.width = (hpLeft / currentBoss.hp * 100) + '%'; + const tEl = document.getElementById('boss-task'); + tEl.innerHTML = 'Задание ' + (taskIdx + 1) + ' / ' + currentBoss.tasks.length + '' + t.q + ''; + renderMath(tEl); + const opts = document.getElementById('boss-opts'); opts.innerHTML = ''; + document.getElementById('boss-fb').style.display = 'none'; + if(t.t === 'select'){ + t.opts.forEach((o,k)=>{ + const b = document.createElement('button'); + b.className = 'btn'; b.innerHTML = o; b.style.cssText = 'text-align:left'; + b.addEventListener('click', ()=>checkAnswer(k === t.ok, b)); + opts.appendChild(b); + }); + renderMath(opts); + } else if(t.t === 'yesno'){ + ['Да','Нет'].forEach((lab, k)=>{ + const b = document.createElement('button'); + b.className = 'btn'; b.textContent = lab; + b.addEventListener('click', ()=>checkAnswer((k === 0) === t.ok, b)); + opts.appendChild(b); + }); + } else if(t.t === 'input'){ + const wrap = document.createElement('div'); + wrap.style.cssText = 'display:flex;gap:8px;align-items:center'; + const inp = document.createElement('input'); + inp.type = 'text'; inp.placeholder = 'ваш ответ'; + inp.style.cssText = 'flex:1;padding:8px;border:1.5px solid var(--border);border-radius:8px'; + const go = document.createElement('button'); go.className = 'btn primary'; go.textContent = 'Ответ'; + go.addEventListener('click', ()=>{ + const u = parseFloat(inp.value.replace(',','.')); + const ok = t.ans.some(a => Math.abs(u - a) < 1e-6); + checkAnswer(ok, go); + }); + inp.addEventListener('keyup', e=>{ if(e.key === 'Enter') go.click(); }); + wrap.appendChild(inp); wrap.appendChild(go); opts.appendChild(wrap); + } + } + function checkAnswer(ok, btn){ + const fb = document.getElementById('boss-fb'); fb.style.display = 'block'; + if(ok){ + if(btn) btn.classList.add('ok'); + feedback(fb, true, '✓ Точное попадание!'); + taskIdx++; + setTimeout(showTask, 700); + } else { + if(btn) btn.classList.add('fail'); + hpLeft = Math.max(0, hpLeft - 0); + feedback(fb, false, '✗ Промах! Попробуйте ещё.'); + } + } + function winBoss(){ + BOSS_STATE[currentBoss.id] = 1; saveBosses(); + achievement('boss_' + currentBoss.id, 'Победил: ' + currentBoss.name); + bumpProgress('final2', 14); confetti(); + document.getElementById('boss-task').innerHTML = '' + currentBoss.icon + 'Победа!Босс «' + currentBoss.name + '» повержен.'; + document.getElementById('boss-opts').innerHTML = 'К списку боссов'; + document.getElementById('boss-fb').style.display = 'none'; + document.getElementById('boss-back').addEventListener('click', closeBoss); + renderBossGrid(); + if(BOSSES.every(b => BOSS_STATE[b.id])){ + setTimeout(()=>{ achievement('all_bosses', 'Магистр квадратных уравнений!'); confetti(); refreshCert(); }, 800); + } + } + function closeBoss(){ document.getElementById('boss-arena').style.display = 'none'; currentBoss = null; } + document.getElementById('boss-quit').addEventListener('click', closeBoss); + renderBossGrid(); + + /* ===== PRACTICE ===== */ + (function(){ + let cur = null, total = 0, score = 0, streak = 0; + function gen(){ + const t = Math.floor(Math.random()*5); + if(t === 0){ + const r1 = -5 + Math.floor(Math.random()*11), r2 = -5 + Math.floor(Math.random()*11); + if(r1 === r2) return gen(); + const b = -(r1+r2), c = r1*r2; + return { q:'Решите $x^2 ' + (b >= 0 ? '+ ' + b : '- ' + Math.abs(b)) + 'x ' + (c >= 0 ? '+ ' + c : '- ' + Math.abs(c)) + ' = 0$. Введите больший корень.', ans:[Math.max(r1, r2)] }; + } + if(t === 1){ + const r = 1 + Math.floor(Math.random()*7); + return { q:'$x^2 - ' + (r*r) + ' = 0 \\Rightarrow x = \\pm ?$', ans:[r] }; + } + if(t === 2){ + const r1 = 1 + Math.floor(Math.random()*7), r2 = -(1 + Math.floor(Math.random()*7)); + const b = -(r1+r2), c = r1*r2; + return { q:'Сумма корней $x^2 ' + (b >= 0 ? '+ ' + b : '- ' + Math.abs(b)) + 'x ' + (c >= 0 ? '+ ' + c : '- ' + Math.abs(c)) + ' = 0$?', ans:[r1+r2] }; + } + if(t === 3){ + const a = 1 + Math.floor(Math.random()*3), b = -(2 + Math.floor(Math.random()*8)), c = 1 + Math.floor(Math.random()*6); + return { q:'Найдите $D$ для $' + a + 'x^2 ' + (b >= 0 ? '+ ' + b : '- ' + Math.abs(b)) + 'x ' + (c >= 0 ? '+ ' + c : '- ' + Math.abs(c)) + ' = 0$', ans:[b*b - 4*a*c] }; + } + const r = 1 + Math.floor(Math.random()*4); + return { q:'Биквадратное $x^4 - ' + (r*r + 1) + 'x^2 + ' + (r*r) + ' = 0$. Сколько корней?', ans:[r === 1 ? 2 : 4] }; + } + function show(){ + cur = gen(); + document.getElementById('prac-task').innerHTML = cur.q; + renderMath(document.getElementById('prac-task')); + document.getElementById('prac-inp').value = ''; + document.getElementById('prac-fb').style.display = 'none'; + } + document.getElementById('prac-go').addEventListener('click', ()=>{ + const fb = document.getElementById('prac-fb'); fb.style.display = 'block'; + const u = parseFloat(document.getElementById('prac-inp').value.replace(',','.')); + const ok = cur.ans.some(a => Math.abs(u - a) < 1e-6); + total++; if(ok){ score++; streak++; feedback(fb, true, '✓'); if(streak === 5){ achievement('prac_streak', 'Серия из 5!'); confetti(); } } + else { streak = 0; feedback(fb, false, 'Правильно: ' + cur.ans.join(', ')); } + document.getElementById('prac-i').textContent = total; + document.getElementById('prac-score').textContent = score; + document.getElementById('prac-streak').textContent = streak; + if(total >= 5) { bumpProgress('final2', 4); } + }); + document.getElementById('prac-next').addEventListener('click', show); + document.getElementById('prac-inp').addEventListener('keyup', e=>{ if(e.key === 'Enter') document.getElementById('prac-go').click(); }); + show(); + })(); + + /* CERTIFICATE */ + function refreshCert(){ + const won = BOSSES.filter(b => BOSS_STATE[b.id]).length; + const cs = document.getElementById('cert-state'); + if(!cs) return; + if(won === BOSSES.length){ + cs.innerHTML = 'МАГИСТР КВАДРАТНЫХ УРАВНЕНИЙВсе 7 боссов главы 2 повержены — вы освоили §§ 7–12.'; + } else { + cs.innerHTML = 'Побеждено боссов: ' + won + ' / ' + BOSSES.length + '. Победите всех, чтобы получить титул.'; + } + } + refreshCert(); +}
Финал главыИтоговая самооценка, практика, увлекательная математика и финальный босс — в Wave 4.
Вы прошли все 6 параграфов главы «Квадратные уравнения». В финале вас ждут 7 боссов — по одному на каждый параграф и один общий. Победите всех — получите титул «Магистр квадратных уравнений».
Как вы оцените своё знание квадратных уравнений?
Босс «' + currentBoss.name + '» повержен.