diff --git a/frontend/textbooks/algebra_8_ch3.html b/frontend/textbooks/algebra_8_ch3.html index 893c72c..0f3c733 100644 --- a/frontend/textbooks/algebra_8_ch3.html +++ b/frontend/textbooks/algebra_8_ch3.html @@ -1152,8 +1152,592 @@ function init(){ document.addEventListener('DOMContentLoaded', init); /* STUBS */ -function buildP15stub(){ document.getElementById('p15-body').innerHTML = `

§ 15 — Числовые промежутки. Линейные неравенства

Будет в Wave 2.

${secNav('p14','p16')}`; } -function buildP16stub(){ document.getElementById('p16-body').innerHTML = `

§ 16 — Системы неравенств

Будет в Wave 2.

${secNav('p15','p17')}`; } +function buildP15stub(){ buildP15(); } +function buildP16stub(){ buildP16(); } + +/* Helper: SVG-рисовалка интервала / луча / системы */ +function drawNumLine(opts){ + const W = 520, H = 80, M = 30; + const min = opts.min != null ? opts.min : -10; + const max = opts.max != null ? opts.max : 10; + const x = v => M + (v - min) * (W - 2*M) / (max - min); + let s = ''; + // ось + s += ''; + // стрелка вправо + s += ''; + // деления и подписи + for(let v = Math.ceil(min); v <= Math.floor(max); v++){ + const px = x(v); + s += ''; + if(v % Math.max(1, Math.floor((max - min) / 10)) === 0 || max - min <= 12) s += '' + v + ''; + } + // интервалы + (opts.intervals || []).forEach(it => { + const color = it.color || 'var(--sec-acc)'; + const a = it.a != null ? Math.max(min, it.a) : min; + const b = it.b != null ? Math.min(max, it.b) : max; + const xa = x(a), xb = x(b); + // полоска + s += ''; + // концы + if(it.a != null){ + if(it.openA) s += ''; + else s += ''; + } else { + s += ''; + } + if(it.b != null){ + if(it.openB) s += ''; + else s += ''; + } else { + s += ''; + } + }); + s += ''; + return s; +} + +/* ============================================================ + § 15 — ЧИСЛОВЫЕ ПРОМЕЖУТКИ. ЛИНЕЙНЫЕ НЕРАВЕНСТВА + ============================================================ */ +function buildP15(){ + const box = document.getElementById('p15-body'); + let html = ''; + + html += makeCard('repeat','Повторение',null,` + `); + + html += makeCard('theory','5 видов промежутков','15.1',` + + + + + + + + + +
НазваниеНеравенствоЗаписьИзображение
Отрезок$a \\leq x \\leq b$$[a;\\,b]$● ━━━ ●
Интервал$a < x < b$$(a;\\,b)$○ ━━━ ○
Полуинтервал$a \\leq x < b$$[a;\\,b)$● ━━━ ○
Луч$x \\geq a$$[a;\\,+\\infty)$● ━━━ →
Открытый луч$x < a$$(-\\infty;\\,a)$← ━━━ ○
+

Запомните: квадратная скобка $[\\,]$ — точка входит. Круглая $(\\,)$ — выколота. У бесконечности всегда круглая.

`); + + html += makeCard('algo','Решение линейного неравенства','15.2',` +
    +
  1. Раскрыть скобки, если есть.
  2. +
  3. Перенести члены с $x$ в одну часть, числа — в другую (со сменой знака при переносе).
  4. +
  5. Привести подобные.
  6. +
  7. Разделить на коэффициент при $x$. Если он отрицательный — знак неравенства меняется.
  8. +
  9. Записать ответ как промежуток.
  10. +
`); + + html += makeCard('example','Пример',null,` +

Решим: $3x - 7 > 2x + 1$.

+

1) $3x - 2x > 1 + 7$. 2) $x > 8$. 3) Ответ: $x \\in (8;\\,+\\infty)$.

+

С отрицательным коэффициентом: $-2x \\geq 6$ → делим на $-2$: $x \\leq -3$. Ответ: $x \\in (-\\infty;\\,-3]$.

