feat(geom9 phase11 final): итоговая шпаргалка + 7 боссов + ачивка «Магистр геометрии 9»

This commit is contained in:
Maxim Dolgolyov
2026-05-29 10:32:24 +03:00
parent c494ec92fb
commit 1b6865a491
+412 -7
View File
@@ -140,6 +140,65 @@ main{max-width:1100px;margin:0 auto;padding:32px 24px 60px}
.boss-overall-bar .lab{font-weight:700;font-size:.95rem;color:var(--text);min-width:200px}
.boss-overall-bar .bar{flex:1;min-width:160px;height:9px;background:rgba(225,29,72,.12);border-radius:5px;overflow:hidden}
.boss-overall-bar .fill{height:100%;background:linear-gradient(90deg,#e11d48,#fb7185,#f59e0b);transition:width .5s;border-radius:5px}
/* CHEAT SHEET */
.cheat-grid{display:grid;grid-template-columns:1fr;gap:14px;margin-bottom:28px}
@media(min-width:680px){.cheat-grid{grid-template-columns:1fr 1fr}}
.cheat-card{border:1.5px solid var(--border);border-radius:13px;padding:14px 16px;background:var(--card);position:relative;overflow:hidden}
.cheat-card::before{content:'';position:absolute;left:0;top:0;bottom:0;width:4px}
.cheat-card.c1::before{background:linear-gradient(180deg,var(--ch1),var(--ch1-d))}
.cheat-card.c2::before{background:linear-gradient(180deg,var(--ch2),var(--ch2-d))}
.cheat-card.c3::before{background:linear-gradient(180deg,var(--ch3),var(--ch3-d))}
.cheat-card.c4::before{background:linear-gradient(180deg,var(--ch4),var(--ch4-d))}
.cheat-head{display:flex;align-items:center;gap:9px;margin-bottom:9px;padding-left:6px}
.cheat-badge{font-size:.7rem;font-weight:800;padding:2px 8px;border-radius:99px;color:#fff;letter-spacing:.05em;text-transform:uppercase}
.cheat-card.c1 .cheat-badge{background:var(--ch1)}
.cheat-card.c2 .cheat-badge{background:var(--ch2)}
.cheat-card.c3 .cheat-badge{background:var(--ch3)}
.cheat-card.c4 .cheat-badge{background:var(--ch4)}
.cheat-title{font-weight:800;color:var(--text);font-size:.98rem}
.cheat-list{list-style:none;padding-left:6px;margin:0}
.cheat-list li{padding:6px 0;border-bottom:1px dashed var(--border);font-size:.92rem;line-height:1.5;color:var(--text)}
.cheat-list li:last-child{border-bottom:0}
/* BOSS CARDS */
.boss-card{background:var(--card);border:2px solid var(--border);border-radius:14px;padding:16px;margin-bottom:14px;transition:border-color .35s,box-shadow .35s,transform .2s}
.boss-card.solved{border-color:#10b981;box-shadow:0 0 0 3px rgba(16,185,129,.18)}
.boss-head{display:flex;align-items:center;gap:10px;margin-bottom:10px;flex-wrap:wrap}
.boss-tag{font-size:.7rem;font-weight:800;padding:3px 9px;border-radius:99px;background:rgba(225,29,72,.12);color:var(--pri-d);letter-spacing:.04em;text-transform:uppercase}
html.dark .boss-tag{color:#fecdd3}
.boss-title{font-family:'Outfit',sans-serif;font-weight:800;color:var(--text);font-size:1.02rem;flex:1;min-width:0}
.boss-q{padding:12px 14px;background:rgba(225,29,72,.06);border-radius:10px;font-size:.96rem;line-height:1.55;margin-bottom:10px;color:var(--text)}
.boss-row{display:flex;gap:8px;align-items:center;flex-wrap:wrap;margin-bottom:6px}
.boss-input{padding:8px 12px;border:1.5px solid var(--border);border-radius:8px;background:var(--card);color:var(--text);font-family:'JetBrains Mono',monospace;width:130px;text-align:center;font-size:.95rem;transition:border-color .15s}
.boss-input:focus{outline:0;border-color:var(--pri);box-shadow:0 0 0 3px var(--pri-soft)}
.boss-btn{padding:8px 16px;border-radius:9px;background:var(--card);color:var(--text);border:1.5px solid var(--border);font-weight:700;font-size:.88rem;cursor:pointer;font-family:inherit;transition:background .15s,border-color .15s,transform .1s}
.boss-btn:hover{background:var(--pri-soft);border-color:var(--pri)}
.boss-btn:active{transform:scale(.96)}
.boss-btn.primary{background:linear-gradient(135deg,var(--pri),#fb7185);color:#fff;border-color:transparent}
.boss-btn.primary:hover{filter:brightness(1.08)}
.boss-fb{padding:10px 14px;border-radius:9px;font-weight:600;font-size:.88rem;margin-top:8px;display:none;line-height:1.45}
.boss-fb.ok{display:block;background:#d1fae5;color:#065f46;border-left:4px solid #10b981}
.boss-fb.fail{display:block;background:#fee2e2;color:#7f1d1d;border-left:4px solid #dc2626}
html.dark .boss-fb.ok{background:rgba(16,185,129,.18);color:#a7f3d0}
html.dark .boss-fb.fail{background:rgba(220,38,38,.18);color:#fecaca}
.boss-hint-txt{margin-top:8px;padding:9px 13px;background:rgba(245,158,11,.12);border-left:3px solid #f59e0b;border-radius:6px;font-size:.86rem;color:var(--text);display:none;line-height:1.5}
.boss-hint-txt.show{display:block}
/* FINAL CTA */
.final-cta{margin-top:24px;padding:18px 20px;border-radius:14px;background:linear-gradient(135deg,#fef3c7,#fde68a);border:1.5px solid #fbbf24;display:none;align-items:center;gap:14px;flex-wrap:wrap}
.final-cta.show{display:flex}
html.dark .final-cta{background:linear-gradient(135deg,rgba(245,158,11,.18),rgba(217,119,6,.15));border-color:#d97706}
.final-cta-icon{width:48px;height:48px;border-radius:12px;background:linear-gradient(135deg,#fbbf24,#f59e0b);display:flex;align-items:center;justify-content:center;flex-shrink:0}
.final-cta-icon svg{width:28px;height:28px;stroke:#fff;fill:none;stroke-width:2;stroke-linecap:round;stroke-linejoin:round}
.final-cta-txt{flex:1;min-width:180px}
.final-cta-title{font-weight:800;color:#92400e;font-size:1.05rem;font-family:'Outfit',sans-serif}
html.dark .final-cta-title{color:#fde68a}
.final-cta-sub{font-size:.86rem;color:#78350f;margin-top:2px}
html.dark .final-cta-sub{color:#fcd34d}
.final-cta-btn{padding:10px 18px;border-radius:10px;background:linear-gradient(135deg,var(--pri),#fb7185);color:#fff;text-decoration:none;font-weight:800;font-size:.9rem;display:inline-flex;align-items:center;gap:7px;transition:filter .15s}
.final-cta-btn:hover{filter:brightness(1.1)}
.final-cta-btn svg{width:16px;height:16px;stroke:currentColor;fill:none;stroke-width:2;stroke-linecap:round;stroke-linejoin:round}
</style>
</head>
<body>
@@ -276,14 +335,86 @@ main{max-width:1100px;margin:0 auto;padding:32px 24px 60px}
<div class="final-body" id="final-body">
<div class="boss-overall-bar">
<div class="lab">Боссов побеждено: 0 / 7</div>
<div class="bar"><div class="fill" style="width:0%"></div></div>
<div class="fin-section-title">
<svg viewBox="0 0 24 24"><path d="M4 6h16M4 12h16M4 18h10"/></svg>
Шпаргалка курса
</div>
<div class="fin-placeholder">
<div class="ph-title">Финал курса в разработке</div>
<div class="ph-sub">Итоговая шпаргалка и 7 интегрированных боссов появятся в <b>Phase 11</b>. Пока изучайте главы курса — параграфы наполняются в Phase 7+.</div>
<div class="cheat-grid">
<div class="cheat-card c1">
<div class="cheat-head">
<span class="cheat-badge">Гл. 1</span>
<span class="cheat-title">Прямоугольный треугольник</span>
</div>
<ul class="cheat-list">
<li>$\sin A = \dfrac{a}{c}$, $\cos A = \dfrac{b}{c}$, $\tan A = \dfrac{a}{b}$</li>
<li>$\sin^2\alpha + \cos^2\alpha = 1$</li>
<li>Тупой угол: $\sin(180°-\alpha) = \sin\alpha$, $\cos(180°-\alpha) = -\cos\alpha$</li>
<li>$S_\triangle = \dfrac{1}{2}ab\sin C$</li>
<li>Высота к гипотенузе: $h^2 = a_1 b_1$</li>
</ul>
</div>
<div class="cheat-card c2">
<div class="cheat-head">
<span class="cheat-badge">Гл. 2</span>
<span class="cheat-title">Вписанные и описанные окружности</span>
</div>
<ul class="cheat-list">
<li>$S = pr$ (вписанная), $R = \dfrac{abc}{4S}$ (описанная)</li>
<li>Прямоуг. треуг.: $R = \dfrac{c}{2}$, $r = \dfrac{a+b-c}{2}$</li>
<li>Вписанный 4-уг: $\alpha + \gamma = 180°$</li>
<li>Описанный 4-уг: $a + c = b + d$</li>
</ul>
</div>
<div class="cheat-card c3">
<div class="cheat-head">
<span class="cheat-badge">Гл. 3</span>
<span class="cheat-title">Теоремы синусов и косинусов</span>
</div>
<ul class="cheat-list">
<li>Синусов: $\dfrac{a}{\sin A} = \dfrac{b}{\sin B} = \dfrac{c}{\sin C} = 2R$</li>
<li>Косинусов: $a^2 = b^2 + c^2 - 2bc\cos A$</li>
<li>Герон: $S = \sqrt{p(p-a)(p-b)(p-c)}$, $p = \dfrac{a+b+c}{2}$</li>
</ul>
</div>
<div class="cheat-card c4">
<div class="cheat-head">
<span class="cheat-badge">Гл. 4</span>
<span class="cheat-title">Правильные многоугольники</span>
</div>
<ul class="cheat-list">
<li>Внутр. угол: $\beta = \dfrac{180°(n-2)}{n}$</li>
<li>Сторона: $a_n = 2R\sin\dfrac{180°}{n}$</li>
<li>$a_3 = R\sqrt{3}$, $a_4 = R\sqrt{2}$, $a_6 = R$</li>
<li>Окружность: $C = 2\pi R$, $S_{\text{круга}} = \pi R^2$</li>
</ul>
</div>
</div>
<div class="fin-section-title">
<svg viewBox="0 0 24 24"><path d="M14.5 3.5l-5 5L4 4l1.5 6L3 12l5 1 1 5 2.5-2.5 6 1.5-4.5-5.5 5-5"/></svg>
7 интегрированных боссов
</div>
<div class="boss-overall-bar">
<div class="lab" id="fin-boss-lab">Боссов побеждено: 0 / 7</div>
<div class="bar"><div class="fill" id="fin-boss-fill" style="width:0%"></div></div>
</div>
<div id="fin-bosses-container"></div>
<div class="final-cta" id="final-cta">
<div class="final-cta-icon">
<svg viewBox="0 0 24 24"><path d="M6 9H4l-1-3h18l-1 3h-2M6 9l1 6h10l1-6M6 9h12"/><path d="M9 21h6M12 15v6"/></svg>
</div>
<div class="final-cta-txt">
<div class="final-cta-title">Курс Геометрия 9 пройден!</div>
<div class="final-cta-sub">Вы прошли всю итоговую проверку курса. +50 XP, ачивка «Магистр геометрии 9» получена.</div>
</div>
<a href="/textbooks" class="final-cta-btn">
К каталогу учебников
<svg viewBox="0 0 24 24"><polyline points="9 18 15 12 9 6"/></svg>
</a>
</div>
</div>
@@ -340,7 +471,8 @@ var CH_IDX = {
'geometry-9-ch4': 4,
};
var FIN_ACH_KEY = 'geometry9_course_master';
var FIN_ACH_KEY = 'geometry9_course_master';
var FIN_BOSS_KEY = 'geometry9_course_bosses';
function setChProg(idx, readCount, total) {
var pct = total ? Math.round(readCount * 100 / total) : 0;
@@ -394,6 +526,265 @@ function renderProgress(children) {
}
}
/* COURSE FINAL — bosses */
var FIN_BOSSES = [
{
n: 1,
title: 'Прямой угол + Окружность',
tag: 'Гл. 1 + Гл. 2',
q: 'В прямоугольном треугольнике катеты $5$ и $12$. Найдите <b>сумму</b> радиусов описанной и вписанной окружностей.',
hint: 'Гипотенуза $c = \\sqrt{25 + 144} = 13$. Для прямоугольного: $R = c/2$, $r = (a + b - c)/2$.',
ans: 8.5,
tol: 0.05,
step: '0.1'
},
{
n: 2,
title: 'Синус через окружность',
tag: 'Гл. 3 + Гл. 2',
q: 'В треугольнике сторона $a = 6$, противолежащий угол $A = 30°$. Найдите радиус описанной окружности $R$.',
hint: 'Теорема синусов: $\\dfrac{a}{\\sin A} = 2R$, значит $R = \\dfrac{a}{2\\sin A}$. $\\sin 30° = 0{,}5$.',
ans: 6,
tol: 0.01,
step: '1'
},
{
n: 3,
title: 'Тригонометрия + Площадь',
tag: 'Гл. 1 + Гл. 3',
q: 'В треугольнике стороны $4$ и $7$, угол между ними $90°$. Найдите площадь.',
hint: '$S = \\dfrac{1}{2}ab\\sin C$. $\\sin 90° = 1$.',
ans: 14,
tol: 0.01,
step: '1'
},
{
n: 4,
title: 'Правильный 6-уг. + Окружность',
tag: 'Гл. 4 + Гл. 2',
q: 'В правильный шестиугольник вписана окружность радиуса $\\sqrt{3}$. Найдите его сторону.',
hint: 'Для $n=6$: $a = R$ (сторона равна радиусу описанной), $r = \\dfrac{R\\sqrt{3}}{2}$. Из $r = \\sqrt{3}$ найдите $R$.',
ans: 2,
tol: 0.01,
step: '1'
},
{
n: 5,
title: 'Косинусы + Площадь',
tag: 'Гл. 3',
q: 'В треугольнике стороны $3$ и $4$, угол между ними $60°$. Найдите <b>сумму</b> площади и третьей стороны (округлите до 0{,}1).',
hint: '$S = \\dfrac{1}{2}\\cdot 3\\cdot 4\\cdot \\sin 60° = 3\\sqrt{3}$. По теореме косинусов: $a^2 = 9 + 16 - 24\\cdot \\cos 60° = 13$, $a = \\sqrt{13}$. Сложите.',
ans: 8.8,
tol: 0.1,
step: '0.1'
},
{
n: 6,
title: 'Многоугольник + Длина',
tag: 'Гл. 4',
q: 'Найдите длину окружности, описанной около правильного шестиугольника со стороной $3$. Возьмите $\\pi = 3{,}14$.',
hint: 'Для $n=6$ сторона равна радиусу: $R = a = 3$. Тогда $C = 2\\pi R$.',
ans: 18.84,
tol: 0.05,
step: '0.01'
},
{
n: 7,
title: 'Магистр Геометрии',
tag: 'синтез всего курса',
q: 'В прямоугольном треугольнике катеты $6$ и $8$. Из прямого угла опущена высота к гипотенузе. Найдите <b>произведение</b> длины этой высоты и радиуса вписанной окружности.',
hint: 'Гипотенуза $c = 10$. Высота $h = \\dfrac{ab}{c} = \\dfrac{48}{10}$. Вписанная: $r = \\dfrac{a+b-c}{2} = 2$.',
ans: 9.6,
tol: 0.05,
step: '0.1'
}
];
function loadFinBossState(){
try { return JSON.parse(localStorage.getItem(FIN_BOSS_KEY) || '{}') || {}; }
catch(e) { return {}; }
}
function saveFinBossState(s){
try { localStorage.setItem(FIN_BOSS_KEY, JSON.stringify(s)); } catch(e){}
}
function finRenderKatex(root){
if (typeof window.renderMathInElement !== 'function') return;
try {
window.renderMathInElement(root, {
delimiters: [
{left: '$$', right: '$$', display: true},
{left: '$', right: '$', display: false}
],
throwOnError: false
});
} catch(e){}
}
function updateFinBossBar(state){
var won = 0;
for (var k in state) if (state[k]) won++;
var lab = document.getElementById('fin-boss-lab');
var fill = document.getElementById('fin-boss-fill');
if (lab) lab.textContent = 'Боссов побеждено: ' + won + ' / ' + FIN_BOSSES.length;
if (fill) fill.style.width = Math.round(won * 100 / FIN_BOSSES.length) + '%';
return won;
}
function maybeUnlockMaster(state){
if (localStorage.getItem(FIN_ACH_KEY) === '1') return;
var won = 0;
for (var k in state) if (state[k]) won++;
if (won < FIN_BOSSES.length) return;
localStorage.setItem(FIN_ACH_KEY, '1');
/* +50 XP */
var xp = parseInt(localStorage.getItem('geometry9_xp') || '0', 10) || 0;
localStorage.setItem('geometry9_xp', String(xp + 50));
try {
if (window.LS && typeof window.LS.addXp === 'function') {
window.LS.addXp(50, 'geometry9-master');
} else if (typeof window.addXp === 'function') {
window.addXp(50, 'geometry9-master');
}
} catch(e){}
/* confetti */
try { if (typeof window.confetti === 'function') window.confetti({particleCount: 160, spread: 90, origin: {y: .6}}); } catch(e){}
/* light up ach-strip */
var strip = document.getElementById('ach-strip');
var sub = document.getElementById('ach-sub');
if (strip) strip.classList.add('lit');
if (sub) sub.textContent = 'Выполнено! Вы — Магистр геометрии 9.';
/* show CTA */
var cta = document.getElementById('final-cta');
if (cta) cta.classList.add('show');
/* refresh XP badge */
var xpBadge = document.getElementById('hero-xp-badge');
if (xpBadge) {
var newXp = parseInt(localStorage.getItem('geometry9_xp') || '0', 10) || 0;
xpBadge.style.display = '';
xpBadge.textContent = newXp + ' XP';
}
}
function buildFinBoss(b, state){
var solvedClass = state[b.n] ? ' solved' : '';
var stepAttr = ' step="' + (b.step || '0.01') + '"';
return '<div class="boss-card' + solvedClass + '" id="fin-boss-' + b.n + '-card">'
+ '<div class="boss-head">'
+ '<span class="boss-tag">' + b.tag + '</span>'
+ '<span class="boss-title">Босс ' + b.n + '. ' + b.title + '</span>'
+ '</div>'
+ '<div class="boss-q" id="fin-boss-' + b.n + '-q">' + b.q + '</div>'
+ '<div class="boss-row">'
+ '<input type="number"' + stepAttr + ' class="boss-input" id="fin-boss-' + b.n + '-inp" placeholder="число"' + (state[b.n] ? ' value="' + b.ans + '" disabled' : '') + '>'
+ '<button class="boss-btn primary" id="fin-boss-' + b.n + '-go"' + (state[b.n] ? ' disabled' : '') + '>Атаковать</button>'
+ '<button class="boss-btn" id="fin-boss-' + b.n + '-hint">Подсказка</button>'
+ '</div>'
+ '<div class="boss-hint-txt" id="fin-boss-' + b.n + '-hinttxt">' + b.hint + '</div>'
+ '<div class="boss-fb' + (state[b.n] ? ' ok' : '') + '" id="fin-boss-' + b.n + '-fb">' + (state[b.n] ? 'Победа! +15 XP. Босс уже повержен.' : '') + '</div>'
+ '</div>';
}
function bindFinBoss(b){
var state = loadFinBossState();
var goBtn = document.getElementById('fin-boss-' + b.n + '-go');
var hintBtn = document.getElementById('fin-boss-' + b.n + '-hint');
var inp = document.getElementById('fin-boss-' + b.n + '-inp');
var fb = document.getElementById('fin-boss-' + b.n + '-fb');
var hintTx = document.getElementById('fin-boss-' + b.n + '-hinttxt');
var card = document.getElementById('fin-boss-' + b.n + '-card');
if (!goBtn) return;
if (hintBtn) hintBtn.addEventListener('click', function(){
if (hintTx) hintTx.classList.toggle('show');
});
if (state[b.n]) return; /* already solved */
goBtn.addEventListener('click', function(){
var v = parseFloat((inp.value || '').replace(',', '.'));
if (isNaN(v)) {
fb.className = 'boss-fb fail';
fb.textContent = 'Введите число.';
return;
}
var tol = (typeof b.tol === 'number') ? b.tol : 1e-9;
if (Math.abs(v - b.ans) <= tol + 1e-9) {
fb.className = 'boss-fb ok';
fb.textContent = 'Победа! +15 XP. Босс повержен.';
card.classList.add('solved');
goBtn.disabled = true;
inp.disabled = true;
var s = loadFinBossState();
if (!s[b.n]) {
s[b.n] = true;
saveFinBossState(s);
/* +15 XP */
var xp = parseInt(localStorage.getItem('geometry9_xp') || '0', 10) || 0;
localStorage.setItem('geometry9_xp', String(xp + 15));
try {
if (window.LS && typeof window.LS.addXp === 'function') window.LS.addXp(15, 'fin-boss-' + b.n);
else if (typeof window.addXp === 'function') window.addXp(15, 'fin-boss-' + b.n);
} catch(e){}
var xpBadge = document.getElementById('hero-xp-badge');
if (xpBadge) {
var nXp = parseInt(localStorage.getItem('geometry9_xp') || '0', 10) || 0;
xpBadge.style.display = '';
xpBadge.textContent = nXp + ' XP';
}
updateFinBossBar(s);
maybeUnlockMaster(s);
}
} else {
fb.className = 'boss-fb fail';
fb.textContent = 'Не то. Перепроверь решение и попробуй снова.';
}
});
inp.addEventListener('keydown', function(e){
if (e.key === 'Enter') { e.preventDefault(); goBtn.click(); }
});
}
var FIN_BOSSES_RENDERED = false;
function renderFinBosses(){
if (FIN_BOSSES_RENDERED) return;
var cont = document.getElementById('fin-bosses-container');
if (!cont) return;
var state = loadFinBossState();
var html = '';
for (var i = 0; i < FIN_BOSSES.length; i++) html += buildFinBoss(FIN_BOSSES[i], state);
cont.innerHTML = html;
for (var j = 0; j < FIN_BOSSES.length; j++) bindFinBoss(FIN_BOSSES[j]);
var wrap = document.getElementById('course-final');
finRenderKatex(wrap);
updateFinBossBar(state);
if (localStorage.getItem(FIN_ACH_KEY) === '1') {
var cta = document.getElementById('final-cta');
if (cta) cta.classList.add('show');
var strip = document.getElementById('ach-strip');
var sub = document.getElementById('ach-sub');
if (strip) strip.classList.add('lit');
if (sub) sub.textContent = 'Выполнено! Вы — Магистр геометрии 9.';
}
FIN_BOSSES_RENDERED = true;
}
/* FINAL accordion */
(function bindFinalAccordion(){
var head = document.getElementById('final-head');
@@ -404,6 +795,10 @@ function renderProgress(children) {
var willOpen = !wrap.classList.contains('open');
wrap.classList.toggle('open');
head.setAttribute('aria-expanded', willOpen ? 'true' : 'false');
if (willOpen) {
renderFinBosses();
finRenderKatex(wrap);
}
}
head.addEventListener('click', toggle);
@@ -412,6 +807,16 @@ function renderProgress(children) {
});
})();
/* sync ach-strip + CTA on load if already mastered */
(function syncMasterOnLoad(){
if (localStorage.getItem(FIN_ACH_KEY) === '1') {
var strip = document.getElementById('ach-strip');
var sub = document.getElementById('ach-sub');
if (strip) strip.classList.add('lit');
if (sub) sub.textContent = 'Выполнено! Вы — Магистр геометрии 9.';
}
})();
function loadProgress() {
if (typeof window.LS === 'undefined' || typeof window.LS.api !== 'function') {
renderProgress([]);