feat(geom10 W2): Раздел 1 §3 Сечения + Финал R1 (4 босса + ачивка)

§3 Построения сечений:
- Hero: куб с шестиугольным сечением через M, N, P (4-шаговая анимация: точки → 2 ребра → 6 точек → заливка)
- 3 типа сечений куба: треугольник / прямоугольник / правильный шестиугольник
- Метод следов: куб с M, N, K и следом плоскости сечения на основании
- 4 теоретические карточки (определение, метод следов, параллельные сечения, max сторон)
- 3 тренажёра: тип многоугольника (6), max сторон (5), метод следов (5)
- Босс §3: 5 этапов, +70 XP

Финал раздела 1 (4 босса):
- Босс 1 Элементы тел (4 этапа, +35 XP)
- Босс 2 Аксиомы (4 этапа, +35 XP)
- Босс 3 Сечения (4 этапа, +35 XP)
- Босс 4 Сборная (5 этапов, +45 XP)
- Celebration: ачивка stereo10_r1_master + 100 XP бонус
- Прогресс хранится в STATE.bosses{f1..f4} + geometry10_achievements в localStorage
This commit is contained in:
Maxim Dolgolyov
2026-05-29 14:54:52 +03:00
parent bf794f76a6
commit 0e52fedc2d
+492 -13
View File
@@ -195,8 +195,8 @@ main{max-width:1100px;margin:0 auto;padding:32px 24px 80px}
<div class="sec-nav-inner">
<a class="sec-tab active" data-tab="1" href="#para-1"><span class="dot"></span>§1 Фигуры</a>
<a class="sec-tab" data-tab="2" href="#para-2"><span class="dot"></span>§2 Аксиомы</a>
<a class="sec-tab locked" data-tab="3" href="#para-3"><span class="dot"></span>§3 Сечения</a>
<a class="sec-tab locked" data-tab="final" href="#para-final"><span class="dot"></span>Финал</a>
<a class="sec-tab" data-tab="3" href="#para-3"><span class="dot"></span>§3 Сечения</a>
<a class="sec-tab" data-tab="final" href="#para-final"><span class="dot"></span>Финал</a>
</div>
</nav>
@@ -503,10 +503,124 @@ main{max-width:1100px;margin:0 auto;padding:32px 24px 80px}
<div class="para-h-sub">Метод следов · сечения куба, призмы, пирамиды</div>
</div>
</div>
<div class="stub-card">
<b>В разработке (Волна W2)</b>
Параграф появится в следующей волне: сложная анимированная визуализация построения сечений многогранников.
<small>Сейчас сосредоточься на §1 и §2 — они дают контекст для §3.</small>
<div class="viz">
<div class="viz-title"><span class="badge">МЕТОД 3 ТОЧЕК</span> Сечение куба плоскостью через $M$, $N$, $P$ — правильный шестиугольник</div>
<div id="viz3-hero" style="text-align:center"></div>
<div class="rot-row" style="margin-top:14px">
<button id="viz3-step-btn" class="mark-btn" style="padding:8px 18px;font-size:.84rem">Шаг построения →</button>
<span id="viz3-step-lab" style="display:inline-flex;align-items:center;font-family:JetBrains Mono,monospace;color:var(--pri-d);font-weight:700">Шаг 1 / 4</span>
</div>
<div class="viz-cap" id="viz3-cap">На рёбрах $AB$, $BC$, $CC_1$ куба отмечены середины $M$, $N$, $P$. Нажми «Шаг построения», чтобы увидеть, как через них проводится плоскость и какая фигура получится.</div>
</div>
<div class="viz">
<div class="viz-title"><span class="badge">ТИПЫ СЕЧЕНИЙ КУБА</span> От треугольника до шестиугольника</div>
<div class="viz-row">
<div class="viz-cell"><div id="viz3-tri"></div><div class="viz-cell-label">Треугольник</div></div>
<div class="viz-cell"><div id="viz3-quad"></div><div class="viz-cell-label">Прямоугольник</div></div>
<div class="viz-cell"><div id="viz3-hex"></div><div class="viz-cell-label">Шестиугольник (max)</div></div>
</div>
<div class="viz-cap">Куб «теряет» одну грань при пересечении плоскостью — поэтому сечение имеет <b>от 3 до 6 сторон</b>. Тетраэдр (4 грани) — максимум 4-угольник. Призма с $n$-угольным основанием — максимум $(n+2)$-угольник.</div>
</div>
<div class="viz">
<div class="viz-title"><span class="badge">МЕТОД СЛЕДОВ</span> Как строится сечение, выходящее за пределы видимых граней</div>
<div id="viz3-trace" style="text-align:center"></div>
<div class="viz-cap">Пусть в кубе отмечены $M$, $N$ на верхних рёбрах и $K$ на боковом ребре. Тогда: 1) находим <span style="color:#dc2626;font-weight:800">след</span> плоскости сечения на основании (продолжая $MN$ и проводя прямую через $K$ параллельно ребру); 2) от следа достраиваем сечение, пересекая остальные рёбра.</div>
</div>
<div class="theory">
<div class="t-card">
<span class="t-tag">3.1</span>
<div class="t-title">Сечение многогранника</div>
<div class="t-body">
<p><b>Сечение</b> многогранника плоскостью $\sigma$ — это многоугольник, образованный пересечением плоскости со всеми гранями многогранника.</p>
<p>Его стороны — отрезки пересечения $\sigma$ с гранями; его вершины — точки, где $\sigma$ пересекает рёбра.</p>
</div>
</div>
<div class="t-card">
<span class="t-tag">3.2</span>
<div class="t-title">Метод следов</div>
<div class="t-body">
<p><b>Следом</b> называется линия пересечения плоскости сечения с одной из граней (обычно с плоскостью основания).</p>
<p>Зная след в плоскости основания и одну точку выше, можно построить пересечения с остальными гранями, продолжая прямые и применяя аксиому A2.</p>
</div>
</div>
<div class="t-card">
<span class="t-tag">3.3</span>
<div class="t-title">Параллельные сечения</div>
<div class="t-body">
<p>Если плоскость сечения <b>параллельна основанию</b> пирамиды, сечение — многоугольник, <b>подобный основанию</b> (с коэффициентом подобия, зависящим от высоты).</p>
<p>В призме параллельное основанию сечение даёт многоугольник, <b>равный основанию</b>.</p>
</div>
</div>
<div class="t-card">
<span class="t-tag">3.4</span>
<div class="t-title">Максимальное число сторон</div>
<div class="t-body">
<p>Плоскость может пересечь каждую грань не более чем по одному отрезку. Поэтому число сторон сечения не превосходит число граней многогранника.</p>
<ul>
<li>Куб (6 граней) → max <b>6 сторон</b> (правильный шестиугольник через 6 средин рёбер).</li>
<li>Тетраэдр (4 грани) → max <b>4 стороны</b>.</li>
<li>$n$-угольная призма ($n{+}2$ граней) → max $n{+}2$ сторон.</li>
</ul>
</div>
</div>
</div>
<div class="inter" data-inter="i3-type">
<div class="inter-h">
<div class="inter-icon">1</div>
<div class="inter-title">Какой многоугольник получится в сечении?</div>
<div class="inter-progress" id="i3-type-prog">0 / 6</div>
</div>
<div class="quiz">
<div class="quiz-q" id="i3-type-q"></div>
<div class="quiz-opts" id="i3-type-opts"></div>
</div>
<div class="quiz-feedback" id="i3-type-fb"></div>
</div>
<div class="inter" data-inter="i3-max">
<div class="inter-h">
<div class="inter-icon">2</div>
<div class="inter-title">Максимальное число сторон сечения</div>
<div class="inter-progress" id="i3-max-prog">0 / 5</div>
</div>
<div class="quiz">
<div class="quiz-q" id="i3-max-q"></div>
<div class="quiz-input">
<input id="i3-max-in" type="text" inputmode="numeric" placeholder="?">
<button id="i3-max-go">OK</button>
</div>
</div>
<div class="quiz-feedback" id="i3-max-fb"></div>
</div>
<div class="inter" data-inter="i3-trace">
<div class="inter-h">
<div class="inter-icon">3</div>
<div class="inter-title">Метод следов: верно или нет?</div>
<div class="inter-progress" id="i3-trace-prog">0 / 5</div>
</div>
<div class="quiz">
<div class="quiz-q" id="i3-trace-q"></div>
<div class="quiz-opts" id="i3-trace-opts"></div>
</div>
<div class="quiz-feedback" id="i3-trace-fb"></div>
</div>
<div class="boss" id="boss-3"></div>
<div class="para-actions">
<button class="mark-btn" id="mark-3">
<svg class="ic" viewBox="0 0 24 24"><polyline points="20 6 9 17 4 12"/></svg>
<span>Отметить §3 как изученный</span>
</button>
</div>
</section>
@@ -515,14 +629,21 @@ main{max-width:1100px;margin:0 auto;padding:32px 24px 80px}
<div class="para-num"></div>
<div class="para-h">
<h2>Финал раздела 1</h2>
<div class="para-h-sub">4 интегральных босса · ачивка «Введение в стереометрию пройдено!»</div>
<div class="para-h-sub">4 интегральных босса · ачивка «Введение в стереометрию пройдено»</div>
</div>
</div>
<div class="stub-card">
<b>Откроется после §3 (Волна W2)</b>
Финал содержит 4 босса (элементы тел, аксиомы, сечения, сборная задача) и спецачивку.
<small>До этого момента — побеждай боссов §1 и §2, чтобы заработать XP.</small>
<div class="viz" style="background:linear-gradient(135deg,#dbeafe,#eff6ff);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 — получишь ачивку и +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>
@@ -601,9 +722,14 @@ function refreshMarkBtn(n){
function refreshTabs(){
document.querySelectorAll('.sec-tab').forEach(function(t){
var n = t.getAttribute('data-tab');
if (n === '1' || n === '2'){
if (n === '1' || n === '2' || n === '3'){
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');
}
});
}
@@ -758,6 +884,160 @@ function buildAxiomVizes(){
})();
}
/* ===== §3 SVGs ===== */
var SECTION_STEP = 0;
function buildSectionHero(){
if (!window.STEREO3D) return setTimeout(buildSectionHero, 80);
var S = window.STEREO3D;
var sc = new S.Scene(440, 360, {view:'CABINET', scale:60});
sc.addCube({center:[0,0,0], size:2.0, labels:true, color:'#dbeafe', opacity:0.18});
// 3 заданные точки (шаг ≥ 1)
if (SECTION_STEP >= 1){
sc.addVertex([0,-1,-1], 'M', {dx:-14, dy:14, color:'#dc2626'});
sc.addVertex([1, 0,-1], 'N', {dx:10, dy:14, color:'#dc2626'});
sc.addVertex([1, 1, 0], 'P', {dx:14, dy:-2, color:'#dc2626'});
}
// Шаг 2: добавляем рёбра первой грани и продолжение
if (SECTION_STEP >= 2){
sc.addEdge([0,-1,-1],[1, 0,-1], {stroke:'#dc2626', width:2.6}); // M-N на нижней грани
sc.addEdge([1, 0,-1],[1, 1, 0], {stroke:'#dc2626', width:2.6}); // N-P на правой грани
}
// Шаг 3: остальные стороны (полный шестиугольник)
if (SECTION_STEP >= 3){
// P-R на задней грани, R на C1D1: R=(0,1,1)
sc.addEdge([1, 1, 0],[0, 1, 1], {stroke:'#dc2626', width:2.6});
// R-S на верхней грани, S на D1A1: S=(-1,0,1)
sc.addEdge([0, 1, 1],[-1, 0, 1], {stroke:'#dc2626', width:2.6});
// S-Q на левой грани, Q на AA1: Q=(-1,-1,0)
sc.addEdge([-1, 0, 1],[-1,-1, 0], {stroke:'#dc2626', width:2.6});
// Q-M на передней грани
sc.addEdge([-1,-1, 0],[ 0,-1,-1], {stroke:'#dc2626', width:2.6});
sc.addVertex([0, 1, 1], 'R', {dx:-12, dy:-10, color:'#dc2626'});
sc.addVertex([-1, 0, 1], 'S', {dx:-22, dy:-4, color:'#dc2626'});
sc.addVertex([-1,-1, 0], 'Q', {dx:-22, dy:6, color:'#dc2626'});
}
// Шаг 4: заливка многоугольника (правильный шестиугольник)
if (SECTION_STEP >= 4){
sc.addFace([[0,-1,-1],[1,0,-1],[1,1,0],[0,1,1],[-1,0,1],[-1,-1,0]],
{fill:'#fca5a5', opacity:0.45, stroke:'#dc2626', strokeWidth:2});
}
document.getElementById('viz3-hero').innerHTML = sc.render();
var lab = document.getElementById('viz3-step-lab');
if (lab) lab.textContent = 'Шаг ' + (SECTION_STEP + 1) + ' / 4';
var cap = document.getElementById('viz3-cap');
var capTexts = [
'На рёбрах $AB$, $BC$, $CC_1$ куба отмечены середины $M$, $N$, $P$. Нажми «Шаг построения», чтобы увидеть, как через них проводится плоскость и какая фигура получится.',
'Точки $M$, $N$, $P$ определяют плоскость (3 неколлинеарные точки). По аксиоме A2 — отрезки $MN$ и $NP$ лежат на гранях куба, попадая в плоскость сечения.',
'Продолжая прямые в плоскостях соседних граней, находим ещё 3 точки пересечения с рёбрами: $R$ на $C_1D_1$, $S$ на $D_1A_1$, $Q$ на $AA_1$.',
'Готовый многоугольник $MNPRSQ$ — <b>правильный шестиугольник</b>. Это классическое сечение куба плоскостью через 6 средин рёбер, лежащих на трёх парах противоположных граней.'
];
if (cap) cap.innerHTML = capTexts[SECTION_STEP];
tryKatex(cap);
}
function buildSectionTypes(){
if (!window.STEREO3D) return setTimeout(buildSectionTypes, 80);
var S = window.STEREO3D;
// Триангольное сечение (угловое)
(function(){
var sc = new S.Scene(220, 220, {view:'CABINET', scale:40});
sc.addCube({center:[0,0,0], size:2.0, labels:false, color:'#dbeafe', opacity:0.18});
sc.addFace([[-0.4,-1,-1],[1,0.4,-1],[1,-1,0.4]], {fill:'#fde047', opacity:0.55, stroke:'#d97706', strokeWidth:2});
sc.addEdge([-0.4,-1,-1],[1,0.4,-1], {stroke:'#d97706', width:2.4});
sc.addEdge([1,0.4,-1],[1,-1,0.4], {stroke:'#d97706', width:2.4});
sc.addEdge([1,-1,0.4],[-0.4,-1,-1], {stroke:'#d97706', width:2.4});
document.getElementById('viz3-tri').innerHTML = sc.render();
})();
// Прямоугольник (плоскость пересекает 4 параллельных ребра)
(function(){
var sc = new S.Scene(220, 220, {view:'CABINET', scale:40});
sc.addCube({center:[0,0,0], size:2.0, labels:false, color:'#dbeafe', opacity:0.18});
// Плоскость y = 0 → сечение — прямоугольник (-1,0,-1),(1,0,-1),(1,0,1),(-1,0,1)
sc.addFace([[-1,0,-1],[1,0,-1],[1,0,1],[-1,0,1]], {fill:'#86efac', opacity:0.55, stroke:'#059669', strokeWidth:2});
sc.addEdge([-1,0,-1],[1,0,-1], {stroke:'#059669', width:2.4});
sc.addEdge([1,0,-1],[1,0,1], {stroke:'#059669', width:2.4});
sc.addEdge([1,0,1],[-1,0,1], {stroke:'#059669', width:2.4});
sc.addEdge([-1,0,1],[-1,0,-1], {stroke:'#059669', width:2.4});
document.getElementById('viz3-quad').innerHTML = sc.render();
})();
// Шестиугольник (правильный, как в hero)
(function(){
var sc = new S.Scene(220, 220, {view:'CABINET', scale:40});
sc.addCube({center:[0,0,0], size:2.0, labels:false, color:'#dbeafe', opacity:0.18});
sc.addFace([[0,-1,-1],[1,0,-1],[1,1,0],[0,1,1],[-1,0,1],[-1,-1,0]],
{fill:'#fca5a5', opacity:0.55, stroke:'#dc2626', strokeWidth:2});
document.getElementById('viz3-hex').innerHTML = sc.render();
})();
}
function buildMethodOfTraces(){
if (!window.STEREO3D) return setTimeout(buildMethodOfTraces, 80);
var S = window.STEREO3D;
var sc = new S.Scene(440, 360, {view:'CABINET', scale:60});
sc.addCube({center:[0,0,0], size:2.0, labels:true, color:'#dbeafe', opacity:0.18});
// Точки M на A1B1, N на B1C1, K на CC1
var M = [-0.3, -1, 1];
var N = [1, 0.3, 1];
var K = [1, 1, -0.4];
sc.addVertex(M, 'M', {dx:-14, dy:-8, color:'#dc2626'});
sc.addVertex(N, 'N', {dx:12, dy:-8, color:'#dc2626'});
sc.addVertex(K, 'K', {dx:14, dy:6, color:'#dc2626'});
// Сечение M-N-K и достроенное
sc.addEdge(M, N, {stroke:'#dc2626', width:2.4});
sc.addEdge(N, K, {stroke:'#dc2626', width:2.4});
// След плоскости сечения на нижней грани (z=-1)
// Прямая MN продолжена до плоскости z=-1. Параметризуем M + t(N-M):
// M=(-0.3,-1,1), N=(1,0.3,1). z всегда 1 — параллельна нижней. Используем K и линию из K параллельную MN.
// Возьмём точки на нижней грани: проекции M и N сдвинуты по вертикали (z=−1). Для иллюстрации проведём след — прямую на нижней грани.
var TraceA = [-0.3, -1, -1];
var TraceB = [1, 0.3, -1];
sc.addEdge(TraceA, TraceB, {stroke:'#d97706', width:2.4, dash:'5 3'});
sc.addLabel('след', [-0.5, -0.4, -1], {color:'#d97706', fontSize:13, dy:14});
// Соединяем K с точкой на нижней (визуальная подсказка)
var Kproj = [1, 1, -1];
sc.addEdge(K, Kproj, {stroke:'#94a3b8', width:1.2, dash:'4 3'});
document.getElementById('viz3-trace').innerHTML = sc.render();
}
/* ===== Quiz items §3 ===== */
var i3TypeItems = [
{ q:'Плоскость пересекает три ребра, выходящие из одной вершины куба, в точках близко к вершине. Какое сечение?', opts:['Треугольник','Четырёхугольник','Шестиугольник','Невозможно'], correct:0, explain:'Угловой срез куба даёт треугольник (3 ребра — 3 точки — 3 стороны).' },
{ q:'Плоскость параллельна одной из граней куба. Какое сечение?', opts:['Треугольник','Квадрат','Шестиугольник','Эллипс'], correct:1, explain:'Параллельное основанию сечение куба — квадрат, равный основанию.' },
{ q:'Плоскость проходит через 6 средин рёбер, лежащих на 3 парах противоположных граней. Какое сечение?', opts:['Треугольник','Прямоугольник','Правильный шестиугольник','Окружность'], correct:2, explain:'Через 6 средин получается правильный шестиугольник — классическое сечение куба.' },
{ q:'Может ли сечение куба быть пятиугольником?', opts:['Да','Нет','Только при наклоне'], correct:0, explain:'Да: плоскость, пересекающая 5 граней куба из 6, даёт 5-угольник.' },
{ q:'Сечение тетраэдра — это всегда $n$-угольник, где $n \\le$ ?', opts:['3','4','5','6'], correct:1, explain:'У тетраэдра 4 грани ⇒ максимум 4-угольное сечение.' },
{ q:'Сечение плоскостью, параллельной основанию пирамиды, подобно чему?', opts:['Боковой грани','Основанию','Высоте','Не подобно'], correct:1, explain:'Параллельное основанию сечение пирамиды подобно основанию.' }
];
var i3MaxItems = [
{ q:'Максимальное число сторон сечения куба?', answer:'6', explain:'Куб имеет 6 граней — плоскость пересечёт их максимум по 6 отрезкам.' },
{ q:'Максимальное число сторон сечения тетраэдра?', answer:'4', explain:'У тетраэдра 4 грани ⇒ максимум 4-угольник.' },
{ q:'Максимальное число сторон сечения шестиугольной призмы?', answer:'8', explain:'У 6-уг. призмы $6+2=8$ граней ⇒ максимум 8-угольник.' },
{ q:'Сечение пятиугольной пирамиды — максимум $n$-угольник. Найди $n$.', answer:'5', explain:'У 5-уг. пирамиды $5+1=6$ граней, но плоскость не может пересечь основание и его параллельную плоскость — максимум 5 сторон.' },
{ q:'У многогранника $Г = 10$ граней. Максимальное число сторон сечения?', answer:'10', explain:'Не больше числа граней — то есть 10.' }
];
var i3TraceItems = [
{ q:'Следом плоскости сечения называется линия её пересечения с одной из граней.', opts:['Верно','Неверно'], correct:0, explain:'Точно так. След — обычно с плоскостью основания.' },
{ q:'Если плоскость параллельна основанию пирамиды, она имеет след на основании.', opts:['Верно','Неверно'], correct:1, explain:'Параллельные плоскости не пересекаются — следа на основании нет.' },
{ q:'Зная след в основании и одну точку выше, можно построить всё сечение.', opts:['Верно','Неверно'], correct:0, explain:'Это и есть суть метода следов.' },
{ q:'След проводится с помощью аксиомы A3 о пересечении плоскостей.', opts:['Верно','Неверно'], correct:0, explain:'Да: след — это прямая пересечения плоскости сечения с плоскостью основания.' },
{ q:'Сечение всегда лежит в одной плоскости.', opts:['Верно','Неверно'], correct:0, explain:'По определению — плоское сечение лежит в одной (секущей) плоскости.' }
];
function runQuizMC(opts){
var state = STATE.interactives[opts.id] || { idx: 0, solved: 0 };
var qEl = document.getElementById(opts.id + '-q');
@@ -939,9 +1219,184 @@ var BOSS_DEFS = {
{ q:'Сколько способов однозначно задать плоскость?', type:'mc', opts:['2','3','4','5'], correct:2, explain:'4 способа: 3 точки, прямая + точка, 2 пересек., 2 парал.' },
{ q:'Сколько плоскостей задают 4 точки общего положения?', type:'input', a:'4', explain:'$C_4^3 = 4$ тройки точек.' }
]
},
3: {
title:'§3 — Построения сечений',
xp:70,
stages:[
{ q:'Сечением куба может ли быть шестиугольник?', type:'mc', opts:['Да','Нет','Только наклонный'], correct:0, explain:'Да: классическое сечение через 6 средин рёбер.' },
{ q:'Максимальное число сторон сечения куба?', type:'input', a:'6', explain:'6 граней ⇒ max 6 сторон.' },
{ q:'Максимальное число сторон сечения треугольной пирамиды (тетраэдра)?', type:'input', a:'4', explain:'4 грани тетраэдра ⇒ max 4 стороны.' },
{ q:'Плоскость, параллельная основанию пирамиды, даёт сечение, $?$ основанию.', type:'mc', opts:['Равное','Подобное','Перпендикулярное','Совпадающее'], correct:1, explain:'Подобное основанию (коэффициент подобия зависит от высоты).' },
{ q:'Какое сечение даёт плоскость куба, проходящая через 6 средин рёбер на 3 парах противоположных граней?', type:'mc', opts:['Квадрат','Треугольник','Правильный шестиугольник','Эллипс'], correct:2, explain:'Правильный шестиугольник.' }
]
}
};
var FINAL_BOSS_DEFS = {
f1: {
title:'Босс 1 · Элементы тел',
xp:35,
stages:[
{ q:'Сколько рёбер у 7-угольной призмы?', type:'input', a:'21', explain:'$3n = 21$.' },
{ q:'Сколько граней у 6-угольной пирамиды?', type:'input', a:'7', explain:'$n+1=7$.' },
{ q:'Сколько вершин у тетраэдра?', type:'input', a:'4', explain:'4 вершины.' },
{ q:'$В=20, Р=30$. Сколько граней по Эйлеру?', type:'input', a:'12', explain:'$20-30+Г=2 \\Rightarrow Г=12$. Это додекаэдр.' }
]
},
f2: {
title:'Босс 2 · Аксиомы',
xp:35,
stages:[
{ q:'Через прямую и точку вне её проходит:', type:'mc', opts:['1 плоскость','2','Бесконечно'], correct:0, explain:'Единственная.' },
{ q:'Две плоскости пересекаются по:', type:'mc', opts:['Точке','Прямой','Плоскости','Не пересекаются'], correct:1, explain:'По прямой (A3).' },
{ q:'Если две точки прямой лежат в плоскости, то…', type:'mc', opts:['Прямая параллельна плоскости','Вся прямая лежит в плоскости','Прямая пересекает плоскость','Утверждение неверно'], correct:1, explain:'A2.' },
{ q:'Скрещивающиеся прямые лежат в одной плоскости?', type:'mc', opts:['Да','Нет','Иногда'], correct:1, explain:'По определению — нет.' }
]
},
f3: {
title:'Босс 3 · Сечения',
xp:35,
stages:[
{ q:'Может ли быть сечением куба пятиугольник?', type:'mc', opts:['Да','Нет','Только правильный'], correct:0, explain:'Да: плоскость пересекает 5 граней из 6.' },
{ q:'Максимум сторон у сечения 4-угольной призмы (параллелепипеда)?', type:'input', a:'6', explain:'$n+2=4+2=6$.' },
{ q:'Сечение параллельное основанию призмы:', type:'mc', opts:['Подобное основанию','Равное основанию','Меньше основания','Перпендикулярно основанию'], correct:1, explain:'У призмы — равное основанию.' },
{ q:'След плоскости сечения — это:', type:'mc', opts:['Точка','Линия пересечения с гранью','Касательная','Высота сечения'], correct:1, explain:'Линия пересечения секущей плоскости с гранью (обычно — с основанием).' }
]
},
f4: {
title:'Босс 4 · Сборная',
xp:45,
stages:[
{ q:'Сколько рёбер пересечёт плоскость, если сечение куба — шестиугольник?', type:'input', a:'6', explain:'Каждая сторона сечения — пересечение с одним ребром (или гранью). 6 сторон ⇒ 6 рёбер.' },
{ q:'У многогранника $В=8, Р=12, Г=6$. Что это?', type:'mc', opts:['Тетраэдр','Куб','Октаэдр','Додекаэдр'], correct:1, explain:'Куб: $8-12+6=2$ ✓.' },
{ q:'В пирамиде с 5-угольным основанием сечение, параллельное основанию, имеет:', type:'mc', opts:['3 стороны','4 стороны','5 сторон','6 сторон'], correct:2, explain:'Подобно основанию — тоже 5-угольник.' },
{ q:'Сколько плоскостей задают вершины тетраэдра?', type:'input', a:'4', explain:'$C_4^3 = 4$ грани (плоскости).' },
{ q:'У призмы $n$ боковых рёбер. У 9-угольной — сколько?', type:'input', a:'9', explain:'Ровно $n=9$.' }
]
}
};
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,'');
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">★ Раздел 1 пройден! ★</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_r1_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_r1_master') < 0){
list.push('stereo10_r1_master');
localStorage.setItem(achKey, JSON.stringify(list));
addXp(100, 'ачивка: Введение в стереометрию');
}
}
function renderBoss(num){
var def = BOSS_DEFS[num];
if (!def) return;
@@ -1057,19 +1512,43 @@ function start(){
buildPrismOblique();
buildRotCube();
buildAxiomVizes();
buildSectionHero();
buildSectionTypes();
buildMethodOfTraces();
// Кнопка «Шаг построения»
var stepBtn = document.getElementById('viz3-step-btn');
if (stepBtn){
stepBtn.addEventListener('click', function(){
SECTION_STEP = (SECTION_STEP + 1) % 4;
buildSectionHero();
stepBtn.querySelector ? null : null;
stepBtn.textContent = SECTION_STEP === 3 ? 'Сначала' : 'Шаг построения →';
});
}
runQuizMC({ id:'i1-solid', items:i1SolidItems, xpPerAll:12, title:'узнавание тел' });
runQuizInput({ id:'i1-count', items:i1CountItems, xpPerAll:15, title:'счёт элементов' });
runQuizMC({ id:'i2-axiom', items:i2AxiomItems, xpPerAll:12, title:'аксиомы' });
runQuizMC({ id:'i2-plane', items:i2PlaneItems, xpPerAll:10, title:'задание плоскости' });
runQuizMC({ id:'i2-count', items:i2CountItems, xpPerAll:10, title:'счёт плоскостей' });
runQuizMC({ id:'i3-type', items:i3TypeItems, xpPerAll:14, title:'тип сечения' });
runQuizInput({ id:'i3-max', items:i3MaxItems, xpPerAll:14, title:'max сторон сечения' });
runQuizMC({ id:'i3-trace', items:i3TraceItems, xpPerAll:10, title:'метод следов' });
renderBoss(1);
renderBoss(2);
renderBoss(3);
renderFinalBoss('f1');
renderFinalBoss('f2');
renderFinalBoss('f3');
renderFinalBoss('f4');
checkFinalComplete();
document.getElementById('mark-1').addEventListener('click', function(){ markRead(1); });
document.getElementById('mark-2').addEventListener('click', function(){ markRead(2); });
refreshMarkBtn(1); refreshMarkBtn(2);
document.getElementById('mark-3').addEventListener('click', function(){ markRead(3); });
refreshMarkBtn(1); refreshMarkBtn(2); refreshMarkBtn(3);
refreshTabs();
var tabs = document.querySelectorAll('.sec-tab[data-tab]');