feat(geom10 W7): Финал Раздела 3 — 5 боссов + ачивка stereo10_r3_master
- Босс 1 Прямая ⊥ плоскость (4 этапа, +30 XP)
- Босс 2 Расстояния (4 этапа, +30 XP)
- Босс 3 Угол + ТТП (4 этапа, +35 XP, поддержка √2/2, 1/√3 и т.п.)
- Босс 4 ⊥-плоскости (4 этапа, +30 XP)
- Босс 5 Сборная (5 этапов, +45 XP — диагональ куба √3, sin угла 1/√3)
- Celebration: ачивка stereo10_r3_master + 130 XP бонус
- sec-nav: финал-таб разблокирован, refreshTabs учитывает {f1..f5}
- Состояние: STATE.bosses{f1..f5} + geometry10_achievements в localStorage
This commit is contained in:
@@ -194,7 +194,7 @@ main{max-width:1100px;margin:0 auto;padding:32px 24px 80px}
|
||||
<a class="sec-tab" data-tab="8" href="#para-8"><span class="dot"></span>§8 Расстояния</a>
|
||||
<a class="sec-tab" data-tab="9" href="#para-9"><span class="dot"></span>§9 Угол</a>
|
||||
<a class="sec-tab" data-tab="10" href="#para-10"><span class="dot"></span>§10 Пл-сти ⊥</a>
|
||||
<a class="sec-tab locked" data-tab="final" href="#para-final"><span class="dot"></span>Финал</a>
|
||||
<a class="sec-tab" data-tab="final" href="#para-final"><span class="dot"></span>Финал</a>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
@@ -731,7 +731,6 @@ main{max-width:1100px;margin:0 auto;padding:32px 24px 80px}
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Final stub -->
|
||||
<section id="para-final" class="para">
|
||||
<div class="para-head">
|
||||
<div class="para-num">★</div>
|
||||
@@ -740,10 +739,19 @@ main{max-width:1100px;margin:0 auto;padding:32px 24px 80px}
|
||||
<div class="para-h-sub">5 интегральных боссов · ачивка «Перпендикулярность освоена»</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="stub-card">
|
||||
<b>Откроется в Волне W7</b>
|
||||
Финал содержит 5 боссов и спецачивку <code>stereo10_r3_master</code>.
|
||||
|
||||
<div class="viz" style="background:linear-gradient(135deg,var(--pri-soft),var(--pri-soft-2));border-color:var(--pri-l)">
|
||||
<div class="viz-title" style="color:var(--pri-d)"><span class="badge">ФИНАЛЬНОЕ ИСПЫТАНИЕ</span> Победи 5 боссов подряд</div>
|
||||
<div class="viz-cap" style="color:var(--text-2);margin-top:0">Каждый босс — на одну тему: <b>прямая⊥плоскость</b>, <b>расстояния</b>, <b>угол наклонной + ТТП</b>, <b>⊥-плоскости</b>, <b>сборная задача</b>. После победы над всеми — ачивка <code>stereo10_r3_master</code> и +130 XP бонусом.</div>
|
||||
</div>
|
||||
|
||||
<div class="boss" id="boss-f1"></div>
|
||||
<div class="boss" id="boss-f2"></div>
|
||||
<div class="boss" id="boss-f3"></div>
|
||||
<div class="boss" id="boss-f4"></div>
|
||||
<div class="boss" id="boss-f5"></div>
|
||||
|
||||
<div id="celebration" style="display:none"></div>
|
||||
</section>
|
||||
|
||||
</main>
|
||||
@@ -825,6 +833,11 @@ function refreshTabs(){
|
||||
if (n === '7' || n === '8' || n === '9' || n === '10'){
|
||||
if (STATE.read.indexOf(parseInt(n,10)) >= 0) t.classList.add('read');
|
||||
else t.classList.remove('read');
|
||||
} else if (n === 'final'){
|
||||
var allBeat = ['f1','f2','f3','f4','f5'].every(function(k){
|
||||
return STATE.bosses && STATE.bosses[k] && STATE.bosses[k].defeated;
|
||||
});
|
||||
if (allBeat) t.classList.add('read');
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -1329,6 +1342,188 @@ var BOSS_DEFS = {
|
||||
}
|
||||
};
|
||||
|
||||
var FINAL_BOSS_DEFS = {
|
||||
f1: {
|
||||
title:'Босс 1 · Прямая ⊥ плоскость',
|
||||
xp:30,
|
||||
stages:[
|
||||
{ q:'Признак $l \\perp \\alpha$: $l$ перпендикулярна двум $?$ прямым в $\\alpha$.', type:'mc', opts:['Параллельным','Пересекающимся','Произвольным'], correct:1, explain:'Двум ПЕРЕСЕКАЮЩИМСЯ прямым.' },
|
||||
{ q:'Через точку проходит сколько прямых, перпендикулярных данной плоскости?', type:'input', a:'1', explain:'Единственная.' },
|
||||
{ q:'Если $l \\perp \\alpha$ и $a \\subset \\alpha$, то $l$ и $a$…', type:'mc', opts:['Параллельны','Перпендикулярны','Скрещиваются'], correct:1, explain:'$l$ перпендикулярна любой прямой плоскости.' },
|
||||
{ q:'Куб: ребро $DD_1$ перпендикулярно плоскости $ABCD$?', type:'mc', opts:['Да','Нет','Зависит'], correct:0, explain:'Все боковые рёбра ⊥ основанию.' }
|
||||
]
|
||||
},
|
||||
f2: {
|
||||
title:'Босс 2 · Расстояния',
|
||||
xp:30,
|
||||
stages:[
|
||||
{ q:'Куб с ребром $3$. Расстояние от $A$ до плоскости $A_1B_1C_1D_1$?', type:'input', a:'3', explain:'Длина $AA_1 = 3$.' },
|
||||
{ q:'Куб с ребром $1$. Расстояние между скрещ. рёбрами $AB$ и $D_1C_1$?', type:'input', a:['√2','sqrt(2)','sqrt2','1.41','1.414'], explain:'Диагональ грани $\\sqrt{2}$.' },
|
||||
{ q:'Расстояние между скрещ. прямыми реализуется на каком отрезке?', type:'mc', opts:['Любом','Общем перпендикуляре','Биссектрисе','Касательной'], correct:1, explain:'Общий перпендикуляр.' },
|
||||
{ q:'Перпендикуляр из точки на плоскость … любой наклонной из той же точки.', type:'mc', opts:['Длиннее','Короче','Равен'], correct:1, explain:'Короче (кратчайший отрезок).' }
|
||||
]
|
||||
},
|
||||
f3: {
|
||||
title:'Босс 3 · Угол + ТТП',
|
||||
xp:35,
|
||||
stages:[
|
||||
{ q:'Что такое проекция наклонной $AB$ на плоскость $\\alpha$ (где $AH \\perp \\alpha$, $H \\in \\alpha$)?', type:'mc', opts:['$AH$','$HB$','$AB$'], correct:1, explain:'Проекция — отрезок $HB$.' },
|
||||
{ q:'Угол между прямой и плоскостью — это угол между прямой и…', type:'mc', opts:['Перпендикуляром','Проекцией','Любой прямой'], correct:1, explain:'Наклонная и её проекция.' },
|
||||
{ q:'ТТП: $AH \\perp \\alpha, BC \\subset \\alpha, HB \\perp BC$. Что верно?', type:'mc', opts:['$AB \\perp BC$','$AB \\parallel BC$','Ничего'], correct:0, explain:'$AB \\perp BC$.' },
|
||||
{ q:'Куб (ребро 1): $\\tg$ угла между диагональю $AC_1$ и плоскостью $ABCD$?', type:'input', a:['1/√2','1/sqrt(2)','√2/2','sqrt(2)/2','0.707'], explain:'$\\tg \\varphi = \\frac{1}{\\sqrt{2}}$.' }
|
||||
]
|
||||
},
|
||||
f4: {
|
||||
title:'Босс 4 · ⊥-плоскости',
|
||||
xp:30,
|
||||
stages:[
|
||||
{ q:'Двугранный угол — это…', type:'mc', opts:['Угол наклонной','2 полуплоскости с общим ребром','Угол прямых'], correct:1, explain:'Полуплоскости + общее ребро.' },
|
||||
{ q:'Признак $\\alpha \\perp \\beta$: $\\alpha$ содержит прямую, $?$ к $\\beta$.', type:'mc', opts:['Параллельную','Перпендикулярную','Произвольную'], correct:1, explain:'Перпендикулярную $\\beta$.' },
|
||||
{ q:'Сколько граней куба перпендикулярно одной заданной грани?', type:'input', a:'4', explain:'4 смежные.' },
|
||||
{ q:'Линейный угол двугранного угла зависит от выбора точки на ребре?', type:'mc', opts:['Да','Нет'], correct:1, explain:'Не зависит.' }
|
||||
]
|
||||
},
|
||||
f5: {
|
||||
title:'Босс 5 · Сборная',
|
||||
xp:45,
|
||||
stages:[
|
||||
{ q:'Куб с ребром $1$. Расстояние от $A$ до $C_1$? (длина диагонали куба)', type:'input', a:['√3','sqrt(3)','sqrt3','1.73','1.732'], explain:'Диагональ куба $= a\\sqrt{3}$.' },
|
||||
{ q:'Куб (ребро 1): $\\sin$ угла между $AC_1$ и плоскостью $ABCD$?', type:'input', a:['1/√3','1/sqrt(3)','√3/3','sqrt(3)/3','0.577'], explain:'$\\sin \\varphi = \\frac{AA_1}{AC_1} = \\frac{1}{\\sqrt{3}}$.' },
|
||||
{ q:'Если $l_1 \\perp \\alpha$ и $l_2 \\perp \\alpha$, то $l_1$ и $l_2$ — …', type:'mc', opts:['$\\parallel$','$\\perp$','Скрещиваются'], correct:0, explain:'Параллельны.' },
|
||||
{ q:'В пирамиде высота $SO \\perp$ основание. Применяем ТТП: $OB \\perp BC \\Rightarrow$ ?', type:'mc', opts:['$SB \\perp BC$','$SB \\parallel BC$','Ничего'], correct:0, explain:'По ТТП: $SB \\perp BC$.' },
|
||||
{ q:'Куб: диагональная плоскость $BDD_1B_1$ и грань $ABCD$ — это…', type:'mc', opts:['Параллельны','Перпендикулярны','Под $45°$'], correct:1, explain:'$BB_1 \\subset BDD_1B_1$ и $BB_1 \\perp ABCD$ ⇒ перпендикулярны.' }
|
||||
]
|
||||
}
|
||||
};
|
||||
|
||||
function renderFinalBoss(id){
|
||||
var def = FINAL_BOSS_DEFS[id];
|
||||
if (!def) return;
|
||||
var el = document.getElementById('boss-' + id);
|
||||
if (!el) return;
|
||||
if (!STATE.bosses) STATE.bosses = {};
|
||||
var st = STATE.bosses[id] || { stage:0, defeated:false };
|
||||
STATE.bosses[id] = st;
|
||||
if (st.defeated){
|
||||
el.classList.add('victory');
|
||||
el.innerHTML = '<div class="boss-defeated">'
|
||||
+ '<div class="boss-defeated-title">' + def.title + ' побеждён!</div>'
|
||||
+ '<span class="boss-defeated-xp">+' + def.xp + ' XP</span>'
|
||||
+ '</div>';
|
||||
checkFinalComplete();
|
||||
return;
|
||||
}
|
||||
el.classList.remove('victory');
|
||||
var total = def.stages.length;
|
||||
var stage = def.stages[st.stage];
|
||||
var hp = Math.round((1 - st.stage/total) * 100);
|
||||
var optsHtml;
|
||||
if (stage.type === 'mc'){
|
||||
optsHtml = '<div class="boss-opts">';
|
||||
for (var i = 0; i < stage.opts.length; i++){
|
||||
optsHtml += '<button class="boss-opt" data-i="' + i + '">' + stage.opts[i] + '</button>';
|
||||
}
|
||||
optsHtml += '</div>';
|
||||
} else {
|
||||
optsHtml = '<div class="boss-input"><input type="text" id="boss-' + id + '-in" inputmode="text" placeholder="ответ"><button id="boss-' + id + '-go">Атака</button></div>';
|
||||
}
|
||||
el.innerHTML = '<div class="boss-h">'
|
||||
+ '<span class="boss-badge">Финал</span>'
|
||||
+ '<span class="boss-title">' + def.title + '</span>'
|
||||
+ '</div>'
|
||||
+ '<div class="boss-hp"><div class="boss-hp-label"><span>HP босса</span><span>' + hp + '%</span></div>'
|
||||
+ '<div class="boss-hp-bar"><div class="boss-hp-fill" style="width:' + hp + '%"></div></div></div>'
|
||||
+ '<div class="boss-question">'
|
||||
+ '<div class="boss-stage-label">Этап ' + (st.stage+1) + ' / ' + total + '</div>'
|
||||
+ '<div class="boss-q">' + stage.q + '</div>'
|
||||
+ optsHtml + '</div>';
|
||||
|
||||
function normalizeAns(s){
|
||||
return String(s||'').toLowerCase()
|
||||
.replace(/\s+/g,'')
|
||||
.replace(/°/g,'')
|
||||
.replace(/sqrt/g,'√')
|
||||
.replace(/корень/g,'√')
|
||||
.replace(/,/g,'.');
|
||||
}
|
||||
|
||||
if (stage.type === 'mc'){
|
||||
el.querySelectorAll('.boss-opt').forEach(function(btn){
|
||||
btn.addEventListener('click', function(){
|
||||
var i = parseInt(btn.getAttribute('data-i'), 10);
|
||||
var ok = (i === stage.correct);
|
||||
if (ok){
|
||||
btn.classList.add('correct');
|
||||
setTimeout(function(){ advanceFinalBoss(id); }, 600);
|
||||
} else {
|
||||
btn.classList.add('wrong');
|
||||
setTimeout(function(){ btn.classList.remove('wrong'); }, 600);
|
||||
}
|
||||
});
|
||||
});
|
||||
} else {
|
||||
var inEl = document.getElementById('boss-' + id + '-in');
|
||||
var goEl = document.getElementById('boss-' + id + '-go');
|
||||
var box = inEl.parentNode;
|
||||
function attack(){
|
||||
var v = normalizeAns(inEl.value);
|
||||
var answers = Array.isArray(stage.a) ? stage.a.map(normalizeAns) : [normalizeAns(stage.a)];
|
||||
if (answers.indexOf(v) >= 0){
|
||||
inEl.style.background = 'rgba(34,197,94,.25)';
|
||||
setTimeout(function(){ advanceFinalBoss(id); }, 500);
|
||||
} else {
|
||||
box.classList.add('wrong');
|
||||
inEl.style.background = 'rgba(220,38,38,.25)';
|
||||
setTimeout(function(){ box.classList.remove('wrong'); inEl.style.background=''; }, 600);
|
||||
}
|
||||
}
|
||||
goEl.addEventListener('click', attack);
|
||||
inEl.addEventListener('keydown', function(e){ if (e.key === 'Enter') attack(); });
|
||||
}
|
||||
|
||||
tryKatex(el);
|
||||
}
|
||||
|
||||
function advanceFinalBoss(id){
|
||||
var st = STATE.bosses[id];
|
||||
var def = FINAL_BOSS_DEFS[id];
|
||||
st.stage++;
|
||||
if (st.stage >= def.stages.length){
|
||||
st.defeated = true;
|
||||
saveState();
|
||||
addXp(def.xp, def.title);
|
||||
} else {
|
||||
saveState();
|
||||
}
|
||||
renderFinalBoss(id);
|
||||
}
|
||||
|
||||
function checkFinalComplete(){
|
||||
var allBeat = ['f1','f2','f3','f4','f5'].every(function(k){
|
||||
return STATE.bosses[k] && STATE.bosses[k].defeated;
|
||||
});
|
||||
if (!allBeat) return;
|
||||
var cel = document.getElementById('celebration');
|
||||
if (!cel) return;
|
||||
if (cel.dataset.shown === '1') return;
|
||||
cel.dataset.shown = '1';
|
||||
cel.style.display = 'block';
|
||||
cel.innerHTML = '<div class="boss victory" style="text-align:center;padding:40px 24px">'
|
||||
+ '<div style="font-family:Unbounded,sans-serif;font-size:1.8rem;font-weight:900;color:#fef3c7;letter-spacing:-.01em;margin-bottom:8px">★ Раздел 3 пройден! ★</div>'
|
||||
+ '<div style="font-size:1rem;color:#dcfce7;margin-bottom:16px">Все 5 финальных боссов побеждены. Перпендикулярность — освоена.</div>'
|
||||
+ '<span class="boss-defeated-xp" style="font-size:1rem;padding:10px 22px">+ 130 XP бонус + ачивка «stereo10_r3_master»</span>'
|
||||
+ '</div>';
|
||||
var achKey = 'geometry10_achievements';
|
||||
var raw = localStorage.getItem(achKey);
|
||||
var list = [];
|
||||
try { list = raw ? JSON.parse(raw) : []; } catch(e){}
|
||||
if (list.indexOf('stereo10_r3_master') < 0){
|
||||
list.push('stereo10_r3_master');
|
||||
localStorage.setItem(achKey, JSON.stringify(list));
|
||||
addXp(130, 'ачивка: Перпендикулярность освоена');
|
||||
}
|
||||
}
|
||||
|
||||
function renderBoss(num){
|
||||
var def = BOSS_DEFS[num];
|
||||
if (!def) return;
|
||||
@@ -1476,6 +1671,12 @@ function start(){
|
||||
renderBoss(8);
|
||||
renderBoss(9);
|
||||
renderBoss(10);
|
||||
renderFinalBoss('f1');
|
||||
renderFinalBoss('f2');
|
||||
renderFinalBoss('f3');
|
||||
renderFinalBoss('f4');
|
||||
renderFinalBoss('f5');
|
||||
checkFinalComplete();
|
||||
|
||||
document.getElementById('mark-7').addEventListener('click', function(){ markRead(7); });
|
||||
document.getElementById('mark-8').addEventListener('click', function(){ markRead(8); });
|
||||
|
||||
Reference in New Issue
Block a user