`); + + /* INT 1 — Конструктор промежутка */ + html += widget('Конструктор промежутка','INTERACT 1','Выбери концы $a$, $b$ и тип. Промежуток нарисуется на числовой прямой.',` +
+ + +
+
+ + + + + + +
+
+
`); + + /* INT 2 — Конвертация: запись → неравенство */ + html += widget('Конвертация записи','INTERACT 2','Найди соответствие между записью промежутка и неравенством.',` +
Раунд 1 / 8Очки: 0
+
+
+ + `); + + /* INT 3 — Пошаговый решатель линейного */ + html += widget('Пошаговый решатель','INTERACT 3','Введите $a, b, c, d$ для $ax + b \\geq cx + d$ и нажимайте «Дальше».',` +
+ $x +$ + $\\geq$ + $x +$ + + + + +
+
`); + + /* INT 4 — Тренажёр линейных */ + html += widget('Тренажёр линейных','INTERACT 4','Решите неравенство и введите ответ в формате $(a; +\\infty)$ или $[-\\infty; b)$. Используйте `inf` для бесконечности.',` +
Задача 1 / 8Очки: 0
+
+
+ + + + +
+
+ + +
+ + `); + + /* INT 5 — Drag: какой промежуток */ + html += widget('Сопоставь неравенство и промежуток','INTERACT 5','Отнеси каждое неравенство к правильной записи промежутка.',` + ${DND_HINT_HTML} +
+
+
$[a;b]$
+
$(a;b)$
+
$(a;+\\infty)$
+
$(-\\infty;b]$
+
+
+ `); + + html += makeCard('oral','Устно',null,` +
    +
  1. Запишите промежуток для $x > 5$.
  2. +
  3. Что означает скобка $[$?
  4. +
  5. Решите устно: $2x > 6$.
  6. +
`); + + html += makeCard('class','Класс — решите',null,` +
    +
  1. $5x - 3 < 12$
  2. +
  3. $-3x + 1 \\geq 7$
  4. +
  5. $2(x - 1) > 3(x + 2)$
  6. +
  7. Запишите $[-2; 5)$ через неравенство.
  8. +
`); + + html += makeCard('home','Домашка',null,` +
    +
  1. $7x + 2 \\leq 4x - 7$
  2. +
  3. $-\\dfrac{x}{3} > 2$
  4. +
  5. $3 - 2x \\leq x + 9$
  6. +
  7. Изобразите $(-\\infty; -1) \\cup [3; +\\infty)$.
  8. +
`); + + html += secNav('p14', 'p16'); + box.innerHTML = html; + if(window.renderMathInElement) setTimeout(()=>renderMath(box), 0); + + /* INIT 1 — Конструктор */ + (function(){ + const aE = document.getElementById('p15c-a'), bE = document.getElementById('p15c-b'); + const lineE = document.getElementById('p15c-line'), out = document.getElementById('p15c-out'); + let type = 'cc', done = false; + document.querySelectorAll('.p15c-type').forEach(btn => btn.addEventListener('click', ()=>{ + document.querySelectorAll('.p15c-type').forEach(x => x.classList.remove('active')); + btn.classList.add('active'); type = btn.dataset.t; refresh(); + })); + function refresh(){ + const a = +aE.value, b = +bE.value; + document.getElementById('p15c-a-val').textContent = a; + document.getElementById('p15c-b-val').textContent = b; + let interval = null, label = '', ineq = ''; + if(type === 'cc'){ if(b < a){ out.innerHTML = 'Нужно $a \\leq b$'; renderMath(out); return; } interval = { a, b, openA:false, openB:false }; label = '$[' + a + ';\\,' + b + ']$'; ineq = a + ' \\leq x \\leq ' + b; } + else if(type === 'oo'){ if(b <= a){ out.innerHTML = 'Нужно $a < b$'; renderMath(out); return; } interval = { a, b, openA:true, openB:true }; label = '$(' + a + ';\\,' + b + ')$'; ineq = a + ' < x < ' + b; } + else if(type === 'co'){ if(b <= a){ out.innerHTML = 'Нужно $a < b$'; renderMath(out); return; } interval = { a, b, openA:false, openB:true }; label = '$[' + a + ';\\,' + b + ')$'; ineq = a + ' \\leq x < ' + b; } + else if(type === 'oc'){ if(b <= a){ out.innerHTML = 'Нужно $a < b$'; renderMath(out); return; } interval = { a, b, openA:true, openB:false }; label = '$(' + a + ';\\,' + b + ']$'; ineq = a + ' < x \\leq ' + b; } + else if(type === 'rcr'){ interval = { a, b:null, openA:false, openB:false }; label = '$[' + a + ';\\,+\\infty)$'; ineq = 'x \\geq ' + a; } + else if(type === 'rol'){ interval = { a:null, b, openA:false, openB:true }; label = '$(-\\infty;\\,' + b + ')$'; ineq = 'x < ' + b; } + lineE.innerHTML = drawNumLine({ intervals:[interval] }); + out.innerHTML = '
Запись: ' + label + '
Неравенство: $' + ineq + '$
'; + renderMath(out); + if(!done){ done = true; setTimeout(()=>{ achievement('p15_line'); bumpProgress('p15', 14); }, 300); } + } + [aE,bE].forEach(e => e.addEventListener('input', refresh)); + refresh(); + })(); + + /* INIT 2 — Конвертация */ + (function(){ + const tasks = [ + { q:'Запись: $[3;\\,7]$. Какое неравенство?', opts:['$3 \\leq x \\leq 7$','$3 < x < 7$','$x \\geq 3$','$3 \\leq x < 7$'], ok:0 }, + { q:'Неравенство: $-2 < x \\leq 5$. Какая запись?', opts:['$(-2;\\,5]$','$[-2;\\,5)$','$[-2;\\,5]$','$(-2;\\,5)$'], ok:0 }, + { q:'Запись: $(-\\infty;\\,4)$. Что значит?', opts:['$x < 4$','$x \\leq 4$','$x > 4$','$-\\infty < x < 4$ строго'], ok:0 }, + { q:'Неравенство: $x \\geq 6$. Какая запись?', opts:['$[6;\\,+\\infty)$','$(6;\\,+\\infty)$','$(-\\infty;\\,6]$','$[-6;\\,+\\infty)$'], ok:0 }, + { q:'Запись: $(0;\\,1)$. Содержит ли точку $0$?', opts:['Нет, выколота','Да, входит','Только если $0 > 0$','Зависит от контекста'], ok:0 }, + { q:'Запись с двумя круглыми скобками означает:', opts:['Оба конца выколоты','Оба конца входят','Один выколот, один входит','Только справа выколот'], ok:0 }, + { q:'Какой записью обозначить «все числа меньше или равны $-3$»?', opts:['$(-\\infty;\\,-3]$','$(-\\infty;\\,-3)$','$[-3;\\,+\\infty)$','$(-3;\\,+\\infty)$'], ok:0 }, + { q:'Промежуток $[2;\\,2]$ содержит:', opts:['Только число $2$','Все числа от $0$ до $2$','Пуст','Все числа $\\geq 2$'], ok:0 }, + ]; + let cur = null, i = 1, score = 0, shuffled = []; + function show(){ + cur = shuffled[i-1]; + document.getElementById('p15v-i').textContent = i; + document.getElementById('p15v-task').innerHTML = cur.q; + renderMath(document.getElementById('p15v-task')); + const opts = document.getElementById('p15v-opts'); opts.innerHTML = ''; + cur.opts.forEach((o, k)=>{ + const b = document.createElement('button'); + b.className = 'btn'; b.innerHTML = o; b.style.cssText = 'text-align:left'; + b.addEventListener('click', ()=>{ + const fb = document.getElementById('p15v-fb'); fb.style.display = 'block'; + if(k === cur.ok){ score++; b.classList.add('ok'); feedback(fb, true, '✓'); } + else { b.classList.add('fail'); feedback(fb, false, 'Не то.'); } + document.getElementById('p15v-score').textContent = score; + if(i >= shuffled.length){ setTimeout(()=>{ feedback(fb, score >= 6, 'Итог: ' + score + '/' + shuffled.length); if(score >= 6){ achievement('p15_convert'); bumpProgress('p15', 14); confetti(); } }, 600); } + else { i++; setTimeout(show, 800); } + }); + opts.appendChild(b); + }); + renderMath(opts); + document.getElementById('p15v-fb').style.display = 'none'; + } + document.getElementById('p15v-start').addEventListener('click', ()=>{ i=1; score=0; document.getElementById('p15v-score').textContent = 0; shuffled = [...tasks].sort(()=>Math.random()-0.5); show(); }); + })(); + + /* INIT 3 — Шаговый решатель */ + (function(){ + const stage = document.getElementById('p15s-stage'); + const goBtn = document.getElementById('p15s-go'), nextBtn = document.getElementById('p15s-next'), resetBtn = document.getElementById('p15s-reset'); + let steps = [], idx = 0, awarded = false; + function build(a, b, c, d){ + // ax + b >= cx + d -> (a-c)x >= d-b + const k = a - c, r = d - b; + const arr = []; + arr.push('Дано: $' + a + 'x ' + (b >= 0 ? '+ ' + b : '- ' + Math.abs(b)) + ' \\geq ' + c + 'x ' + (d >= 0 ? '+ ' + d : '- ' + Math.abs(d)) + '$'); + arr.push('Шаг 1. Переносим $x$ влево, числа — вправо: $' + a + 'x - ' + c + 'x \\geq ' + d + ' - (' + b + ') \\Rightarrow ' + k + 'x \\geq ' + r + '$'); + if(k === 0){ + arr.push('Шаг 2. $0 \\cdot x \\geq ' + r + '$. ' + (r <= 0 ? 'Верно для любого $x$ — решение $x \\in \\mathbb{R}$.' : 'Невозможно — решений нет.')); + arr.push('Ответ: ' + (r <= 0 ? '$(-\\infty;\\,+\\infty)$' : 'нет решений')); + } else if(k > 0){ + arr.push('Шаг 2. Делим на $' + k + ' > 0$: $x \\geq ' + r + '/' + k + ' = ' + fmt(r/k) + '$'); + arr.push('Ответ: $x \\in [' + fmt(r/k) + ';\\,+\\infty)$'); + } else { + arr.push('Шаг 2. Делим на $' + k + ' < 0$ — знак меняется! $x \\leq ' + r + '/' + k + ' = ' + fmt(r/k) + '$'); + arr.push('Ответ: $x \\in (-\\infty;\\,' + fmt(r/k) + ']$'); + } + return arr; + } + function render(){ + stage.innerHTML = steps.slice(0, idx + 1).map(s => `
${s}
`).join(''); + renderMath(stage); + if(idx >= steps.length - 1){ + nextBtn.disabled = true; nextBtn.textContent = 'Готово'; + if(!awarded){ awarded = true; achievement('p15_solver'); bumpProgress('p15', 14); confetti(); } + } else { nextBtn.disabled = false; nextBtn.textContent = 'Дальше (' + (idx + 1) + '/' + steps.length + ')'; } + } + goBtn.addEventListener('click', ()=>{ + const a = +document.getElementById('p15s-a').value, b = +document.getElementById('p15s-b').value; + const c = +document.getElementById('p15s-c').value, d = +document.getElementById('p15s-d').value; + steps = build(a, b, c, d); idx = 0; awarded = false; + goBtn.style.display = 'none'; nextBtn.style.display = ''; resetBtn.style.display = ''; + render(); + }); + nextBtn.addEventListener('click', ()=>{ if(idx < steps.length - 1){ idx++; render(); } }); + resetBtn.addEventListener('click', ()=>{ idx = 0; stage.innerHTML = ''; goBtn.style.display = ''; nextBtn.style.display = 'none'; resetBtn.style.display = 'none'; }); + })(); + + /* INIT 4 — Тренажёр линейных */ + (function(){ + function gen(){ + const a = (Math.random() < 0.5 ? -1 : 1) * (1 + Math.floor(Math.random()*4)); + const b = -5 + Math.floor(Math.random()*11); + const c = -5 + Math.floor(Math.random()*11); + // ax + b > c + const v = (c - b) / a; + // знак ответа зависит от знака a + const baseCmp = ['gt','ge','lt','le'][Math.floor(Math.random()*4)]; + const flipped = a < 0 ? { gt:'lt', ge:'le', lt:'gt', le:'ge' }[baseCmp] : baseCmp; + return { a, b, c, baseCmp, ansCmp: flipped, ansK: v, txt: a + 'x ' + (b >= 0 ? '+ ' + b : '- ' + Math.abs(b)) + ' ' + ({gt:'>',ge:'\\geq',lt:'<',le:'\\leq'})[baseCmp] + ' ' + c }; + } + let cur = null, i = 1, score = 0, chosen = null; + function show(){ + cur = gen(); + document.getElementById('p15t-i').textContent = i; + document.getElementById('p15t-task').innerHTML = '$' + cur.txt + '$'; + renderMath(document.getElementById('p15t-task')); + document.getElementById('p15t-k').value = ''; + chosen = null; + document.querySelectorAll('#p15t-gt,#p15t-ge,#p15t-lt,#p15t-le').forEach(b => b.classList.remove('primary')); + document.getElementById('p15t-fb').style.display = 'none'; + } + function check(){ + const fb = document.getElementById('p15t-fb'); fb.style.display = 'block'; + const u = +document.getElementById('p15t-k').value; + const ok = chosen === cur.ansCmp && Math.abs(u - cur.ansK) < 1e-6; + if(ok){ score++; feedback(fb, true, '✓ $x ' + ({gt:'>',ge:'\\geq',lt:'<',le:'\\leq'})[cur.ansCmp] + ' ' + fmt(cur.ansK) + '$'); renderMath(fb); } + else feedback(fb, false, 'Правильно: $x ' + ({gt:'>',ge:'\\geq',lt:'<',le:'\\leq'})[cur.ansCmp] + ' ' + fmt(cur.ansK) + '$'); + renderMath(fb); + document.getElementById('p15t-score').textContent = score; + if(i >= 8){ setTimeout(()=>{ feedback(fb, score >= 5, 'Итог: ' + score + '/8'); if(score >= 5){ achievement('p15_train'); bumpProgress('p15', 16); confetti(); } }, 700); } + else { i++; setTimeout(show, 900); } + } + document.getElementById('p15t-start').addEventListener('click', ()=>{ i=1; score=0; document.getElementById('p15t-score').textContent = 0; show(); }); + ['gt','ge','lt','le'].forEach(c => document.getElementById('p15t-' + c).addEventListener('click', ()=>{ + chosen = c; + document.querySelectorAll('#p15t-gt,#p15t-ge,#p15t-lt,#p15t-le').forEach(b => b.classList.remove('primary')); + document.getElementById('p15t-' + c).classList.add('primary'); + })); + document.getElementById('p15t-go').addEventListener('click', ()=>{ if(!chosen){ const fb = document.getElementById('p15t-fb'); fb.style.display='block'; feedback(fb, false, 'Выберите знак.'); return; } check(); }); + })(); + + /* INIT 5 — Drag */ + (function(){ + const items = [ + { id:1, html:'$2 \\leq x \\leq 7$', cat:'cc' }, + { id:2, html:'$-3 < x < 4$', cat:'oo' }, + { id:3, html:'$x > 5$', cat:'rop' }, + { id:4, html:'$x \\leq 0$', cat:'lcr' }, + { id:5, html:'$-1 \\leq x \\leq 6$', cat:'cc' }, + { id:6, html:'$0 < x < 10$', cat:'oo' }, + { id:7, html:'$x > 8$', cat:'rop' }, + { id:8, html:'$x \\leq -2$', cat:'lcr' }, + ]; + const sorter = setupSorter({ poolId:'p15d-pool', cats:['cc','oo','rop','lcr'], items, scopeSelector:'#p15-body' }); + document.getElementById('p15d-check').addEventListener('click', ()=>{ + const fb = document.getElementById('p15d-fb'); fb.style.display = 'block'; + if(Object.keys(sorter.placed).length < items.length){ feedback(fb, false, '⚠ Разложите все.'); return; } + let ok = 0; items.forEach(it=>{ if(sorter.placed[it.id] === it.cat) ok++; }); + if(ok === items.length){ feedback(fb, true, '✓ Все верно!'); achievement('p15_drag'); bumpProgress('p15', 14); confetti(); } + else feedback(fb, false, 'Верно ' + ok + ' из ' + items.length); + }); + document.getElementById('p15d-reset').addEventListener('click', ()=>{ sorter.reset(); document.getElementById('p15d-fb').style.display='none'; }); + })(); +} + +/* ============================================================ + § 16 — СИСТЕМЫ И СОВОКУПНОСТИ НЕРАВЕНСТВ + ============================================================ */ +function buildP16(){ + const box = document.getElementById('p16-body'); + let html = ''; + + html += makeCard('repeat','Повторение § 15',null,` + `); + + html += makeCard('theory','Система и совокупность','16.1',` +

Система неравенств — несколько неравенств, выполненных одновременно («И»). Решение — пересечение решений каждого. Записывается фигурной скобкой:

+
$$\\begin{cases} x \\geq 1 \\\\ x < 5 \\end{cases} \\Rightarrow x \\in [1;\\,5)$$
+

Совокупность — неравенства, из которых выполняется хотя бы одно («ИЛИ»). Решение — объединение. Записывается квадратной скобкой:

+
$$\\left[\\begin{array}{l} x < -2 \\\\ x \\geq 3 \\end{array}\\right. \\Rightarrow x \\in (-\\infty;\\,-2) \\cup [3;\\,+\\infty)$$
`); + + html += makeCard('algo','Алгоритм решения системы',null,` +
    +
  1. Решить каждое неравенство отдельно.
  2. +
  3. Изобразить решения на одной числовой прямой.
  4. +
  5. Найти пересечение (общую часть для системы) или объединение (для совокупности).
  6. +
  7. Записать ответ как промежуток.
  8. +
`); + + html += makeCard('example','Пример',null,` +

Решим систему: $\\begin{cases} 2x - 1 > 3 \\\\ x + 4 \\leq 10 \\end{cases}$

+

Первое: $2x > 4 \\Rightarrow x > 2$, т.е. $(2;\\,+\\infty)$.

+

Второе: $x \\leq 6$, т.е. $(-\\infty;\\,6]$.

+

Пересечение: $x \\in (2;\\,6]$.

`); + + /* INT 1 — Пересечение промежутков */ + html += widget('Пересечение двух промежутков','INTERACT 1','Сдвигай границы каждого промежутка и наблюдай пересечение.',` +
+ + + + +
+
+
`); + + /* INT 2 — Пошаговый решатель */ + html += widget('Шаговый решатель системы','INTERACT 2','Решаем систему пошагово: каждое неравенство отдельно, затем пересечение.',` +

Система: $\\begin{cases} 3x - 5 \\geq 1 \\\\ -2x + 4 > -6 \\end{cases}$

+
+
`); + + /* INT 3 — Drag: система или совокупность */ + html += widget('Что это: система или совокупность?','INTERACT 3','По логической связке («И» / «ИЛИ») определи тип записи.',` + ${DND_HINT_HTML} +
+
+
Система (И, пересечение)
+
Совокупность (ИЛИ, объединение)
+
+
+ `); + + /* INT 4 — Тренажёр систем */ + html += widget('Тренажёр систем','INTERACT 4','Решите систему и введите ответ как промежуток.',` +
Задача 1 / 6Очки: 0
+
+
+ + `); + + /* INT 5 — Совокупность визуально */ + html += widget('Совокупность: объединение','INTERACT 5','Двигай $a$ и $b$, смотри, как меняется $(-\\infty;a) \\cup (b;+\\infty)$.',` +
+ + +
+
+
`); + + html += makeCard('class','Класс — решите',null,` +
    +
  1. $\\begin{cases} x > 2 \\\\ x < 8 \\end{cases}$
  2. +
  3. $\\begin{cases} 2x - 1 \\leq 5 \\\\ x + 3 > -2 \\end{cases}$
  4. +
  5. $\\left[\\begin{array}{l} x < -1 \\\\ x \\geq 4 \\end{array}\\right.$
  6. +
`); + + html += makeCard('home','Домашка',null,` +
    +
  1. $\\begin{cases} 3x + 1 > 7 \\\\ x - 2 \\leq 3 \\end{cases}$
  2. +
  3. $\\begin{cases} 5 - 2x \\geq 1 \\\\ x + 1 > -3 \\end{cases}$
  4. +
  5. $\\left[\\begin{array}{l} 2x > 8 \\\\ -x \\geq 2 \\end{array}\\right.$
  6. +
`); + + html += secNav('p15', 'p17'); + box.innerHTML = html; + if(window.renderMathInElement) setTimeout(()=>renderMath(box), 0); + + /* INIT 1 — Пересечение промежутков */ + (function(){ + const ids = ['p16i-a1','p16i-b1','p16i-a2','p16i-b2']; + const lineE = document.getElementById('p16i-line'), out = document.getElementById('p16i-out'); + let done = false; + function refresh(){ + const a1 = +document.getElementById('p16i-a1').value, b1 = +document.getElementById('p16i-b1').value; + const a2 = +document.getElementById('p16i-a2').value, b2 = +document.getElementById('p16i-b2').value; + document.getElementById('p16i-a1-val').textContent = a1; + document.getElementById('p16i-b1-val').textContent = b1; + document.getElementById('p16i-a2-val').textContent = a2; + document.getElementById('p16i-b2-val').textContent = b2; + const intervals = []; + if(a1 <= b1) intervals.push({ a:a1, b:b1, openA:false, openB:false, color:'#6366f1' }); + if(a2 <= b2) intervals.push({ a:a2, b:b2, openA:false, openB:false, color:'#f59e0b' }); + const lo = Math.max(a1, a2), hi = Math.min(b1, b2); + if(lo <= hi) intervals.push({ a:lo, b:hi, openA:false, openB:false, color:'#10b981' }); + lineE.innerHTML = drawNumLine({ intervals }); + let s = '
[' + a1 + ';' + b1 + '][' + a2 + ';' + b2 + '] = '; + if(lo > hi) s += '∅ (пусто)
'; + else s += '[' + lo + ';' + hi + ']'; + out.innerHTML = s; + if(!done){ done = true; setTimeout(()=>{ achievement('p16_intersect'); bumpProgress('p16', 14); }, 300); } + } + ids.forEach(id => document.getElementById(id).addEventListener('input', refresh)); + refresh(); + })(); + + /* INIT 2 — Шаговый решатель */ + (function(){ + const stage = document.getElementById('p16s-stage'); + const goBtn = document.getElementById('p16s-go'), nextBtn = document.getElementById('p16s-next'), resetBtn = document.getElementById('p16s-reset'); + const steps = [ + 'Шаг 1. Решим первое: $3x - 5 \\geq 1 \\Rightarrow 3x \\geq 6 \\Rightarrow x \\geq 2$, т.е. $[2;\\,+\\infty)$.', + 'Шаг 2. Решим второе: $-2x + 4 > -6 \\Rightarrow -2x > -10 \\Rightarrow x < 5$ (знак сменился!), т.е. $(-\\infty;\\,5)$.', + 'Шаг 3. Пересечение: $[2;\\,+\\infty) \\cap (-\\infty;\\,5) = [2;\\,5)$.', + 'Ответ: $x \\in [2;\\,5)$.', + ]; + let idx = 0, awarded = false; + function render(){ + stage.innerHTML = steps.slice(0, idx + 1).map(s => `
${s}
`).join(''); + renderMath(stage); + if(idx >= steps.length - 1){ + nextBtn.disabled = true; nextBtn.textContent = 'Готово'; + if(!awarded){ awarded = true; achievement('p16_solver'); bumpProgress('p16', 14); confetti(); } + } else { nextBtn.disabled = false; nextBtn.textContent = 'Дальше (' + (idx + 1) + '/' + steps.length + ')'; } + } + goBtn.addEventListener('click', ()=>{ idx = 0; awarded = false; goBtn.style.display = 'none'; nextBtn.style.display = ''; resetBtn.style.display = ''; render(); }); + nextBtn.addEventListener('click', ()=>{ if(idx < steps.length - 1){ idx++; render(); } }); + resetBtn.addEventListener('click', ()=>{ idx = 0; stage.innerHTML = ''; goBtn.style.display = ''; nextBtn.style.display = 'none'; resetBtn.style.display = 'none'; }); + })(); + + /* INIT 3 — Drag система/совокупность */ + (function(){ + const items = [ + { id:1, html:'$\\begin{cases} x > 2 \\\\ x \\leq 5 \\end{cases}$', cat:'sys' }, + { id:2, html:'$\\left[\\begin{array}{l} x < -3 \\\\ x \\geq 4 \\end{array}\\right.$', cat:'sov' }, + { id:3, html:'Оба условия одновременно', cat:'sys' }, + { id:4, html:'Хотя бы одно условие', cat:'sov' }, + { id:5, html:'$\\begin{cases} x \\geq -1 \\\\ x < 7 \\end{cases}$', cat:'sys' }, + { id:6, html:'$\\left[\\begin{array}{l} x \\leq 0 \\\\ x > 5 \\end{array}\\right.$', cat:'sov' }, + { id:7, html:'Пересечение решений', cat:'sys' }, + { id:8, html:'Объединение решений', cat:'sov' }, + ]; + const sorter = setupSorter({ poolId:'p16d-pool', cats:['sys','sov'], items, scopeSelector:'#p16-body' }); + document.getElementById('p16d-check').addEventListener('click', ()=>{ + const fb = document.getElementById('p16d-fb'); fb.style.display = 'block'; + if(Object.keys(sorter.placed).length < items.length){ feedback(fb, false, '⚠ Разложите все.'); return; } + let ok = 0; items.forEach(it=>{ if(sorter.placed[it.id] === it.cat) ok++; }); + if(ok === items.length){ feedback(fb, true, '✓ Все верно!'); achievement('p16_union'); bumpProgress('p16', 14); confetti(); } + else feedback(fb, false, 'Верно ' + ok + ' из ' + items.length); + }); + document.getElementById('p16d-reset').addEventListener('click', ()=>{ sorter.reset(); document.getElementById('p16d-fb').style.display='none'; }); + })(); + + /* INIT 4 — Тренажёр систем */ + (function(){ + const tasks = [ + { q:'$\\begin{cases} x > 3 \\\\ x \\leq 7 \\end{cases}$', opts:['$(3;\\,7]$','$[3;\\,7)$','$(3;\\,7)$','$\\emptyset$'], ok:0 }, + { q:'$\\begin{cases} x \\geq -2 \\\\ x < 4 \\end{cases}$', opts:['$[-2;\\,4)$','$(-2;\\,4)$','$[-2;\\,4]$','$\\emptyset$'], ok:0 }, + { q:'$\\begin{cases} x > 5 \\\\ x < 2 \\end{cases}$', opts:['$\\emptyset$ (нет решений)','$(2;\\,5)$','$(5;\\,2)$','любое $x$'], ok:0 }, + { q:'$\\left[\\begin{array}{l} x < -1 \\\\ x \\geq 3 \\end{array}\\right.$', opts:['$(-\\infty;\\,-1) \\cup [3;\\,+\\infty)$','$(-1;\\,3)$','$[-1;\\,3)$','$\\emptyset$'], ok:0 }, + { q:'$\\begin{cases} 2x \\geq 6 \\\\ x \\leq 10 \\end{cases}$', opts:['$[3;\\,10]$','$[3;\\,10)$','$(3;\\,10]$','$[6;\\,10]$'], ok:0 }, + { q:'$\\begin{cases} x > 0 \\\\ x < 0 \\end{cases}$', opts:['$\\emptyset$','$\\{0\\}$','$\\mathbb{R}$','$(-\\infty;\\,0)$'], ok:0 }, + ]; + let cur = null, i = 1, score = 0, shuffled = []; + function show(){ + cur = shuffled[i-1]; + document.getElementById('p16t-i').textContent = i; + document.getElementById('p16t-task').innerHTML = cur.q; + renderMath(document.getElementById('p16t-task')); + const opts = document.getElementById('p16t-opts'); opts.innerHTML = ''; + cur.opts.forEach((o, k)=>{ + const b = document.createElement('button'); + b.className = 'btn'; b.innerHTML = o; b.style.cssText = 'text-align:left'; + b.addEventListener('click', ()=>{ + const fb = document.getElementById('p16t-fb'); fb.style.display = 'block'; + if(k === cur.ok){ score++; b.classList.add('ok'); feedback(fb, true, '✓'); } + else { b.classList.add('fail'); feedback(fb, false, 'Не то.'); } + document.getElementById('p16t-score').textContent = score; + if(i >= shuffled.length){ setTimeout(()=>{ feedback(fb, score >= 4, 'Итог: ' + score + '/' + shuffled.length); if(score >= 4){ achievement('p16_train'); bumpProgress('p16', 16); confetti(); } }, 700); } + else { i++; setTimeout(show, 900); } + }); + opts.appendChild(b); + }); + renderMath(opts); + document.getElementById('p16t-fb').style.display = 'none'; + } + document.getElementById('p16t-start').addEventListener('click', ()=>{ i=1; score=0; document.getElementById('p16t-score').textContent = 0; shuffled = [...tasks].sort(()=>Math.random()-0.5); show(); }); + })(); + + /* INIT 5 — Совокупность визуально */ + (function(){ + const aE = document.getElementById('p16u-a'), bE = document.getElementById('p16u-b'); + const lineE = document.getElementById('p16u-line'), out = document.getElementById('p16u-out'); + function refresh(){ + const a = +aE.value, b = +bE.value; + document.getElementById('p16u-a-val').textContent = a; + document.getElementById('p16u-b-val').textContent = b; + const intervals = [ + { a:null, b:a, openA:false, openB:true, color:'#8b5cf6' }, + { a:b, b:null, openA:true, openB:false, color:'#8b5cf6' }, + ]; + lineE.innerHTML = drawNumLine({ intervals }); + out.innerHTML = 'Совокупность: $x < ' + a + '$ или $x > ' + b + '$. Решение: $(-\\infty;\\,' + a + ') \\cup (' + b + ';\\,+\\infty)$.'; + renderMath(out); + } + [aE,bE].forEach(e => e.addEventListener('input', refresh)); + refresh(); + })(); +} function buildP17stub(){ document.getElementById('p17-body').innerHTML = `

§ 17 — Квадратные неравенства. Метод интервалов

Будет в Wave 3.

${secNav('p16','p18')}`; } function buildP18stub(){ document.getElementById('p18-body').innerHTML = `

§ 18 — Дробно-рациональные неравенства

Будет в Wave 3.

${secNav('p17','final3')}`; } function buildFinal3stub(){ document.getElementById('final3-body').innerHTML = `

Финал главы

Будет в Wave 4 — 7 боссов, увлекательная математика, практика.

${secNav('p18',null)}`; }