From 4423a726355ac96f555aef027adde75173887ccd Mon Sep 17 00:00:00 2001 From: Maxim Dolgolyov Date: Fri, 29 May 2026 15:06:29 +0300 Subject: [PATCH] =?UTF-8?q?feat(geom11=20ch4=20wave3=20+=20final):=20?= =?UTF-8?q?=C2=A711=20=C2=AB=D0=9F=D0=BE=D1=81=D1=82=D1=80=D0=BE=D0=B5?= =?UTF-8?q?=D0=BD=D0=B8=D1=8F=C2=BB=20+=20=D0=A4=D0=B8=D0=BD=D0=B0=D0=BB?= =?UTF-8?q?=20=D0=A0=D0=B0=D0=B7=D0=B4=D0=B5=D0=BB=D0=B0=204?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/textbooks/geometry_11_ch4.html | 530 +++++++++++++++++++++++- 1 file changed, 528 insertions(+), 2 deletions(-) diff --git a/frontend/textbooks/geometry_11_ch4.html b/frontend/textbooks/geometry_11_ch4.html index f25182b..abafa14 100644 --- a/frontend/textbooks/geometry_11_ch4.html +++ b/frontend/textbooks/geometry_11_ch4.html @@ -314,7 +314,8 @@ const ACH_LABELS = { p10_done:"Координаты и векторы освоено!", p11_done:"Геометрические построения освоено!", start:"Начало раздела 4!", - ch4_done:"Раздел 4 пройден!" + ch4_done:"Раздел 4 пройден!", + r4_done:"Магистр повторения" }; function loadProgress(){ @@ -400,7 +401,7 @@ function buildParaSelector(){ } const BUILT=new Set(); -const BUILDERS = { p8:()=>buildP8(), p9:()=>buildP9(), p10:()=>buildP10(), p11:()=>buildStub('p11'), final4:()=>buildStub('final4') }; +const BUILDERS = { p8:()=>buildP8(), p9:()=>buildP9(), p10:()=>buildP10(), p11:()=>buildP11(), final4:()=>buildFinal4() }; function ensureBuilt(id){ if(BUILT.has(id)) return; const fn=BUILDERS[id]; if(fn){ fn(); BUILT.add(id); } } function goTo(id){ STATE.current=id; ensureBuilt(id); @@ -1607,6 +1608,531 @@ function buildP10(){ wireReadBtn('p10'); } +/* ===== §11 «Геометрические построения» (циркуль и линейка) ===== */ + +function buildP11(){ + const box = document.getElementById('p11-body'); + if(!box) return; + let html = ''; + + /* === ТЕОРИЯ === */ + + html += makeCard('theory', 'Основные построения циркулем и линейкой', '§ 11.1', + '

Инструменты: циркуль (для окружностей и переноса длин) и линейка без делений (только проводить прямые через две точки). С их помощью решается удивительно широкий класс задач.

' + + '

6 базовых построений — основа всего курса:

' + + '' + + '
Почему серединный перпендикуляр работает?
' + + '

Точки $P_1$ и $P_2$ равноудалены от $A$ и $B$ (по построению $|P_i A| = |P_i B| = r$). Множество точек, равноудалённых от концов отрезка, — это серединный перпендикуляр (это его геометрическое определение). Значит, прямая $P_1 P_2$ и есть искомый перпендикуляр.

' + + '
'); + + html += makeCard('rule', 'Алгоритм решения задач на построение', '§ 11.2', + '

Любая «настоящая» задача на построение решается по 4 этапам:

' + + '
    ' + + '
  1. Анализ. Предположим, искомая фигура уже построена. Делаем «эскиз результата», ищем связи между известными и искомыми элементами. Часто отсюда «всплывает» вспомогательное построение (серединный $\\perp$, биссектриса, дуга...).
  2. ' + + '
  3. Построение. Пошагово, циркулем и линейкой. Каждый шаг — точное действие: «Проведём окружность радиуса $r$ с центром в точке $M$», «Проведём прямую через точки $X$ и $Y$».
  4. ' + + '
  5. Доказательство. Показываем, что построенная фигура удовлетворяет всем условиям задачи (опираясь на теоремы планиметрии).
  6. ' + + '
  7. Исследование. При каких условиях задача имеет решение? Сколько решений? Единственно ли оно? Например: «построить треугольник по трём сторонам» возможно только при выполнении неравенства треугольника.
  8. ' + + '
' + + '

В школьной практике этапы 1 и 4 часто опускают, но именно они отличают задачу на построение от простого описания построения.

'); + + /* === ИНТЕРАКТИВ 1 — Анимация серединного перпендикуляра === */ + html += '
' + + '
анимация · 5 шагов
Построение серединного $\\perp$ к отрезку $AB$
' + + '
Двигай слайдер шагов — увидишь, как из двух дуг рождается серединный перпендикуляр. Дойди до шага 5 — +10 XP.
' + + '
' + + '
' + + '' + + '' + + '
' + + '
' + + '
'; + + /* === ИНТЕРАКТИВ 2 — Анимация биссектрисы === */ + html += '
' + + '
анимация · 4 шага
Построение биссектрисы угла $\\angle AOB$
' + + '
Двигай слайдер шагов — биссектриса появится из пересечения двух дуг. Дойди до шага 4 — +10 XP.
' + + '
' + + '
' + + '' + + '' + + '
' + + '
' + + '
'; + + /* === ИНТЕРАКТИВ 3 — Квикфайр «Какое построение?» === */ + html += '
' + + '
квикфайр · 6 вопросов
Какое базовое построение нужно?
' + + '
Для каждой ситуации выбери базовое построение. После 6 верных — +15 XP.
' + + '
' + + '
Верно: 0 / 6
' + + '
'; + + html += secNavFor('p11'); + html += readButton('p11'); + + box.innerHTML = html; + renderMath(box); + + /* === IV1 — анимация серединного перпендикуляра === */ + (function(){ + const svg = document.getElementById('p11-iv1-svg'); + const sl = document.getElementById('p11-iv1-step'); + const sv = document.getElementById('p11-iv1-step-v'); + const desc = document.getElementById('p11-iv1-desc'); + let xpGiven = false, maxStep = 1; + + /* координаты в SVG */ + const A = {x:130, y:160}, B = {x:350, y:160}; + const Mx = (A.x + B.x) / 2; + const r = 130; /* радиус > |AB|/2 = 110 */ + /* пересечения двух окружностей */ + const dx = (B.x - A.x); + const half = dx / 2; + const hY = Math.sqrt(Math.max(0, r*r - half*half)); + const P1 = {x:Mx, y:160 - hY}; + const P2 = {x:Mx, y:160 + hY}; + const M = {x:Mx, y:160}; + + const TXT = [ + 'Шаг 1. Дан отрезок $AB$. Его нужно разделить пополам перпендикуляром.', + 'Шаг 2. Проводим окружность радиуса $r > |AB|/2$ с центром в точке $A$.', + 'Шаг 3. Тем же радиусом — окружность с центром в точке $B$.', + 'Шаг 4. Окружности пересеклись в точках $P_1$ (сверху) и $P_2$ (снизу).', + 'Шаг 5. Прямая $P_1 P_2$ — серединный перпендикуляр; она пересекает $AB$ в середине $M$.' + ]; + + function draw(step){ + let g = ''; + /* фон-сетка */ + g += ''; + /* отрезок AB всегда */ + g += ''; + g += ''; + g += ''; + g += 'A'; + g += 'B'; + + if(step >= 2){ + g += ''; + } + if(step >= 3){ + g += ''; + } + if(step >= 4){ + g += ''; + g += ''; + g += 'P1'; + g += 'P2'; + } + if(step >= 5){ + g += ''; + g += ''; + g += 'M'; + /* пометка прямого угла */ + g += ''; + } + svg.innerHTML = g; + } + function update(){ + const s = +sl.value; + sv.textContent = s; + desc.innerHTML = TXT[s-1]; + try{ renderMath(desc); }catch(e){} + draw(s); + if(s > maxStep) maxStep = s; + if(maxStep >= 5 && !xpGiven){ + xpGiven = true; + addXp(10, 'p11-iv1'); + bumpProgress('p11', 25); + } + } + sl.addEventListener('input', update); + update(); + })(); + + /* === IV2 — анимация биссектрисы === */ + (function(){ + const svg = document.getElementById('p11-iv2-svg'); + const sl = document.getElementById('p11-iv2-step'); + const sv = document.getElementById('p11-iv2-step-v'); + const desc = document.getElementById('p11-iv2-desc'); + let xpGiven = false, maxStep = 1; + + /* угол с вершиной в O */ + const O = {x:120, y:220}; + const angA = -10 * Math.PI/180; /* луч OA — почти горизонтальный */ + const angB = -55 * Math.PI/180; /* луч OB — выше */ + const angBis = (angA + angB) / 2; + const Ld = 320; /* длина лучей */ + + const Aend = {x:O.x + Ld*Math.cos(angA), y:O.y + Ld*Math.sin(angA)}; + const Bend = {x:O.x + Ld*Math.cos(angB), y:O.y + Ld*Math.sin(angB)}; + + const r1 = 110; + const X = {x:O.x + r1*Math.cos(angA), y:O.y + r1*Math.sin(angA)}; + const Y = {x:O.x + r1*Math.cos(angB), y:O.y + r1*Math.sin(angB)}; + + /* пересечение Z двух одинаковых дуг из X и Y радиуса r2 — на биссектрисе */ + const r2 = 90; + /* Z на луче биссектрисы от O */ + const Zd = r1*Math.cos((angB-angA)/2) + Math.sqrt(Math.max(0, r2*r2 - (r1*Math.sin((angB-angA)/2))*(r1*Math.sin((angB-angA)/2)))); + const Z = {x:O.x + Zd*Math.cos(angBis), y:O.y + Zd*Math.sin(angBis)}; + + const TXT = [ + 'Шаг 1. Дан угол $\\angle AOB$ с вершиной в $O$. Нужно построить его биссектрису.', + 'Шаг 2. Окружность с центром $O$ — пересекает лучи в точках $X$ (на $OA$) и $Y$ (на $OB$).', + 'Шаг 3. Из $X$ и $Y$ — окружности равного радиуса. Они пересекаются в точке $Z$ внутри угла.', + 'Шаг 4. Луч $OZ$ — биссектриса; он делит угол $\\angle AOB$ ровно пополам.' + ]; + + function draw(step){ + let g = ''; + g += ''; + /* лучи угла */ + g += ''; + g += ''; + g += ''; + g += 'O'; + g += 'A'; + g += 'B'; + + if(step >= 2){ + g += ''; + g += ''; + g += ''; + g += 'X'; + g += 'Y'; + } + if(step >= 3){ + g += ''; + g += ''; + g += ''; + g += 'Z'; + } + if(step >= 4){ + const Bend2 = {x:O.x + Ld*Math.cos(angBis), y:O.y + Ld*Math.sin(angBis)}; + g += ''; + /* стрелка */ + const tipx = Bend2.x, tipy = Bend2.y; + const back = 14; + const bx = O.x + (Ld-back)*Math.cos(angBis), by = O.y + (Ld-back)*Math.sin(angBis); + const nx = -Math.sin(angBis), ny = Math.cos(angBis); + g += ''; + } + svg.innerHTML = g; + } + function update(){ + const s = +sl.value; + sv.textContent = s; + desc.innerHTML = TXT[s-1]; + try{ renderMath(desc); }catch(e){} + draw(s); + if(s > maxStep) maxStep = s; + if(maxStep >= 4 && !xpGiven){ + xpGiven = true; + addXp(10, 'p11-iv2'); + bumpProgress('p11', 25); + } + } + sl.addEventListener('input', update); + update(); + })(); + + /* === IV3 — квикфайр === */ + (function(){ + const tasks = [ + { q:'Нужно разделить отрезок $AB$ пополам.', a:'midperp' }, + { q:'Нужно разделить угол $\\angle AOB$ пополам.', a:'bisect' }, + { q:'Из внешней точки $P$ провести прямую, касающуюся данной окружности.', a:'tangent' }, + { q:'Через точку $P$ провести прямую, параллельную данной прямой $l$.', a:'parallel' }, + { q:'Найти центр окружности, описанной около треугольника $ABC$.', a:'midperp' }, + { q:'Найти центр окружности, вписанной в треугольник $ABC$.', a:'bisect' } + ]; + const LABELS = {midperp:'Серединный $\\perp$', bisect:'Биссектриса', tangent:'Касательная', parallel:'Параллельная'}; + const HINTS = { + midperp:'центр описанной окружности лежит в точке пересечения серединных перпендикуляров сторон', + bisect:'центр вписанной окружности — в точке пересечения биссектрис углов', + tangent:'строится через окружность с диаметром на отрезке от центра до внешней точки', + parallel:'строится через копирование угла или равное расстояние' + }; + const area = document.getElementById('p11-iv3-area'); + const scoreEl = document.getElementById('p11-iv3-score'); + const solved = new Set(); + let xpGiven = false; + + area.innerHTML = tasks.map(function(t, i){ + return '
' + + '
Вопрос '+(i+1)+'. '+t.q+'
' + + '
' + + '' + + '' + + '' + + '' + + '
' + + '' + + '
'; + }).join(''); + renderMath(area); + + area.querySelectorAll('button[data-i]').forEach(function(b){ + b.addEventListener('click', function(){ + const i = +b.dataset.i, v = b.dataset.v, t = tasks[i]; + const fb = document.getElementById('p11-iv3-fb-'+i); + if(v === t.a){ + feedback(fb, true, '✓ Верно — '+LABELS[t.a]+'!'); + if(!solved.has(i)){ + solved.add(i); + scoreEl.textContent = solved.size; + if(solved.size === tasks.length && !xpGiven){ + xpGiven = true; + addXp(15, 'p11-iv3'); + bumpProgress('p11', 30); + setTimeout(function(){ achievement('p11_done'); }, 400); + } + } + } else { + feedback(fb, false, '✗ Не то. Подумай: '+HINTS[t.a]+'.'); + } + }); + }); + })(); + + wireReadBtn('p11'); +} + +/* ===== Финал Раздела 4 «Повторение» ===== */ + +function buildFinal4(){ + const box = document.getElementById('final4-body'); + if(!box) return; + let html = ''; + + /* === Часть А — Шпаргалка раздела 4 (4 mini-карточки) === */ + html += '
' + + '
' + + '
' + ICONS.theory + '
' + + '
Шпаргалка раздела 4
' + + '
Итог
' + + '
' + + '
' + + '

Ключевые формулы и идеи четырёх параграфов раздела — пробеги глазами перед битвой с боссами.

' + + '
' + /* §8 Планиметрия */ + + '
' + + '
' + + '' + + '
§ 8 · Планиметрия
' + + '
' + + '
Пифагор $a^2+b^2=c^2$. Синусов $\\dfrac{a}{\\sin A}=2R$. Косинусов $a^2=b^2+c^2-2bc\\cos A$. Замечательные точки: $G$ (медианы), $H$ (высоты), $O$ (серединные $\\perp$), $I$ (биссектрисы). Параллелограмм, ромб, трапеция.
' + + '
' + /* §9 Площади и объёмы */ + + '
' + + '
' + + '' + + '
§ 9 · Площади и объёмы
' + + '
' + + '
$S_\\triangle=\\tfrac{1}{2}ab\\sin C$, Герон $S=\\sqrt{p(p-a)(p-b)(p-c)}$. Круг $S=\\pi R^2$. Призма $V=S_{осн}h$, цилиндр $V=\\pi R^2 h$. Пирамида $V=\\tfrac{1}{3}S_{осн}h$. Шар $V=\\tfrac{4}{3}\\pi R^3$, $S=4\\pi R^2$.
' + + '
' + /* §10 Векторы 3D */ + + '
' + + '
' + + '' + + '
§ 10 · Векторы 3D
' + + '
' + + '
$|\\vec{v}|=\\sqrt{x^2+y^2+z^2}$. $\\vec{a}\\cdot\\vec{b}=a_1 b_1 + a_2 b_2 + a_3 b_3$. $\\cos\\alpha=\\dfrac{\\vec{a}\\cdot\\vec{b}}{|\\vec{a}||\\vec{b}|}$. Перпендикулярность: $\\vec{a}\\cdot\\vec{b}=0$.
' + + '
' + /* §11 Построения */ + + '
' + + '
' + + '' + + '
§ 11 · Построения
' + + '
' + + '
6 базовых: серединный $\\perp$, биссектриса, перпендикуляр, параллельная, касательная, описанная / вписанная окружность. Алгоритм: анализ → построение → доказательство → исследование.
' + + '
' + + '
' + + '
' + + '
'; + + /* === Часть Б — анонс 5 боссов === */ + html += '
' + + '
' + + '
' + ICONS.rule + '
' + + '
Боссы раздела 4
' + + '
5
' + + '
' + + '
' + + '

5 интегрированных задач — каждая опирается на темы § 8, § 9, § 10 или их синтез. За каждого побеждённого босса: +10 XP, +18% к прогрессу. Победишь всех — ачивка «Магистр повторения» и +50 XP бонус.

' + + '

Для расчётов с $\\pi$ используй $\\pi\\approx 3{,}14$. Допуск — $\\pm 0{,}05$ (для больших чисел — $\\pm 0{,}5$).

' + + '
' + + '
'; + + html += '
'; + + /* Прогресс-итог + ачивка */ + html += '
' + + '
Прогресс по боссам
' + + '
0 / 5 боссов побеждено
' + + '
' + + '
' + + '
' + + '' + + '
'; + + html += secNav('p11', null); + + box.innerHTML = html; + renderMath(box); + + /* === Боссы === */ + const BOSSES = [ + { + n:1, color:'#2563eb', + title:'Циклоп Планиметрии', + tag:'§ 8', + q:'В треугольнике стороны равны 6, 8 и 10. Найдите наибольший угол (в градусах).', + ans:90, tol:0.05, + hint:'$6^2+8^2=36+64=100=10^2$ — треугольник прямоугольный (теорема, обратная Пифагору). Наибольший угол — против гипотенузы $= 90°$.' + }, + { + n:2, color:'#dc2626', + title:'Минотавр Объёмов', + tag:'§ 9', + q:'Шар с радиусом $R=3$. Найдите объём $V$ (используй $\\pi\\approx 3{,}14$).', + ans:113.04, tol:0.5, + hint:'$V=\\tfrac{4}{3}\\pi R^3=\\tfrac{4}{3}\\cdot 3{,}14\\cdot 27 \\approx 113{,}04$.' + }, + { + n:3, color:'#7c3aed', + title:'Гарпия Векторов', + tag:'§ 10', + q:'Найдите скалярное произведение $\\vec{a}=(2;\\, -1;\\, 3)$ и $\\vec{b}=(1;\\, 4;\\, 2)$.', + ans:4, tol:0.05, + hint:'$\\vec{a}\\cdot\\vec{b}=2\\cdot 1 + (-1)\\cdot 4 + 3\\cdot 2 = 2-4+6 = 4$.' + }, + { + n:4, color:'#0891b2', + title:'Дракон Угла', + tag:'§ 10', + q:'Векторы $\\vec{a}=(1;\\, 0;\\, 1)$ и $\\vec{b}=(0;\\, 1;\\, 0)$. Найдите угол между ними (в градусах).', + ans:90, tol:0.05, + hint:'$\\vec{a}\\cdot\\vec{b}=1\\cdot 0+0\\cdot 1+1\\cdot 0 = 0$, значит $\\vec{a}\\perp\\vec{b}$ — угол $= 90°$.' + }, + { + n:5, color:'#f59e0b', + title:'Мастер Геометрии', + tag:'синтез § 9 + § 10', + q:'Прямоугольный параллелепипед со сторонами $3$, $4$, $12$. Найдите длину его главной диагонали.', + ans:13, tol:0.05, + hint:'$d=\\sqrt{3^2+4^2+12^2}=\\sqrt{9+16+144}=\\sqrt{169}=13$.' + } + ]; + + const cont = document.getElementById('r4-bosses-container'); + const STATE_KEY = 'geometry11_ch4_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=>{ + 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('r4-boss-overall'); + const fill = document.getElementById('r4-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('r4-final-reward'); + if(reward && reward.style.display === 'none'){ + reward.style.display = 'block'; + if(!STATE.achievements.has('r4_done')){ + achievement('r4_done','Магистр повторения'); + addXp(50, 'r4-bonus'); + bumpProgress('final4', 30); + if(window.confetti){ try{ confetti(); }catch(e){} } + } + } + } + } + + BOSSES.forEach((b, idx)=>{ + const card = document.getElementById('boss4-'+b.n+'-card'); + const goBtn = document.getElementById('boss4-'+b.n+'-go'); + const hintBtn= document.getElementById('boss4-'+b.n+'-hint'); + const ansInp = document.getElementById('boss4-'+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.innerHTML = '✓ Повержен'; + ansInp.disabled = true; + } + goBtn.addEventListener('click', ()=>{ + if(BOSS_STATE[idx].defeated) return; + const fb = document.getElementById('boss4-'+b.n+'-fb'); + const raw = (ansInp.value||'').replace(',', '.').trim(); + const val = parseFloat(raw); + if(!isFinite(val)){ feedback(fb, false, '✗ Введи число.'); return; } + if(Math.abs(val - b.ans) <= b.tol){ + BOSS_STATE[idx].defeated = true; saveBosses(); + feedback(fb, true, '✓ Босс '+b.n+' повержен! +10 XP. '+b.hint); + addXp(10, 'boss-r4-'+b.n); + bumpProgress('final4', 18); + goBtn.disabled = true; goBtn.style.opacity = .55; goBtn.innerHTML = '✓ Повержен'; + 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('boss4-'+b.n+'-fb'); + fb.className = 'feedback ok'; + fb.innerHTML = 'Подсказка: '+b.hint; + fb.style.display = 'block'; + fb.style.background = 'var(--warn-bg,#fef3c7)'; + fb.style.color = '#92400e'; + fb.style.borderLeftColor = 'var(--warn,#f59e0b)'; + try{ renderMath(fb); }catch(e){} + }); + ansInp.addEventListener('keydown', e=>{ if(e.key === 'Enter') goBtn.click(); }); + }); + + refreshOverall(); +} + /* ===== Search ===== */ const SEARCH_INDEX = (function(){ const arr=[];