feat(geom10 W4): Финал Раздела 2 — 4 босса + ачивка stereo10_r2_master
- Босс 1 Прямые в пространстве (4 этапа, +35 XP)
- Босс 2 Прямая и плоскость (4 этапа, +35 XP)
- Босс 3 Две плоскости (4 этапа, +35 XP)
- Босс 4 Сборная (5 этапов, +45 XP)
- Celebration: ачивка stereo10_r2_master + 100 XP бонус
- sec-nav: финал-таб разблокирован, отмечается при победе над всеми 4 боссами
- Состояние: STATE.bosses{f1..f4} + geometry10_achievements в localStorage
This commit is contained in:
@@ -193,7 +193,7 @@ main{max-width:1100px;margin:0 auto;padding:32px 24px 80px}
|
||||
<a class="sec-tab active" data-tab="4" href="#para-4"><span class="dot"></span>§4 Прямые</a>
|
||||
<a class="sec-tab" data-tab="5" href="#para-5"><span class="dot"></span>§5 Прямая и плоск.</a>
|
||||
<a class="sec-tab" data-tab="6" href="#para-6"><span class="dot"></span>§6 Две плоскости</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>
|
||||
|
||||
@@ -561,7 +561,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>
|
||||
@@ -570,11 +569,18 @@ main{max-width:1100px;margin:0 auto;padding:32px 24px 80px}
|
||||
<div class="para-h-sub">4 интегральных босса · ачивка «Параллельность освоена»</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="stub-card">
|
||||
<b>Откроется в Волне W4</b>
|
||||
Финал содержит 4 босса (прямые в пространстве, прямая и плоскость, две плоскости, сборная задача) и спецачивку <code>stereo10_r2_master</code>.
|
||||
<small>До этого момента — побеждай боссов §4, §5, §6, чтобы заработать XP.</small>
|
||||
|
||||
<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> Победи 4 боссов подряд</div>
|
||||
<div class="viz-cap" style="color:var(--text-2);margin-top:0">Каждый босс — на одну тему: <b>прямые в пространстве</b>, <b>прямая и плоскость</b>, <b>две плоскости</b>, <b>сборная задача</b>. После победы над всеми 4 — получишь ачивку <code>stereo10_r2_master</code> и +100 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 id="celebration" style="display:none"></div>
|
||||
</section>
|
||||
|
||||
</main>
|
||||
@@ -656,6 +662,11 @@ function refreshTabs(){
|
||||
if (n === '4' || n === '5' || n === '6'){
|
||||
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'].every(function(k){
|
||||
return STATE.bosses && STATE.bosses[k] && STATE.bosses[k].defeated;
|
||||
});
|
||||
if (allBeat) t.classList.add('read');
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -1031,6 +1042,169 @@ var BOSS_DEFS = {
|
||||
}
|
||||
};
|
||||
|
||||
var FINAL_BOSS_DEFS = {
|
||||
f1: {
|
||||
title:'Босс 1 · Прямые в пространстве',
|
||||
xp:35,
|
||||
stages:[
|
||||
{ q:'Куб: пара $A_1B_1$ и $D_1C_1$ — это…', type:'mc', opts:['Параллельные','Пересекающиеся','Скрещивающиеся'], correct:0, explain:'Противоположные стороны верхней грани — параллельны.' },
|
||||
{ q:'Куб: пара $AD$ и $B_1C_1$ — это…', type:'mc', opts:['Параллельные','Пересекающиеся','Скрещивающиеся'], correct:0, explain:'$AD \\parallel BC \\parallel B_1C_1$ (параллельные).' },
|
||||
{ q:'Угол между рёбрами куба $AB$ и $A_1D_1$?', type:'input', a:'90', explain:'Сдвинем $A_1D_1$ в нижнюю грань — получим $AD \\perp AB$. Угол $90°$.' },
|
||||
{ q:'Сколько пар скрещивающихся рёбер можно найти у куба, выходящих из одной вершины $A$? (учитывая только пары без общей точки и не параллельные)', type:'mc', opts:['0','1','2','3'], correct:0, explain:'Из одной вершины все 3 ребра пересекаются в этой вершине — скрещивающихся пар нет.' }
|
||||
]
|
||||
},
|
||||
f2: {
|
||||
title:'Босс 2 · Прямая и плоскость',
|
||||
xp:35,
|
||||
stages:[
|
||||
{ q:'Сколько случаев расположения прямой и плоскости?', type:'input', a:'3', explain:'Лежит / пересекает / параллельна.' },
|
||||
{ q:'Прямая $a \\parallel \\alpha$. Сколько у них общих точек?', type:'input', a:'0', explain:'Параллельность — отсутствие общих точек.' },
|
||||
{ q:'Куб: прямая $AC$ и плоскость грани $A_1B_1C_1D_1$. Расположение?', type:'mc', opts:['Лежит','Пересекает','Параллельна'], correct:2, explain:'$AC \\parallel A_1C_1 \\subset$ верх. грани ⇒ параллельна.' },
|
||||
{ q:'$a \\parallel b \\subset \\alpha$, $a \\not\\subset \\alpha$. Вывод?', type:'mc', opts:['$a \\cap \\alpha$','$a \\parallel \\alpha$','$a \\subset \\alpha$'], correct:1, explain:'Признак параллельности прямой и плоскости.' }
|
||||
]
|
||||
},
|
||||
f3: {
|
||||
title:'Босс 3 · Две плоскости',
|
||||
xp:35,
|
||||
stages:[
|
||||
{ q:'Сколько случаев расположения двух различных плоскостей?', type:'input', a:'2', explain:'Параллельны или пересекаются по прямой.' },
|
||||
{ q:'$\\alpha$ содержит 2 пересекающиеся прямые $a, b \\parallel \\beta$. Вывод?', type:'mc', opts:['$\\alpha = \\beta$','$\\alpha \\parallel \\beta$','$\\alpha \\cap \\beta = c$'], correct:1, explain:'Признак параллельности плоскостей.' },
|
||||
{ q:'Куб: плоскости верхней и нижней граней — это…', type:'mc', opts:['Параллельны','Пересекаются','Совпадают'], correct:0, explain:'Расстояние = ребру куба, плоскости параллельны.' },
|
||||
{ q:'$\\alpha \\parallel \\beta$, $\\gamma$ пересекает $\\alpha$ по $a$. Линия пересечения $\\gamma$ и $\\beta$ — это $b$. Тогда $a$ и $b$…', type:'mc', opts:['Скрещиваются','Параллельны','Перпендикулярны'], correct:1, explain:'$a \\parallel b$ — линии пересечения параллельных плоскостей третьей плоскостью.' }
|
||||
]
|
||||
},
|
||||
f4: {
|
||||
title:'Босс 4 · Сборная',
|
||||
xp:45,
|
||||
stages:[
|
||||
{ q:'Через 2 параллельные прямые проходит ровно … плоскостей.', type:'input', a:'1', explain:'Единственная — следствие из A1.' },
|
||||
{ q:'Если $a \\parallel \\alpha$ и $b \\parallel a$, то $b$ относительно $\\alpha$…', type:'mc', opts:['$\\parallel \\alpha$ или $\\subset \\alpha$','Пересекает','Скрещивается'], correct:0, explain:'Транзитивность параллельности.' },
|
||||
{ q:'Куб $ABCDA_1B_1C_1D_1$: сколько рёбер параллельно плоскости $ABCD$ и не лежит в ней?', type:'input', a:'4', explain:'4 ребра верхней грани $A_1B_1C_1D_1$ параллельны нижней плоскости.' },
|
||||
{ q:'Скрещивающиеся прямые лежат в одной плоскости?', type:'mc', opts:['Да','Нет','Иногда'], correct:1, explain:'По определению — нет.' },
|
||||
{ q:'Сколько прямых в плоскости $\\alpha$ можно построить, параллельных данной прямой $a \\parallel \\alpha$?', type:'mc', opts:['1','2','Бесконечно'], correct:2, explain:'Все прямые в $\\alpha$, параллельные $a$, — целое семейство (через каждую точку $\\alpha$).' }
|
||||
]
|
||||
}
|
||||
};
|
||||
|
||||
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>';
|
||||
|
||||
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 = (inEl.value || '').trim().toLowerCase().replace(/\s+/g,'').replace('°','');
|
||||
var a = String(stage.a).toLowerCase().replace(/\s+/g,'');
|
||||
if (v === a){
|
||||
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'].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">★ Раздел 2 пройден! ★</div>'
|
||||
+ '<div style="font-size:1rem;color:#dcfce7;margin-bottom:16px">Все 4 финальных босса побеждены. Параллельность — освоена.</div>'
|
||||
+ '<span class="boss-defeated-xp" style="font-size:1rem;padding:10px 22px">+ 100 XP бонус + ачивка «stereo10_r2_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_r2_master') < 0){
|
||||
list.push('stereo10_r2_master');
|
||||
localStorage.setItem(achKey, JSON.stringify(list));
|
||||
addXp(100, 'ачивка: Параллельность освоена');
|
||||
}
|
||||
}
|
||||
|
||||
function renderBoss(num){
|
||||
var def = BOSS_DEFS[num];
|
||||
if (!def) return;
|
||||
@@ -1162,6 +1336,11 @@ function start(){
|
||||
renderBoss(4);
|
||||
renderBoss(5);
|
||||
renderBoss(6);
|
||||
renderFinalBoss('f1');
|
||||
renderFinalBoss('f2');
|
||||
renderFinalBoss('f3');
|
||||
renderFinalBoss('f4');
|
||||
checkFinalComplete();
|
||||
|
||||
document.getElementById('mark-4').addEventListener('click', function(){ markRead(4); });
|
||||
document.getElementById('mark-5').addEventListener('click', function(){ markRead(5); });
|
||||
|
||||
Reference in New Issue
Block a user