feat(alg11 ch3 wave4 + final): §10 «Логарифмические неравенства» + Финал Главы 3

This commit is contained in:
Maxim Dolgolyov
2026-05-29 12:26:14 +03:00
parent c8385205b4
commit e2f0bb61af
+757 -14
View File
@@ -1952,31 +1952,774 @@ function buildP9(){
function buildP10(){
const box = document.getElementById('p10-body');
let html = '';
html += makeCard('theory', 'В разработке', '10.0', `
<p>Содержание параграфа <b>Логарифмические неравенства</b> будет добавлено в Phase 1+.</p>
<p style="color:var(--muted);font-size:.9rem">Раздел Phase 0 — skeleton. Здесь появятся теория, примеры и интерактивы.</p>
<p style="color:var(--muted);font-size:.9rem">Ключевая формула: $\log_a f > b$</p>
`);
html += secNavFor('p10');
/* === ТЕОРИЯ === */
html += makeCard('theory', 'Основное правило и ОДЗ', '10.1', `
<p><b>Логарифмическим неравенством</b> называется неравенство, в котором переменная содержится <b>под знаком логарифма</b>. Простейший вид:</p>
<p style="text-align:center;font-size:1.04rem">$\\log_a f(x) > \\log_a g(x)$ &nbsp;(или $<$, $\\ge$, $\\le$).</p>
<p>Здесь $a > 0$, $a \\ne 1$. Подход к решению опирается на <b>монотонность</b> логарифмической функции (см. §8):</p>
<p style="text-align:center;padding:10px;background:var(--sec-acc-soft);border-radius:9px;margin:10px 0">
$\\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}$
</p>
<p>Кроме того <b>всегда</b> требуем положительности выражений под логарифмами (ОДЗ):</p>
<p style="text-align:center;padding:10px;background:var(--sec-acc-soft);border-radius:9px;margin:10px 0">
$\\begin{cases} f(x) > 0 \\\\\\\\ g(x) > 0 \\end{cases}$
</p>
<p style="padding:10px 12px;background:#fef3c7;border-left:4px solid #f59e0b;border-radius:6px;margin:12px 0"><b>Запомни:</b></p>
<ul style="margin:8px 0 8px 22px;line-height:1.75">
<li>При $a > 1$ — функция возрастает, знак неравенства <b>сохраняется</b>.</li>
<li>При $0 < a < 1$ — функция убывает, знак неравенства <b>меняется</b>.</li>
<li>ОДЗ требует <b>положительности обоих</b> выражений под логарифмами.</li>
</ul>
<details class="spoiler"><summary>Почему знак меняется при $0 < a < 1$?</summary><div class="spoiler-body">
<p>Функция $y = \\log_a x$ при $0 < a < 1$ строго убывает: чем больше $x$, тем меньше $y$.</p>
<p>Поэтому неравенство $\\log_a f > \\log_a g$ означает $f < g$: чтобы $\\log$ был больше, аргумент должен быть меньше.</p>
</div></details>`);
html += makeCard('rule', 'Алгоритм решения', '10.2', `
<p><b>Шаги</b> для решения неравенства $\\log_a f(x) \\gtrless \\log_a g(x)$:</p>
<ol style="margin:8px 0 8px 22px;line-height:1.75">
<li>Записать <b>ОДЗ</b>: $f(x) > 0$ и $g(x) > 0$.</li>
<li>Привести обе части к <b>одному основанию</b> (если нужно).</li>
<li>Использовать правило монотонности: при $a > 1$ <b>сохранить</b> знак, при $0 < a < 1$ — <b>изменить</b>.</li>
<li>Решить полученное алгебраическое неравенство.</li>
<li><b>Пересечь</b> ответ с ОДЗ — это и есть финальное решение.</li>
</ol>
<hr style="margin:14px 0;border:none;border-top:1px solid var(--border)">
<p><b>Пример 1.</b> $\\log_2 (x - 1) > 3$.</p>
<p><b>ОДЗ:</b> $x - 1 > 0 \\Rightarrow x > 1$.</p>
<p>Перепишем $3 = \\log_2 8$: $\\log_2 (x - 1) > \\log_2 8$. $\\;a = 2 > 1$ — знак сохраняется:</p>
<p>$x - 1 > 8 \\Rightarrow x > 9$.</p>
<p>Пересечение с ОДЗ ($x > 1$): <b>$x > 9$</b>.</p>
<p><b>Ответ:</b> $x \\in (9; +\\infty)$.</p>
<hr style="margin:14px 0;border:none;border-top:1px solid var(--border)">
<p><b>Пример 2.</b> $\\log_{1/3} (x - 2) > -2$.</p>
<p><b>ОДЗ:</b> $x - 2 > 0 \\Rightarrow x > 2$.</p>
<p>Перепишем $-2 = \\log_{1/3} 9$ (т.к. $(1/3)^{-2} = 9$): $\\log_{1/3} (x - 2) > \\log_{1/3} 9$. $\\;a = 1/3 < 1$ — знак <b>меняется</b>:</p>
<p>$x - 2 < 9 \\Rightarrow x < 11$.</p>
<p>Пересечение с ОДЗ: $2 < x < 11$.</p>
<p><b>Ответ:</b> $x \\in (2; 11)$.</p>`);
html += makeCard('example', 'Замена переменной и графический метод', '10.3', `
<p><b>Метод замены.</b> При наличии в неравенстве $\\log_a x$ и $\\log_a^2 x$ выполняют подстановку $t = \\log_a x$ — и получают <b>квадратное</b> неравенство относительно $t$.</p>
<p><b>Пример.</b> $\\log_2^2 x - 3 \\log_2 x + 2 \\le 0$.</p>
<p>ОДЗ: $x > 0$. Замена $t = \\log_2 x$: $t^2 - 3t + 2 \\le 0$.</p>
<p>Корни уравнения $t^2 - 3t + 2 = 0$: $t_1 = 1$, $t_2 = 2$. Парабола ветвями вверх $\\le 0$ между корнями: $t \\in [1; 2]$.</p>
<p>Обратно: $1 \\le \\log_2 x \\le 2 \\Leftrightarrow 2^1 \\le x \\le 2^2 \\Leftrightarrow 2 \\le x \\le 4$.</p>
<p><b>Ответ:</b> $x \\in [2; 4]$.</p>
<hr style="margin:14px 0;border:none;border-top:1px solid var(--border)">
<p><b>Графический метод.</b> Если неравенство нельзя свести к одному основанию или замене — используем графики. Строим $y = \\log_a f(x)$ и $y = \\log_a g(x)$ (или константу) на одной координатной плоскости и смотрим, где один график выше другого.</p>
<p style="padding:10px 12px;background:var(--sec-acc-soft);border-radius:9px"><b>Когда применять?</b> Когда $f$ и $g$ имеют разную природу (например, степенная и логарифмическая). Метод не даёт точный ответ, но позволяет понять <b>число и расположение</b> решений.</p>
<p><b>Пример.</b> $\\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)$.</p>`);
/* === ИНТЕРАКТИВЫ === */
/* IV1 — пошаговый решатель неравенств с числовой прямой */
html += `<div class="wg" id="p10-iv1">
<div class="wg-header"><span class="wg-badge">ИНТЕРАКТИВ 1</span><div class="wg-title">Решатель логарифмических неравенств</div></div>
<div class="wg-help">Выбери задачу ползунком и нажимай «Следующий шаг ▶». Особое внимание — ОДЗ и правилу монотонности. После шага «Ответ» появится визуализация на числовой прямой. Просмотри все 5 задач — получишь XP.</div>
<div class="sliders">
<label>Задача № <b id="p10-iv1-n">1</b> / 5<input type="range" id="p10-iv1-sn" min="1" max="5" step="1" value="1"></label>
</div>
<div id="p10-iv1-q" style="padding:14px;background:var(--sec-acc-soft);border-radius:10px;font-size:1.08rem;text-align:center;margin-bottom:10px"></div>
<div id="p10-iv1-steps" style="display:flex;flex-direction:column;gap:8px;margin-bottom:10px"></div>
<div id="p10-iv1-axis" style="display:flex;justify-content:center;margin-bottom:10px"></div>
<div class="actions" style="justify-content:center">
<button class="btn primary" id="p10-iv1-next">Следующий шаг ▶</button>
<button class="btn" id="p10-iv1-all">Показать все шаги</button>
<button class="btn" id="p10-iv1-reset">Скрыть шаги</button>
</div>
</div>`;
/* IV2 — калькулятор log_a (kx + b) ⋚ c */
html += `<div class="wg" id="p10-iv2">
<div class="wg-header"><span class="wg-badge">ИНТЕРАКТИВ 2</span><div class="wg-title">Калькулятор $\\log_a (kx + b) \\gtrless c$</div></div>
<div class="wg-help">Введи основание $a$, коэффициенты $k$, $b$, правую часть $c$ и выбери знак — калькулятор решит неравенство с учётом монотонности и ОДЗ, покажет числовую прямую.</div>
<div style="display:flex;gap:10px;align-items:center;flex-wrap:wrap;justify-content:center;margin-bottom:8px">
<span style="font-family:'JetBrains Mono',monospace">$a$ =</span>
<input type="number" id="p10-iv2-a" class="tinp" style="width:70px;text-align:center" value="2" step="0.5">
<span style="font-family:'JetBrains Mono',monospace">$k$ =</span>
<input type="number" id="p10-iv2-k" class="tinp" style="width:70px;text-align:center" value="1" step="1">
<span style="font-family:'JetBrains Mono',monospace">$b$ =</span>
<input type="number" id="p10-iv2-b" class="tinp" style="width:70px;text-align:center" value="-1" step="1">
<select id="p10-iv2-sign" class="tinp" style="width:70px;text-align:center">
<option value="gt">&gt;</option>
<option value="lt">&lt;</option>
<option value="ge">&ge;</option>
<option value="le">&le;</option>
</select>
<span style="font-family:'JetBrains Mono',monospace">$c$ =</span>
<input type="number" id="p10-iv2-c" class="tinp" style="width:70px;text-align:center" value="3" step="1">
<button class="btn primary" id="p10-iv2-go">Решить</button>
</div>
<div style="display:flex;gap:6px;flex-wrap:wrap;justify-content:center;margin-bottom:10px;font-size:.82rem">
<span style="color:var(--muted)">Примеры:</span>
<button class="btn" data-set="2,1,-1,gt,3" style="padding:4px 10px;font-size:.82rem">$\\log_2(x-1)>3$</button>
<button class="btn" data-set="0.333,1,-2,gt,-2" style="padding:4px 10px;font-size:.82rem">$\\log_{1/3}(x-2)>-2$</button>
<button class="btn" data-set="5,2,1,lt,1" style="padding:4px 10px;font-size:.82rem">$\\log_5(2x+1)<1$</button>
<button class="btn" data-set="10,1,1,le,1" style="padding:4px 10px;font-size:.82rem">$\\lg(x+1) \\le 1$</button>
</div>
<div id="p10-iv2-out" style="padding:12px 14px;background:var(--card);border-radius:9px;font-size:.96rem;min-height:60px;line-height:1.8"></div>
<div id="p10-iv2-axis" style="display:flex;justify-content:center;margin-top:10px"></div>
<div class="feedback" id="p10-iv2-fb"></div>
</div>`;
/* IV3 — знак сохраняется или меняется? */
html += `<div class="wg" id="p10-iv3">
<div class="wg-header"><span class="wg-badge">ИНТЕРАКТИВ 3</span><div class="wg-title">Знак меняется или сохраняется?</div></div>
<div class="wg-help">Дано логарифмическое неравенство. Определи: при переходе к выражениям под логарифмом знак <b>сохраняется</b> ($a > 1$) или <b>меняется</b> ($0 < a < 1$)? 8 заданий.</div>
<div class="score-display"><span>Задача <b id="p10-iv3-i">1</b> / 8</span><span>Очки: <b id="p10-iv3-s">0</b> / 8</span></div>
<div id="p10-iv3-q" style="padding:14px;background:var(--sec-acc-soft);border-radius:10px;font-size:1.08rem;margin-bottom:10px;text-align:center;min-height:54px"></div>
<div id="p10-iv3-opts" style="display:grid;grid-template-columns:1fr 1fr;gap:10px;max-width:420px;margin:0 auto"></div>
<div class="feedback" id="p10-iv3-fb"></div>
<div class="actions" style="justify-content:center"><button class="btn" id="p10-iv3-restart">Начать заново</button></div>
</div>`;
/* IV4 — тренажёр неравенств */
html += `<div class="wg" id="p10-iv4">
<div class="wg-header"><span class="wg-badge">ИНТЕРАКТИВ 4</span><div class="wg-title">Тренажёр неравенств</div></div>
<div class="wg-help">Реши неравенство и введи <b>число</b> — указанную границу интервала ответа. 6 задач.</div>
<div class="score-display"><span>Задача <b id="p10-iv4-i">1</b> / 6</span><span>Очки: <b id="p10-iv4-s">0</b> / 6</span></div>
<div id="p10-iv4-q" style="padding:14px;background:var(--sec-acc-soft);border-radius:10px;font-size:1.12rem;margin-bottom:10px;text-align:center;min-height:54px"></div>
<div style="display:flex;gap:10px;align-items:center;flex-wrap:wrap;justify-content:center">
<span style="font-family:'JetBrains Mono',monospace">ответ =</span>
<input type="number" id="p10-iv4-ans" class="tinp" style="width:120px;text-align:center" step="0.01">
<button class="btn primary" id="p10-iv4-go">Проверить</button>
<button class="btn" id="p10-iv4-start">Заново</button>
</div>
<div class="feedback" id="p10-iv4-fb"></div>
</div>`;
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 viewBox="0 0 '+W+' '+H+'" width="100%" style="max-width:480px;background:var(--card);border-radius:8px;border:1px solid var(--border)">';
// ось
svg += '<line x1="'+ax+'" y1="'+y0+'" x2="'+bx+'" y2="'+y0+'" stroke="#475569" stroke-width="1.5"/>';
// стрелки
svg += '<polygon points="'+(bx)+','+y0+' '+(bx-8)+','+(y0-4)+' '+(bx-8)+','+(y0+4)+'" fill="#475569"/>';
svg += '<polygon points="'+(ax)+','+y0+' '+(ax+8)+','+(y0-4)+' '+(ax+8)+','+(y0+4)+'" fill="#475569"/>';
// деления
const ticks = [];
for(let v = Math.ceil(lo); v <= Math.floor(hi); v++) ticks.push(v);
ticks.forEach(v => {
const x = xpos(v);
svg += '<line x1="'+x+'" y1="'+(y0-3)+'" x2="'+x+'" y2="'+(y0+3)+'" stroke="#475569" stroke-width="1"/>';
svg += '<text x="'+x+'" y="'+(y0+18)+'" text-anchor="middle" font-size="11" fill="#475569" font-family="JetBrains Mono,monospace">'+v+'</text>';
});
// интервалы
segs.forEach(s => {
const x1 = xpos(s.a), x2 = xpos(s.b);
const color = s.color || '#7c3aed';
svg += '<line x1="'+x1+'" y1="'+y0+'" x2="'+x2+'" y2="'+y0+'" stroke="'+color+'" stroke-width="5" opacity="0.7"/>';
// концы
if(s.a !== -Infinity){
if(s.closedA) svg += '<circle cx="'+x1+'" cy="'+y0+'" r="5" fill="'+color+'"/>';
else svg += '<circle cx="'+x1+'" cy="'+y0+'" r="5" fill="var(--card)" stroke="'+color+'" stroke-width="2"/>';
}
if(s.b !== +Infinity){
if(s.closedB) svg += '<circle cx="'+x2+'" cy="'+y0+'" r="5" fill="'+color+'"/>';
else svg += '<circle cx="'+x2+'" cy="'+y0+'" r="5" fill="var(--card)" stroke="'+color+'" stroke-width="2"/>';
}
});
svg += '</svg>';
return svg;
}
/* === IV1 — пошаговый решатель неравенств === */
(function(){
const TASKS = [
{
q: '$\\log_2 (x - 1) > 3$',
steps: [
'<b>ОДЗ:</b> $x - 1 > 0 \\Rightarrow x > 1$.',
'Переписываем правую часть: $3 = \\log_2 2^3 = \\log_2 8$. Неравенство: $\\log_2 (x - 1) > \\log_2 8$.',
'Учитываем монотонность: $a = 2 > 1$, знак <b>сохраняется</b>: $x - 1 > 8$.',
'Решаем: $x > 9$.',
'Пересечение с ОДЗ ($x > 1$): $x > 9$.',
'<b>Ответ:</b> $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: [
'<b>ОДЗ:</b> $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$, знак <b>меняется</b>: $x - 2 < 9$.',
'Решаем: $x < 11$.',
'Пересечение с ОДЗ ($x > 2$): $2 < x < 11$.',
'<b>Ответ:</b> $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: [
'<b>ОДЗ:</b> $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$.',
'<b>Ответ:</b> $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: [
'<b>ОДЗ:</b> $x > 0$.',
'Замена $t = \\log_2 x$: $t^2 - 3t + 2 \\le 0$.',
'Корни уравнения: $t = 1, t = 2$. Парабола ветвями вверх $\\le 0$ <b>между</b> корнями: $1 \\le t \\le 2$.',
'Обратно: $1 \\le \\log_2 x \\le 2 \\Leftrightarrow 2 \\le x \\le 4$.',
'$[2; 4] \\subset (0; +\\infty)$ — ОДЗ выполняется.',
'<b>Ответ:</b> $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: [
'<b>ОДЗ:</b> $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$.',
'<b>Ответ:</b> $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 '<div data-i="'+i+'" style="padding:10px 12px;background:'+bg+';border:'+brd+';border-radius:8px;font-size:.95rem;display:'+(visible?'block':'none')+'"><span style="color:var(--muted);font-weight:700;margin-right:6px">Шаг '+(i+1)+':</span>'+s+'</div>';
}).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, '&#10007; Введи все четыре числа.'); return; }
if(a <= 0 || Math.abs(a - 1) < 1e-9){ feedback(fb, false, '&#10007; Основание $a$ должно быть $> 0$ и $\\ne 1$.'); return; }
if(k === 0){ feedback(fb, false, '&#10007; При $k = 0$ выражение под логарифмом не зависит от $x$.'); return; }
const fx = fxLabel(k, b);
const eq = '$' + aLabel(a) + '(' + fx + ') ' + signStr(s) + ' ' + c + '$';
let html = '<div style="font-size:1.04rem;margin-bottom:8px;text-align:center">Неравенство: '+eq+'</div>';
// ОДЗ: 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 += '<div style="margin-bottom:6px"><b>Шаг 1. ОДЗ:</b> $' + fx + ' > 0 \\Rightarrow$ ' + odzStr + '.</div>';
// Шаг 2: перевод правой части a^c
const ac = Math.pow(a, c);
html += '<div style="margin-bottom:6px"><b>Шаг 2.</b> Переписываем правую часть: $' + c + ' = ' + aLabel(a) + ' ' + a + '^{' + c + '} = ' + aLabel(a) + ' ' + (+ac.toFixed(4)) + '$.</div>';
// Шаг 3: монотонность
let curSign = s;
let monoText;
if(a > 1){
monoText = '$a = ' + a + ' > 1$ — знак <b>сохраняется</b>';
} else {
monoText = '$0 < a = ' + (+a.toFixed(4)) + ' < 1$ — знак <b>меняется</b>';
curSign = flipSign(curSign);
}
html += '<div style="margin-bottom:6px"><b>Шаг 3.</b> Монотонность: ' + monoText + '. Переход: $' + fx + ' ' + signStr(curSign) + ' ' + (+ac.toFixed(4)) + '$.</div>';
// Шаг 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 += '<div style="margin-bottom:6px"><b>Шаг 4.</b> Решаем: $kx ' + signStr(curSign) + ' ' + (+ac.toFixed(4)) + ' - (' + b + ')';
if(k < 0) html += '$, делим на $k = ' + k + ' < 0$ — знак меняется: $x ' + signStr(finalSign) + ' ' + (+rhs.toFixed(4)) + '$.</div>';
else html += ' \\Rightarrow x ' + signStr(finalSign) + ' ' + (+rhs.toFixed(4)) + '$.</div>';
// Шаг 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 += '<div style="margin-bottom:4px;color:#b91c1c"><b>Шаг 5.</b> Пересечение пустое — решений нет.</div>';
html += '<div style="font-weight:700;color:#b91c1c">Ответ: $x \\in \\varnothing$.</div>';
axisEl.innerHTML = '';
} else {
// Сформируем строку интервала
const lp = (lo === -Infinity) ? '(-\\infty' : ((cLo ? '[' : '(') + (+lo.toFixed(4)));
const rp = (hi === +Infinity) ? '+\\infty)' : ((+hi.toFixed(4)) + (cHi ? ']' : ')'));
html += '<div style="margin-bottom:4px;color:#15803d"><b>Шаг 5.</b> Пересечение исходного и ОДЗ: $' + lp + ';\\, ' + rp + '$.</div>';
html += '<div style="font-weight:700;color:#15803d">Ответ: $x \\in ' + lp + ';\\, ' + rp + '$.</div>';
// Визуализация
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, '&#10003; Решено.');
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 = '<b>Готово!</b> Результат: '+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) => '<button class="btn primary" data-k="'+k+'">'+o+'</button>').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, '&#10003; Верно! '+item.hint+' Дальше ▶'); }
else feedback(fb, false, '&#10007; Неверно. Правильно: <b>'+OPTS[item.ans]+'</b>. '+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$ — введи <b>нижнюю</b> границу интервала ответа', ans: 9, hint: 'ОДЗ $x > 1$. $x - 1 > 8 \\Rightarrow x > 9$. Ответ $(9; +\\infty)$, граница — $9$.' },
{ q: '$\\log_{1/3} (x - 2) > -2$ — введи <b>верхнюю</b> границу интервала ответа', ans: 11, hint: 'ОДЗ $x > 2$. $-2 = \\log_{1/3} 9$, знак меняется: $x - 2 < 9 \\Rightarrow x < 11$. Ответ $(2; 11)$, верхняя граница — $11$.' },
{ q: '$\\log_5 x < 2$ — введи <b>верхнюю</b> границу интервала ответа', ans: 25, hint: 'ОДЗ $x > 0$. $2 = \\log_5 25$, знак сохраняется: $x < 25$. Ответ $(0; 25)$, верхняя граница — $25$.' },
{ q: '$\\log_{1/2} (x - 3) \\ge -2$ — введи <b>верхнюю</b> границу интервала ответа', 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$ — введи <b>нижнюю</b> границу интервала ответа', ans: 10, hint: 'ОДЗ $x > 0$. $1 = \\lg 10$, знак сохраняется: $x > 10$. Ответ $(10; +\\infty)$, граница — $10$.' },
{ q: '$\\log_3 (x + 1) \\le 2$ — введи <b>верхнюю</b> границу интервала ответа', 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 = '<b>Готово!</b> Результат: '+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, '&#10007; Введи число.'); return; }
if(Math.abs(ans - Q[i].ans) < 0.05){
score++;
feedback(fb, true, '&#10003; Верно! '+Q[i].hint+' Дальше ▶');
} else {
feedback(fb, false, '&#10007; Неверно. Ответ: $'+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', `
<p>Содержание финала главы <b>Финал главы</b> будет добавлено в Phase 1+.</p>
<p style="color:var(--muted);font-size:.9rem">Раздел Phase 0 — skeleton. Здесь появятся теория, примеры и интерактивы.</p>
<p style="color:var(--muted);font-size:.9rem">Ключевая формула: Итоги · боссы главы 3</p>
`);
html += secNavFor('final3');
html += readButton('final3');
/* Часть А — Шпаргалка главы 3 (4 mini-карточки) */
html += `<div class="card">
<div class="card-header">
<div class="card-icon theory">${ICONS.theory}</div>
<div class="card-title">Шпаргалка главы 3</div>
<div class="card-num">Итог</div>
</div>
<div class="card-body">
<p>Ключевые формулы и идеи всех четырёх параграфов в одном месте — просмотри перед битвой с боссами.</p>
<div style="display:grid;grid-template-columns:repeat(auto-fit,minmax(220px,1fr));gap:12px;margin-top:10px">
<div style="padding:12px 14px;background:var(--sec-acc-soft);border-radius:11px;border-left:3px solid var(--pri)">
<div style="display:flex;align-items:center;gap:8px;margin-bottom:6px">
<svg viewBox="0 0 24 24" fill="none" stroke="#7c3aed" stroke-width="2" style="width:18px;height:18px"><path d="M12 3v18"/><path d="M3 12h18"/><circle cx="12" cy="12" r="9"/></svg>
<div style="font-family:'Unbounded',sans-serif;font-weight:700;color:var(--pri2);font-size:.92rem">§ 7 · Свойства логарифмов</div>
</div>
<div style="font-size:.95rem">$\\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}$.</div>
</div>
<div style="padding:12px 14px;background:var(--sec-acc-soft);border-radius:11px;border-left:3px solid var(--pri)">
<div style="display:flex;align-items:center;gap:8px;margin-bottom:6px">
<svg viewBox="0 0 24 24" fill="none" stroke="#6d28d9" stroke-width="2" style="width:18px;height:18px"><path d="M3 21c4-12 8-18 18-18"/><line x1="3" y1="21" x2="21" y2="21"/></svg>
<div style="font-family:'Unbounded',sans-serif;font-weight:700;color:var(--pri2);font-size:.92rem">§ 8 · Логарифмическая функция</div>
</div>
<div style="font-size:.95rem">$y = \\log_a x$. $D = (0; +\\infty)$, $E = \\mathbb{R}$. При $a > 1$ — возрастает, при $0 < a < 1$ — убывает. Точка $(1; 0)$. Асимптота $x = 0$. Обратная к $y = a^x$.</div>
</div>
<div style="padding:12px 14px;background:var(--sec-acc-soft);border-radius:11px;border-left:3px solid var(--pri)">
<div style="display:flex;align-items:center;gap:8px;margin-bottom:6px">
<svg viewBox="0 0 24 24" fill="none" stroke="#0891b2" stroke-width="2" style="width:18px;height:18px"><path d="M5 12h14"/><polyline points="12 5 19 12 12 19"/></svg>
<div style="font-family:'Unbounded',sans-serif;font-weight:700;color:var(--pri2);font-size:.92rem">§ 9 · Логарифм. уравнения</div>
</div>
<div style="font-size:.95rem">$\\log_a f = \\log_a g \\Rightarrow f = g$ + ОДЗ ($f, g > 0$). Методы: потенциирование, замена, свойства, графический. <b>Проверять корни!</b></div>
</div>
<div style="padding:12px 14px;background:var(--sec-acc-soft);border-radius:11px;border-left:3px solid var(--pri)">
<div style="display:flex;align-items:center;gap:8px;margin-bottom:6px">
<svg viewBox="0 0 24 24" fill="none" stroke="#f59e0b" stroke-width="2" style="width:18px;height:18px"><line x1="3" y1="6" x2="21" y2="6"/><line x1="3" y1="18" x2="21" y2="18"/><polyline points="7 10 3 14 7 18"/></svg>
<div style="font-family:'Unbounded',sans-serif;font-weight:700;color:var(--pri2);font-size:.92rem">§ 10 · Логарифм. неравенства</div>
</div>
<div style="font-size:.95rem">При $a > 1$ знак <b>сохраняется</b>, при $0 < a < 1$ — <b>меняется</b>. ВСЕГДА учитываем ОДЗ: всё под $\\log$ должно быть строго положительно.</div>
</div>
</div>
</div>
</div>`;
/* Часть Б — 5 боссов (intro) */
html += `<div class="card">
<div class="card-header">
<div class="card-icon rule">${ICONS.rule}</div>
<div class="card-title">Боссы главы 3</div>
<div class="card-num">5</div>
</div>
<div class="card-body">
<p>5 интегрированных задач по всей главе. За каждого побеждённого босса: <b>+10 XP, +18% к прогрессу</b>. Победишь всех — ачивка <b>«Магистр логарифмической функции»</b> и <b>+50 XP бонус</b>.</p>
</div>
</div>`;
html += '<div id="ch3-bosses-container"></div>';
html += `<div style="margin-top:18px;padding:18px 20px;background:linear-gradient(135deg,var(--pri-soft),var(--sec-acc-soft));border-radius:14px;border:1.5px solid var(--pri);text-align:center" id="ch3-final-summary">
<div style="font-family:'Unbounded',sans-serif;font-weight:800;color:var(--pri2);font-size:1.1rem;margin-bottom:6px">Прогресс по боссам</div>
<div id="ch3-boss-overall" style="font-size:.95rem;color:var(--text);margin-bottom:10px">0 / 5 боссов побеждено</div>
<div style="height:12px;background:var(--card);border-radius:8px;overflow:hidden;border:1px solid var(--border)">
<div id="ch3-boss-overall-fill" style="height:100%;width:0%;background:linear-gradient(90deg,#7c3aed,#a78bfa);transition:width .35s"></div>
</div>
<div id="ch3-final-reward" style="margin-top:14px;display:none;padding:14px;background:var(--card);border-radius:11px;border:2px solid #7c3aed">
<div style="font-family:'Unbounded',sans-serif;font-weight:800;color:#6d28d9;font-size:1.05rem;margin-bottom:6px">Магистр логарифмической функции</div>
<div style="font-size:.92rem;margin-bottom:10px">Все 3 главы изучены. Готовы к финалу курса! +50 XP бонус.</div>
<a class="btn primary" href="/textbook/algebra-11" style="text-decoration:none">К хабу Алгебры 11 <svg class="ic" viewBox="0 0 24 24"><polyline points="9 18 15 12 9 6"/></svg></a>
</div>
</div>`;
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$. Найдите <b>сумму корней</b>.',
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$. Найдите <b>наибольшее целое</b> $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$. Найдите <b>произведение корней</b>.',
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 '<div class="boss-card" id="boss3-'+b.n+'-card" style="padding:16px;background:var(--card);border-radius:12px;border:2px solid '+b.color+';margin-bottom:14px">'
+'<div style="display:flex;align-items:center;gap:10px;margin-bottom:10px;flex-wrap:wrap">'
+'<svg viewBox="0 0 24 24" fill="none" stroke="'+b.color+'" stroke-width="2.2" style="width:28px;height:28px;flex-shrink:0"><polygon points="12,2 22,20 2,20"/></svg>'
+'<div style="font-family:\'Unbounded\',sans-serif;font-weight:800;color:'+b.color+';font-size:1.05rem">Босс '+b.n+': '+b.title+'</div>'
+'<div style="margin-left:auto;font-size:.78rem;color:var(--muted);padding:3px 8px;background:var(--sec-acc-soft);border-radius:6px">'+b.tag+'</div>'
+'</div>'
+'<div class="boss-q" id="boss3-'+b.n+'-q" style="padding:12px 14px;background:var(--sec-acc-soft);border-radius:9px;font-size:1rem;line-height:1.5;margin-bottom:10px">'+b.q+'</div>'
+'<div style="display:flex;gap:8px;align-items:center;flex-wrap:wrap">'
+'<span style="font-family:\'JetBrains Mono\',monospace;font-size:.92rem">ответ =</span>'
+'<input type="number" id="boss3-'+b.n+'-ans" class="tinp" style="width:120px;text-align:center" step="0.01" placeholder="число">'
+'<button class="btn primary" id="boss3-'+b.n+'-go" style="background:'+b.color+';border-color:'+b.color+'">Атаковать</button>'
+'<button class="btn" id="boss3-'+b.n+'-hint">Подсказка</button>'
+'</div>'
+'<div class="feedback" id="boss3-'+b.n+'-fb"></div>'
+'</div>';
}).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, '&#10007; Введи число.'); return; }
if(Math.abs(val - b.ans) < 0.05){
BOSS_STATE[idx].defeated = true; saveBosses();
feedback(fb, true, '&#10003; Босс '+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, '&#10007; Промах. Попробуй ещё. Подсказка доступна.');
}
});
hintBtn.addEventListener('click', ()=>{
const fb = document.getElementById('boss3-'+b.n+'-fb');
fb.className = 'feedback ok';
fb.innerHTML = '<b>Подсказка:</b> '+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 ===== */