feat(geom9 ch1 final): Финал Главы 1 (5 боссов + ачивка)

This commit is contained in:
Maxim Dolgolyov
2026-05-29 09:50:25 +03:00
parent 7534a79842
commit 58a73365fd
+234 -8
View File
@@ -2395,16 +2395,242 @@ function buildP6(){
}
function buildFinal1(){
const body = document.getElementById('final1-body');
const box = document.getElementById('final1-body');
let html = '';
html += makeCard('theory', 'Финал главы 1', '★', `
<p>Итоговый раздел главы <b>«Соотношения в прямоугольном треугольнике»</b> будет добавлен в следующих обновлениях.</p>
<p style="color:var(--muted);font-size:.9rem">Раздел Phase 7.</p>`);
html += readButton('final1');
/* Часть А — Шпаргалка главы (6 mini-карточек) */
html += `<div class="card">
<div class="card-header">
<span class="card-icon theory">${ICONS.theory}</span>
<span class="card-title">Шпаргалка главы 1</span>
<span class="card-num">Итог</span>
</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(--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="var(--pri2)" stroke-width="2" style="width:18px;height:18px"><path d="M3 21h18"/><path d="M3 21L21 3"/><path d="M3 21L3 12"/></svg>
<span style="font-family:'Unbounded',sans-serif;font-weight:700;color:var(--pri2);font-size:.92rem">§ 1 · Тригонометрия</span>
</div>
<div style="font-size:.92rem">$\\sin\\alpha = \\dfrac{a}{c},\\ \\cos\\alpha = \\dfrac{b}{c},\\ \\tan\\alpha = \\dfrac{a}{b},\\ \\cot\\alpha = \\dfrac{1}{\\tan\\alpha}$</div>
</div>
<div style="padding:12px 14px;background:var(--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="var(--pri2)" stroke-width="2" style="width:18px;height:18px"><polygon points="3 21 21 21 21 3"/></svg>
<span style="font-family:'Unbounded',sans-serif;font-weight:700;color:var(--pri2);font-size:.92rem">§ 2 · Решение прямоуг.</span>
</div>
<div style="font-size:.92rem">Теорема Пифагора: $c^2 = a^2 + b^2$. Достаточно <b>2 стороны</b> или <b>сторона + угол</b>.</div>
</div>
<div style="padding:12px 14px;background:var(--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="var(--pri2)" stroke-width="2" style="width:18px;height:18px"><path d="M4 17l6-6 4 4 6-8"/></svg>
<span style="font-family:'Unbounded',sans-serif;font-weight:700;color:var(--pri2);font-size:.92rem">§ 3 · Формулы</span>
</div>
<div style="font-size:.92rem">$\\sin^2\\alpha + \\cos^2\\alpha = 1$. Эталоны $30°/45°/60°$ — из специальных треугольников.</div>
</div>
<div style="padding:12px 14px;background:var(--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="var(--pri2)" stroke-width="2" style="width:18px;height:18px"><circle cx="12" cy="12" r="9"/><line x1="3" y1="12" x2="21" y2="12"/></svg>
<span style="font-family:'Unbounded',sans-serif;font-weight:700;color:var(--pri2);font-size:.92rem">§ 4 · Тупой угол</span>
</div>
<div style="font-size:.92rem">Единичная окружность: $\\sin(180°-\\alpha) = \\sin\\alpha$, $\\cos(180°-\\alpha) = -\\cos\\alpha$.</div>
</div>
<div style="padding:12px 14px;background:var(--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="var(--pri2)" stroke-width="2" style="width:18px;height:18px"><rect x="3" y="3" width="18" height="18" rx="2"/><path d="M3 3l18 18"/></svg>
<span style="font-family:'Unbounded',sans-serif;font-weight:700;color:var(--pri2);font-size:.92rem">§ 5 · Площадь</span>
</div>
<div style="font-size:.92rem">$S_\\triangle = \\tfrac{1}{2}ab\\sin C$, $S_{пар} = ab\\sin\\alpha$, $S_{4-уг} = \\tfrac{1}{2}d_1 d_2\\sin\\varphi$.</div>
</div>
<div style="padding:12px 14px;background:var(--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="var(--pri2)" stroke-width="2" style="width:18px;height:18px"><polygon points="3 20 21 20 12 4"/><line x1="12" y1="4" x2="12" y2="20"/></svg>
<span style="font-family:'Unbounded',sans-serif;font-weight:700;color:var(--pri2);font-size:.92rem">§ 6 · Среднее геом.</span>
</div>
<div style="font-size:.92rem">Высота к гипотенузе: $h^2 = a_1 b_1$, $a^2 = c \\cdot a_1$, $b^2 = c \\cdot b_1$.</div>
</div>
</div>
</div>
</div>`;
/* Часть Б — 5 боссов */
html += `<div class="card">
<div class="card-header">
<span class="card-icon rule">${ICONS.rule}</span>
<span class="card-title">Боссы главы 1</span>
<span class="card-num">5</span>
</div>
<div class="card-body">
<p>5 интегрированных задач — каждая комбинирует несколько тем главы. За каждого побеждённого босса — <b>+10 XP</b> и +18% к прогрессу. Победишь всех — <b>+50 XP бонус</b> и ачивка «Мастер прямоугольного треугольника»!</p>
</div>
</div>`;
html += '<div id="ch1G-bosses-container"></div>';
html += `<div style="margin-top:18px;padding:18px 20px;background:linear-gradient(135deg,var(--pri-soft),var(--acc-soft));border-radius:14px;border:1.5px solid var(--pri);text-align:center" id="ch1G-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="ch1G-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="ch1G-boss-overall-fill" style="height:100%;width:0%;background:linear-gradient(90deg,var(--pri),var(--acc));transition:width .35s"></div>
</div>
<div id="ch1G-final-reward" style="margin-top:14px;display:none;padding:14px;background:var(--card);border-radius:11px;border:2px solid var(--warn)">
<div style="display:flex;align-items:center;justify-content:center;gap:8px;margin-bottom:6px">
<svg viewBox="0 0 24 24" fill="none" stroke="var(--warn)" stroke-width="2.2" style="width:22px;height:22px"><polygon points="12 2 15 9 22 9 17 14 19 22 12 18 5 22 7 14 2 9 9 9"/></svg>
<div style="font-family:'Unbounded',sans-serif;font-weight:800;color:var(--pri2);font-size:1.05rem">Мастер прямоугольного треугольника</div>
</div>
<div style="font-size:.92rem;margin-bottom:10px">Глава 1 пройдена! Все 5 боссов повержены. +50 XP бонус.</div>
<a class="btn primary" href="/textbook/geometry-9-ch2" style="text-decoration:none">Дальше: Глава 2 <svg class="ic" viewBox="0 0 24 24"><polyline points="9 18 15 12 9 6"/></svg></a>
</div>
</div>`;
html += secNav('p6', null);
body.innerHTML = html;
wireReadBtn('final1');
if(window.renderMathInElement) renderMath(body);
box.innerHTML = html;
renderMath(box);
/* Боссы */
const BOSSES = [
{
n:1, color:'#10b981',
title:'Сфинкс Тригонометрии',
tag:'§ 1 + § 3',
q:'В прямоугольном треугольнике катеты $a = 3$ и $b = 4$. Чему равен <b>синус большего острого угла</b>?',
ans:0.8, decimal:true,
hint:'Гипотенуза $c = \\sqrt{3^2 + 4^2} = 5$. Больший острый угол лежит против большего катета (4). $\\sin = \\dfrac{4}{5} = 0{,}8$.'
},
{
n:2, color:'#0891b2',
title:'Минотавр Решения',
tag:'§ 2 + § 1',
q:'В прямоугольном треугольнике $\\angle A = 30°$, гипотенуза $c = 12$. Найди длину катета, <b>противолежащего</b> углу $A$.',
ans:6, decimal:false,
hint:'$a = c \\sin A = 12 \\cdot \\sin 30° = 12 \\cdot \\dfrac{1}{2} = 6$.'
},
{
n:3, color:'#7c3aed',
title:'Гарпия Тупого Угла',
tag:'§ 4 + § 3',
q:'Вычисли: $\\sin 150° + \\cos 60° = \\,?$',
ans:1, decimal:false,
hint:'$\\sin 150° = \\sin(180°-30°) = \\sin 30° = \\dfrac{1}{2}$. $\\cos 60° = \\dfrac{1}{2}$. Сумма $= 1$.'
},
{
n:4, color:'#dc2626',
title:'Дракон Площади',
tag:'§ 5 + § 1',
q:'Треугольник со сторонами $a = 8$, $b = 5$ и углом $C = 30°$ между ними. Найди площадь $S$.',
ans:10, decimal:false,
hint:'$S = \\dfrac{1}{2} ab \\sin C = \\dfrac{1}{2} \\cdot 8 \\cdot 5 \\cdot \\sin 30° = 20 \\cdot \\dfrac{1}{2} = 10$.'
},
{
n:5, color:'#f59e0b',
title:'Мастер Прямоугольного',
tag:'§§ 1-6 — синтез',
q:'В прямоугольном треугольнике катеты $a = 6$, $b = 8$. Из прямого угла опущена <b>высота на гипотенузу</b>. Найди её длину.',
ans:4.8, decimal:true,
hint:'Гипотенуза $c = \\sqrt{36+64} = 10$. Из $S = \\dfrac{1}{2}ab = \\dfrac{1}{2}ch$ следует $h = \\dfrac{ab}{c} = \\dfrac{48}{10} = 4{,}8$.'
},
];
const cont = document.getElementById('ch1G-bosses-container');
const STATE_KEY = 'geometry9_ch1_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)=>{
const stepAttr = b.decimal ? 'step="0.1"' : 'step="1"';
const ph = b.decimal ? 'число (можно с запятой)' : 'целое число';
return '<div class="boss-card" id="bossG1-'+b.n+'-card" style="padding:16px;background:var(--card);border-radius:12px;border:2px solid '+b.color+';margin-bottom:14px;transition:background .3s,box-shadow .3s">'
+'<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(--acc-soft);border-radius:6px">'+b.tag+'</div>'
+'</div>'
+'<div id="bossG1-'+b.n+'-q" style="padding:12px 14px;background:var(--acc-soft);border-radius:9px;font-size:1rem;line-height:1.55;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" '+stepAttr+' id="bossG1-'+b.n+'-ans" class="tinp" style="width:150px;text-align:center" placeholder="'+ph+'">'
+'<button class="btn primary" id="bossG1-'+b.n+'-go" style="background:'+b.color+';border-color:'+b.color+'">Атаковать</button>'
+'<button class="btn" id="bossG1-'+b.n+'-hint">Подсказка</button>'
+'</div>'
+'<div class="feedback" id="bossG1-'+b.n+'-fb"></div>'
+'</div>';
}).join('');
renderMath(cont);
function markDefeatedUI(b, idx){
const card = document.getElementById('bossG1-'+b.n+'-card');
const goBtn = document.getElementById('bossG1-'+b.n+'-go');
const ansInp = document.getElementById('bossG1-'+b.n+'-ans');
if(!card || !goBtn || !ansInp) return;
card.style.background = 'linear-gradient(135deg,var(--acc-soft),var(--pri-soft))';
card.style.boxShadow = '0 0 0 2px '+b.color+'33, 0 8px 24px rgba(16,185,129,.12)';
goBtn.disabled = true; goBtn.style.opacity = .55;
goBtn.innerHTML = '<svg class="ic" viewBox="0 0 24 24" style="margin-right:4px"><polyline points="20 6 9 17 4 12"/></svg> Повержен';
ansInp.disabled = true;
}
function refreshOverall(){
const won = BOSS_STATE.filter(s => s.defeated).length;
const txt = document.getElementById('ch1G-boss-overall');
const fill = document.getElementById('ch1G-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('ch1G-final-reward');
if(reward && reward.style.display === 'none'){
reward.style.display = 'block';
if(!STATE.achievements.has('ch1_done')){
achievement('ch1_done','Мастер прямоугольного треугольника');
addXp(50, 'ch1-bonus');
bumpProgress('final1', 10);
if(window.confetti){ try{ window.confetti(); }catch(e){} }
}
}
}
}
BOSSES.forEach((b, idx)=>{
const goBtn = document.getElementById('bossG1-'+b.n+'-go');
const hintBtn = document.getElementById('bossG1-'+b.n+'-hint');
const ansInp = document.getElementById('bossG1-'+b.n+'-ans');
if(BOSS_STATE[idx].defeated) markDefeatedUI(b, idx);
goBtn.addEventListener('click', ()=>{
if(BOSS_STATE[idx].defeated) return;
const fb = document.getElementById('bossG1-'+b.n+'-fb');
const raw = (ansInp.value||'').replace(',', '.').trim();
const val = parseFloat(raw);
if(isNaN(val) || raw === ''){ feedback(fb, false, '&#10007; Введи число.'); return; }
const ok = Math.abs(val - b.ans) < 0.05;
if(ok){
BOSS_STATE[idx].defeated = true; saveBosses();
feedback(fb, true, '&#10003; Босс '+b.n+' повержен! +10 XP. '+b.hint);
addXp(10, 'boss-ch1-'+b.n);
bumpProgress('final1', 18);
markDefeatedUI(b, idx);
refreshOverall();
} else {
feedback(fb, false, '&#10007; Промах. Попробуй ещё. Подсказка доступна.');
}
});
hintBtn.addEventListener('click', ()=>{
const fb = document.getElementById('bossG1-'+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 ===== */