diff --git a/frontend/textbooks/algebra_11_ch3.html b/frontend/textbooks/algebra_11_ch3.html
index 127d2bc..819395b 100644
--- a/frontend/textbooks/algebra_11_ch3.html
+++ b/frontend/textbooks/algebra_11_ch3.html
@@ -1952,31 +1952,774 @@ function buildP9(){
function buildP10(){
const box = document.getElementById('p10-body');
let html = '';
- html += makeCard('theory', 'В разработке', '10.0', `
-
Содержание параграфа Логарифмические неравенства будет добавлено в Phase 1+.
- Раздел Phase 0 — skeleton. Здесь появятся теория, примеры и интерактивы.
- Ключевая формула: $\log_a f > b$
- `);
- html += secNavFor('p10');
+
+ /* === ТЕОРИЯ === */
+
+ html += makeCard('theory', 'Основное правило и ОДЗ', '10.1', `
+ Логарифмическим неравенством называется неравенство, в котором переменная содержится под знаком логарифма . Простейший вид:
+ $\\log_a f(x) > \\log_a g(x)$ (или $<$, $\\ge$, $\\le$).
+ Здесь $a > 0$, $a \\ne 1$. Подход к решению опирается на монотонность логарифмической функции (см. §8):
+
+ $\\log_a f(x) > \\log_a g(x) \\;\\Leftrightarrow\\; \\begin{cases} f(x) > g(x), & \\text{если } a > 1 \\\\\\\\ f(x) < g(x), & \\text{если } 0 < a < 1 \\end{cases}$
+
+ Кроме того всегда требуем положительности выражений под логарифмами (ОДЗ):
+
+ $\\begin{cases} f(x) > 0 \\\\\\\\ g(x) > 0 \\end{cases}$
+
+ Запомни:
+
+ При $a > 1$ — функция возрастает, знак неравенства сохраняется .
+ При $0 < a < 1$ — функция убывает, знак неравенства меняется .
+ ОДЗ требует положительности обоих выражений под логарифмами.
+
+ Почему знак меняется при $0 < a < 1$?
+
Функция $y = \\log_a x$ при $0 < a < 1$ строго убывает: чем больше $x$, тем меньше $y$.
+
Поэтому неравенство $\\log_a f > \\log_a g$ означает $f < g$: чтобы $\\log$ был больше, аргумент должен быть меньше.
+
`);
+
+ html += makeCard('rule', 'Алгоритм решения', '10.2', `
+ Шаги для решения неравенства $\\log_a f(x) \\gtrless \\log_a g(x)$:
+
+ Записать ОДЗ : $f(x) > 0$ и $g(x) > 0$.
+ Привести обе части к одному основанию (если нужно).
+ Использовать правило монотонности: при $a > 1$ сохранить знак, при $0 < a < 1$ — изменить .
+ Решить полученное алгебраическое неравенство.
+ Пересечь ответ с ОДЗ — это и есть финальное решение.
+
+
+ Пример 1. $\\log_2 (x - 1) > 3$.
+ ОДЗ: $x - 1 > 0 \\Rightarrow x > 1$.
+ Перепишем $3 = \\log_2 8$: $\\log_2 (x - 1) > \\log_2 8$. $\\;a = 2 > 1$ — знак сохраняется:
+ $x - 1 > 8 \\Rightarrow x > 9$.
+ Пересечение с ОДЗ ($x > 1$): $x > 9$ .
+ Ответ: $x \\in (9; +\\infty)$.
+
+ Пример 2. $\\log_{1/3} (x - 2) > -2$.
+ ОДЗ: $x - 2 > 0 \\Rightarrow x > 2$.
+ Перепишем $-2 = \\log_{1/3} 9$ (т.к. $(1/3)^{-2} = 9$): $\\log_{1/3} (x - 2) > \\log_{1/3} 9$. $\\;a = 1/3 < 1$ — знак меняется :
+ $x - 2 < 9 \\Rightarrow x < 11$.
+ Пересечение с ОДЗ: $2 < x < 11$.
+ Ответ: $x \\in (2; 11)$.
`);
+
+ html += makeCard('example', 'Замена переменной и графический метод', '10.3', `
+ Метод замены. При наличии в неравенстве $\\log_a x$ и $\\log_a^2 x$ выполняют подстановку $t = \\log_a x$ — и получают квадратное неравенство относительно $t$.
+ Пример. $\\log_2^2 x - 3 \\log_2 x + 2 \\le 0$.
+ ОДЗ: $x > 0$. Замена $t = \\log_2 x$: $t^2 - 3t + 2 \\le 0$.
+ Корни уравнения $t^2 - 3t + 2 = 0$: $t_1 = 1$, $t_2 = 2$. Парабола ветвями вверх $\\le 0$ между корнями: $t \\in [1; 2]$.
+ Обратно: $1 \\le \\log_2 x \\le 2 \\Leftrightarrow 2^1 \\le x \\le 2^2 \\Leftrightarrow 2 \\le x \\le 4$.
+ Ответ: $x \\in [2; 4]$.
+
+ Графический метод. Если неравенство нельзя свести к одному основанию или замене — используем графики. Строим $y = \\log_a f(x)$ и $y = \\log_a g(x)$ (или константу) на одной координатной плоскости и смотрим, где один график выше другого.
+ Когда применять? Когда $f$ и $g$ имеют разную природу (например, степенная и логарифмическая). Метод не даёт точный ответ, но позволяет понять число и расположение решений.
+ Пример. $\\log_2 x < 3 - x$. Строим $y = \\log_2 x$ (растёт) и $y = 3 - x$ (убывает). Точка пересечения примерно при $x \\approx 2$. При $x < 2$ кривая логарифма ниже прямой — это и есть решение. Уточнение: проверяем $x = 2$: $\\log_2 2 = 1$, $3 - 2 = 1$. Значит ответ $x \\in (0; 2)$.
`);
+
+ /* === ИНТЕРАКТИВЫ === */
+
+ /* IV1 — пошаговый решатель неравенств с числовой прямой */
+ html += `
+
+
Выбери задачу ползунком и нажимай «Следующий шаг ▶». Особое внимание — ОДЗ и правилу монотонности. После шага «Ответ» появится визуализация на числовой прямой. Просмотри все 5 задач — получишь XP.
+
+ Задача № 1 / 5
+
+
+
+
+
+ Следующий шаг ▶
+ Показать все шаги
+ Скрыть шаги
+
+
`;
+
+ /* IV2 — калькулятор log_a (kx + b) ⋚ c */
+ html += `
+
+
Введи основание $a$, коэффициенты $k$, $b$, правую часть $c$ и выбери знак — калькулятор решит неравенство с учётом монотонности и ОДЗ, покажет числовую прямую.
+
+ $a$ =
+
+ $k$ =
+
+ $b$ =
+
+
+ >
+ <
+ ≥
+ ≤
+
+ $c$ =
+
+ Решить
+
+
+ Примеры:
+ $\\log_2(x-1)>3$
+ $\\log_{1/3}(x-2)>-2$
+ $\\log_5(2x+1)<1$
+ $\\lg(x+1) \\le 1$
+
+
+
+
+
`;
+
+ /* IV3 — знак сохраняется или меняется? */
+ html += `
+
+
Дано логарифмическое неравенство. Определи: при переходе к выражениям под логарифмом знак сохраняется ($a > 1$) или меняется ($0 < a < 1$)? 8 заданий.
+
Задача 1 / 8 Очки: 0 / 8
+
+
+
+
Начать заново
+
`;
+
+ /* IV4 — тренажёр неравенств */
+ html += `
+
+
Реши неравенство и введи число — указанную границу интервала ответа. 6 задач.
+
Задача 1 / 6 Очки: 0 / 6
+
+
+ ответ =
+
+ Проверить
+ Заново
+
+
+
`;
+
+ html += secNav('p9', 'final3');
html += readButton('p10');
+
box.innerHTML = html;
renderMath(box);
+
+ /* Хелпер: рисует числовую прямую с интервалом */
+ function drawAxis(lo, hi, segs){
+ // segs: [{a, b, closedA, closedB, color}]
+ // a/b могут быть -Infinity / +Infinity
+ const W = 480, H = 80;
+ const padL = 40, padR = 40;
+ const ax = padL, bx = W - padR;
+ const y0 = 46;
+ const range = hi - lo;
+ function xpos(v){
+ if(v === -Infinity) return ax;
+ if(v === +Infinity) return bx;
+ return ax + (v - lo) / range * (bx - ax);
+ }
+ let svg = '';
+ // ось
+ svg += ' ';
+ // стрелки
+ svg += ' ';
+ svg += ' ';
+ // деления
+ const ticks = [];
+ for(let v = Math.ceil(lo); v <= Math.floor(hi); v++) ticks.push(v);
+ ticks.forEach(v => {
+ const x = xpos(v);
+ svg += ' ';
+ svg += ''+v+' ';
+ });
+ // интервалы
+ segs.forEach(s => {
+ const x1 = xpos(s.a), x2 = xpos(s.b);
+ const color = s.color || '#7c3aed';
+ svg += ' ';
+ // концы
+ if(s.a !== -Infinity){
+ if(s.closedA) svg += ' ';
+ else svg += ' ';
+ }
+ if(s.b !== +Infinity){
+ if(s.closedB) svg += ' ';
+ else svg += ' ';
+ }
+ });
+ svg += ' ';
+ return svg;
+ }
+
+ /* === IV1 — пошаговый решатель неравенств === */
+ (function(){
+ const TASKS = [
+ {
+ q: '$\\log_2 (x - 1) > 3$',
+ steps: [
+ 'ОДЗ: $x - 1 > 0 \\Rightarrow x > 1$.',
+ 'Переписываем правую часть: $3 = \\log_2 2^3 = \\log_2 8$. Неравенство: $\\log_2 (x - 1) > \\log_2 8$.',
+ 'Учитываем монотонность: $a = 2 > 1$, знак сохраняется : $x - 1 > 8$.',
+ 'Решаем: $x > 9$.',
+ 'Пересечение с ОДЗ ($x > 1$): $x > 9$.',
+ 'Ответ: $x \\in (9; +\\infty)$.'
+ ],
+ axis: { lo: 0, hi: 14, segs: [{a:9, b:+Infinity, closedA:false, color:'#7c3aed'}] }
+ },
+ {
+ q: '$\\log_{1/3} (x - 2) > -2$',
+ steps: [
+ 'ОДЗ: $x - 2 > 0 \\Rightarrow x > 2$.',
+ 'Переписываем: $-2 = \\log_{1/3} (1/3)^{-2} = \\log_{1/3} 9$. Неравенство: $\\log_{1/3} (x - 2) > \\log_{1/3} 9$.',
+ 'Учитываем монотонность: $a = 1/3 < 1$, знак меняется : $x - 2 < 9$.',
+ 'Решаем: $x < 11$.',
+ 'Пересечение с ОДЗ ($x > 2$): $2 < x < 11$.',
+ 'Ответ: $x \\in (2; 11)$.'
+ ],
+ axis: { lo: 0, hi: 14, segs: [{a:2, b:11, closedA:false, closedB:false, color:'#7c3aed'}] }
+ },
+ {
+ q: '$\\log_5 (2x + 1) < 1$',
+ steps: [
+ 'ОДЗ: $2x + 1 > 0 \\Rightarrow x > -\\dfrac{1}{2}$.',
+ 'Переписываем: $1 = \\log_5 5$. Неравенство: $\\log_5 (2x + 1) < \\log_5 5$.',
+ 'Учитываем монотонность: $a = 5 > 1$, знак сохраняется: $2x + 1 < 5$.',
+ 'Решаем: $2x < 4 \\Rightarrow x < 2$.',
+ 'Пересечение с ОДЗ ($x > -1/2$): $-\\dfrac{1}{2} < x < 2$.',
+ 'Ответ: $x \\in \\left(-\\dfrac{1}{2};\\,2\\right)$.'
+ ],
+ axis: { lo: -3, hi: 5, segs: [{a:-0.5, b:2, closedA:false, closedB:false, color:'#7c3aed'}] }
+ },
+ {
+ q: '$\\log_2^2 x - 3 \\log_2 x + 2 \\le 0$',
+ steps: [
+ 'ОДЗ: $x > 0$.',
+ 'Замена $t = \\log_2 x$: $t^2 - 3t + 2 \\le 0$.',
+ 'Корни уравнения: $t = 1, t = 2$. Парабола ветвями вверх $\\le 0$ между корнями: $1 \\le t \\le 2$.',
+ 'Обратно: $1 \\le \\log_2 x \\le 2 \\Leftrightarrow 2 \\le x \\le 4$.',
+ '$[2; 4] \\subset (0; +\\infty)$ — ОДЗ выполняется.',
+ 'Ответ: $x \\in [2; 4]$.'
+ ],
+ axis: { lo: 0, hi: 6, segs: [{a:2, b:4, closedA:true, closedB:true, color:'#7c3aed'}] }
+ },
+ {
+ q: '$\\lg (x + 1) \\le 1$',
+ steps: [
+ 'ОДЗ: $x + 1 > 0 \\Rightarrow x > -1$.',
+ 'Переписываем: $1 = \\lg 10$. Неравенство: $\\lg (x + 1) \\le \\lg 10$.',
+ 'Учитываем монотонность: $a = 10 > 1$, знак сохраняется: $x + 1 \\le 10$.',
+ 'Решаем: $x \\le 9$.',
+ 'Пересечение с ОДЗ ($x > -1$): $-1 < x \\le 9$.',
+ 'Ответ: $x \\in (-1; 9]$.'
+ ],
+ axis: { lo: -3, hi: 12, segs: [{a:-1, b:9, closedA:false, closedB:true, color:'#7c3aed'}] }
+ }
+ ];
+ const sn = document.getElementById('p10-iv1-sn');
+ const nL = document.getElementById('p10-iv1-n');
+ const qEl = document.getElementById('p10-iv1-q');
+ const stepsEl = document.getElementById('p10-iv1-steps');
+ const axisEl = document.getElementById('p10-iv1-axis');
+ const nextBtn = document.getElementById('p10-iv1-next');
+ const allBtn = document.getElementById('p10-iv1-all');
+ const resetBtn = document.getElementById('p10-iv1-reset');
+ const seen = new Set();
+ let _done = false;
+ let cur = 0, shown = 0;
+
+ function render(){
+ const t = TASKS[cur];
+ nL.textContent = (cur + 1);
+ qEl.innerHTML = t.q;
+ stepsEl.innerHTML = t.steps.map((s, i) => {
+ const visible = i < shown;
+ const isLast = i === t.steps.length - 1 && visible;
+ const bg = isLast ? '#dcfce7' : 'var(--card)';
+ const brd = isLast ? '2px solid #16a34a' : '1px solid var(--border)';
+ return 'Шаг '+(i+1)+': '+s+'
';
+ }).join('');
+ renderMath(qEl);
+ renderMath(stepsEl);
+ if(shown >= t.steps.length){
+ axisEl.innerHTML = drawAxis(t.axis.lo, t.axis.hi, t.axis.segs);
+ seen.add(cur);
+ if(!_done && seen.size >= 5){ _done = true; addXp(10, 'p10-iv1'); bumpProgress('p10', 15); }
+ } else {
+ axisEl.innerHTML = '';
+ }
+ }
+ function load(n){ cur = Math.max(0, Math.min(TASKS.length - 1, n)); shown = 1; render(); }
+ sn.addEventListener('input', () => load((+sn.value) - 1));
+ nextBtn.addEventListener('click', () => {
+ if(shown < TASKS[cur].steps.length){ shown++; render(); }
+ });
+ allBtn.addEventListener('click', () => { shown = TASKS[cur].steps.length; render(); });
+ resetBtn.addEventListener('click', () => { shown = 1; render(); });
+ load(0);
+ })();
+
+ /* === IV2 — калькулятор log_a (kx + b) ⋚ c === */
+ (function(){
+ const aI = document.getElementById('p10-iv2-a');
+ const kI = document.getElementById('p10-iv2-k');
+ const bI = document.getElementById('p10-iv2-b');
+ const cI = document.getElementById('p10-iv2-c');
+ const sSel = document.getElementById('p10-iv2-sign');
+ const go = document.getElementById('p10-iv2-go');
+ const out = document.getElementById('p10-iv2-out');
+ const axisEl = document.getElementById('p10-iv2-axis');
+ const fb = document.getElementById('p10-iv2-fb');
+ const used = new Set();
+ let _done = false;
+
+ document.querySelectorAll('#p10-iv2 [data-set]').forEach(btn => {
+ btn.addEventListener('click', () => {
+ const v = btn.dataset.set.split(',');
+ aI.value = v[0]; kI.value = v[1]; bI.value = v[2]; sSel.value = v[3]; cI.value = v[4];
+ calc();
+ });
+ });
+
+ function aLabel(a){
+ if(Math.abs(a - 10) < 1e-9) return '\\lg';
+ if(Math.abs(a - 1/3) < 1e-3) return '\\log_{1/3}';
+ if(Math.abs(a - 0.5) < 1e-9) return '\\log_{1/2}';
+ return '\\log_{' + (+a.toFixed(4)) + '}';
+ }
+ function fxLabel(k, b){
+ let s = '';
+ if(k === 1) s = 'x';
+ else if(k === -1) s = '-x';
+ else s = k + 'x';
+ if(b > 0) s += ' + ' + b;
+ else if(b < 0) s += ' - ' + (-b);
+ return s;
+ }
+ function signStr(s){
+ return {gt:'>', lt:'<', ge:'\\ge', le:'\\le'}[s];
+ }
+ function flipSign(s){
+ return {gt:'lt', lt:'gt', ge:'le', le:'ge'}[s];
+ }
+ function isStrict(s){ return s === 'gt' || s === 'lt'; }
+
+ function calc(){
+ const a = parseFloat(aI.value);
+ const k = parseFloat(kI.value);
+ const b = parseFloat(bI.value);
+ const c = parseFloat(cI.value);
+ const s = sSel.value;
+ if(![a,k,b,c].every(isFinite)){ feedback(fb, false, '✗ Введи все четыре числа.'); return; }
+ if(a <= 0 || Math.abs(a - 1) < 1e-9){ feedback(fb, false, '✗ Основание $a$ должно быть $> 0$ и $\\ne 1$.'); return; }
+ if(k === 0){ feedback(fb, false, '✗ При $k = 0$ выражение под логарифмом не зависит от $x$.'); return; }
+
+ const fx = fxLabel(k, b);
+ const eq = '$' + aLabel(a) + '(' + fx + ') ' + signStr(s) + ' ' + c + '$';
+ let html = 'Неравенство: '+eq+'
';
+
+ // ОДЗ: kx + b > 0
+ const odzBound = -b / k;
+ const odzDir = k > 0 ? 'gt' : 'lt';
+ const odzStr = k > 0 ? '$x > ' + (+odzBound.toFixed(4)) + '$' : '$x < ' + (+odzBound.toFixed(4)) + '$';
+ html += 'Шаг 1. ОДЗ: $' + fx + ' > 0 \\Rightarrow$ ' + odzStr + '.
';
+
+ // Шаг 2: перевод правой части a^c
+ const ac = Math.pow(a, c);
+ html += 'Шаг 2. Переписываем правую часть: $' + c + ' = ' + aLabel(a) + ' ' + a + '^{' + c + '} = ' + aLabel(a) + ' ' + (+ac.toFixed(4)) + '$.
';
+
+ // Шаг 3: монотонность
+ let curSign = s;
+ let monoText;
+ if(a > 1){
+ monoText = '$a = ' + a + ' > 1$ — знак сохраняется ';
+ } else {
+ monoText = '$0 < a = ' + (+a.toFixed(4)) + ' < 1$ — знак меняется ';
+ curSign = flipSign(curSign);
+ }
+ html += 'Шаг 3. Монотонность: ' + monoText + '. Переход: $' + fx + ' ' + signStr(curSign) + ' ' + (+ac.toFixed(4)) + '$.
';
+
+ // Шаг 4: решить относительно x
+ // kx + b [sign] ac => kx [sign] ac - b => x [sign|flip if k<0] (ac - b)/k
+ const rhs = (ac - b) / k;
+ let finalSign = curSign;
+ if(k < 0) finalSign = flipSign(finalSign);
+ html += 'Шаг 4. Решаем: $kx ' + signStr(curSign) + ' ' + (+ac.toFixed(4)) + ' - (' + b + ')';
+ if(k < 0) html += '$, делим на $k = ' + k + ' < 0$ — знак меняется: $x ' + signStr(finalSign) + ' ' + (+rhs.toFixed(4)) + '$.
';
+ else html += ' \\Rightarrow x ' + signStr(finalSign) + ' ' + (+rhs.toFixed(4)) + '$.';
+
+ // Шаг 5: пересечь с ОДЗ
+ // Решение исходного: x [finalSign] rhs. ОДЗ: x [odzDir] odzBound.
+ // Найдём интервал пересечения
+ function rangeFromIneq(dir, bound){
+ // dir in {gt, lt, ge, le}
+ if(dir === 'gt') return {lo: bound, hi: +Infinity, closedLo: false, closedHi: false};
+ if(dir === 'ge') return {lo: bound, hi: +Infinity, closedLo: true, closedHi: false};
+ if(dir === 'lt') return {lo: -Infinity, hi: bound, closedLo: false, closedHi: false};
+ if(dir === 'le') return {lo: -Infinity, hi: bound, closedLo: false, closedHi: true};
+ }
+ const r1 = rangeFromIneq(finalSign, rhs);
+ const r2 = rangeFromIneq(odzDir, odzBound);
+ const lo = Math.max(r1.lo, r2.lo);
+ const hi = Math.min(r1.hi, r2.hi);
+ const closedLo = (lo === r1.lo ? r1.closedLo : false) && (lo === r2.lo ? r2.closedLo : false) || (lo === r1.lo && lo !== r2.lo && r1.closedLo) || (lo === r2.lo && lo !== r1.lo && r2.closedLo);
+ // Простая логика: closedLo true только если lo пришёл из границы, которая closed; ОДЗ всегда строгое, так что closedLo возможен только если lo === rhs && finalSign in {ge, le}
+ let cLo = false, cHi = false;
+ if(lo === r1.lo) cLo = r1.closedLo;
+ if(lo === r2.lo) cLo = cLo && r2.closedLo;
+ if(hi === r1.hi) cHi = r1.closedHi;
+ if(hi === r2.hi) cHi = cHi && r2.closedHi;
+ // Однако если lo берётся только из одной стороны (другая = -Inf), то closed зависит только от той стороны.
+ if(r1.lo === -Infinity) cLo = r2.closedLo;
+ if(r2.lo === -Infinity) cLo = r1.closedLo;
+ if(r1.hi === +Infinity) cHi = r2.closedHi;
+ if(r2.hi === +Infinity) cHi = r1.closedHi;
+
+ if(lo >= hi && !(lo === hi && cLo && cHi)){
+ html += 'Шаг 5. Пересечение пустое — решений нет.
';
+ html += 'Ответ: $x \\in \\varnothing$.
';
+ axisEl.innerHTML = '';
+ } else {
+ // Сформируем строку интервала
+ const lp = (lo === -Infinity) ? '(-\\infty' : ((cLo ? '[' : '(') + (+lo.toFixed(4)));
+ const rp = (hi === +Infinity) ? '+\\infty)' : ((+hi.toFixed(4)) + (cHi ? ']' : ')'));
+ html += 'Шаг 5. Пересечение исходного и ОДЗ: $' + lp + ';\\, ' + rp + '$.
';
+ html += 'Ответ: $x \\in ' + lp + ';\\, ' + rp + '$.
';
+
+ // Визуализация
+ const lo2 = (lo === -Infinity) ? Math.floor(hi) - 5 : Math.floor(lo) - 2;
+ const hi2 = (hi === +Infinity) ? Math.ceil(lo) + 5 : Math.ceil(hi) + 2;
+ const segLo = (lo === -Infinity) ? -Infinity : lo;
+ const segHi = (hi === +Infinity) ? +Infinity : hi;
+ axisEl.innerHTML = drawAxis(lo2, hi2, [{a:segLo, b:segHi, closedA:cLo, closedB:cHi, color:'#7c3aed'}]);
+ }
+
+ out.innerHTML = html;
+ renderMath(out);
+ feedback(fb, true, '✓ Решено.');
+ used.add(aI.value+','+kI.value+','+bI.value+','+sSel.value+','+cI.value);
+ if(!_done && used.size >= 4){ _done = true; addXp(10, 'p10-iv2'); bumpProgress('p10', 15); }
+ }
+ go.addEventListener('click', calc);
+ [aI, kI, bI, cI].forEach(i => i.addEventListener('keydown', e => { if(e.key === 'Enter') calc(); }));
+ sSel.addEventListener('change', calc);
+ calc();
+ })();
+
+ /* === IV3 — знак сохраняется или меняется? === */
+ (function(){
+ const OPTS = ['Сохраняется', 'Меняется'];
+ const Q = [
+ { q: '$\\log_2 (x - 1) > \\log_2 5$', ans: 0, hint: 'Основание $a = 2 > 1$ — функция возрастает, знак сохраняется.' },
+ { q: '$\\log_{1/3} (x + 2) < \\log_{1/3} 7$', ans: 1, hint: 'Основание $a = 1/3 < 1$ — функция убывает, знак меняется.' },
+ { q: '$\\log_5 x > 2$', ans: 0, hint: 'Перепишем $2 = \\log_5 25$. $a = 5 > 1$ — знак сохраняется.' },
+ { q: '$\\log_{0{,}5} (x - 1) \\ge 3$', ans: 1, hint: 'Основание $a = 0{,}5 < 1$ — знак меняется.' },
+ { q: '$\\lg (2x) > 1$', ans: 0, hint: '$\\lg = \\log_{10}$, $a = 10 > 1$ — знак сохраняется.' },
+ { q: '$\\log_{1/2} x \\le 4$', ans: 1, hint: 'Основание $a = 1/2 < 1$ — знак меняется.' },
+ { q: '$\\log_3 (x + 4) > 0$', ans: 0, hint: 'Основание $a = 3 > 1$ — знак сохраняется. (А $0 = \\log_3 1$.)' },
+ { q: '$\\log_{0{,}1} x \\ge -1$', ans: 1, hint: 'Основание $a = 0{,}1 < 1$ — знак меняется.' },
+ ];
+ let i = 0, score = 0;
+ const qEl = document.getElementById('p10-iv3-q');
+ const oEl = document.getElementById('p10-iv3-opts');
+ const fb = document.getElementById('p10-iv3-fb');
+ const iEl = document.getElementById('p10-iv3-i');
+ const sEl = document.getElementById('p10-iv3-s');
+
+ function show(){
+ if(i >= Q.length){
+ qEl.innerHTML = 'Готово! Результат: '+score+' / '+Q.length;
+ oEl.innerHTML = '';
+ if(score === Q.length){ addXp(15, 'p10-iv3'); bumpProgress('p10', 25); }
+ else if(score >= 6){ addXp(8, 'p10-iv3'); bumpProgress('p10', 15); }
+ return;
+ }
+ iEl.textContent = (i + 1);
+ sEl.textContent = score;
+ const item = Q[i];
+ qEl.innerHTML = item.q;
+ oEl.innerHTML = OPTS.map((o, k) => ''+o+' ').join('');
+ fb.style.display = 'none';
+ renderMath(qEl);
+ oEl.querySelectorAll('button').forEach(b => {
+ b.addEventListener('click', () => {
+ const k = +b.dataset.k;
+ if(k === item.ans){ score++; feedback(fb, true, '✓ Верно! '+item.hint+' Дальше ▶'); }
+ else feedback(fb, false, '✗ Неверно. Правильно: '+OPTS[item.ans]+' . '+item.hint+' Дальше ▶');
+ sEl.textContent = score;
+ oEl.querySelectorAll('button').forEach(x => x.disabled = true);
+ i++;
+ setTimeout(show, 1700);
+ });
+ });
+ }
+ document.getElementById('p10-iv3-restart').addEventListener('click', () => { i = 0; score = 0; show(); });
+ show();
+ })();
+
+ /* === IV4 — тренажёр неравенств === */
+ (function(){
+ const Q = [
+ { q: '$\\log_2 (x - 1) > 3$ — введи нижнюю границу интервала ответа', ans: 9, hint: 'ОДЗ $x > 1$. $x - 1 > 8 \\Rightarrow x > 9$. Ответ $(9; +\\infty)$, граница — $9$.' },
+ { q: '$\\log_{1/3} (x - 2) > -2$ — введи верхнюю границу интервала ответа', ans: 11, hint: 'ОДЗ $x > 2$. $-2 = \\log_{1/3} 9$, знак меняется: $x - 2 < 9 \\Rightarrow x < 11$. Ответ $(2; 11)$, верхняя граница — $11$.' },
+ { q: '$\\log_5 x < 2$ — введи верхнюю границу интервала ответа', ans: 25, hint: 'ОДЗ $x > 0$. $2 = \\log_5 25$, знак сохраняется: $x < 25$. Ответ $(0; 25)$, верхняя граница — $25$.' },
+ { q: '$\\log_{1/2} (x - 3) \\ge -2$ — введи верхнюю границу интервала ответа', ans: 7, hint: 'ОДЗ $x > 3$. $-2 = \\log_{1/2} 4$, знак меняется: $x - 3 \\le 4 \\Rightarrow x \\le 7$. Ответ $(3; 7]$, верхняя граница — $7$.' },
+ { q: '$\\lg x > 1$ — введи нижнюю границу интервала ответа', ans: 10, hint: 'ОДЗ $x > 0$. $1 = \\lg 10$, знак сохраняется: $x > 10$. Ответ $(10; +\\infty)$, граница — $10$.' },
+ { q: '$\\log_3 (x + 1) \\le 2$ — введи верхнюю границу интервала ответа', ans: 8, hint: 'ОДЗ $x > -1$. $2 = \\log_3 9$, знак сохраняется: $x + 1 \\le 9 \\Rightarrow x \\le 8$. Ответ $(-1; 8]$, верхняя граница — $8$.' },
+ ];
+ let i = 0, score = 0;
+ function show(){
+ const qEl = document.getElementById('p10-iv4-q');
+ const iEl = document.getElementById('p10-iv4-i');
+ const sEl = document.getElementById('p10-iv4-s');
+ const fb = document.getElementById('p10-iv4-fb');
+ const ansI = document.getElementById('p10-iv4-ans');
+ if(i >= Q.length){
+ qEl.innerHTML = 'Готово! Результат: '+score+' / '+Q.length;
+ if(score === Q.length){ addXp(15, 'p10-iv4'); bumpProgress('p10', 25); }
+ else if(score >= 4){ addXp(8, 'p10-iv4'); bumpProgress('p10', 15); }
+ return;
+ }
+ iEl.textContent = (i + 1);
+ sEl.textContent = score;
+ qEl.innerHTML = Q[i].q;
+ ansI.value = '';
+ renderMath(qEl);
+ fb.style.display = 'none';
+ setTimeout(() => ansI.focus(), 30);
+ }
+ function go(){
+ if(i >= Q.length) return;
+ const fb = document.getElementById('p10-iv4-fb');
+ const raw = document.getElementById('p10-iv4-ans').value.replace(',', '.');
+ const ans = parseFloat(raw);
+ if(isNaN(ans)){ feedback(fb, false, '✗ Введи число.'); return; }
+ if(Math.abs(ans - Q[i].ans) < 0.05){
+ score++;
+ feedback(fb, true, '✓ Верно! '+Q[i].hint+' Дальше ▶');
+ } else {
+ feedback(fb, false, '✗ Неверно. Ответ: $'+Q[i].ans+'$. '+Q[i].hint+' Дальше ▶');
+ }
+ document.getElementById('p10-iv4-s').textContent = score;
+ i++;
+ setTimeout(show, 1700);
+ }
+ document.getElementById('p10-iv4-go').addEventListener('click', go);
+ document.getElementById('p10-iv4-ans').addEventListener('keydown', e => { if(e.key === 'Enter') go(); });
+ document.getElementById('p10-iv4-start').addEventListener('click', () => { i = 0; score = 0; show(); });
+ show();
+ })();
+
wireReadBtn('p10');
}
function buildFinal3(){
const box = document.getElementById('final3-body');
let html = '';
- html += makeCard('theory', 'В разработке', '★.0', `
- Содержание финала главы Финал главы будет добавлено в Phase 1+.
- Раздел Phase 0 — skeleton. Здесь появятся теория, примеры и интерактивы.
- Ключевая формула: Итоги · боссы главы 3
- `);
- html += secNavFor('final3');
- html += readButton('final3');
+
+ /* Часть А — Шпаргалка главы 3 (4 mini-карточки) */
+ html += `
+
+
+
Ключевые формулы и идеи всех четырёх параграфов в одном месте — просмотри перед битвой с боссами.
+
+
+
+
+
§ 7 · Свойства логарифмов
+
+
$\\log_a (bc) = \\log_a b + \\log_a c$, $\\;\\log_a \\dfrac{b}{c} = \\log_a b - \\log_a c$, $\\;\\log_a b^n = n \\log_a b$, $\\;\\log_a b = \\dfrac{\\log_c b}{\\log_c a}$.
+
+
+
+
+
§ 8 · Логарифмическая функция
+
+
$y = \\log_a x$. $D = (0; +\\infty)$, $E = \\mathbb{R}$. При $a > 1$ — возрастает, при $0 < a < 1$ — убывает. Точка $(1; 0)$. Асимптота $x = 0$. Обратная к $y = a^x$.
+
+
+
+
+
§ 9 · Логарифм. уравнения
+
+
$\\log_a f = \\log_a g \\Rightarrow f = g$ + ОДЗ ($f, g > 0$). Методы: потенциирование, замена, свойства, графический. Проверять корни!
+
+
+
+
+
§ 10 · Логарифм. неравенства
+
+
При $a > 1$ знак сохраняется , при $0 < a < 1$ — меняется . ВСЕГДА учитываем ОДЗ: всё под $\\log$ должно быть строго положительно.
+
+
+
+
`;
+
+ /* Часть Б — 5 боссов (intro) */
+ html += `
+
+
+
5 интегрированных задач по всей главе. За каждого побеждённого босса: +10 XP, +18% к прогрессу . Победишь всех — ачивка «Магистр логарифмической функции» и +50 XP бонус .
+
+
`;
+
+ html += '
';
+
+ html += `
+
Прогресс по боссам
+
0 / 5 боссов побеждено
+
+
+
Магистр логарифмической функции
+
Все 3 главы изучены. Готовы к финалу курса! +50 XP бонус.
+
К хабу Алгебры 11
+
+
`;
+
+ html += secNav('p10', null);
+
box.innerHTML = html;
renderMath(box);
- wireReadBtn('final3');
+
+ /* Боссы */
+ const BOSSES = [
+ {
+ n:1, color:'#10b981',
+ title:'Циклоп Свойств',
+ tag:'§ 7',
+ q:'Вычислите: $\\log_2 24 - \\log_2 3 - \\log_2 \\sqrt{16}$. Введите числовой ответ.',
+ ans:1,
+ hint:'$\\log_2 24 - \\log_2 3 = \\log_2 \\dfrac{24}{3} = \\log_2 8 = 3$. $\\;\\log_2 \\sqrt{16} = \\log_2 4 = 2$. Итог: $3 - 2 = 1$.'
+ },
+ {
+ n:2, color:'#0891b2',
+ title:'Минотавр Логарифмической',
+ tag:'§ 8',
+ q:'У функции $y = \\log_{1/3} x$ найдите $y(81)$.',
+ ans:-4,
+ hint:'$\\log_{1/3} 81 = \\log_{1/3} (1/3)^{-4} = -4$ (т.к. $(1/3)^{-4} = 3^4 = 81$).'
+ },
+ {
+ n:3, color:'#7c3aed',
+ title:'Гарпия Уравнений',
+ tag:'§ 9',
+ q:'Решите $\\log_3 (x^2 - 2x) = \\log_3 3$. Найдите сумму корней .',
+ ans:2,
+ hint:'Потенциируем: $x^2 - 2x = 3 \\Rightarrow (x-3)(x+1) = 0 \\Rightarrow x = 3, x = -1$. ОДЗ $x^2 - 2x > 0$: $x < 0$ или $x > 2$. Оба корня подходят. Сумма $= 3 + (-1) = 2$.'
+ },
+ {
+ n:4, color:'#dc2626',
+ title:'Дракон Неравенств',
+ tag:'§ 10',
+ q:'Решите $\\log_{1/2} (x - 3) \\ge -2$. Найдите наибольшее целое $x$ из решения.',
+ ans:7,
+ hint:'ОДЗ $x > 3$. $-2 = \\log_{1/2} 4$, $a < 1$ — знак меняется: $x - 3 \\le 4 \\Rightarrow x \\le 7$. Решение $(3; 7]$, наибольшее целое — $7$.'
+ },
+ {
+ n:5, color:'#f59e0b',
+ title:'Мастер Логарифмов',
+ tag:'§ 7 + § 8 + § 9 + § 10',
+ q:'Решите $\\log_2^2 x - \\log_2 x = 2$. Найдите произведение корней .',
+ ans:2,
+ hint:'Замена $t = \\log_2 x$: $t^2 - t - 2 = 0 \\Rightarrow t = 2$ или $t = -1$. $x_1 = 2^2 = 4$, $x_2 = 2^{-1} = 1/2$. Произведение: $4 \\cdot 1/2 = 2$.'
+ },
+ ];
+
+ const cont = document.getElementById('ch3-bosses-container');
+ const STATE_KEY = 'algebra11_ch3_bosses';
+ const BOSS_STATE = (function(){
+ try{ const s = localStorage.getItem(STATE_KEY); if(s){ const p = JSON.parse(s); if(Array.isArray(p) && p.length === BOSSES.length) return p; } }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('ch3-boss-overall');
+ const fill = document.getElementById('ch3-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('ch3-final-reward');
+ if(reward && reward.style.display === 'none'){
+ reward.style.display = 'block';
+ if(!STATE.achievements.has('ch3_done')){
+ achievement('ch3_done','Магистр логарифмической функции');
+ addXp(50, 'ch3-bonus');
+ bumpProgress('final3', 30);
+ if(window.confetti){ try{ confetti(); }catch(e){} }
+ }
+ }
+ }
+ }
+
+ BOSSES.forEach((b, idx)=>{
+ const card = document.getElementById('boss3-'+b.n+'-card');
+ const goBtn = document.getElementById('boss3-'+b.n+'-go');
+ const hintBtn = document.getElementById('boss3-'+b.n+'-hint');
+ const ansInp = document.getElementById('boss3-'+b.n+'-ans');
+ if(BOSS_STATE[idx].defeated){
+ card.style.background = 'linear-gradient(135deg,var(--sec-acc-soft),var(--pri-soft))';
+ card.classList.add('glow');
+ 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('boss3-'+b.n+'-fb');
+ const raw = ansInp.value.replace(',', '.');
+ const val = parseFloat(raw);
+ if(isNaN(val)){ feedback(fb, false, '✗ Введи число.'); return; }
+ if(Math.abs(val - b.ans) < 0.05){
+ BOSS_STATE[idx].defeated = true; saveBosses();
+ feedback(fb, true, '✓ Босс '+b.n+' повержен! +10 XP. '+b.hint);
+ addXp(10, 'boss-ch3-'+b.n);
+ bumpProgress('final3', 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))';
+ card.classList.add('glow','pulse');
+ setTimeout(()=>card.classList.remove('pulse'), 900);
+ refreshOverall();
+ } else {
+ feedback(fb, false, '✗ Промах. Попробуй ещё. Подсказка доступна.');
+ }
+ });
+ hintBtn.addEventListener('click', ()=>{
+ const fb = document.getElementById('boss3-'+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 ===== */