feat(geom8): Wave 4 Главы 1 — финал главы (шпаргалка, карта связей, 7 боссов)

Часть 1: 9 mini-cards с формулами всех 16 параграфов (KaTeX).
Часть 2: интерактивная SVG-карта иерархии четырёхугольников
(клик по узлу — подсветка свойств).
Часть 3: 7 интегрированных боссов (по 10 XP):
  - Босс 1: многоугольник из суммы углов 1620°
  - Босс 2: параллелограмм через треугольник ABD
  - Босс 3: средние линии прямоугольника → ромб
  - Босс 4: ромб 60° → диагонали (Пифагор)
  - Босс 5: теорема Фалеса, 3 подзадачи
  - Босс 6: треугольник 12-16-20 — средняя линия + медиана + центроид
  - Босс 7: равнобедренная трапеция 20/8/10
Часть 4: при победе над всеми — achievement 'Мастер многоугольников Главы 1',
+50 XP бонус, confetti, кнопка перехода к Главе 2.

File: 5194 → 5558 LOC. Глава 1 полностью наполнена.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Maxim Dolgolyov
2026-05-28 08:38:19 +03:00
parent ecda85e8ef
commit 22bd60cf0f
+366 -2
View File
@@ -495,7 +495,7 @@ const BUILDERS = {
p5:()=>buildP5(), p6:()=>buildP6(), p7:()=>buildP7(), p8:()=>buildP8(),
p9:()=>buildP9(), p10:()=>buildP10(), p11:()=>buildP11(), p12:()=>buildP12(),
p13:()=>buildP13(), p14:()=>buildP14(), p15:()=>buildP15(), p16:()=>buildP16(),
final1:()=>buildFinal1stub(),
final1:()=>buildFinal1(),
};
function ensureBuilt(id){ if(BUILT.has(id)) return; const fn = BUILDERS[id]; if(fn){ fn(); BUILT.add(id); } }
function goTo(id){
@@ -5188,7 +5188,371 @@ function buildP16(){
})();
renderMath(box);
}
function buildFinal1stub(){ document.getElementById('final1-body').innerHTML = '<div class="card"><div class="card-body"><p><b>Финал главы 1 — Волна 1</b>: боссы и итоги появятся в следующем обновлении.</p></div></div>' + secNav('p16',null); }
/* ============================================================
ФИНАЛ ГЛАВЫ 1 — Итоги · 7 боссов · Достижение
============================================================ */
function buildFinal1(){
const box = document.getElementById('final1-body');
let html = '';
/* === ЧАСТЬ 1: Итоговая шпаргалка === */
html += `<div class="card" style="border-color:var(--sec-acc,var(--pri));background:linear-gradient(135deg,var(--card),var(--sec-acc-soft,var(--pri-soft)))">
<div class="card-header">
<div class="card-icon theory">
<svg class="ic" viewBox="0 0 24 24"><path d="M4 19.5A2.5 2.5 0 0 1 6.5 17H20"/><path d="M6.5 2H20v20H6.5A2.5 2.5 0 0 1 4 19.5v-15A2.5 2.5 0 0 1 6.5 2z"/></svg>
</div>
<div class="card-title">Итоговая шпаргалка · Вся Глава 1</div>
</div>
<div class="card-body">
<div style="display:grid;grid-template-columns:repeat(auto-fill,minmax(230px,1fr));gap:10px">
<div style="background:var(--card);border:1.5px solid var(--border);border-radius:11px;padding:12px">
<div style="font-family:'Unbounded',sans-serif;font-size:.7rem;font-weight:800;color:var(--pri2);text-transform:uppercase;letter-spacing:.07em;margin-bottom:7px">§13 Многоугольники</div>
<p style="font-size:.88rem;margin-bottom:5px">Диагонали: $D = \\dfrac{n(n-3)}{2}$</p>
<p style="font-size:.88rem;margin-bottom:5px">Сумма углов: $(n-2)\\cdot180°$</p>
<p style="font-size:.88rem;margin-bottom:0">Сумма внешних: $360°$ (всегда)</p>
<p style="font-size:.78rem;color:var(--muted);margin-top:5px">Пример: шестиугольник — 9 диагоналей, $720°$ внутренних</p>
</div>
<div style="background:var(--card);border:1.5px solid var(--border);border-radius:11px;padding:12px">
<div style="font-family:'Unbounded',sans-serif;font-size:.7rem;font-weight:800;color:var(--pri2);text-transform:uppercase;letter-spacing:.07em;margin-bottom:7px">§46 Параллелограмм</div>
<p style="font-size:.86rem;margin-bottom:4px"><b>Свойства:</b> противоположные стороны равны; противоположные углы равны; смежные углы в сумме $180°$; диагонали делятся пополам</p>
<p style="font-size:.86rem;margin-bottom:0"><b>Признаки:</b> 2 пары равных сторон; 1 пара параллельных и равных; диагонали делятся пополам</p>
</div>
<div style="background:var(--card);border:1.5px solid var(--border);border-radius:11px;padding:12px">
<div style="font-family:'Unbounded',sans-serif;font-size:.7rem;font-weight:800;color:var(--pri2);text-transform:uppercase;letter-spacing:.07em;margin-bottom:7px">§78 Прямоугольник</div>
<p style="font-size:.88rem;margin-bottom:5px">Все углы $= 90°$</p>
<p style="font-size:.88rem;margin-bottom:5px">Диагонали равны: $AC = BD$</p>
<p style="font-size:.88rem;margin-bottom:0">$d = \\sqrt{a^2 + b^2}$</p>
<p style="font-size:.78rem;color:var(--muted);margin-top:5px">Признак: параллелограмм с равными диагоналями</p>
</div>
<div style="background:var(--card);border:1.5px solid var(--border);border-radius:11px;padding:12px">
<div style="font-family:'Unbounded',sans-serif;font-size:.7rem;font-weight:800;color:var(--pri2);text-transform:uppercase;letter-spacing:.07em;margin-bottom:7px">§9 Ромб</div>
<p style="font-size:.88rem;margin-bottom:5px">Все стороны равны</p>
<p style="font-size:.88rem;margin-bottom:5px">Диагонали $\\perp$ и делят углы пополам</p>
<p style="font-size:.88rem;margin-bottom:0">$S = \\dfrac{d_1 \\cdot d_2}{2}$</p>
<p style="font-size:.78rem;color:var(--muted);margin-top:5px">Пример: ромб со стороной 5 и диагоналями 6, 8 — $S=24$</p>
</div>
<div style="background:var(--card);border:1.5px solid var(--border);border-radius:11px;padding:12px">
<div style="font-family:'Unbounded',sans-serif;font-size:.7rem;font-weight:800;color:var(--pri2);text-transform:uppercase;letter-spacing:.07em;margin-bottom:7px">§10 Квадрат</div>
<p style="font-size:.88rem;margin-bottom:5px">Прямоугольник + ромб одновременно</p>
<p style="font-size:.88rem;margin-bottom:5px">$d = a\\sqrt{2}$</p>
<p style="font-size:.88rem;margin-bottom:0">$S = a^2$, $P = 4a$</p>
<p style="font-size:.78rem;color:var(--muted);margin-top:5px">Все свойства ромба и прямоугольника вместе</p>
</div>
<div style="background:var(--card);border:1.5px solid var(--border);border-radius:11px;padding:12px">
<div style="font-family:'Unbounded',sans-serif;font-size:.7rem;font-weight:800;color:var(--pri2);text-transform:uppercase;letter-spacing:.07em;margin-bottom:7px">§11 Теорема Фалеса</div>
<p style="font-size:.88rem;margin-bottom:5px">Параллельные прямые отсекают пропорциональные отрезки</p>
<p style="font-size:.88rem;margin-bottom:0">Если $n$ параллельных делят одну прямую на равные части — они делят на равные части и другую прямую</p>
</div>
<div style="background:var(--card);border:1.5px solid var(--border);border-radius:11px;padding:12px">
<div style="font-family:'Unbounded',sans-serif;font-size:.7rem;font-weight:800;color:var(--pri2);text-transform:uppercase;letter-spacing:.07em;margin-bottom:7px">§12 Медианы</div>
<p style="font-size:.88rem;margin-bottom:5px">3 медианы пересекаются в центроиде $G$</p>
<p style="font-size:.88rem;margin-bottom:0">$G$ делит каждую медиану в отношении $2:1$ от вершины</p>
<p style="font-size:.78rem;color:var(--muted);margin-top:5px">$BG = \\tfrac{2}{3}m_b$, $GM_b = \\tfrac{1}{3}m_b$</p>
</div>
<div style="background:var(--card);border:1.5px solid var(--border);border-radius:11px;padding:12px">
<div style="font-family:'Unbounded',sans-serif;font-size:.7rem;font-weight:800;color:var(--pri2);text-transform:uppercase;letter-spacing:.07em;margin-bottom:7px">§13 Средняя линия &Delta;</div>
<p style="font-size:.88rem;margin-bottom:5px">Соединяет середины двух сторон</p>
<p style="font-size:.88rem;margin-bottom:5px">Параллельна третьей стороне</p>
<p style="font-size:.88rem;margin-bottom:0">$MN = \\dfrac{1}{2} \\cdot BC$</p>
</div>
<div style="background:var(--card);border:1.5px solid var(--border);border-radius:11px;padding:12px">
<div style="font-family:'Unbounded',sans-serif;font-size:.7rem;font-weight:800;color:var(--pri2);text-transform:uppercase;letter-spacing:.07em;margin-bottom:7px">§1416 Трапеция</div>
<p style="font-size:.88rem;margin-bottom:5px">Одна пара параллельных сторон ($a \\parallel b$)</p>
<p style="font-size:.88rem;margin-bottom:5px">Средняя линия: $m = \\dfrac{a+b}{2}$</p>
<p style="font-size:.88rem;margin-bottom:0">Равнобедренная: углы при основании равны, диагонали равны</p>
<p style="font-size:.78rem;color:var(--muted);margin-top:5px">Признаки: равные углы при осн. или равные диагонали</p>
</div>
</div>
</div>
</div>`;
/* === ЧАСТЬ 2: Карта связей — иерархия четырёхугольников === */
html += `<div class="wg" id="final1-hier-wrap">
<div class="wg-header"><span class="wg-badge">КАРТА СВЯЗЕЙ</span><div class="wg-title">Иерархия четырёхугольников</div></div>
<div class="wg-help">Нажми на фигуру, чтобы увидеть её ключевые свойства. Стрелки означают «является частным случаем».</div>
<div id="final1-hier-svg" style="display:flex;justify-content:center;overflow-x:auto"></div>
<div id="final1-hier-info" style="min-height:56px;padding:12px 16px;background:var(--sec-acc-soft,var(--pri-soft));border-radius:10px;margin-top:10px;font-size:.9rem;line-height:1.65;color:var(--text)">Нажми на фигуру в схеме выше</div>
</div>`;
/* === ЧАСТЬ 3: 7 боссов === */
html += `<div class="wg" style="border-color:#d97706;background:linear-gradient(135deg,var(--card),#fef9c3)">
<div class="wg-header">
<span class="wg-badge" style="background:#d97706">7 БОССОВ ГЛАВЫ 1</span>
<div class="wg-title">Интегрированные задачи</div>
</div>
<div class="wg-help">Каждая задача объединяет 2–3 темы главы. +10 XP за каждого побеждённого босса. Победи всех семерых — получишь +50 XP и достижение!</div>
<div id="final1-bosses"></div>
</div>`;
/* === ЧАСТЬ 4: Финальная плашка === */
html += `<div id="final1-finish" style="display:none;margin-top:20px;padding:24px;background:linear-gradient(135deg,#fef3c7,#d1fae5);border:2px solid var(--ok,#10b981);border-radius:16px;text-align:center">
<div style="font-family:'Unbounded',sans-serif;font-size:1.2rem;font-weight:900;color:#065f46;margin-bottom:10px">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" style="width:28px;height:28px;vertical-align:middle;margin-right:6px"><polygon points="12,2 15.09,8.26 22,9.27 17,14.14 18.18,21.02 12,17.77 5.82,21.02 7,14.14 2,9.27 8.91,8.26"/></svg>
Мастер многоугольников Главы 1!
</div>
<p style="color:#065f46;font-size:.95rem;margin-bottom:16px">Ты победил всех 7 боссов и освоил всю Главу 1. Это серьёзно!</p>
<a href="/textbook/geometry-8-ch2" class="btn primary" style="font-size:.98rem;padding:12px 28px">
<svg class="ic" viewBox="0 0 24 24"><polyline points="9 18 15 12 9 6"/></svg>
Перейти к Главе 2
</a>
</div>`;
html += `<div style="margin-top:18px;display:flex;justify-content:center">
<button class="btn primary" id="final1-read-btn" onclick="addXp(10,'final1-read');bumpProgress('final1',40);this.textContent='Прочитано!';this.disabled=true;">
<svg class="ic" viewBox="0 0 24 24"><path d="M4 19.5A2.5 2.5 0 0 1 6.5 17H20"/><path d="M6.5 2H20v20H6.5A2.5 2.5 0 0 1 4 19.5v-15A2.5 2.5 0 0 1 6.5 2z"/></svg>
Я изучил Главу 1 (+10 XP)
</button>
</div>`;
html += secNav('p16', null);
box.innerHTML = html;
/* === JS: Карта связей SVG === */
(function(){
const W = 680, H = 320;
const nodes = [
{ id:'quad', x:340, y:30, label:'Четырёхугольник', r:52,
props:'4 стороны, 4 угла, сумма углов = 360°. Диагонали — отрезки, соединяющие несмежные вершины.' },
{ id:'trap', x:120, y:130, label:'Трапеция', r:42,
props:'Одна пара параллельных сторон. Средняя линия = (a+b)/2. Признак равнобедренной: равные углы при основании или равные диагонали.' },
{ id:'istrap', x:90, y:250, label:'Равнобедренная трапеция', r:42,
props:'Боковые стороны равны. Углы при каждом основании равны. Диагонали равны.' },
{ id:'para', x:400, y:130, label:'Параллелограмм', r:48,
props:'Две пары параллельных сторон. Противоположные стороны и углы равны. Диагонали делятся пополам.' },
{ id:'rect', x:290, y:250, label:'Прямоугольник', r:44,
props:'Все углы = 90°. Диагонали равны: AC = BD. d = √(a²+b²). Признак: параллелограмм с равными диагоналями.' },
{ id:'rhomb', x:490, y:250, label:'Ромб', r:40,
props:'Все стороны равны. Диагонали ⊥ и делят углы пополам. S = d₁·d₂/2.' },
{ id:'sq', x:390, y:300, label:'Квадрат', r:38,
props:'Прямоугольник и ромб одновременно. d = a√2. S = a². Все свойства ромба и прямоугольника.' },
];
const edges = [
['quad','trap'], ['quad','para'],
['trap','istrap'], ['para','rect'], ['para','rhomb'],
['rect','sq'], ['rhomb','sq'],
];
let sel = null;
function draw(selId){
const colors = { quad:'#d97706', trap:'#f97316', istrap:'#ef4444', para:'#8b5cf6', rect:'#2563eb', rhomb:'#0891b2', sq:'#059669' };
let s = `<svg viewBox="0 0 ${W} ${H}" style="width:100%;max-width:700px;background:var(--card);border:1.5px solid var(--border);border-radius:14px;cursor:pointer">`;
s += `<defs><marker id="fh-arr" markerWidth="7" markerHeight="7" refX="6" refY="3.5" orient="auto"><polygon points="0 0,7 3.5,0 7" fill="#94a3b8"/></marker></defs>`;
edges.forEach(([a,b])=>{
const na=nodes.find(n=>n.id===a), nb=nodes.find(n=>n.id===b);
const dx=nb.x-na.x, dy=nb.y-na.y, len=Math.sqrt(dx*dx+dy*dy);
const sx=na.x+dx/len*na.r, sy=na.y+dy/len*na.r;
const ex=nb.x-dx/len*(nb.r+7), ey=nb.y-dy/len*(nb.r+7);
const isActive = selId===a||selId===b;
s += `<line x1="${sx}" y1="${sy}" x2="${ex}" y2="${ey}" stroke="${isActive?'#d97706':'#94a3b8'}" stroke-width="${isActive?2.5:1.5}" marker-end="url(#fh-arr)"/>`;
});
nodes.forEach(n=>{
const isS = selId===n.id;
const col = colors[n.id] || '#d97706';
s += `<ellipse cx="${n.x}" cy="${n.y}" rx="${n.r}" ry="${n.r*0.52}" fill="${isS?col:'var(--card)'}" stroke="${col}" stroke-width="${isS?3:2}" data-nid="${n.id}" style="cursor:pointer;transition:all .18s"/>`;
const words = n.label.split(' ');
const line1 = words.slice(0,2).join(' '), line2 = words.slice(2).join(' ');
const textCol = isS ? '#fff' : col;
if(line2){
s += `<text x="${n.x}" y="${n.y-5}" text-anchor="middle" font-size="9" font-weight="800" font-family="Unbounded,sans-serif" fill="${textCol}" style="pointer-events:none">${line1}</text>`;
s += `<text x="${n.x}" y="${n.y+8}" text-anchor="middle" font-size="9" font-weight="800" font-family="Unbounded,sans-serif" fill="${textCol}" style="pointer-events:none">${line2}</text>`;
} else {
s += `<text x="${n.x}" y="${n.y+4}" text-anchor="middle" font-size="9" font-weight="800" font-family="Unbounded,sans-serif" fill="${textCol}" style="pointer-events:none">${line1}</text>`;
}
});
s += '</svg>';
document.getElementById('final1-hier-svg').innerHTML = s;
document.getElementById('final1-hier-svg').querySelector('svg').addEventListener('click', function(e){
const el = e.target.closest('[data-nid]');
if(!el) return;
const nid = el.dataset.nid;
sel = (sel===nid) ? null : nid;
const nd = nodes.find(n=>n.id===nid);
if(sel && nd) document.getElementById('final1-hier-info').innerHTML = '<b>' + nd.label + '</b>: ' + nd.props;
else document.getElementById('final1-hier-info').textContent = 'Нажми на фигуру в схеме выше';
draw(sel);
});
}
draw(null);
})();
/* === JS: 7 боссов === */
(function(){
const bosses = [
{
n: 1,
title: 'Многоугольники и углы',
color: '#d97706',
cond: 'В выпуклом многоугольнике сумма внутренних углов равна $1620°$.',
parts: [
{ label: 'Сколько у него сторон?', ans: 11, hint: '$(n-2)\\cdot180°=1620°$ → $n-2=9$ → $n=11$' },
{ label: 'Сколько диагоналей?', ans: 44, hint: '$\\dfrac{11\\cdot(11-3)}{2}=\\dfrac{88}{2}=44$' },
{ label: 'Если он правильный, найди один внутренний угол (в градусах).', ans: 147, hint: '$\\dfrac{1620°}{11}\\approx147.27°$ — ответ: 147 (до целых)', tol: 1 },
{ label: 'Один внешний угол правильного 11-угольника (в градусах, до целых).', ans: 33, hint: '$\\dfrac{360°}{11}\\approx32.7°$ — ответ: 33', tol: 1 },
],
},
{
n: 2,
title: 'Параллелограмм через диагональ',
color: '#8b5cf6',
cond: 'В параллелограмме $ABCD$ диагональ $BD$ делит его на два треугольника. $\\angle ABD=40°$, $\\angle ADB=65°$.',
parts: [
{ label: 'Найди угол A параллелограмма (в градусах).', ans: 75, hint: 'В треугольнике ABD: $\\angle A = 180°-40°-65°=75°$' },
{ label: 'Найди угол B параллелограмма (в градусах).', ans: 105, hint: 'Смежные углы параллелограмма в сумме $180°$: $\\angle B=180°-75°=105°$' },
{ label: 'Найди угол D параллелограмма (в градусах).', ans: 105, hint: 'Противоположные углы параллелограмма равны: $\\angle D=\\angle B=105°$' },
],
},
{
n: 3,
title: 'Прямоугольник и ромб',
color: '#2563eb',
cond: 'В прямоугольнике $ABCD$: $AB=6$, $AD=8$. Точки $M, N, K, L$ — середины сторон $AB, BC, CD, DA$ соответственно.',
parts: [
{ label: 'Найди длину MN (стороны MNKL).', ans: 5, hint: 'По теореме средней линии треугольника: $MN = \\tfrac{1}{2}AC$, $AC=\\sqrt{36+64}=10$, $MN=5$' },
{ label: 'Докажи, что MNKL — ромб. Все стороны MNKL равны? (1 — да, 0 — нет)', ans: 1, hint: 'Все стороны = 5 (по теореме средней линии во всех четырёх треугольниках). MNKL — ромб.' },
{ label: 'Найди площадь MNKL.', ans: 24, hint: '$S_{MNKL} = \\tfrac{1}{2}S_{ABCD}=\\tfrac{1}{2}\\cdot48=24$' },
],
},
{
n: 4,
title: 'Ромб и теорема Пифагора',
color: '#0891b2',
cond: 'В ромбе $ABCD$ сторона $a=10$, угол $\\angle A=60°$.',
parts: [
{ label: 'Найди длину бо́льшей диагонали (AC, проходит через угол A=60°, единицы).', ans: 10, hint: 'Треугольник ABD при угле 60°: $\\triangle ABD$ равносторонний ($AB=BD=AD=10$). $AC=10$. Диагональ через вершины с углом 60° равна стороне.' },
{ label: 'Найди длину меньшей диагонали BD (единицы), используя теорему Пифагора.', ans: 17, hint: '$AO=5$ (полудиагональ AC), $BO=\\sqrt{10^2-5^2}=\\sqrt{75}\\approx8.66$. $BD=2\\cdot8.66\\approx17.32$. Округли до 17.', tol: 1 },
{ label: 'Найди площадь ромба (округли до целых).', ans: 87, hint: '$S=\\dfrac{d_1\\cdot d_2}{2}=\\dfrac{10\\cdot17.32}{2}\\approx86.6\\approx87$', tol: 1 },
],
},
{
n: 5,
title: 'Теорема Фалеса',
color: '#16a34a',
cond: 'По теореме Фалеса параллельные прямые делят отрезки пропорционально.',
parts: [
{ label: 'Отрезок AB разделён на 5 равных частей параллельными прямыми, пересекающими отрезок CD (CD ∥ AB, CD=20). Расстояние между соседними точками деления на CD (число).', ans: 4, hint: '5 равных частей делят CD тоже на 5 равных частей по теореме Фалеса: $20/5=4$' },
{ label: 'Отрезок AB=15 делится параллельными прямыми на n равных частей, расстояние между точками деления = 3. Найди n.', ans: 5, hint: 'Шаг = $AB/n = 15/n = 3$ → $n=5$' },
{ label: 'Три параллельные прямые делят отрезок PQ на части 4, 4, 4 (три равные части). Отрезок RS, параллельный PQ, имеет длину 30. Какова длина каждой части RS?', ans: 10, hint: 'По теореме Фалеса — тоже 3 равные части: $30/3=10$' },
],
},
{
n: 6,
title: 'Медиана и средняя линия',
color: '#e11d48',
cond: 'В треугольнике $ABC$: $AB=12$, $BC=16$, $AC=20$. Это прямоугольный треугольник ($\\angle B=90°$).',
parts: [
{ label: 'M — середина AB, N — середина AC. Найди MN (средняя линия треугольника).', ans: 8, hint: '$MN = \\tfrac{1}{2}BC = \\tfrac{1}{2}\\cdot16 = 8$' },
{ label: 'G — центроид. Длина медианы из B к середине AC: $m_b=\\tfrac{1}{2}AC=10$. Найди BG.', ans: 7, hint: '$BG = \\tfrac{2}{3}m_b = \\tfrac{2}{3}\\cdot10 = 6.67 \\approx 7$', tol: 1 },
{ label: 'В прямоугольном треугольнике медиана из прямого угла равна половине гипотенузы. Гипотенуза AC=20. Найди медиану из B (m_b).', ans: 10, hint: '$m_b = \\tfrac{1}{2}\\cdot AC = \\tfrac{1}{2}\\cdot20 = 10$' },
],
},
{
n: 7,
title: 'Трапеция (комплексная)',
color: '#f97316',
cond: 'В равнобедренной трапеции $ABCD$: $AD \\parallel BC$, $AD=20$, $BC=8$, боковая сторона $AB=10$.',
parts: [
{ label: 'Найди высоту трапеции h.', ans: 8, hint: 'Проекция основания: $(20-8)/2=6$. $h=\\sqrt{10^2-6^2}=\\sqrt{64}=8$' },
{ label: 'Найди среднюю линию трапеции m.', ans: 14, hint: '$m=\\dfrac{20+8}{2}=14$' },
{ label: 'Найди длину диагонали AC (округли до целых).', ans: 16, hint: 'Сдвиг горизонтальный = $(20-8)/2=6$. $AC=\\sqrt{(8+6)^2+8^2}=\\sqrt{196+64}=\\sqrt{260}\\approx16$', tol: 1 },
],
},
];
window.final1BossSolved = new Set();
const bossBox = document.getElementById('final1-bosses');
bossBox.innerHTML = bosses.map(b => {
const partsHtml = b.parts.map((p,pi) => `
<div style="padding:10px 0;border-top:1px dashed var(--border)">
<div style="font-size:.9rem;margin-bottom:7px">${p.label}</div>
<div style="display:flex;gap:8px;align-items:center;flex-wrap:wrap">
<input type="number" class="tinp final1-boss-inp" data-b="${b.n-1}" data-p="${pi}" placeholder="Ответ" style="width:110px">
<button class="btn primary final1-boss-check" data-b="${b.n-1}" data-p="${pi}" style="background:${b.color};border-color:${b.color}">Проверить</button>
<span class="final1-boss-ok" data-b="${b.n-1}" data-p="${pi}" style="display:none;color:var(--ok);font-weight:700">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="3" style="width:16px;height:16px;vertical-align:middle"><polyline points="20 6 9 17 4 12"/></svg>
</span>
</div>
<div class="feedback final1-boss-fb" data-b="${b.n-1}" data-p="${pi}" style="display:none;margin-top:6px"></div>
</div>`).join('');
return `<div style="padding:16px;background:var(--card);border-radius:12px;border:2px solid ${b.color};margin-bottom:14px" id="final1-boss-card-${b.n-1}">
<div style="display:flex;align-items:center;gap:10px;margin-bottom:12px;flex-wrap:wrap">
<span style="background:${b.color};color:#fff;padding:4px 10px;border-radius:6px;font-family:'Unbounded',sans-serif;font-size:.7rem;font-weight:800">БОСС ${b.n}</span>
<span style="font-family:'Unbounded',sans-serif;font-size:.88rem;font-weight:800;color:${b.color}">${b.title}</span>
<span id="final1-boss-xp-${b.n-1}" style="margin-left:auto;display:none;background:var(--ok-bg);color:#065f46;padding:3px 10px;border-radius:99px;font-size:.78rem;font-weight:800">+10 XP</span>
</div>
<div style="padding:10px 14px;background:linear-gradient(135deg,#fef3c7,#fff);border-left:4px solid ${b.color};border-radius:9px;font-size:.92rem;line-height:1.6;margin-bottom:4px">${b.cond}</div>
${partsHtml}
</div>`;
}).join('');
/* attach logic */
function checkPart(bi, pi){
const boss = bosses[bi];
const part = boss.parts[pi];
const inp = bossBox.querySelector(`.final1-boss-inp[data-b="${bi}"][data-p="${pi}"]`);
const fb = bossBox.querySelector(`.final1-boss-fb[data-b="${bi}"][data-p="${pi}"]`);
const ok = bossBox.querySelector(`.final1-boss-ok[data-b="${bi}"][data-p="${pi}"]`);
if(!inp) return;
const val = parseFloat(inp.value);
const tol = part.tol !== undefined ? part.tol : 0;
if(Math.abs(val - part.ans) <= tol){
feedback(fb, true, 'Верно! ' + (part.hint ? '<br><span style="font-size:.82rem;opacity:.85">' + part.hint + '</span>' : ''));
inp.disabled = true;
const btn = bossBox.querySelector(`.final1-boss-check[data-b="${bi}"][data-p="${pi}"]`);
if(btn) btn.disabled = true;
if(ok) ok.style.display = 'inline';
/* check if all parts of boss solved */
const allDone = boss.parts.every((_, pj) => {
const el = bossBox.querySelector(`.final1-boss-inp[data-b="${bi}"][data-p="${pj}"]`);
return el && el.disabled;
});
if(allDone && !window.final1BossSolved.has(bi)){
window.final1BossSolved.add(bi);
addXp(10, 'final1-boss-' + (bi+1));
const xpBadge = document.getElementById('final1-boss-xp-' + bi);
if(xpBadge) xpBadge.style.display = 'inline';
bumpProgress('final1', 8);
/* check all bosses done */
if(window.final1BossSolved.size === bosses.length){
setTimeout(()=>{
confetti();
if(!STATE.achievements.has('final1-master')){
STATE.achievements.set('final1-master', 'Мастер многоугольников Главы 1');
saveProgress();
const pop = document.getElementById('ach-popup');
if(pop){ document.getElementById('ach-text').textContent = 'Мастер многоугольников Главы 1!'; pop.classList.add('show'); setTimeout(()=>pop.classList.remove('show'), 4000); }
}
addXp(50, 'final1-all-bosses');
bumpProgress('final1', 20);
const fin = document.getElementById('final1-finish');
if(fin) fin.style.display = 'block';
}, 500);
}
}
} else {
feedback(fb, false, 'Неверно. Подсказка: ' + part.hint);
}
}
bossBox.querySelectorAll('.final1-boss-check').forEach(btn=>{
btn.addEventListener('click', ()=>{ checkPart(+btn.dataset.b, +btn.dataset.p); });
});
bossBox.querySelectorAll('.final1-boss-inp').forEach(inp=>{
inp.addEventListener('keydown', e=>{ if(e.key==='Enter'){ const btn=bossBox.querySelector(`.final1-boss-check[data-b="${inp.dataset.b}"][data-p="${inp.dataset.p}"]`); if(btn)btn.click(); } });
});
})();
renderMath(box);
}
</script>
</body>
</html>