Files
Learn_System/frontend/textbooks/geometry_10_r3.html
T

1213 lines
88 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!doctype html>
<html lang="ru">
<head>
<meta charset="UTF-8">
<meta http-equiv="Cache-Control" content="no-cache, no-store, must-revalidate">
<meta http-equiv="Pragma" content="no-cache">
<meta http-equiv="Expires" content="0">
<title>Геометрия 10 · Раздел 3 · «Перпендикулярность»</title>
<meta name="viewport" content="width=device-width,initial-scale=1">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/katex@0.16.9/dist/katex.min.css">
<script defer src="https://cdn.jsdelivr.net/npm/katex@0.16.9/dist/katex.min.js"></script>
<script defer src="https://cdn.jsdelivr.net/npm/katex@0.16.9/dist/contrib/auto-render.min.js"
onload="renderMathInElement(document.body,{delimiters:[{left:'$$',right:'$$',display:true},{left:'$',right:'$',display:false},{left:'\\[',right:'\\]',display:true},{left:'\\(',right:'\\)',display:false}],throwOnError:false})"></script>
<script src="/js/api.js" defer></script>
<script src="/js/xp.js" defer></script>
<script src="/js/stereo3d.js"></script>
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800&family=Manrope:wght@600;700;800;900&family=Unbounded:wght@700;800;900&family=JetBrains+Mono:wght@500;700&display=swap" rel="stylesheet">
<style>
:root{
--bg:#fafafa; --card:#fff; --card-soft:#f8fafc; --text:#0f172a; --ink:#0f172a; --muted:#64748b;
--border:#e2e8f0; --sh:0 1px 3px rgba(0,0,0,.06); --sh2:0 4px 14px rgba(0,0,0,.08);
--pri:#e11d48; --pri2:#be123c; --pri-soft:#fecdd3;
--acc:#f43f5e; --acc2:#e11d48; --acc-soft:#fda4af;
--ok:#10b981; --ok-bg:#d1fae5; --warn:#f59e0b; --warn-bg:#fef3c7;
--bad:#ef4444; --fail:#dc2626; --fail-bg:#fee2e2;
}
.dark{--bg:#170a0e; --card:#1d0d14; --card-soft:#240f1a; --text:#fecdd3; --ink:#fecdd3; --muted:#fda4af; --border:#7f1d1d}
*{margin:0;padding:0;box-sizing:border-box;-webkit-tap-highlight-color:transparent}
html,body{font-family:'Inter',system-ui,sans-serif;background:var(--bg);color:var(--text);line-height:1.55;font-size:15px}
button,input,select,textarea{font-family:inherit;font-size:inherit}
button{cursor:pointer;border:0;background:transparent;color:inherit}
a{color:inherit;text-decoration:none}
.ic{width:16px;height:16px;display:inline-block;flex-shrink:0;stroke:currentColor;fill:none;stroke-width:2;stroke-linecap:round;stroke-linejoin:round;vertical-align:middle}
.hdr{position:relative;background:linear-gradient(110deg,#7f1d1d 0%,#e11d48 55%,#fda4af 100%);color:#fff;padding:46px 22px 30px;overflow:hidden;border-bottom:2px solid rgba(254,205,211,.2);min-height:130px}
.hdr::before{content:'РАЗДЕЛ 3';position:absolute;right:-12px;top:50%;transform:translateY(-50%);font-family:'Unbounded',sans-serif;font-size:clamp(5rem,15vw,11rem);font-weight:900;letter-spacing:-.04em;color:transparent;-webkit-text-stroke:1.5px rgba(255,255,255,.12);line-height:1;pointer-events:none;user-select:none;z-index:0}
.hdr-row{position:relative;z-index:1;display:flex;align-items:center;gap:14px;flex-wrap:wrap}
.hdr h1{font-family:'Unbounded',sans-serif;font-size:1.5rem;font-weight:900;letter-spacing:-.01em;line-height:1.3;padding-top:4px}
.hdr-sub{font-size:.85rem;opacity:.88;margin-top:6px;font-weight:500;line-height:1.4}
.hdr-side{margin-left:auto;display:flex;gap:8px;align-items:center;flex-wrap:wrap}
.hdr-btn{padding:7px 12px;border-radius:9px;background:rgba(255,255,255,.14);color:#fff;font-weight:600;font-size:.82rem;display:inline-flex;align-items:center;gap:6px;transition:background .15s;text-decoration:none}
.hdr-btn:hover{background:rgba(255,255,255,.24)}
.main{max-width:1240px;margin:0 auto;padding:22px;width:100%;display:grid;grid-template-columns:1fr 280px;gap:24px}
@media(max-width:980px){.main{grid-template-columns:1fr;padding:14px}}
.col-main{min-width:0}
.hero{background:linear-gradient(135deg,var(--pri-soft) 0%,var(--acc-soft) 50%,var(--pri-soft) 100%);background-size:200% 200%;animation:heroShift 12s ease-in-out infinite;border:1px solid var(--border);border-radius:18px;padding:24px 22px;margin-bottom:24px;position:relative;overflow:hidden}
@keyframes heroShift{0%,100%{background-position:0% 50%}50%{background-position:100% 50%}}
.hero::before{content:'\22A5';position:absolute;right:0;top:-30px;font-size:clamp(2rem,12vw,8rem);font-weight:900;color:var(--pri);opacity:.10;line-height:1;pointer-events:none;font-family:'Unbounded',sans-serif}
.hero h2{font-family:'Unbounded',sans-serif;font-size:1.55rem;font-weight:800;color:var(--pri2);margin-bottom:10px;letter-spacing:-.01em}
.hero p{font-size:.95rem;color:var(--text);opacity:.88;margin-bottom:14px;max-width:640px}
.hero-row{display:flex;gap:14px;flex-wrap:wrap;align-items:center}
.btn-primary{padding:11px 22px;background:linear-gradient(135deg,var(--pri),var(--pri2));color:#fff;border-radius:11px;font-weight:700;font-size:.92rem;display:inline-flex;align-items:center;gap:8px;box-shadow:var(--sh2);transition:transform .15s,box-shadow .15s}
.btn-primary:hover{transform:translateY(-1px);box-shadow:0 8px 28px rgba(0,0,0,.18)}
.hero-progress{flex:1;min-width:200px;max-width:280px}
.hp-label{font-size:.74rem;font-weight:700;color:var(--muted);text-transform:uppercase;letter-spacing:.06em;display:block;margin-bottom:5px}
.hp-bar{height:8px;background:rgba(0,0,0,.12);border-radius:5px;overflow:hidden}
.hp-fill{height:100%;background:linear-gradient(90deg,var(--pri),var(--acc));border-radius:5px;width:0%;transition:width .6s cubic-bezier(.16,1,.3,1)}
.hp-text{font-size:.78rem;color:var(--muted);font-weight:700;margin-top:4px;display:block}
.hero-xp-badge{display:inline-flex;align-items:center;gap:6px;padding:6px 12px;background:linear-gradient(135deg,var(--warn,#f59e0b),var(--pri));color:#fff;border-radius:99px;font-size:.82rem;font-weight:800;letter-spacing:.02em;box-shadow:0 4px 12px rgba(0,0,0,.18);font-family:'Unbounded',sans-serif}
.psel{margin-bottom:24px}
.psel-title{font-size:.72rem;font-weight:800;color:var(--muted);text-transform:uppercase;letter-spacing:.08em;margin-bottom:10px}
.psel-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(180px,1fr));gap:10px}
.psel-card{background:var(--card);border:1.5px solid var(--border);border-radius:13px;padding:14px;cursor:pointer;transition:transform .2s,box-shadow .2s,border-color .2s;text-align:left;position:relative}
.psel-card:hover{transform:translateY(-3px);box-shadow:var(--sh2);border-color:var(--pri)}
.psel-card.active{border-color:var(--pri);background:linear-gradient(135deg,var(--pri-soft),var(--card));box-shadow:var(--sh2)}
.psel-card.active::after{content:'';position:absolute;top:0;left:0;right:0;height:3px;background:linear-gradient(90deg,var(--pri),var(--acc));border-radius:13px 13px 0 0}
.psel-num{font-family:'Unbounded',sans-serif;font-size:.72rem;font-weight:800;color:var(--pri);text-transform:uppercase;letter-spacing:.08em;margin-bottom:5px}
.psel-name{font-size:.86rem;font-weight:700;color:var(--text);line-height:1.3;margin-bottom:8px}
.psel-prog{height:4px;background:rgba(0,0,0,.10);border-radius:3px;overflow:hidden}
.psel-prog-fill{height:100%;background:var(--pri);width:0%;transition:width .4s}
.psel-card.final{background:linear-gradient(135deg,var(--warn-bg),var(--pri-soft))}
.psel-card.final .psel-num{color:var(--warn)}
.psel-card .psel-done{position:absolute;top:6px;right:6px;width:18px;height:18px;border-radius:50%;background:#10b981;display:none;align-items:center;justify-content:center;box-shadow:0 2px 6px rgba(16,185,129,.45);z-index:2}
.psel-card .psel-done svg{width:11px;height:11px;stroke:#fff;fill:none;stroke-width:3;stroke-linecap:round;stroke-linejoin:round}
.psel-card.done .psel-done{display:flex}
.sec{display:none;position:relative;animation:fadeIn .35s ease}
.sec.active{display:block}
@keyframes fadeIn{from{opacity:0;transform:translateY(8px)}to{opacity:1;transform:none}}
.sec::before{content:attr(data-watermark);position:absolute;right:-20px;top:10%;font-family:'Unbounded',sans-serif;font-size:clamp(6rem,18vw,14rem);font-weight:900;color:transparent;-webkit-text-stroke:1.5px var(--pri-soft);line-height:1;pointer-events:none;user-select:none;z-index:0;opacity:.35}
.sec-header{margin-bottom:22px;padding-bottom:14px;border-bottom:2px solid var(--pri-soft);position:relative;z-index:1}
.sec-num{display:inline-block;padding:4px 10px;background:linear-gradient(135deg,var(--pri),var(--pri2));color:#fff;border-radius:7px;font-family:'Unbounded',sans-serif;font-size:.78rem;font-weight:800;letter-spacing:.04em;margin-bottom:8px}
.sec-h{font-family:'Unbounded',sans-serif;font-size:1.6rem;font-weight:800;color:var(--pri2);letter-spacing:-.01em;line-height:1.25}
.card{background:var(--card);border:1px solid var(--border);border-radius:14px;padding:18px 20px;margin-bottom:16px;box-shadow:0 1px 3px rgba(0,0,0,.04),0 8px 24px rgba(0,0,0,.04);position:relative;z-index:1;transition:transform .25s cubic-bezier(.16,1,.3,1),box-shadow .25s}
.card:hover{transform:translateY(-2px);box-shadow:0 4px 10px rgba(0,0,0,.06),0 16px 36px rgba(0,0,0,.08)}
.card-header{display:flex;align-items:center;gap:10px;margin-bottom:12px;padding-bottom:10px;border-bottom:1px dashed var(--border)}
.card-icon{width:32px;height:32px;border-radius:9px;display:flex;align-items:center;justify-content:center;flex-shrink:0;color:#fff}
.card-icon.repeat{background:#0ea5e9}.card-icon.theory{background:#8b5cf6}.card-icon.algo{background:#f59e0b}.card-icon.rule{background:#ec4899}.card-icon.example{background:#10b981}.card-icon.oral{background:#06b6d4}
.card-icon .ic{width:18px;height:18px}
.card-title{font-family:'Unbounded',sans-serif;font-size:.82rem;font-weight:800;text-transform:uppercase;letter-spacing:.06em;color:var(--muted);flex:1}
.card-num{font-size:.74rem;font-weight:700;color:var(--muted);background:var(--pri-soft);padding:3px 7px;border-radius:5px}
.card-body{font-size:.94rem;line-height:1.65}
.card-body p{margin-bottom:8px}
.card-body p:last-child{margin-bottom:0}
.card-body ul{margin:6px 0 6px 22px;line-height:1.7}
.btn{padding:8px 16px;border-radius:8px;background:var(--card);color:var(--text);border:1.5px solid var(--border);font-weight:600;font-size:.88rem;transition:background .15s,border-color .15s,transform .1s}
.btn:hover{background:var(--pri-soft);border-color:var(--pri)}
.btn:active{transform:scale(.96)}
.btn.primary{background:var(--pri);color:#fff;border-color:var(--pri)}
.btn.primary:hover{background:var(--pri2);border-color:var(--pri2)}
.feedback{padding:10px 14px;border-radius:9px;font-weight:600;font-size:.88rem;margin-top:8px;display:none}
.feedback.ok{display:block;background:var(--ok-bg);color:#065f46;border-left:4px solid var(--ok)}
.feedback.fail{display:block;background:var(--fail-bg);color:#7f1d1d;border-left:4px solid var(--fail)}
.wg{background:linear-gradient(135deg,var(--card),var(--pri-soft));border:1.5px solid var(--pri);border-radius:14px;padding:18px 20px;margin-bottom:18px;box-shadow:var(--sh2);position:relative;z-index:1}
.wg-header{display:flex;align-items:center;gap:8px;margin-bottom:14px}
.wg-badge{padding:4px 9px;background:var(--pri);color:#fff;border-radius:6px;font-family:'Unbounded',sans-serif;font-size:.68rem;font-weight:800;text-transform:uppercase;letter-spacing:.06em}
.wg-title{font-family:'Unbounded',sans-serif;font-size:1.05rem;font-weight:800;color:var(--pri2);flex:1}
.wg-help{font-size:.88rem;color:var(--text);margin-bottom:12px;line-height:1.55;background:linear-gradient(135deg,var(--warn-bg),var(--pri-soft));border-left:4px solid var(--warn);padding:9px 14px;border-radius:9px}
.tinp{padding:8px 12px;border:1.5px solid var(--border);border-radius:8px;background:var(--card);color:var(--text);transition:border-color .15s;font-family:'JetBrains Mono',monospace;width:140px}
.tinp:focus{outline:0;border-color:var(--pri);box-shadow:0 0 0 3px var(--pri-soft)}
.actions{display:flex;gap:8px;flex-wrap:wrap;margin-top:10px;align-items:center}
.spoiler{border:1px solid var(--border);border-radius:10px;background:var(--card);margin:10px 0;overflow:hidden}
.spoiler summary{padding:8px 14px;background:var(--pri-soft);font-weight:700;cursor:pointer;font-size:.88rem;color:var(--pri2);list-style:none;display:flex;align-items:center;gap:8px}
.spoiler summary::-webkit-details-marker{display:none}
.spoiler summary::before{content:'+';font-size:1.2rem;font-weight:900;color:var(--pri);width:18px}
.spoiler[open] summary::before{content:'\2212'}
.spoiler-body{padding:10px 14px;font-size:.92rem;line-height:1.6}
.opts-row{display:flex;gap:8px;flex-wrap:wrap;margin-top:8px}
.opt-btn{padding:8px 14px;background:var(--card);border:1.5px solid var(--border);border-radius:9px;font-weight:700;font-size:.88rem;color:var(--text);cursor:pointer;transition:all .15s}
.opt-btn:hover{background:var(--pri-soft);border-color:var(--pri)}
.opt-btn.correct{background:var(--ok-bg);border-color:var(--ok);color:#065f46}
.opt-btn.wrong{background:var(--fail-bg);border-color:var(--fail);color:#991b1b}
.col-side{position:sticky;top:14px;align-self:start;height:fit-content;max-height:calc(100vh - 28px);overflow-y:auto}
.sidecard{background:var(--card);border:1px solid var(--border);border-radius:14px;padding:16px;margin-bottom:14px;box-shadow:var(--sh)}
.sidecard h4{font-family:'Unbounded',sans-serif;font-size:.74rem;font-weight:800;color:var(--pri2);text-transform:uppercase;letter-spacing:.07em;margin-bottom:10px;padding-bottom:8px;border-bottom:1px solid var(--border)}
.sidecard-row{margin-bottom:8px;font-size:.86rem;line-height:1.6}
.sidecard-row b{color:var(--pri);font-weight:700}
.sidecard-row:last-child{margin-bottom:0}
@media(max-width:980px){.col-side{position:static;max-height:none}}
.xp-card{background:linear-gradient(135deg,var(--warn-bg),var(--pri-soft));border:1.5px solid var(--warn);border-radius:12px;padding:14px;margin-bottom:14px}
.xp-card-title{font-size:.68rem;font-weight:800;color:var(--warn);text-transform:uppercase;letter-spacing:.07em;margin-bottom:8px;display:flex;align-items:center;justify-content:space-between}
.xp-level{font-size:1.1rem;font-weight:900;color:#92400e;font-family:'Unbounded',sans-serif}
.xp-bar{height:9px;background:rgba(0,0,0,.10);border-radius:6px;overflow:hidden;margin:7px 0}
.xp-fill{height:100%;background:linear-gradient(90deg,var(--warn),var(--pri));border-radius:6px;transition:width .5s cubic-bezier(.4,0,.2,1)}
.xp-nums{font-size:.74rem;color:var(--muted);display:flex;justify-content:space-between}
.sec-nav{display:flex;gap:10px;margin-top:24px;padding-top:20px;border-top:1px solid var(--border);justify-content:space-between;flex-wrap:wrap}
.foot{text-align:center;padding:30px 16px;color:var(--muted);font-size:.78rem;border-top:1px solid var(--border);margin-top:30px}
.ach-popup{position:fixed;top:80px;right:18px;background:linear-gradient(135deg,var(--pri),var(--warn));color:#fff;padding:12px 18px;border-radius:11px;font-weight:700;font-size:.9rem;box-shadow:0 8px 28px rgba(0,0,0,.32);z-index:1002;display:none;align-items:center;gap:8px;max-width:340px}
.ach-popup.show{display:flex}
.col-side-backdrop{position:fixed;inset:0;background:rgba(0,0,0,.42);z-index:9990;display:none}
.col-side-backdrop.show{display:block}
@media(max-width:980px){
.col-side{position:fixed;top:0;right:0;height:100vh;width:300px;max-width:88vw;background:var(--bg);box-shadow:-12px 0 24px rgba(0,0,0,.18);padding:18px 16px;overflow-y:auto;transform:translateX(100%);transition:transform .25s ease;z-index:9991;max-height:none}
.col-side.open{transform:none}
}
.viz3d{background:var(--card);border:1px solid var(--border);border-radius:11px;padding:12px;text-align:center;margin:10px 0}
.viz3d-row{display:flex;gap:12px;flex-wrap:wrap;justify-content:center;margin:10px 0}
.viz3d-cell{flex:1 1 180px;max-width:220px;text-align:center}
.viz3d-label{font-size:.78rem;color:var(--muted);font-weight:700;margin-top:6px}
.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:var(--pri-soft);color:var(--pri2);letter-spacing:.04em;text-transform:uppercase}
.boss-title{font-family:'Unbounded',sans-serif;font-weight:800;color:var(--text);font-size:1.02rem;flex:1;min-width:0}
.boss-q{padding:12px 14px;background:var(--pri-soft);border-radius:10px;font-size:.96rem;line-height:1.55;margin-bottom:10px;color:var(--text)}
.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:140px;text-align:center;font-size:.95rem}
.boss-row{display:flex;gap:8px;align-items:center;flex-wrap:wrap;margin-bottom:6px}
.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:var(--ok-bg);color:#065f46;border-left:4px solid var(--ok)}
.boss-fb.fail{display:block;background:var(--fail-bg);color:#7f1d1d;border-left:4px solid var(--fail)}
.stub-note{padding:18px 22px;background:linear-gradient(135deg,var(--pri-soft),var(--warn-bg));border:1.5px dashed var(--pri);border-radius:13px;text-align:center;color:var(--text);margin-bottom:14px}
.stub-note h3{font-family:'Unbounded',sans-serif;color:var(--pri2);margin-bottom:8px;font-size:1.05rem}
.stub-note p{color:var(--muted);font-size:.9rem;line-height:1.55}
</style>
</head>
<body>
<header class="hdr">
<div class="hdr-row">
<div>
<h1>Геометрия 10 · Раздел 3</h1>
<div class="hdr-sub">Перпендикулярность · прямая ⊥ плоскость · расстояния · ТТП · двугранный угол</div>
</div>
<div class="hdr-side">
<a href="/textbook/geometry-10" class="hdr-btn"><svg class="ic" viewBox="0 0 24 24"><polyline points="15 18 9 12 15 6"/></svg> К геометрии 10</a>
<button id="sidebar-btn" class="hdr-btn"><svg class="ic" viewBox="0 0 24 24"><line x1="4" y1="6" x2="20" y2="6"/><line x1="4" y1="12" x2="20" y2="12"/><line x1="4" y1="18" x2="14" y2="18"/></svg> Шпаргалка</button>
<button id="theme-btn" class="hdr-btn"><svg class="ic" viewBox="0 0 24 24"><path d="M21 12.8A9 9 0 1 1 11.2 3a7 7 0 0 0 9.8 9.8z"/></svg><span id="theme-lab">Тёмная</span></button>
</div>
</div>
</header>
<main class="main">
<div class="col-main">
<section class="hero">
<h2>Перпендикулярность в пространстве</h2>
<p>Прямая, перпендикулярная плоскости — основа стереометрии. Расстояния, теорема о трёх перпендикулярах, двугранный угол и перпендикулярность плоскостей.</p>
<div class="hero-row">
<button class="btn-primary" onclick="goTo('p1')"><svg class="ic" viewBox="0 0 24 24"><polygon points="6 4 20 12 6 20 6 4" fill="currentColor" stroke="none"/></svg> Начать § 7</button>
<div class="hero-progress">
<span class="hp-label">Прогресс по разделу</span>
<div class="hp-bar"><div id="hero-hp-fill" class="hp-fill"></div></div>
<span id="hero-hp-text" class="hp-text">0%</span>
</div>
<div id="hero-xp-badge" class="hero-xp-badge" data-gamified></div>
</div>
</section>
<section class="psel">
<div class="psel-title">Параграфы раздела</div>
<div id="psel-grid" class="psel-grid"></div>
</section>
<section id="sec-p1" class="sec" data-watermark="&#8869;"><div class="sec-header"><span class="sec-num">§ 7</span><h2 class="sec-h">Перпендикулярность прямой и плоскости</h2></div><div id="p1-body"></div></section>
<section id="sec-p2" class="sec" data-watermark="&#8741;"><div class="sec-header"><span class="sec-num">§ 8</span><h2 class="sec-h">Расстояния</h2></div><div id="p2-body"></div></section>
<section id="sec-p3" class="sec" data-watermark="&#8736;"><div class="sec-header"><span class="sec-num">§ 9</span><h2 class="sec-h">Угол между прямой и плоскостью (ТТП)</h2></div><div id="p3-body"></div></section>
<section id="sec-p4" class="sec" data-watermark="&#8895;"><div class="sec-header"><span class="sec-num">§ 10</span><h2 class="sec-h">Перпендикулярность плоскостей</h2></div><div id="p4-body"></div></section>
<section id="sec-final" class="sec" data-watermark="&#9733;"><div class="sec-header"><span class="sec-num" style="background:linear-gradient(135deg,#d97706,#f59e0b)">&#9733;</span><h2 class="sec-h">Финал раздела</h2></div><div id="final-body"></div></section>
</div>
<aside class="col-side" id="col-side"><div id="sidebar-content"></div></aside>
<div class="col-side-backdrop" id="col-side-backdrop"></div>
</main>
<footer class="foot">Интерактивный учебник «Геометрия 10» · Раздел 3 · «Перпендикулярность» · LearnSpace</footer>
<div id="ach-popup" class="ach-popup"><svg class="ic" viewBox="0 0 24 24" style="width:22px;height:22px"><polygon points="12,2 22,20 2,20"/></svg><span id="ach-text">Достижение!</span></div>
<script>
'use strict';
const STATE = { current:'p1', progress:{}, achievements:new Map(), xp:0, level:1 };
const TOTAL_PARAS = 4;
const _TB_SLUG = 'geometry-10-r3';
const PARAS = [
{ id:'p1', num:'§ 7', name:'Прямая ⊥ плоскость', sub:'Признак + свойства' },
{ id:'p2', num:'§ 8', name:'Расстояния', sub:'Точка → плоскость, скрещ.' },
{ id:'p3', num:'§ 9', name:'Угол + ТТП', sub:'Наклонная, проекция, ТТП' },
{ id:'p4', num:'§ 10', name:'⊥-плоскости', sub:'Двугранный угол, признак' },
{ id:'final', num:'&#9733;', name:'Финал раздела', sub:'5 интегр. боссов', final:true }
];
PARAS.forEach(p => { STATE.progress[p.id] = 0; });
function calcLevel(xp){ return Math.floor(Math.sqrt((xp||0)/100))+1; }
function _xpForLevel(lv){ return (lv-1)*(lv-1)*100; }
const ACH_LABELS = {
p1_done:'§7 — перпендикулярность освоена',
p2_done:'§8 — расстояния освоены',
p3_done:'§9 — ТТП освоена',
p4_done:'§10 — ⊥-плоскости освоены',
start:'Начало раздела 3!',
r3_done:'Перпендикулярность освоена!'
};
function loadProgress(){
try{
const s=localStorage.getItem('geometry10_r3_progress'); if(s) Object.assign(STATE.progress, JSON.parse(s));
const a=localStorage.getItem('geometry10_r3_achievements');
if(a){ const p=JSON.parse(a); if(p&&typeof p==='object'){ for(const[id,t] of Object.entries(p)) STATE.achievements.set(id,(t&&t!==id)?t:(ACH_LABELS[id]||id)); } }
STATE.xp=+(localStorage.getItem('geometry10_xp')||0); STATE.level=calcLevel(STATE.xp);
}catch(e){}
}
function saveProgress(){
try{
localStorage.setItem('geometry10_r3_progress', JSON.stringify(STATE.progress));
localStorage.setItem('geometry10_r3_achievements', JSON.stringify(Object.fromEntries(STATE.achievements)));
localStorage.setItem('geometry10_xp', String(STATE.xp));
}catch(e){}
}
function bumpProgress(key, delta){
STATE.progress[key]=Math.max(0,Math.min(100,(STATE.progress[key]||0)+delta));
saveProgress(); refreshProgressUI();
if(STATE.progress[key]>=50) markParaRead(key);
}
const _markedRead=new Set();
let _pendingProgressBody=null, _progressTimer=null;
function _flushProgress(){
const body=_pendingProgressBody; _pendingProgressBody=null; if(!body) return;
const tok=(window.LS&&LS.getToken)?LS.getToken():''; if(!tok) return;
fetch('/api/textbooks/'+_TB_SLUG+'/progress',{method:'POST',headers:{'Content-Type':'application/json','Authorization':'Bearer '+tok},body:JSON.stringify(body),keepalive:true}).catch(()=>{});
}
function _queueProgress(patch){ _pendingProgressBody=Object.assign(_pendingProgressBody||{},patch); if(_progressTimer) clearTimeout(_progressTimer); _progressTimer=setTimeout(_flushProgress, 600); }
function markLastPara(id){ _queueProgress({last_para:id}); }
function markParaRead(id){ if(_markedRead.has(id)) return; _markedRead.add(id); _queueProgress({mark_read:id}); }
window.addEventListener('beforeunload', _flushProgress);
function addXp(n,src){
if(!n) return;
const prev=STATE.level; STATE.xp=Math.max(0,(STATE.xp||0)+n); STATE.level=calcLevel(STATE.xp);
saveProgress(); refreshProgressUI();
if(window.LS&&window.LS.xp) window.LS.xp.add(n,'geometry10-r3-'+(src||'misc'));
if(STATE.level>prev){
const pop=document.getElementById('ach-popup');
if(pop){ document.getElementById('ach-text').textContent='Уровень '+STATE.level+'!'; pop.classList.add('show'); setTimeout(()=>pop.classList.remove('show'),2600); }
}
}
function refreshProgressUI(){
const total=Math.round(Object.values(STATE.progress).reduce((a,b)=>a+b,0)/TOTAL_PARAS);
const f=document.getElementById('hero-hp-fill'); if(f) f.style.width=total+'%';
const t=document.getElementById('hero-hp-text'); if(t) t.textContent=total+'% пройдено';
document.querySelectorAll('[data-prog-card]').forEach(el=>{ const k=el.dataset.progCard; const fl=el.querySelector('.psel-prog-fill'); if(fl) fl.style.width=(STATE.progress[k]||0)+'%'; if((STATE.progress[k]||0)>=100) el.classList.add('done'); });
const xpBadge=document.getElementById('hero-xp-badge');
if(xpBadge){ xpBadge.innerHTML='<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" style="width:13px;height:13px"><polygon points="12 2 22 20 2 20"/></svg> Ур. '+STATE.level+' \xb7 '+(STATE.xp||0)+' XP'; }
if(STATE.current && document.getElementById('sidebar-content')){ try{ buildSidebar(STATE.current); }catch(e){} }
}
function achievement(id,text){
if(STATE.achievements.has(id)) return;
STATE.achievements.set(id, text||ACH_LABELS[id]||id); saveProgress();
const pop=document.getElementById('ach-popup');
if(pop){ document.getElementById('ach-text').textContent=text||ACH_LABELS[id]||id; pop.classList.add('show'); setTimeout(()=>pop.classList.remove('show'),3300); }
addXp(20,'ach-'+id);
}
function buildParaSelector(){
const g=document.getElementById('psel-grid'); g.innerHTML='';
PARAS.forEach(p=>{
const card=document.createElement('div');
card.className='psel-card'+(p.final?' final':'');
card.dataset.id=p.id; card.dataset.progCard=p.id;
card.innerHTML='<div class="psel-num">'+p.num+'</div><div class="psel-name">'+p.name+'</div><div class="psel-prog"><div class="psel-prog-fill"></div></div><div class="psel-done"><svg viewBox="0 0 24 24"><polyline points="20 6 9 17 4 12"/></svg></div>';
card.addEventListener('click', ()=>goTo(p.id));
g.appendChild(card);
});
}
const BUILT=new Set();
const BUILDERS = { p1:()=>buildP1(), p2:()=>buildP2(), p3:()=>buildP3(), p4:()=>buildP4(), final:()=>buildFinal() };
function ensureBuilt(id){ if(BUILT.has(id)) return; const fn=BUILDERS[id]; if(fn){ fn(); BUILT.add(id); } }
function goTo(id){
STATE.current=id; ensureBuilt(id);
document.querySelectorAll('.sec').forEach(s=>s.classList.remove('active'));
const el=document.getElementById('sec-'+id); if(el) el.classList.add('active');
document.querySelectorAll('.psel-card').forEach(c=>c.classList.toggle('active', c.dataset.id===id));
buildSidebar(id);
window.scrollTo({top:0,behavior:'smooth'});
if((STATE.progress[id]||0)<10) bumpProgress(id, 10);
if(window.renderMathInElement) setTimeout(()=>renderMath(el), 0);
markLastPara(id);
}
const SIDEBARS = {
p1:{title:'Шпаргалка § 7', rows:[['Признак','$l \\perp m, l \\perp n, m \\cap n \\in \\alpha \\Rightarrow l \\perp \\alpha$'],['Свойство','$l \\perp \\alpha, a \\subset \\alpha \\Rightarrow l \\perp a$'],['$\\parallel$','$l_1 \\perp \\alpha, l_2 \\perp \\alpha \\Rightarrow l_1 \\parallel l_2$'],['Через точку','Единственная $l \\perp \\alpha$']]},
p2:{title:'Шпаргалка § 8', rows:[['Точка → пл-сть','Длина $\\perp$'],['Прямая → парал.пл-сть','$\\rho$ — постоянна'],['Парал. плоск.','Общий $\\perp$'],['Скрещ. прямые','Общий $\\perp$'],['Куб (ребро 1)','$|AC|=\\sqrt{2}$, $|AC_1|=\\sqrt{3}$']]},
p3:{title:'Шпаргалка § 9', rows:[['Перп.','$AH \\perp \\alpha$'],['Наклонная','$AB$ ($B \\in \\alpha, B \\neq H$)'],['Проекция','$HB$'],['Угол','$\\angle ABH$'],['ТТП','$AH \\perp \\alpha, HB \\perp BC \\Rightarrow AB \\perp BC$'],['Формулы','$\\tg \\varphi = h/p$']]},
p4:{title:'Шпаргалка § 10', rows:[['Двугран. угол','2 полупл. + ребро'],['Линейный угол','$MP \\perp l, MQ \\perp l$'],['Признак $\\alpha \\perp \\beta$','$\\alpha \\supset l \\perp \\beta$'],['Куб','Смежные грани $\\perp$ (4 из 5)']]},
final:{title:'Финал раздела 3', rows:[['§ 7-§ 10','4 параграфа'],['Боссов','5 интегрированных'],['Награда','+130 XP + ачивка']]}
};
const TIPS=[
{sec:'p1',html:'§ 7 — признак $l \\perp \\alpha$: <b>две пересекающиеся</b> прямые в плоскости. Параллельных мало.'},
{sec:'p2',html:'§ 8 — расстояние ВСЕГДА длина перпендикуляра. Перпендикуляр короче любой наклонной.'},
{sec:'p3',html:'§ 9 — ТТП: «3 перпендикуляра» = $AH, HB, BC$. Если 2 из них перпендикулярны — третья пара тоже.'},
{sec:'p4',html:'§ 10 — признак $\\alpha \\perp \\beta$: достаточно <b>одной</b> прямой в $\\alpha$, перпендикулярной $\\beta$.'},
{sec:'final',html:'5 боссов на интегральные задачи. После победы — ачивка stereo10_r3_master + 130 XP.'}
];
function buildSidebar(id){
const box=document.getElementById('sidebar-content');
const sb=SIDEBARS[id]||SIDEBARS[PARAS[0].id];
let html='';
const xpForLv=_xpForLevel(STATE.level), xpNext=_xpForLevel(STATE.level+1);
const xpInLv=STATE.xp-xpForLv, xpRange=xpNext-xpForLv;
const xpPct=xpRange>0?Math.round(xpInLv/xpRange*100):100;
html+='<div class="xp-card" data-gamified><div class="xp-card-title" data-gamified><span>XP-прогресс</span><span class="xp-level">Ур. '+STATE.level+'</span></div><div class="xp-bar"><div class="xp-fill" style="width:'+xpPct+'%"></div></div><div class="xp-nums"><span>'+STATE.xp+' XP</span><span>'+xpNext+' XP</span></div></div>';
html+='<div class="sidecard"><h4>'+sb.title+'</h4>';
sb.rows.forEach(([k,v])=>{ html+='<div class="sidecard-row"><b>'+k+'</b>'+(v?' — '+v:'')+'</div>'; });
html+='</div>';
const tip=TIPS.find(t=>t.sec===id)||TIPS[0];
if(tip){
html+='<div class="sidecard" style="background:linear-gradient(135deg,var(--warn-bg),var(--pri-soft));border-color:var(--warn)"><h4 style="color:#92400e;display:flex;align-items:center;gap:6px"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" style="width:14px;height:14px"><polygon points="12,2 22,20 2,20"/></svg>Подсказка</h4><div class="sidecard-row" style="margin-bottom:0;font-size:.84rem;line-height:1.55">'+tip.html+'</div></div>';
}
if(STATE.achievements.size>0){
html+='<div class="sidecard"><h4>Достижения <span style="color:var(--warn);float:right">'+STATE.achievements.size+'</span></h4>';
[...STATE.achievements.values()].slice(-4).forEach(text=>{ html+='<div class="sidecard-row" style="font-size:.78rem;color:var(--ok)">&#10003; '+text+'</div>'; });
html+='</div>';
}
box.innerHTML=html;
if(window.renderMathInElement) try{ renderMath(box); }catch(e){}
}
function initTheme(){
const t=localStorage.getItem('geometry10_theme')||localStorage.getItem('theme')||'light';
if(t==='dark') document.documentElement.classList.add('dark');
document.getElementById('theme-lab').textContent=t==='dark'?'Светлая':'Тёмная';
document.getElementById('theme-btn').addEventListener('click', ()=>{
document.documentElement.classList.toggle('dark');
const dark=document.documentElement.classList.contains('dark');
localStorage.setItem('geometry10_theme', dark?'dark':'light');
localStorage.setItem('theme', dark?'dark':'light');
document.getElementById('theme-lab').textContent=dark?'Светлая':'Тёмная';
});
document.getElementById('sidebar-btn').addEventListener('click', ()=>{
document.getElementById('col-side').classList.toggle('open');
document.getElementById('col-side-backdrop').classList.toggle('show');
});
document.getElementById('col-side-backdrop').addEventListener('click', ()=>{
document.getElementById('col-side').classList.remove('open');
document.getElementById('col-side-backdrop').classList.remove('show');
});
}
function renderMath(root){ if(window.renderMathInElement){ try{ renderMathInElement(root, {delimiters:[{left:'$$',right:'$$',display:true},{left:'$',right:'$',display:false},{left:'\\[',right:'\\]',display:true},{left:'\\(',right:'\\)',display:false}],throwOnError:false}); }catch(e){} } }
function feedback(elm, ok, text){ if(!elm) return; elm.className='feedback '+(ok?'ok':'fail'); elm.innerHTML=text||(ok?'&#10003; Верно!':'&#10007; Неверно'); elm.style.display='block'; try{renderMath(elm);}catch(e){} }
const ICONS = {
repeat:'<svg class="ic" viewBox="0 0 24 24"><polyline points="9 11 12 14 22 4"/><path d="M21 12v7a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h11"/></svg>',
theory:'<svg class="ic" viewBox="0 0 24 24"><path d="M4 19.5A2.5 2.5 0 0 1 6.5 17H20"/><path d="M6.5 2H20v20H6.5A2.5 2.5 0 0 1 4 19.5v-15A2.5 2.5 0 0 1 6.5 2z"/></svg>',
algo:'<svg class="ic" viewBox="0 0 24 24"><polyline points="17 11 21 7 17 3"/><line x1="21" y1="7" x2="9" y2="7"/><polyline points="7 13 3 17 7 21"/><line x1="3" y1="17" x2="15" y2="17"/></svg>',
rule:'<svg class="ic" viewBox="0 0 24 24"><path d="M6 8a6 6 0 0 1 12 0c0 7 3 9 3 9H3s3-2 3-9"/><path d="M10.3 21a1.94 1.94 0 0 0 3.4 0"/></svg>',
example:'<svg class="ic" viewBox="0 0 24 24"><path d="M9 18h6"/><path d="M10 22h4"/><path d="M12 2a7 7 0 0 0-4 13c1 1 2 2 2 4h4c0-2 1-3 2-4a7 7 0 0 0-4-13z"/></svg>',
oral:'<svg class="ic" viewBox="0 0 24 24"><circle cx="12" cy="12" r="10"/><path d="M9.09 9a3 3 0 0 1 5.83 1c0 2-3 3-3 3"/><line x1="12" y1="17" x2="12.01" y2="17"/></svg>'
};
function makeCard(kind, title, num, body){
const labels = {repeat:'Повторение',theory:'Теория',algo:'Алгоритм',rule:'Правило',example:'Пример',oral:'Устно'};
return '<div class="card"><div class="card-header"><div class="card-icon '+kind+'">'+ICONS[kind]+'</div><div class="card-title">'+(labels[kind]||'')+(title&&title!==labels[kind]?' \xb7 '+title:'')+'</div>'+(num?'<div class="card-num">'+num+'</div>':'')+'</div><div class="card-body">'+body+'</div></div>';
}
function secNavFor(curId){
const idx = PARAS.findIndex(p => p.id === curId);
const prev = idx > 0 ? PARAS[idx-1].id : null;
const next = idx < PARAS.length - 1 ? PARAS[idx+1].id : null;
const NAMES = {p1:'\xA77',p2:'\xA78',p3:'\xA79',p4:'\xA710',final:'Финал'};
let h='<div class="sec-nav">';
h+=prev?'<button class="btn" onclick="goTo(\''+prev+'\')"><svg class="ic" viewBox="0 0 24 24"><polyline points="15 18 9 12 15 6"/></svg> '+NAMES[prev]+'</button>':'<span></span>';
h+=next?'<button class="btn primary" onclick="goTo(\''+next+'\')">'+NAMES[next]+' <svg class="ic" viewBox="0 0 24 24"><polyline points="9 18 15 12 9 6"/></svg></button>':'<span></span>';
h+='</div>'; return h;
}
function readButton(paraId){
const p = PARAS.find(x => x.id === paraId);
const labelTail = p && p.final ? 'финал' : (p ? p.num : '\xA7?');
return '<div style="margin-top:18px;display:flex;justify-content:center">'
+'<button class="btn primary" id="'+paraId+'-read-btn">'
+'<svg class="ic" viewBox="0 0 24 24"><path d="M4 19.5A2.5 2.5 0 0 1 6.5 17H20"/><path d="M6.5 2H20v20H6.5A2.5 2.5 0 0 1 4 19.5v-15A2.5 2.5 0 0 1 6.5 2z"/></svg>'
+' Я прочитал — '+labelTail+' (+10 XP)'
+'</button></div>';
}
function wireReadBtn(paraId){
const btn = document.getElementById(paraId+'-read-btn'); if(!btn) return;
btn.addEventListener('click', ()=>{
addXp(10, paraId+'-read'); bumpProgress(paraId, 30);
btn.textContent='Прочитано! +10 XP'; btn.disabled=true; btn.style.opacity=.6;
const aId = paraId+'_done';
if(ACH_LABELS[aId]) achievement(aId);
});
}
function normalizeAns(s){
return String(s||'').toLowerCase()
.replace(/\s+/g,'').replace(/°/g,'')
.replace(/sqrt/g,'√').replace(/корень/g,'√')
.replace(/,/g,'.');
}
function makeBoss(id, def){
const stage = (typeof def.stage === 'number') ? def.stage : 0;
const solved = !!def.solved;
const total = def.stages.length;
const stageObj = def.stages[Math.min(stage, total-1)];
let optsHtml = '';
if(solved){
return '<div class="boss-card solved" id="boss-'+id+'"><div class="boss-head"><span class="boss-tag">'+(def.tag||'Босс')+'</span><span class="boss-title">'+def.title+'</span></div><div class="boss-q">Побеждён! +'+def.xp+' XP получены.</div></div>';
}
if(stageObj.type === 'mc'){
optsHtml = '<div class="opts-row">';
stageObj.opts.forEach((o,i)=>{ optsHtml += '<button class="opt-btn" data-i="'+i+'">'+o+'</button>'; });
optsHtml += '</div>';
} else {
optsHtml = '<div class="boss-row"><input class="boss-input" id="boss-'+id+'-inp" placeholder="ответ"><button class="btn primary" id="boss-'+id+'-go">Атака</button></div>';
}
return '<div class="boss-card" id="boss-'+id+'"><div class="boss-head"><span class="boss-tag">'+(def.tag||'Босс')+'</span><span class="boss-title">'+def.title+' — этап '+(stage+1)+' / '+total+'</span></div><div class="boss-q">'+stageObj.q+'</div>'+optsHtml+'<div class="boss-fb" id="boss-'+id+'-fb"></div></div>';
}
function bindBoss(id, def, state, save, onWin){
const card = document.getElementById('boss-'+id);
if(!card || state.solved) return;
const stageObj = def.stages[state.stage];
const fb = document.getElementById('boss-'+id+'-fb');
function advance(){
state.stage++;
if(state.stage >= def.stages.length){
state.solved = true; save(); addXp(def.xp, 'boss-'+id);
if(onWin) onWin();
} else { save(); }
rebuildBoss(id, def, state, save, onWin);
}
if(stageObj.type === 'mc'){
card.querySelectorAll('.opt-btn').forEach(btn=>{
btn.addEventListener('click', ()=>{
const i = +btn.dataset.i;
if(i === stageObj.correct){
btn.classList.add('correct');
fb.className='feedback ok'; fb.innerHTML='&#10003; Верно. '+(stageObj.explain||''); fb.style.display='block'; renderMath(fb);
setTimeout(advance, 700);
} else {
btn.classList.add('wrong');
fb.className='feedback fail'; fb.innerHTML='&#10007; Не так. '+(stageObj.explain||''); fb.style.display='block'; renderMath(fb);
}
});
});
} else {
const inp = document.getElementById('boss-'+id+'-inp');
const go = document.getElementById('boss-'+id+'-go');
function attack(){
const v = normalizeAns(inp.value);
const ans = Array.isArray(stageObj.a) ? stageObj.a.map(normalizeAns) : [normalizeAns(stageObj.a)];
if(ans.indexOf(v) >= 0){
fb.className='feedback ok'; fb.innerHTML='&#10003; Верно! '+(stageObj.explain||''); fb.style.display='block'; renderMath(fb);
setTimeout(advance, 600);
} else {
fb.className='feedback fail'; fb.innerHTML='&#10007; Не то. '+(stageObj.explain||''); fb.style.display='block'; renderMath(fb);
}
}
go.addEventListener('click', attack);
inp.addEventListener('keydown', e=>{ if(e.key==='Enter'){ e.preventDefault(); attack(); } });
}
}
function rebuildBoss(id, def, state, save, onWin){
const card = document.getElementById('boss-'+id);
if(!card) return;
card.outerHTML = makeBoss(id, Object.assign({}, def, state));
bindBoss(id, def, state, save, onWin);
renderMath(document.getElementById('boss-'+id));
}
function makeAndBindBoss(slotId, id, def, state, save, onWin){
const slot = document.getElementById(slotId); if(!slot) return;
slot.innerHTML = makeBoss(id, Object.assign({}, def, state));
bindBoss(id, def, state, save, onWin);
renderMath(slot);
}
function ensureStereo(cb){ if(window.STEREO3D) return cb(); setTimeout(()=>ensureStereo(cb), 60); }
/* ===== §7 (p1) ===== */
function buildP1(){
const box = document.getElementById('p1-body'); if(!box) return;
let html = '';
html += makeCard('theory', 'Определение', '§ 7.1',
'<p>Прямая $l$ называется <b>перпендикулярной плоскости</b> $\\alpha$, если она перпендикулярна <b>каждой</b> прямой в $\\alpha$, проходящей через точку их пересечения.</p>'
+ '<p>Обозначение: $l \\perp \\alpha$.</p>'
+ '<div class="viz3d" id="viz7-def"></div>');
html += makeCard('rule', 'Признак перпендикулярности', '§ 7.2',
'<p>Если прямая $l$ перпендикулярна <b>двум пересекающимся</b> прямым $m$ и $n$ в плоскости $\\alpha$, то $l \\perp \\alpha$.</p>'
+ '<p style="text-align:center;margin:10px 0">$$l \\perp m,\\ l \\perp n,\\ m \\cap n = O \\in \\alpha \\Rightarrow l \\perp \\alpha$$</p>'
+ '<div class="viz3d" id="viz7-sign"></div>'
+ '<p style="font-size:.86rem;color:var(--muted)">Двух пересекающихся прямых достаточно — не нужно проверять все.</p>');
html += makeCard('example', 'Свойства', '§ 7.3',
'<ul>'
+ '<li>Если $l \\perp \\alpha$, то $l$ перпендикулярна <b>любой</b> прямой в $\\alpha$: $l \\perp p$ для $\\forall p \\subset \\alpha$.</li>'
+ '<li>Если $l \\perp \\alpha$ и $l \\parallel l\'$, то $l\' \\perp \\alpha$.</li>'
+ '<li>Две прямые, перпендикулярные одной плоскости, <b>параллельны</b>: $l_1, l_2 \\perp \\alpha \\Rightarrow l_1 \\parallel l_2$.</li>'
+ '<li>Через любую точку — единственная прямая, перпендикулярная данной плоскости.</li>'
+ '</ul>');
html += makeCard('example', 'В кубе', '§ 7.4',
'<p>Куб $ABCDA_1B_1C_1D_1$: ребро $AA_1 \\perp ABCD$, потому что $AA_1 \\perp AB$ и $AA_1 \\perp AD$ (две пересек. в плоскости).</p>'
+ '<p>Аналогично — каждое боковое ребро перпендикулярно обоим основаниям.</p>');
html += '<div class="wg"><div class="wg-header"><span class="wg-badge">Инт. 1</span><span class="wg-title">Перпендикулярна ли прямая плоскости?</span></div>'
+ '<div class="wg-help">Решено: <b id="i1-perp-score">0</b> / 6.</div>'
+ '<div id="i1-perp-q" style="margin:8px 0"></div><div class="opts-row" id="i1-perp-opts"></div><div class="feedback" id="i1-perp-fb"></div></div>';
html += '<div class="wg"><div class="wg-header"><span class="wg-badge">Инт. 2</span><span class="wg-title">Применение признака</span></div>'
+ '<div class="wg-help">Решено: <b id="i1-sign-score">0</b> / 5.</div>'
+ '<div id="i1-sign-q" style="margin:8px 0"></div><div class="opts-row" id="i1-sign-opts"></div><div class="feedback" id="i1-sign-fb"></div></div>';
html += '<div class="wg"><div class="wg-header"><span class="wg-badge">Инт. 3</span><span class="wg-title">⊥ в кубе</span></div>'
+ '<div class="wg-help">Решено: <b id="i1-cube-score">0</b> / 5.</div>'
+ '<div id="i1-cube-q" style="margin:8px 0"></div><div class="opts-row" id="i1-cube-opts"></div><div class="feedback" id="i1-cube-fb"></div></div>';
html += '<div id="boss-1-slot"></div>';
html += readButton('p1');
html += secNavFor('p1');
box.innerHTML = html;
ensureStereo(()=>buildPerpDef());
ensureStereo(()=>buildPerpSign());
runQuizMC('i1-perp', I1_PERP_ITEMS, 14, 'перп.');
runQuizMC('i1-sign', I1_SIGN_ITEMS, 12, 'признак');
runQuizMC('i1-cube', I1_CUBE_ITEMS, 12, 'куб');
const bs = loadBossState('boss-1') || { stage:0, solved:false };
makeAndBindBoss('boss-1-slot', '1', BOSS_DEFS.b1, bs,
()=>saveBossState('boss-1', bs),
()=>{ bumpProgress('p1', 40); achievement('p1_done'); });
wireReadBtn('p1');
renderMath(box);
}
/* ===== §8 (p2) ===== */
function buildP2(){
const box = document.getElementById('p2-body'); if(!box) return;
let html = '';
html += makeCard('theory', '4 вида расстояний — всегда длина $\\perp$', '§ 8.1',
'<div class="viz3d-row"><div class="viz3d-cell"><div id="viz8-pt-pl"></div><div class="viz3d-label">Точка → плоск.</div></div><div class="viz3d-cell"><div id="viz8-ln-pl"></div><div class="viz3d-label">Прямая $\\parallel$ плоск.</div></div><div class="viz3d-cell"><div id="viz8-pl-pl"></div><div class="viz3d-cell-label">Парал. плоск.</div></div><div class="viz3d-cell"><div id="viz8-skew"></div><div class="viz3d-label">Скрещ. прямые</div></div></div>');
html += makeCard('rule', 'Точка → плоскость', '§ 8.2',
'<p>Расстояние от $A \\notin \\alpha$ до $\\alpha$ — длина <b>перпендикуляра</b> $AO$ из $A$ на $\\alpha$.</p>'
+ '<p>Любая <b>наклонная</b> $AB$ длиннее перпендикуляра: $|AO| \\le |AB|$.</p>'
+ '<div class="viz3d" id="viz8-detail"></div>');
html += makeCard('example', 'Свойства', '§ 8.3',
'<ul>'
+ '<li>От прямой $a \\parallel \\alpha$ — расстояние от любой точки прямой (оно постоянно).</li>'
+ '<li>Между парал. плоскостями — длина общего перпендикуляра.</li>'
+ '<li>Между скрещ. прямыми — длина общего перпендикуляра (он существует и единствен).</li>'
+ '</ul>');
html += makeCard('example', 'Куб (ребро $a=1$)', '§ 8.4',
'<ul>'
+ '<li>От $A$ до плоскости $A_1B_1C_1D_1$: $|AA_1| = 1$.</li>'
+ '<li>Между рёбрами $AB$ и $CC_1$: общий $\\perp = BC = 1$.</li>'
+ '<li>Между $AB$ и $D_1C_1$: $|AD_1| = \\sqrt{2}$ (диагональ грани).</li>'
+ '<li>Между параллельными гранями: $1$.</li>'
+ '</ul>');
html += '<div class="wg"><div class="wg-header"><span class="wg-badge">Инт. 1</span><span class="wg-title">Расстояния в кубе (ребро $a=1$)</span></div>'
+ '<div class="wg-help">Ответы вида $\\sqrt{2}$ можно ввести как sqrt(2) или 1.41. Решено: <b id="i2-cube-score">0</b> / 6.</div>'
+ '<div id="i2-cube-q" style="margin:8px 0"></div><div class="actions"><input class="tinp" id="i2-cube-inp" placeholder="ответ"><button class="btn primary" id="i2-cube-go">Проверить</button></div><div class="feedback" id="i2-cube-fb"></div></div>';
html += '<div class="wg"><div class="wg-header"><span class="wg-badge">Инт. 2</span><span class="wg-title">Какое расстояние ищется?</span></div>'
+ '<div class="wg-help">Решено: <b id="i2-type-score">0</b> / 5.</div>'
+ '<div id="i2-type-q" style="margin:8px 0"></div><div class="opts-row" id="i2-type-opts"></div><div class="feedback" id="i2-type-fb"></div></div>';
html += '<div class="wg"><div class="wg-header"><span class="wg-badge">Инт. 3</span><span class="wg-title">Верно или неверно</span></div>'
+ '<div class="wg-help">Решено: <b id="i2-tf-score">0</b> / 5.</div>'
+ '<div id="i2-tf-q" style="margin:8px 0"></div><div class="opts-row" id="i2-tf-opts"></div><div class="feedback" id="i2-tf-fb"></div></div>';
html += '<div id="boss-2-slot"></div>';
html += readButton('p2');
html += secNavFor('p2');
box.innerHTML = html;
ensureStereo(()=>buildDistanceCases());
ensureStereo(()=>buildDistanceDetail());
runQuizInput('i2-cube', I2_CUBE_ITEMS, 18, 'кубьрасст.');
runQuizMC('i2-type', I2_TYPE_ITEMS, 12, 'тип расст.');
runQuizMC('i2-tf', I2_TF_ITEMS, 10, 'верно/неверно');
const bs = loadBossState('boss-2') || { stage:0, solved:false };
makeAndBindBoss('boss-2-slot', '2', BOSS_DEFS.b2, bs,
()=>saveBossState('boss-2', bs),
()=>{ bumpProgress('p2', 40); achievement('p2_done'); });
wireReadBtn('p2');
renderMath(box);
}
/* ===== §9 (p3) ===== */
function buildP3(){
const box = document.getElementById('p3-body'); if(!box) return;
let html = '';
html += makeCard('theory', 'Наклонная, проекция, угол', '§ 9.1',
'<p>Точка $A \\notin \\alpha$. <span style="color:#dc2626;font-weight:800">$AH$ — перпендикуляр</span> ($H \\in \\alpha$). <span style="color:#1e3a8a;font-weight:800">$AB$ — наклонная</span> ($B \\in \\alpha, B \\neq H$). <span style="color:#16a34a;font-weight:800">$HB$ — проекция</span> наклонной.</p>'
+ '<p><b>Углом</b> между $AB$ и $\\alpha$ называется $\\angle ABH$ — угол между наклонной и её проекцией.</p>'
+ '<div class="viz3d" id="viz9-angle"></div>');
html += makeCard('rule', 'Теорема о трёх перпендикулярах (ТТП)', '§ 9.2',
'<p><b>Прямая ТТП:</b> $AH \\perp \\alpha, BC \\subset \\alpha, HB \\perp BC \\Rightarrow AB \\perp BC$.</p>'
+ '<p><b>Обратная:</b> $AH \\perp \\alpha, BC \\subset \\alpha, AB \\perp BC \\Rightarrow HB \\perp BC$.</p>'
+ '<div class="viz3d" id="viz9-ttp"></div>'
+ '<p style="font-size:.86rem;color:var(--muted)">Главная теорема пирамидальных задач — связывает перпендикулярность в плоскости с перпендикулярностью наклонной.</p>');
html += makeCard('example', 'Формулы угла', '§ 9.3',
'<p>Если $|AH| = h$ (перп.), $|HB| = p$ (проекция), $|AB| = \\ell$ (наклонная), $\\varphi$ — угол:</p>'
+ '<p style="text-align:center;margin:10px 0">$$\\tg \\varphi = \\dfrac{h}{p}, \\quad \\sin \\varphi = \\dfrac{h}{\\ell}, \\quad \\cos \\varphi = \\dfrac{p}{\\ell}$$</p>'
+ '<details class="spoiler"><summary>Угол диагонали куба с основанием</summary><div class="spoiler-body">'
+ '<p>Куб с ребром $a$. Диагональ $AC_1$, перп. $AA_1 = a$, проекция $AC = a\\sqrt{2}$. $\\tg \\varphi = \\dfrac{a}{a\\sqrt{2}} = \\dfrac{1}{\\sqrt{2}} \\Rightarrow \\varphi \\approx 35°$.</p>'
+ '</div></details>');
html += '<div class="wg"><div class="wg-header"><span class="wg-badge">Инт. 1</span><span class="wg-title">Перпендикуляр, наклонная или проекция?</span></div>'
+ '<div class="wg-help">Решено: <b id="i3-elem-score">0</b> / 6.</div>'
+ '<div id="i3-elem-q" style="margin:8px 0"></div><div class="opts-row" id="i3-elem-opts"></div><div class="feedback" id="i3-elem-fb"></div></div>';
html += '<div class="wg"><div class="wg-header"><span class="wg-badge">Инт. 2</span><span class="wg-title">Углы в кубе (ребро 1)</span></div>'
+ '<div class="wg-help">Решено: <b id="i3-cube-score">0</b> / 5.</div>'
+ '<div id="i3-cube-q" style="margin:8px 0"></div><div class="actions"><input class="tinp" id="i3-cube-inp" placeholder="ответ"><button class="btn primary" id="i3-cube-go">Проверить</button></div><div class="feedback" id="i3-cube-fb"></div></div>';
html += '<div class="wg"><div class="wg-header"><span class="wg-badge">Инт. 3</span><span class="wg-title">Применима ли ТТП?</span></div>'
+ '<div class="wg-help">Решено: <b id="i3-ttp-score">0</b> / 5.</div>'
+ '<div id="i3-ttp-q" style="margin:8px 0"></div><div class="opts-row" id="i3-ttp-opts"></div><div class="feedback" id="i3-ttp-fb"></div></div>';
html += '<div id="boss-3-slot"></div>';
html += readButton('p3');
html += secNavFor('p3');
box.innerHTML = html;
ensureStereo(()=>buildAngleViz());
ensureStereo(()=>buildTTPViz());
runQuizMC('i3-elem', I3_ELEM_ITEMS, 12, 'элементы');
runQuizInput('i3-cube', I3_CUBE_ITEMS, 16, 'углы куб');
runQuizMC('i3-ttp', I3_TTP_ITEMS, 12, 'ТТП');
const bs = loadBossState('boss-3') || { stage:0, solved:false };
makeAndBindBoss('boss-3-slot', '3', BOSS_DEFS.b3, bs,
()=>saveBossState('boss-3', bs),
()=>{ bumpProgress('p3', 40); achievement('p3_done'); });
wireReadBtn('p3');
renderMath(box);
}
/* ===== §10 (p4) ===== */
function buildP4(){
const box = document.getElementById('p4-body'); if(!box) return;
let html = '';
html += makeCard('theory', 'Двугранный угол', '§ 10.1',
'<p><b>Двугранный угол</b> — фигура из двух полуплоскостей с общим ребром $l$.</p>'
+ '<p><b>Линейный угол</b>: возьмём $M \\in l$. В каждой полуплоскости проведём луч $\\perp l$ из $M$. Угол между этими лучами — линейный.</p>'
+ '<p>Линейный угол не зависит от выбора точки $M$ на ребре.</p>'
+ '<div class="viz3d" id="viz10-dihedral"></div>');
html += makeCard('rule', 'Перпендикулярность плоскостей', '§ 10.2',
'<p>Две плоскости называются <b>перпендикулярными</b>, если их двугранный угол прямой ($90°$).</p>'
+ '<p style="text-align:center;margin:10px 0">$$\\alpha \\perp \\beta \\Leftrightarrow \\text{линейный угол} = 90°$$</p>');
html += makeCard('rule', 'Признак $\\alpha \\perp \\beta$', '§ 10.3',
'<p>Если плоскость $\\alpha$ содержит прямую $l$, <b>перпендикулярную</b> плоскости $\\beta$, то $\\alpha \\perp \\beta$.</p>'
+ '<p style="text-align:center;margin:10px 0">$$\\alpha \\supset l,\\ l \\perp \\beta \\Rightarrow \\alpha \\perp \\beta$$</p>'
+ '<div class="viz3d" id="viz10-sign"></div>'
+ '<p style="font-size:.86rem;color:var(--muted)">Достаточно <b>одной</b> перпендикулярной к $\\beta$ прямой в $\\alpha$.</p>');
html += makeCard('example', 'В кубе', '§ 10.4',
'<p>Каждая грань перпендикулярна <b>4 смежным</b> и параллельна <b>1 противоположной</b>.</p>'
+ '<p>Например, $ABCD \\perp ABB_1A_1$, потому что $AA_1 \\subset ABB_1A_1$ и $AA_1 \\perp ABCD$.</p>');
html += '<div class="wg"><div class="wg-header"><span class="wg-badge">Инт. 1</span><span class="wg-title">Двугранный угол: понимаешь?</span></div>'
+ '<div class="wg-help">Решено: <b id="i4-dh-score">0</b> / 5.</div>'
+ '<div id="i4-dh-q" style="margin:8px 0"></div><div class="opts-row" id="i4-dh-opts"></div><div class="feedback" id="i4-dh-fb"></div></div>';
html += '<div class="wg"><div class="wg-header"><span class="wg-badge">Инт. 2</span><span class="wg-title">Признак перпендикулярности плоскостей</span></div>'
+ '<div class="wg-help">Решено: <b id="i4-sign-score">0</b> / 5.</div>'
+ '<div id="i4-sign-q" style="margin:8px 0"></div><div class="opts-row" id="i4-sign-opts"></div><div class="feedback" id="i4-sign-fb"></div></div>';
html += '<div class="wg"><div class="wg-header"><span class="wg-badge">Инт. 3</span><span class="wg-title">⊥-грани куба</span></div>'
+ '<div class="wg-help">Решено: <b id="i4-cube-score">0</b> / 5.</div>'
+ '<div id="i4-cube-q" style="margin:8px 0"></div><div class="opts-row" id="i4-cube-opts"></div><div class="feedback" id="i4-cube-fb"></div></div>';
html += '<div id="boss-4-slot"></div>';
html += readButton('p4');
html += secNavFor('p4');
box.innerHTML = html;
ensureStereo(()=>buildDihedralViz());
ensureStereo(()=>buildPlanePerpSign());
runQuizMC('i4-dh', I4_DH_ITEMS, 12, 'двугран');
runQuizMC('i4-sign', I4_SIGN_ITEMS, 12, '⊥-признак');
runQuizMC('i4-cube', I4_CUBE_ITEMS, 12, '⊥-куб');
const bs = loadBossState('boss-4') || { stage:0, solved:false };
makeAndBindBoss('boss-4-slot', '4', BOSS_DEFS.b4, bs,
()=>saveBossState('boss-4', bs),
()=>{ bumpProgress('p4', 40); achievement('p4_done'); });
wireReadBtn('p4');
renderMath(box);
}
/* ===== Final ===== */
function buildFinal(){
const box = document.getElementById('final-body'); if(!box) return;
let html = '';
html += '<div class="stub-note"><h3>Финальное испытание · 5 боссов</h3><p>Победи 5 интегрированных боссов (⊥-прямая, расстояния, ТТП, ⊥-плоскости, сборная). После — ачивка stereo10_r3_master + 130 XP бонус.</p></div>';
html += '<div id="boss-f1-slot"></div>';
html += '<div id="boss-f2-slot"></div>';
html += '<div id="boss-f3-slot"></div>';
html += '<div id="boss-f4-slot"></div>';
html += '<div id="boss-f5-slot"></div>';
html += '<div id="celebration" style="display:none;margin-top:18px"></div>';
html += secNavFor('final');
box.innerHTML = html;
['f1','f2','f3','f4','f5'].forEach(id=>{
const def = FINAL_BOSS_DEFS[id];
const st = loadBossState('boss-'+id) || { stage:0, solved:false };
makeAndBindBoss('boss-'+id+'-slot', id, def, st,
()=>saveBossState('boss-'+id, st),
()=>{ checkFinalComplete(); });
});
checkFinalComplete();
renderMath(box);
}
function checkFinalComplete(){
const allBeat = ['f1','f2','f3','f4','f5'].every(k=>{
const st = loadBossState('boss-'+k); return st && st.solved;
});
if(!allBeat) return;
const cel = document.getElementById('celebration');
if(!cel || cel.dataset.shown === '1') return;
cel.dataset.shown = '1'; cel.style.display = 'block';
cel.innerHTML = '<div class="boss-card solved" style="background:linear-gradient(135deg,var(--warn-bg),var(--pri-soft));text-align:center"><div style="font-family:Unbounded,sans-serif;font-size:1.4rem;font-weight:900;color:#92400e;margin-bottom:8px">&#9733; Раздел 3 пройден! &#9733;</div><div style="color:var(--text);margin-bottom:14px">Все 5 финальных боссов побеждены. Перпендикулярность — освоена.</div><div style="display:inline-block;padding:8px 18px;background:linear-gradient(135deg,#f59e0b,#dc2626);color:#fff;border-radius:99px;font-family:Unbounded,sans-serif;font-weight:800;font-size:.9rem">+ 130 XP бонус + ачивка stereo10_r3_master</div></div>';
const ach = JSON.parse(localStorage.getItem('geometry10_achievements')||'[]');
if(ach.indexOf('stereo10_r3_master') < 0){
ach.push('stereo10_r3_master');
localStorage.setItem('geometry10_achievements', JSON.stringify(ach));
addXp(130, 'r3-master');
achievement('r3_done', 'Перпендикулярность освоена!');
}
bumpProgress('final', 100);
}
function loadBossState(key){ try{ return JSON.parse(localStorage.getItem('geometry10_r3_'+key)||'null'); }catch(e){ return null; } }
function saveBossState(key, state){ try{ localStorage.setItem('geometry10_r3_'+key, JSON.stringify(state)); }catch(e){} }
function runQuizMC(id, items, xpReward){
const state = JSON.parse(localStorage.getItem('geometry10_r3_quiz_'+id)||'null') || { idx:0, solved:0, awarded:false };
const qEl = document.getElementById(id+'-q');
const optsEl = document.getElementById(id+'-opts');
const fbEl = document.getElementById(id+'-fb');
const scoreEl = document.getElementById(id+'-score');
function save(){ localStorage.setItem('geometry10_r3_quiz_'+id, JSON.stringify(state)); }
function render(){
if(state.solved >= items.length){
qEl.innerHTML = '<b style="color:var(--ok)">Все задания решены!</b> +'+xpReward+' XP.';
optsEl.innerHTML = ''; fbEl.style.display='none';
if(scoreEl) scoreEl.textContent = state.solved;
if(!state.awarded){ state.awarded = true; save(); addXp(xpReward, 'quiz-'+id); }
return;
}
const it = items[state.idx % items.length];
qEl.innerHTML = it.q; optsEl.innerHTML = ''; fbEl.style.display='none';
if(scoreEl) scoreEl.textContent = state.solved;
it.opts.forEach((o,i)=>{
const b = document.createElement('button'); b.className = 'opt-btn'; b.innerHTML = o;
b.addEventListener('click', ()=>{
if(i === it.correct){
b.classList.add('correct'); state.solved++; state.idx++; save();
if(scoreEl) scoreEl.textContent = state.solved;
fbEl.className='feedback ok'; fbEl.innerHTML='&#10003; Верно. '+(it.explain||''); fbEl.style.display='block'; renderMath(fbEl);
setTimeout(render, 850);
} else {
b.classList.add('wrong');
fbEl.className='feedback fail'; fbEl.innerHTML='&#10007; Не так. '+(it.explain||''); fbEl.style.display='block'; renderMath(fbEl);
}
});
optsEl.appendChild(b);
});
renderMath(qEl);
}
render();
}
function runQuizInput(id, items, xpReward){
const state = JSON.parse(localStorage.getItem('geometry10_r3_quiz_'+id)||'null') || { idx:0, solved:0, awarded:false };
const qEl = document.getElementById(id+'-q');
const inp = document.getElementById(id+'-inp');
const go = document.getElementById(id+'-go');
const fbEl = document.getElementById(id+'-fb');
const scoreEl = document.getElementById(id+'-score');
function save(){ localStorage.setItem('geometry10_r3_quiz_'+id, JSON.stringify(state)); }
function render(){
if(state.solved >= items.length){
qEl.innerHTML = '<b style="color:var(--ok)">Все задания решены!</b> +'+xpReward+' XP.';
inp.value=''; inp.disabled=true; go.disabled=true;
if(scoreEl) scoreEl.textContent = state.solved;
if(!state.awarded){ state.awarded = true; save(); addXp(xpReward, 'quiz-'+id); }
return;
}
const it = items[state.idx % items.length];
qEl.innerHTML = it.q; inp.value=''; inp.disabled=false; go.disabled=false; fbEl.style.display='none';
if(scoreEl) scoreEl.textContent = state.solved;
renderMath(qEl);
}
function check(){
const it = items[state.idx % items.length];
const v = normalizeAns(inp.value);
const ans = Array.isArray(it.answer) ? it.answer.map(normalizeAns) : [normalizeAns(it.answer)];
if(ans.indexOf(v) >= 0){
state.solved++; state.idx++; save();
if(scoreEl) scoreEl.textContent = state.solved;
fbEl.className='feedback ok'; fbEl.innerHTML='&#10003; Верно. '+(it.explain||''); fbEl.style.display='block'; renderMath(fbEl);
setTimeout(render, 850);
} else {
fbEl.className='feedback fail'; fbEl.innerHTML='&#10007; Не так. '+(it.explain||''); fbEl.style.display='block'; renderMath(fbEl);
}
}
go.addEventListener('click', check);
inp.addEventListener('keydown', e=>{ if(e.key==='Enter'){ e.preventDefault(); check(); } });
render();
}
/* ===== Stereo3D vizes ===== */
function buildPerpDef(){
const S = window.STEREO3D; if(!S) return;
const sc = new S.Scene(400,280,{view:'CABINET',scale:46});
sc.addPlane([0,0,0],[0,0,1],{size:2.0,label:'α'});
sc.addEdge([0,0,-0.3],[0,0,1.8],{stroke:'#dc2626',width:3});
sc.addLabel('l',[0,0,1.8],{dx:12,dy:-4,color:'#dc2626',fontSize:16,anchor:'start'});
sc.addVertex([0,0,0],'O',{dx:10,dy:14,color:'#0b1d33'});
const dirs = [[1.4,0,0],[0,1.4,0],[1.1,0.9,0],[-1.1,0.9,0]];
dirs.forEach(d=>{
sc.addEdge([-d[0],-d[1],0],d,{stroke:'#1e3a8a',width:1.6});
sc.addRightAngleMark([0,0,0],[0,0,0.3],[d[0]*0.2,d[1]*0.2,0],{color:'#7c3aed',width:1.4,size:0.16});
});
const el = document.getElementById('viz7-def'); if(el) el.innerHTML = sc.render();
}
function buildPerpSign(){
const S = window.STEREO3D; if(!S) return;
const sc = new S.Scene(400,280,{view:'CABINET',scale:46});
sc.addPlane([0,0,0],[0,0,1],{size:2.0,label:'α'});
sc.addEdge([0,0,-0.2],[0,0,1.8],{stroke:'#dc2626',width:3});
sc.addLabel('l',[0,0,1.8],{dx:12,dy:-4,color:'#dc2626',fontSize:16,anchor:'start'});
sc.addEdge([-1.5,0,0],[1.5,0,0],{stroke:'#16a34a',width:2.4});
sc.addLabel('m',[1.5,0,0],{dx:12,dy:-4,color:'#16a34a',fontSize:14,anchor:'start'});
sc.addEdge([0,-1.3,0],[0,1.3,0],{stroke:'#0891b2',width:2.4});
sc.addLabel('n',[0,1.3,0],{dx:12,dy:-4,color:'#0891b2',fontSize:14,anchor:'start'});
sc.addVertex([0,0,0],'O',{dx:-20,dy:14,color:'#0b1d33'});
sc.addRightAngleMark([0,0,0],[0,0,0.3],[0.3,0,0],{color:'#7c3aed',width:1.4,size:0.18});
sc.addRightAngleMark([0,0,0],[0,0,0.3],[0,0.3,0],{color:'#7c3aed',width:1.4,size:0.18});
const el = document.getElementById('viz7-sign'); if(el) el.innerHTML = sc.render();
}
function buildDistanceCases(){
const S = window.STEREO3D; if(!S) return;
(()=>{ const sc = new S.Scene(200,180,{view:'CABINET',scale:38});
sc.addPlane([0,0,0],[0,0,1],{size:1.6,label:'α'});
sc.addVertex([0,0,1.0],'A',{dx:10,dy:-6,color:'#dc2626'});
sc.addEdge([0,0,1.0],[0,0,0],{stroke:'#dc2626',width:2.4});
sc.addVertex([0,0,0],'O',{dx:-14,dy:14,color:'#0b1d33'});
sc.addRightAngleMark([0,0,0],[0,0,0.3],[0.3,0,0],{color:'#7c3aed',width:1.4,size:0.18});
const el=document.getElementById('viz8-pt-pl'); if(el) el.innerHTML=sc.render(); })();
(()=>{ const sc = new S.Scene(200,180,{view:'CABINET',scale:38});
sc.addPlane([0,0,0],[0,0,1],{size:1.6,label:'α'});
sc.addEdge([-1.3,-0.3,0.8],[1.3,0.3,0.8],{stroke:'#16a34a',width:2.4});
sc.addEdge([0,0,0.8],[0,0,0],{stroke:'#dc2626',width:2,dash:'5 3'});
const el=document.getElementById('viz8-ln-pl'); if(el) el.innerHTML=sc.render(); })();
(()=>{ const sc = new S.Scene(200,180,{view:'CABINET',scale:38});
sc.addPlane([0,0,-0.4],[0,0,1],{size:1.3,label:'α'});
sc.addPlane([0,0,0.6],[0,0,1],{size:1.3,label:'β',fill:'#86efac',opacity:0.30});
sc.addEdge([0,0,0.6],[0,0,-0.4],{stroke:'#dc2626',width:2.4});
const el=document.getElementById('viz8-pl-pl'); if(el) el.innerHTML=sc.render(); })();
(()=>{ const sc = new S.Scene(200,180,{view:'CABINET',scale:38});
sc.addEdge([-1.2,-0.4,0],[1.2,0.4,0],{stroke:'#1e3a8a',width:2.2});
sc.addEdge([-1.2,0.4,1.0],[1.2,-0.4,1.0],{stroke:'#0891b2',width:2.2});
sc.addEdge([0,0,0],[0,0,1.0],{stroke:'#dc2626',width:2.4});
const el=document.getElementById('viz8-skew'); if(el) el.innerHTML=sc.render(); })();
}
function buildDistanceDetail(){
const S = window.STEREO3D; if(!S) return;
const sc = new S.Scene(400,280,{view:'CABINET',scale:50});
sc.addPlane([0,0,0],[0,0,1],{size:2.0,label:'α'});
sc.addVertex([0.2,0.3,1.1],'A',{dx:10,dy:-6,color:'#dc2626'});
sc.addEdge([0.2,0.3,1.1],[0.2,0.3,0],{stroke:'#dc2626',width:2.6});
sc.addVertex([0.2,0.3,0],'O',{dx:-18,dy:14,color:'#0b1d33'});
sc.addEdge([0.2,0.3,1.1],[-1.0,-0.6,0],{stroke:'#1e3a8a',width:2.2,dash:'4 3'});
sc.addVertex([-1.0,-0.6,0],'B',{dx:-16,dy:14,color:'#1e3a8a'});
sc.addRightAngleMark([0.2,0.3,0],[0.2,0.3,0.3],[0.5,0.3,0],{color:'#7c3aed',width:1.4,size:0.18});
const el = document.getElementById('viz8-detail'); if(el) el.innerHTML = sc.render();
}
function buildAngleViz(){
const S = window.STEREO3D; if(!S) return;
const sc = new S.Scene(400,280,{view:'CABINET',scale:50});
sc.addPlane([0,0,0],[0,0,1],{size:2.0,label:'α'});
const A=[0,0,1.3], H=[0,0,0], B=[1.2,0.4,0];
sc.addVertex(A,'A',{dx:10,dy:-6,color:'#dc2626'});
sc.addVertex(H,'H',{dx:-18,dy:14,color:'#7c3aed'});
sc.addVertex(B,'B',{dx:12,dy:10,color:'#1e3a8a'});
sc.addEdge(A,H,{stroke:'#dc2626',width:2.6});
sc.addEdge(A,B,{stroke:'#1e3a8a',width:2.4});
sc.addEdge(H,B,{stroke:'#16a34a',width:2.4});
sc.addRightAngleMark(H,[0,0,0.3],[0.3,0.1,0],{color:'#7c3aed',width:1.4,size:0.16});
sc.addAngleMark(B,A,H,{r:0.4,color:'#d97706',width:1.6,label:'φ'});
const el = document.getElementById('viz9-angle'); if(el) el.innerHTML = sc.render();
}
function buildTTPViz(){
const S = window.STEREO3D; if(!S) return;
const sc = new S.Scene(400,280,{view:'CABINET',scale:50});
sc.addPlane([0,0,0],[0,0,1],{size:2.0,label:'α'});
const A=[0,0,1.2], H=[0,0,0], B=[1.1,0,0], C=[1.1,0.8,0];
sc.addVertex(A,'A',{dx:10,dy:-6,color:'#dc2626'});
sc.addVertex(H,'H',{dx:-18,dy:14,color:'#7c3aed'});
sc.addVertex(B,'B',{dx:6,dy:14,color:'#1e3a8a'});
sc.addVertex(C,'C',{dx:12,dy:-4,color:'#16a34a'});
sc.addEdge(A,H,{stroke:'#dc2626',width:2.6});
sc.addEdge(H,B,{stroke:'#7c3aed',width:2.2});
sc.addEdge(B,C,{stroke:'#16a34a',width:2.4});
sc.addEdge(A,B,{stroke:'#1e3a8a',width:2.4});
sc.addRightAngleMark(H,[0,0,0.3],[0.3,0,0],{color:'#7c3aed',width:1.4,size:0.16});
sc.addRightAngleMark(B,[0.8,0,0],[1.1,0.3,0],{color:'#7c3aed',width:1.4,size:0.16});
const el = document.getElementById('viz9-ttp'); if(el) el.innerHTML = sc.render();
}
function buildDihedralViz(){
const S = window.STEREO3D; if(!S) return;
const sc = new S.Scene(400,280,{view:'CABINET',scale:50});
sc.addFace([[-1.3,0,0],[1.3,0,0],[1.3,1.1,0],[-1.3,1.1,0]],{fill:'#dbeafe',opacity:0.45,stroke:'#1e3a8a',strokeWidth:1.2});
const a = Math.PI/3, ca=Math.cos(a), sa=Math.sin(a);
const rot = p => [p[0], p[1]*ca, p[1]*sa];
sc.addFace([rot([-1.3,0,0]),rot([1.3,0,0]),rot([1.3,1.1,0]),rot([-1.3,1.1,0])],{fill:'#fef3c7',opacity:0.50,stroke:'#d97706',strokeWidth:1.2});
sc.addEdge([-1.4,0,0],[1.4,0,0],{stroke:'#dc2626',width:2.8});
sc.addLabel('l',[1.4,0,0],{dx:12,dy:-4,color:'#dc2626',fontSize:14,anchor:'start'});
const M=[0.2,0,0], P=[0.2,0.85,0], Q=rot([0.2,0.85,0]);
sc.addVertex(M,'M',{dx:0,dy:14,color:'#0b1d33'});
sc.addEdge(M,P,{stroke:'#1e3a8a',width:2.2});
sc.addEdge(M,Q,{stroke:'#d97706',width:2.2});
sc.addAngleMark(M,P,Q,{r:0.36,color:'#7c3aed',width:1.6,label:'φ'});
const el = document.getElementById('viz10-dihedral'); if(el) el.innerHTML = sc.render();
}
function buildPlanePerpSign(){
const S = window.STEREO3D; if(!S) return;
const sc = new S.Scene(400,280,{view:'CABINET',scale:46});
sc.addPlane([0,0,0],[0,0,1],{size:2.0,label:'β'});
sc.addPlane([0,0,0.6],[1,0,0],{size:1.3,label:'α',fill:'#fde047',opacity:0.30});
sc.addEdge([0,0,-0.2],[0,0,1.8],{stroke:'#dc2626',width:3});
sc.addLabel('l',[0,0,1.8],{dx:12,dy:-4,color:'#dc2626',fontSize:16,anchor:'start'});
sc.addVertex([0,0,0],'O',{dx:8,dy:14,color:'#0b1d33'});
sc.addRightAngleMark([0,0,0],[0,0,0.3],[0.3,0,0],{color:'#7c3aed',width:1.4,size:0.18});
const el = document.getElementById('viz10-sign'); if(el) el.innerHTML = sc.render();
}
/* ===== Quiz items ===== */
const I1_PERP_ITEMS = [
{ q:'$l$ перп. одной прямой $a \\subset \\alpha$. Достаточно для $l \\perp \\alpha$?', opts:['Да','Нет','Иногда'], correct:1, explain:'Нужны 2 пересек.' },
{ q:'$l$ перп. 2 парал. прямым $a, b \\subset \\alpha$. Достаточно?', opts:['Да','Нет'], correct:1, explain:'Нужны пересек.' },
{ q:'$l$ перп. 2 пересек. $m, n \\subset \\alpha$. $l \\perp \\alpha$?', opts:['Да','Нет','Иногда'], correct:0, explain:'Признак.' },
{ q:'$l \\perp \\alpha, p \\subset \\alpha$. Тогда:', opts:['$l \\perp p$','$l \\parallel p$','скрещ.'], correct:0, explain:'$l \\perp$ любой прямой $\\alpha$.' },
{ q:'$l_1 \\perp \\alpha, l_2 \\perp \\alpha$. $l_1$ и $l_2$:', opts:['Парал.','Пересек.','$\\perp$'], correct:0, explain:'Парал.' },
{ q:'Через точку — сколько прямых $\\perp$ плоскости?', opts:['1','2','Бесконечно'], correct:0, explain:'1.' }
];
const I1_SIGN_ITEMS = [
{ q:'Куб: $AA_1 \\perp AB$ и $AA_1 \\perp AD$. Вывод?', opts:['$AA_1 \\perp ABCD$','$AA_1 \\parallel ABCD$','Никакого'], correct:0, explain:'Признак.' },
{ q:'$l$ перп. 2 прямым $a, b \\subset \\alpha$, $a \\parallel b$. $l \\perp \\alpha$?', opts:['Да','Нет','Иногда'], correct:1, explain:'Нужны пересек.' },
{ q:'$l \\perp \\alpha$. Что верно для $p \\subset \\alpha$?', opts:['$l \\perp p$','$l \\parallel p$','Ничего'], correct:0, explain:'Свойство.' },
{ q:'$l_1 \\perp \\alpha, l_1 \\parallel l_2$. $l_2$:', opts:['$\\perp \\alpha$','$\\parallel \\alpha$','Лежит'], correct:0, explain:'$\\perp$.' },
{ q:'$l$ образует равные углы с 2 пересек. в $\\alpha$. $l \\perp \\alpha$?', opts:['Да','Нет','Иногда'], correct:1, explain:'Нужны прямые.' }
];
const I1_CUBE_ITEMS = [
{ q:'Куб: $AA_1$ и плоскость $ABCD$:', opts:['$\\perp$','$\\parallel$','Лежит'], correct:0, explain:'$AA_1 \\perp AB, AD$.' },
{ q:'$BB_1$ и $ABCD$:', opts:['$\\perp$','$\\parallel$','$45°$'], correct:0, explain:'Бок. ребро.' },
{ q:'$AB$ и $ADD_1A_1$ (левая бок.):', opts:['$\\perp$','$\\parallel$','Лежит'], correct:0, explain:'$AB \\perp AD, AA_1$.' },
{ q:'Какая прямая $\\perp$ грани $BCC_1B_1$?', opts:['$AB$','$BC$','$BB_1$','$AC$'], correct:0, explain:'$AB \\perp BC, BB_1$.' },
{ q:'$AC$ и $AA_1$:', opts:['$\\perp$','$\\parallel$','$45°$'], correct:0, explain:'$AA_1 \\perp ABCD$, $AC \\subset ABCD$.' }
];
const I2_CUBE_ITEMS = [
{ q:'От $A$ до плоскости $A_1B_1C_1D_1$?', answer:'1', explain:'$|AA_1|=1$.' },
{ q:'Между парал. гранями?', answer:'1', explain:'Ребро.' },
{ q:'Между скрещ. $AB$ и $CC_1$?', answer:'1', explain:'$BC=1$.' },
{ q:'Между скрещ. $AB$ и $C_1D_1$?', answer:['√2','sqrt(2)','sqrt2','1.41','1.414'], explain:'Диагональ грани = $\\sqrt{2}$.' },
{ q:'Между парал. прямыми $AB$ и $D_1C_1$?', answer:['√2','sqrt(2)','sqrt2','1.41'], explain:'Диагональ грани.' },
{ q:'Куб с ребром $2$: от $A$ до $A_1B_1C_1D_1$?', answer:'2', explain:'Ребро = 2.' }
];
const I2_TYPE_ITEMS = [
{ q:'«От $A$ до $B_1$ в кубе»:', opts:['ТТ','Т → пл-сть','Т → прямая','Скрещ.'], correct:0, explain:'Длина отрезка.' },
{ q:'«От $A$ до $BB_1C_1C$»:', opts:['ТТ','Т → пл-сть','Т → прямая','Скрещ.'], correct:1, explain:'Длина $\\perp$.' },
{ q:'«Между скрещ. $AD$ и $BB_1$»:', opts:['ТТ','Т → пл-сть','Парал.','Скрещ.'], correct:3, explain:'Общий $\\perp$.' },
{ q:'«Между парал. гранями куба»:', opts:['Т → пл-сть','Парал. плоск.','Парал. прямые','Скрещ.'], correct:1, explain:'Общий $\\perp$.' },
{ q:'«$a \\parallel \\alpha$, найти $\\rho(a, \\alpha)$»:', opts:['ТТ','Прямая → парал.пл-сть','Парал. плоск.','Скрещ.'], correct:1, explain:'От любой точки прямой.' }
];
const I2_TF_ITEMS = [
{ q:'Перпендикуляр — кратчайший отрезок до плоскости.', opts:['Верно','Неверно'], correct:0, explain:'Свойство.' },
{ q:'$|AO| \\ge |AB|$ (перп. и наклонная).', opts:['Верно','Неверно'], correct:1, explain:'Наоборот.' },
{ q:'Расстояние между скрещ. = общий $\\perp$.', opts:['Верно','Неверно'], correct:0, explain:'Так.' },
{ q:'Расст. от $a \\parallel \\alpha$ до $\\alpha$ зависит от точки прямой?', opts:['Верно','Неверно'], correct:1, explain:'Не зависит.' },
{ q:'У парал. плоскостей есть общие $\\perp$ одинаковой длины.', opts:['Верно','Неверно'], correct:0, explain:'Да.' }
];
const I3_ELEM_ITEMS = [
{ q:'$AH \\perp \\alpha, H \\in \\alpha$. $AH$ — это:', opts:['Перпендикуляр','Наклонная','Проекция'], correct:0, explain:'Перп.' },
{ q:'$AB$, $B \\in \\alpha, B \\neq H$. Это:', opts:['Перпендикуляр','Наклонная','Проекция'], correct:1, explain:'Наклонная.' },
{ q:'$HB$. Это:', opts:['Перпендикуляр','Наклонная','Проекция'], correct:2, explain:'Проекция.' },
{ q:'Угол прямой и плоскости — это угол между:', opts:['Прямой и любой $\\subset \\alpha$','Прямой и её проекцией','Прямой и перп.'], correct:1, explain:'Наклонная и проекция.' },
{ q:'Угол $a \\subset \\alpha$ с $\\alpha$:', opts:['$0°$','$45°$','$90°$'], correct:0, explain:'$0°$.' },
{ q:'Угол $\\perp$ прямой с плоскостью:', opts:['$0°$','$45°$','$90°$'], correct:2, explain:'$90°$.' }
];
const I3_CUBE_ITEMS = [
{ q:'Угол $AA_1$ и $ABCD$?', answer:'90', explain:'$\\perp$.' },
{ q:'Угол $AB$ и $ABCD$?', answer:'0', explain:'Лежит.' },
{ q:'Угол диагонали $AC$ и $ABCD$?', answer:'0', explain:'Лежит.' },
{ q:'$\\tg$ угла $AC_1$ и $ABCD$ (ребро 1)?', answer:['1/√2','1/sqrt(2)','√2/2','sqrt(2)/2','0.707'], explain:'$\\tg = \\frac{1}{\\sqrt{2}}$.' },
{ q:'Угол $A_1C$ и $A_1B_1C_1D_1$ — целое °:', answer:'35', explain:'$\\varphi \\approx 35°$.' }
];
const I3_TTP_ITEMS = [
{ q:'$AH \\perp \\alpha, BC \\subset \\alpha, HB \\perp BC$. Вывод:', opts:['$AB \\perp BC$','$AB \\parallel BC$','Ничего'], correct:0, explain:'Прямая ТТП.' },
{ q:'$AH \\perp \\alpha, BC \\subset \\alpha, AB \\perp BC$. Вывод:', opts:['$HB \\perp BC$','$HB \\parallel BC$','Ничего'], correct:0, explain:'Обратная ТТП.' },
{ q:'$BC \\not\\subset \\alpha$. Применима ТТП?', opts:['Да','Нет'], correct:1, explain:'Нет.' },
{ q:'$AH$ — наклонная (не перп.). Применима ТТП?', opts:['Да','Нет'], correct:1, explain:'Нет.' },
{ q:'В пирамиде $SO \\perp$ основ., $OB \\perp CD \\subset$ основ. $SB$ и $CD$:', opts:['$\\perp$ по ТТП','$\\parallel$','Скрещ.'], correct:0, explain:'ТТП.' }
];
const I4_DH_ITEMS = [
{ q:'Двугранный угол — это:', opts:['Угол прямых','2 полупл. + ребро','Угол наклонной','Угол плоск. ($<90°$)'], correct:1, explain:'2 полупл. + ребро.' },
{ q:'Линейный угол строится:', opts:['Произвольно','$\\perp$ к ребру в каждой полупл.','Через высоту','Через диагональ'], correct:1, explain:'$\\perp$ к ребру.' },
{ q:'Зависит ли линейный угол от точки на ребре?', opts:['Да','Нет'], correct:1, explain:'Нет.' },
{ q:'$\\alpha \\perp \\beta \\Leftrightarrow$ линейный угол:', opts:['$0°$','$45°$','$90°$'], correct:2, explain:'$90°$.' },
{ q:'Линейный угол развёрнутого двугран.:', opts:['$0°$','$90°$','$180°$','$360°$'], correct:2, explain:'$180°$.' }
];
const I4_SIGN_ITEMS = [
{ q:'$l \\subset \\alpha, l \\perp \\beta$. Вывод:', opts:['$\\alpha \\parallel \\beta$','$\\alpha \\perp \\beta$','$\\alpha=\\beta$'], correct:1, explain:'Признак.' },
{ q:'Достаточно $\\alpha \\supset l \\perp \\beta$ для $\\alpha \\perp \\beta$?', opts:['Да','Нет, нужно 2','Нужно 3'], correct:0, explain:'Одной хватает.' },
{ q:'$\\alpha \\supset a \\parallel \\beta$. $\\alpha \\perp \\beta$?', opts:['Да','Нет'], correct:1, explain:'Это $\\parallel$, не $\\perp$.' },
{ q:'$\\alpha \\perp \\beta$, $M \\in \\alpha$, $MK \\perp$ линии $\\alpha \\cap \\beta$ (в $\\alpha$). $MK$:', opts:['$\\parallel \\beta$','$\\perp \\beta$','$\\subset \\beta$'], correct:1, explain:'Свойство.' },
{ q:'$\\alpha \\perp \\beta, \\beta \\perp \\gamma$. $\\alpha \\perp \\gamma$?', opts:['Всегда','Не обязательно','Никогда'], correct:1, explain:'Не транзитивно.' }
];
const I4_CUBE_ITEMS = [
{ q:'Грани $ABCD$ и $ABB_1A_1$:', opts:['Парал.','$\\perp$','$45°$'], correct:1, explain:'Смежные грани куба.' },
{ q:'Грани $ABCD$ и $A_1B_1C_1D_1$:', opts:['Парал.','$\\perp$','Совпадают'], correct:0, explain:'Парал.' },
{ q:'Сколько граней $\\perp$ заданной грани?', opts:['2','3','4','6'], correct:2, explain:'4 смежные.' },
{ q:'$ABB_1A_1$ и $BCC_1B_1$ (соседние бок.):', opts:['Парал.','$\\perp$','$60°$'], correct:1, explain:'$\\perp$.' },
{ q:'$ACC_1A_1$ (диаг.) и $ABCD$:', opts:['Парал.','$\\perp$','$45°$'], correct:1, explain:'$AA_1 \\subset ACC_1A_1, AA_1 \\perp ABCD$.' }
];
/* ===== Bosses ===== */
const BOSS_DEFS = {
b1: { title:'Босс §7 — Прямая ⊥ плоскость', tag:'§7', xp:70, stages:[
{ q:'$l$ перп. одной прямой в $\\alpha$. $l \\perp \\alpha$?', type:'mc', opts:['Да','Нет','Иногда'], correct:1, explain:'Нужны 2 пересек.' },
{ q:'$l \\perp m, l \\perp n, m \\cap n = O \\in \\alpha$. Вывод:', type:'mc', opts:['$l \\perp \\alpha$','$l \\parallel \\alpha$','Никакого'], correct:0, explain:'Признак.' },
{ q:'Через точку — сколько $l \\perp \\alpha$?', type:'input', a:'1', explain:'1.' },
{ q:'$l_1, l_2 \\perp \\alpha$. $l_1, l_2$:', type:'mc', opts:['$\\parallel$','$\\perp$','Скрещ.'], correct:0, explain:'Парал.' },
{ q:'Куб: $CC_1 \\perp ABCD$?', type:'mc', opts:['Да','Нет','Зависит'], correct:0, explain:'Бок. ребро.' }
]},
b2: { title:'Босс §8 — Расстояния', tag:'§8', xp:70, stages:[
{ q:'Куб с ребром $1$: от $A_1$ до $ABCD$?', type:'input', a:'1', explain:'$AA_1=1$.' },
{ q:'Куб с ребром $1$: между $AA_1$ и $CC_1$?', type:'input', a:['√2','sqrt(2)','sqrt2','1.41','1.414'], explain:'$AC = \\sqrt{2}$.' },
{ q:'Перпендикуляр короче наклонной?', type:'mc', opts:['Да','Нет','Иногда'], correct:0, explain:'Да.' },
{ q:'Куб с ребром $2$: между парал. гранями?', type:'input', a:'2', explain:'Ребро = 2.' },
{ q:'Расст. между скрещ. — длина чего?', type:'mc', opts:['Любого','Общего $\\perp$','Касательной','Биссектрисы'], correct:1, explain:'Общий $\\perp$.' }
]},
b3: { title:'Босс §9 — Угол и ТТП', tag:'§9', xp:70, stages:[
{ q:'$HB$ — это:', type:'mc', opts:['Перп.','Наклонная','Проекция'], correct:2, explain:'Проекция.' },
{ q:'Угол наклонной с пл-стью — между:', type:'mc', opts:['Наклонной и перп.','Наклонной и проекцией','Любых'], correct:1, explain:'С проекцией.' },
{ q:'ТТП: $AH \\perp \\alpha, BC \\subset \\alpha, HB \\perp BC \\Rightarrow$', type:'mc', opts:['$AB \\perp BC$','$AB \\parallel BC$','Ничего'], correct:0, explain:'$AB \\perp BC$.' },
{ q:'Куб: $\\tg$ угла $AC_1$ с $ABCD$?', type:'input', a:['1/√2','1/sqrt(2)','√2/2','sqrt(2)/2','0.707'], explain:'$\\frac{1}{\\sqrt{2}}$.' },
{ q:'Угол $a \\subset \\alpha$ с $\\alpha$:', type:'input', a:'0', explain:'$0°$.' }
]},
b4: { title:'Босс §10 — ⊥-плоскости', tag:'§10', xp:75, stages:[
{ q:'Двугранный угол:', type:'mc', opts:['Угол плоск.','2 полупл. + ребро','Угол прямых','Угол наклон.'], correct:1, explain:'2 полупл.' },
{ q:'Линейный угол строится:', type:'mc', opts:['Произвольно','$\\perp$ к ребру','Через высоту','Через диаг.'], correct:1, explain:'$\\perp$.' },
{ q:'$\\alpha \\supset l \\perp \\beta$. Вывод:', type:'mc', opts:['$\\alpha \\parallel \\beta$','$\\alpha \\perp \\beta$','$\\alpha=\\beta$'], correct:1, explain:'Признак.' },
{ q:'Сколько граней куба $\\perp$ заданной?', type:'input', a:'4', explain:'4.' },
{ q:'$ABB_1A_1$ и $CDD_1C_1$:', type:'mc', opts:['Парал.','$\\perp$','Совпадают'], correct:0, explain:'Противоп. бок. грани.' },
{ q:'Линейный угол развёрнутого двугран. = °', type:'input', a:'180', explain:'$180°$.' }
]}
};
const FINAL_BOSS_DEFS = {
f1: { title:'Финал · Прямая ⊥ пл-сть', tag:'Финал R3', xp:30, stages:[
{ q:'Признак $l \\perp \\alpha$: $l \\perp$ 2 $?$ прямым.', type:'mc', opts:['Парал.','Пересек.','Произв.'], correct:1, explain:'Пересек.' },
{ q:'Через точку — $l \\perp$ пл-сти:', type:'input', a:'1', explain:'1.' },
{ q:'$l \\perp \\alpha, a \\subset \\alpha$. $l, a$:', type:'mc', opts:['$\\parallel$','$\\perp$','Скрещ.'], correct:1, explain:'$\\perp$.' },
{ q:'Куб: $DD_1 \\perp ABCD$?', type:'mc', opts:['Да','Нет','Зависит'], correct:0, explain:'Да.' }
]},
f2: { title:'Финал · Расстояния', tag:'Финал R3', 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:['Любом','Общем $\\perp$','Биссектрисе','Касат.'], correct:1, explain:'$\\perp$.' },
{ q:'Перп. … любой наклонной из той же т.', type:'mc', opts:['Длиннее','Короче','Равен'], correct:1, explain:'Короче.' }
]},
f3: { title:'Финал · Угол + ТТП', tag:'Финал R3', xp:35, stages:[
{ q:'Проекция $AB$ на $\\alpha$ ($AH \\perp \\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:'$\\perp$.' },
{ q:'Куб (1): $\\tg$ угла $AC_1$ с $ABCD$:', type:'input', a:['1/√2','1/sqrt(2)','√2/2','sqrt(2)/2','0.707'], explain:'$\\frac{1}{\\sqrt{2}}$.' }
]},
f4: { title:'Финал · ⊥-плоскости', tag:'Финал R3', xp:30, stages:[
{ q:'Двугран. угол:', type:'mc', opts:['Угол наклон.','2 полупл. + ребро','Угол прямых'], correct:1, explain:'2 полупл.' },
{ q:'Признак $\\alpha \\perp \\beta$: $\\alpha$ содержит $?$ к $\\beta$.', type:'mc', opts:['Парал. прямую','$\\perp$ прямую','Любую'], correct:1, explain:'$\\perp$.' },
{ q:'Граней куба $\\perp$ заданной:', type:'input', a:'4', explain:'4.' },
{ q:'Линейный угол от точки на ребре зависит?', type:'mc', opts:['Да','Нет'], correct:1, explain:'Нет.' }
]},
f5: { title:'Финал · Сборная', tag:'Финал R3', xp:45, stages:[
{ q:'Куб (1): $|AC_1|$ (диагональ куба)?', type:'input', a:['√3','sqrt(3)','sqrt3','1.73','1.732'], explain:'$\\sqrt{3}$.' },
{ q:'Куб (1): $\\sin$ угла $AC_1$ с $ABCD$?', type:'input', a:['1/√3','1/sqrt(3)','√3/3','sqrt(3)/3','0.577'], explain:'$\\frac{1}{\\sqrt{3}}$.' },
{ q:'$l_1 \\perp \\alpha, l_2 \\perp \\alpha \\Rightarrow$', 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:'ТТП.' },
{ q:'Куб: $BDD_1B_1$ и $ABCD$:', type:'mc', opts:['Парал.','$\\perp$','$45°$'], correct:1, explain:'$BB_1 \\subset BDD_1B_1, BB_1 \\perp ABCD$.' }
]}
};
function init(){
loadProgress(); initTheme(); buildParaSelector(); goTo('p1'); refreshProgressUI();
if(!STATE.achievements.has('start')) achievement('start');
}
if(document.readyState === 'loading') document.addEventListener('DOMContentLoaded', init);
else init();
</script>
</body>
</html>