Files
Learn_System/frontend/textbooks/geometry_10_r4.html
T

1282 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 · Раздел 4 · «Координаты и векторы»</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:#d97706; --pri2:#b45309; --pri-soft:#fef3c7;
--acc:#f59e0b; --acc2:#d97706; --acc-soft:#fde68a;
--ok:#10b981; --ok-bg:#d1fae5; --warn:#f59e0b; --warn-bg:#fef3c7;
--bad:#ef4444; --fail:#dc2626; --fail-bg:#fee2e2;
}
.dark{--bg:#170c02; --card:#1c0f04; --card-soft:#241408; --text:#fef3c7; --ink:#fef3c7; --muted:#fde68a; --border:#78350f}
*{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,#78350f 0%,#d97706 55%,#fcd34d 100%);color:#fff;padding:46px 22px 30px;overflow:hidden;border-bottom:2px solid rgba(254,243,199,.2);min-height:130px}
.hdr::before{content:'РАЗДЕЛ 4';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:'\2192';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,#dc2626,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,#fef3c7,#fde68a)}
.psel-card.final .psel-num{color:#92400e}
.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:160px}
.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(--acc-soft),var(--pri-soft));border:1.5px solid var(--acc);border-radius:12px;padding:14px;margin-bottom:14px}
.xp-card-title{font-size:.68rem;font-weight:800;color:var(--acc2);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:var(--acc2);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(--acc),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(--acc));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}
.formula-plate{background:linear-gradient(135deg,var(--pri-soft),var(--acc-soft));border:1.5px solid var(--pri);border-radius:13px;padding:14px 18px;margin:12px 0;font-size:.96rem;line-height:1.8;color:var(--pri2);text-align:center}
.formula-plate b{color:var(--pri)}
.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:160px;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 · Раздел 4</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> Начать § 11</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="(x;y;z)"><div class="sec-header"><span class="sec-num">§ 11</span><h2 class="sec-h">Координаты в пространстве</h2></div><div id="p1-body"></div></section>
<section id="sec-p2" class="sec" data-watermark="&#8347;"><div class="sec-header"><span class="sec-num">§ 12</span><h2 class="sec-h">Векторы. Действия над векторами</h2></div><div id="p2-body"></div></section>
<section id="sec-p3" class="sec" data-watermark="&#8226;"><div class="sec-header"><span class="sec-num">§ 13</span><h2 class="sec-h">Скалярное произведение</h2></div><div id="p3-body"></div></section>
<section id="sec-p4" class="sec" data-watermark="&#8704;"><div class="sec-header"><span class="sec-num">§ 14</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» · Раздел 4 · «Координаты и векторы» · 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-r4';
const PARAS = [
{ id:'p1', num:'§ 11', name:'Координаты', sub:'ПДСК · расстояние · середина' },
{ id:'p2', num:'§ 12', name:'Векторы', sub:'$\\vec{a}+\\vec{b}, k\\vec{a}$, базис' },
{ id:'p3', num:'§ 13', name:'Скаляр. произв.', sub:'$\\vec{a}\\cdot\\vec{b} = x_1x_2+y_1y_2+z_1z_2$' },
{ id:'p4', num:'§ 14', name:'Применение метода', sub:'Куб в координатах · углы' },
{ id:'final', num:'&#9733;', name:'Финал курса', sub:'Мега-ачивка stereo10_master', 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:'§11 — координаты освоены',
p2_done:'§12 — векторы освоены',
p3_done:'§13 — скалярное произведение освоено',
p4_done:'§14 — применение метода освоено',
start:'Начало раздела 4!',
r4_done:'Раздел 4 пройден!',
master:'&#9733; Геометрия 10 пройдена!'
};
function loadProgress(){
try{
const s=localStorage.getItem('geometry10_r4_progress'); if(s) Object.assign(STATE.progress, JSON.parse(s));
const a=localStorage.getItem('geometry10_r4_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_r4_progress', JSON.stringify(STATE.progress));
localStorage.setItem('geometry10_r4_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-r4-'+(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);
});
if(window.renderMathInElement) try{ renderMath(g); }catch(e){}
}
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:'Шпаргалка § 11', rows:[['Точка','$(x;y;z)$'],['$|AB|$','$\\sqrt{(\\Delta x)^2+(\\Delta y)^2+(\\Delta z)^2}$'],['$|OM|$','$\\sqrt{x^2+y^2+z^2}$'],['Середина','$\\left(\\frac{x_A+x_B}{2}; \\ldots\\right)$']]},
p2:{title:'Шпаргалка § 12', rows:[['$\\vec{AB}$','$(x_B-x_A; y_B-y_A; z_B-z_A)$'],['$|\\vec{a}|$','$\\sqrt{x^2+y^2+z^2}$'],['$\\vec{a}+\\vec{b}$','$(x_1+x_2; y_1+y_2; z_1+z_2)$'],['$k\\vec{a}$','$(kx; ky; kz)$'],['Базис','$\\vec{i}, \\vec{j}, \\vec{k}$']]},
p3:{title:'Шпаргалка § 13', rows:[['Опред.','$\\vec{a}\\cdot\\vec{b} = |\\vec{a}||\\vec{b}|\\cos\\varphi$'],['В коорд.','$x_1x_2+y_1y_2+z_1z_2$'],['$\\vec{a}\\cdot\\vec{a}$','$|\\vec{a}|^2$'],['$\\perp$','$\\vec{a}\\cdot\\vec{b}=0$'],['$\\cos\\varphi$','$\\frac{\\vec{a}\\cdot\\vec{b}}{|\\vec{a}||\\vec{b}|}$']]},
p4:{title:'Шпаргалка § 14', rows:[['Куб (1) $A(0;0;0)$','$C_1(1;1;1)$, $|AC_1|=\\sqrt{3}$'],['Уравн. пл-сти','$Ax+By+Cz+D=0$'],['$\\rho(M,\\alpha)$','$\\frac{|Ax_0+By_0+Cz_0+D|}{\\sqrt{A^2+B^2+C^2}}$'],['Угол прямых','$\\cos = \\frac{|\\vec{a}\\cdot\\vec{b}|}{|\\vec{a}||\\vec{b}|}$']]},
final:{title:'Финал курса', rows:[['§§ 11-14','4 параграфа'],['Боссов','4 интегр.'],['Мега-ачивка','stereo10_master'],['Бонус','+120 XP + 200 XP мега']]}
};
const TIPS=[
{sec:'p1',html:'§ 11 — формула расстояния = 3D-Пифагор: $\\sqrt{\\Delta x^2 + \\Delta y^2 + \\Delta z^2}$.'},
{sec:'p2',html:'§ 12 — координаты $\\vec{AB} = $ конец $-$ начало. Коллинеарность: $\\vec{a} = k\\vec{b}$.'},
{sec:'p3',html:'§ 13 — главное: $\\vec{a}\\cdot\\vec{b}=0 \\Leftrightarrow \\vec{a}\\perp\\vec{b}$. Знак показывает тип угла.'},
{sec:'p4',html:'§ 14 — куб в координатах ($A$ в нуле) даёт простые координаты вершин. Углы — через $\\cos\\varphi$.'},
{sec:'final',html:'4 босса + проверка всех 4 разделов. После — мега-ачивка stereo10_master + 200 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:'\xA711',p2:'\xA712',p3:'\xA713',p4:'\xA714',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,'.').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); }
/* ===== §11 (p1) ===== */
function buildP1(){
const box = document.getElementById('p1-body'); if(!box) return;
let html = '';
html += makeCard('theory', 'ПДСК в пространстве', '§ 11.1',
'<p><b>ПДСК</b> — три попарно перпендикулярные оси $Ox$, $Oy$, $Oz$ с общим началом $O$.</p>'
+ '<p>Точка $M$ задаётся тремя координатами $(x;y;z)$ — её проекции на оси.</p>'
+ '<div class="viz3d" id="viz11-coords"></div>'
+ '<p>Координатные плоскости: $Oxy$ ($z=0$), $Oxz$ ($y=0$), $Oyz$ ($x=0$). Разбивают пространство на 8 октантов.</p>');
html += makeCard('rule', 'Расстояние между точками', '§ 11.2',
'<p>Формула — 3D-аналог теоремы Пифагора.</p>'
+ '<div class="formula-plate">$A(x_1;y_1;z_1), B(x_2;y_2;z_2) \\Rightarrow$<br>$|AB| = \\sqrt{(\\Delta x)^2 + (\\Delta y)^2 + (\\Delta z)^2}$</div>'
+ '<div class="viz3d" id="viz11-dist"></div>'
+ '<p>Расстояние до начала: $|OM| = \\sqrt{x^2+y^2+z^2}$.</p>');
html += makeCard('example', 'Координаты середины отрезка', '§ 11.3',
'<div class="formula-plate">$M = \\left(\\dfrac{x_A+x_B}{2}; \\dfrac{y_A+y_B}{2}; \\dfrac{z_A+z_B}{2}\\right)$</div>'
+ '<p><b>Пример:</b> $A(2;4;6), B(8;6;4) \\Rightarrow M(5;5;5)$.</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-coord-score">0</b> / 6.</div>'
+ '<div id="i1-coord-q" style="margin:8px 0"></div><div class="opts-row" id="i1-coord-opts"></div><div class="feedback" id="i1-coord-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">Ответы: число или sqrt(...). Решено: <b id="i1-dist-score">0</b> / 5.</div>'
+ '<div id="i1-dist-q" style="margin:8px 0"></div><div class="actions"><input class="tinp" id="i1-dist-inp" placeholder="ответ"><button class="btn primary" id="i1-dist-go">Проверить</button></div><div class="feedback" id="i1-dist-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">Формат: x;y;z. Решено: <b id="i1-mid-score">0</b> / 5.</div>'
+ '<div id="i1-mid-q" style="margin:8px 0"></div><div class="actions"><input class="tinp" id="i1-mid-inp" placeholder="x;y;z"><button class="btn primary" id="i1-mid-go">Проверить</button></div><div class="feedback" id="i1-mid-fb"></div></div>';
html += '<div id="boss-1-slot"></div>';
html += readButton('p1');
html += secNavFor('p1');
box.innerHTML = html;
ensureStereo(()=>buildCoordSystem());
ensureStereo(()=>buildDistanceFormula());
runQuizMC('i1-coord', I1_COORD_ITEMS, 12, 'где точка');
runQuizInput('i1-dist', I1_DIST_ITEMS, 14, 'расстояние');
runQuizInput('i1-mid', I1_MID_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);
}
/* ===== §12 (p2) ===== */
function buildP2(){
const box = document.getElementById('p2-body'); if(!box) return;
let html = '';
html += makeCard('theory', 'Вектор и его координаты', '§ 12.1',
'<p><b>Вектор</b> — направленный отрезок $\\vec{AB}$ с началом $A$ и концом $B$. Длина: $|\\vec{AB}| = |AB|$.</p>'
+ '<p>Два вектора <b>равны</b>, если сонаправлены и имеют равные длины.</p>'
+ '<p>Если $A(x_1;y_1;z_1), B(x_2;y_2;z_2)$, то:</p>'
+ '<div class="formula-plate">$\\vec{AB} = (x_2-x_1; y_2-y_1; z_2-z_1)$</div>');
html += makeCard('rule', 'Сложение и умножение на число', '§ 12.2',
'<p><b>Сложение</b> (правило треугольника): $\\vec{AB} + \\vec{BC} = \\vec{AC}$.</p>'
+ '<p>В координатах: $(x_1;y_1;z_1) + (x_2;y_2;z_2) = (x_1+x_2; y_1+y_2; z_1+z_2)$.</p>'
+ '<p><b>Умножение на число:</b> $k\\vec{a} = (kx; ky; kz)$. Длина: $|k\\vec{a}| = |k|\\cdot|\\vec{a}|$.</p>'
+ '<div class="viz3d" id="viz12-add"></div>');
html += makeCard('example', 'Базис $\\vec{i}, \\vec{j}, \\vec{k}$', '§ 12.3',
'<p>Три единичных вектора вдоль осей $Ox, Oy, Oz$ образуют <b>базис</b>.</p>'
+ '<div class="formula-plate">$\\vec{a} = x\\vec{i} + y\\vec{j} + z\\vec{k} \\Leftrightarrow \\vec{a}=(x;y;z)$</div>'
+ '<div class="viz3d" id="viz12-basis"></div>'
+ '<p><b>Коллинеарность:</b> $\\vec{a} \\parallel \\vec{b} \\Leftrightarrow \\vec{a} = k\\vec{b}$, т.е. $\\dfrac{x_1}{x_2} = \\dfrac{y_1}{y_2} = \\dfrac{z_1}{z_2}$.</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">Формат: x;y;z. Решено: <b id="i2-add-score">0</b> / 5.</div>'
+ '<div id="i2-add-q" style="margin:8px 0"></div><div class="actions"><input class="tinp" id="i2-add-inp" placeholder="x;y;z"><button class="btn primary" id="i2-add-go">Проверить</button></div><div class="feedback" id="i2-add-fb"></div></div>';
html += '<div class="wg"><div class="wg-header"><span class="wg-badge">Инт. 2</span><span class="wg-title">Координаты $\\vec{AB}$ и длина</span></div>'
+ '<div class="wg-help">Решено: <b id="i2-coord-score">0</b> / 5.</div>'
+ '<div id="i2-coord-q" style="margin:8px 0"></div><div class="actions"><input class="tinp" id="i2-coord-inp" placeholder="ответ"><button class="btn primary" id="i2-coord-go">Проверить</button></div><div class="feedback" id="i2-coord-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-coll-score">0</b> / 5.</div>'
+ '<div id="i2-coll-q" style="margin:8px 0"></div><div class="opts-row" id="i2-coll-opts"></div><div class="feedback" id="i2-coll-fb"></div></div>';
html += '<div id="boss-2-slot"></div>';
html += readButton('p2');
html += secNavFor('p2');
box.innerHTML = html;
ensureStereo(()=>buildVectorAdd());
ensureStereo(()=>buildVectorBasis());
runQuizInput('i2-add', I2_ADD_ITEMS, 14, 'действия');
runQuizInput('i2-coord', I2_COORD_ITEMS, 14, 'AB длина');
runQuizMC('i2-coll', I2_COLL_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);
}
/* ===== §13 (p3) ===== */
function buildP3(){
const box = document.getElementById('p3-body'); if(!box) return;
let html = '';
html += makeCard('theory', 'Скалярное произведение', '§ 13.1',
'<p>Число, равное произведению длин векторов на косинус угла между ними:</p>'
+ '<div class="formula-plate"><b>Геометрически:</b> $\\vec{a}\\cdot\\vec{b} = |\\vec{a}|\\cdot|\\vec{b}|\\cdot\\cos\\varphi$<br><b>В координатах:</b> $\\vec{a}\\cdot\\vec{b} = x_1x_2 + y_1y_2 + z_1z_2$</div>'
+ '<div class="viz3d" id="viz13-dot"></div>');
html += makeCard('rule', 'Свойства', '§ 13.2',
'<ul>'
+ '<li>$\\vec{a}\\cdot\\vec{a} = |\\vec{a}|^2$</li>'
+ '<li>$\\vec{a}\\cdot\\vec{b} = \\vec{b}\\cdot\\vec{a}$ (коммутативность)</li>'
+ '<li>$(k\\vec{a})\\cdot\\vec{b} = k(\\vec{a}\\cdot\\vec{b})$</li>'
+ '<li>$(\\vec{a}+\\vec{b})\\cdot\\vec{c} = \\vec{a}\\cdot\\vec{c} + \\vec{b}\\cdot\\vec{c}$</li>'
+ '</ul>'
+ '<p><b>Перпендикулярность:</b> $\\vec{a} \\perp \\vec{b} \\Leftrightarrow \\vec{a}\\cdot\\vec{b} = 0$.</p>');
html += makeCard('example', 'Угол и знак произведения', '§ 13.3',
'<div class="formula-plate">$\\cos\\varphi = \\dfrac{\\vec{a}\\cdot\\vec{b}}{|\\vec{a}|\\cdot|\\vec{b}|}$</div>'
+ '<ul>'
+ '<li>$\\vec{a}\\cdot\\vec{b} > 0 \\Leftrightarrow \\varphi < 90°$ (острый);</li>'
+ '<li>$\\vec{a}\\cdot\\vec{b} = 0 \\Leftrightarrow \\varphi = 90°$;</li>'
+ '<li>$\\vec{a}\\cdot\\vec{b} < 0 \\Leftrightarrow \\varphi > 90°$ (тупой).</li>'
+ '</ul>');
html += '<div class="wg"><div class="wg-header"><span class="wg-badge">Инт. 1</span><span class="wg-title">Вычисли $\\vec{a}\\cdot\\vec{b}$</span></div>'
+ '<div class="wg-help">Решено: <b id="i3-calc-score">0</b> / 5.</div>'
+ '<div id="i3-calc-q" style="margin:8px 0"></div><div class="actions"><input class="tinp" id="i3-calc-inp" placeholder="число"><button class="btn primary" id="i3-calc-go">Проверить</button></div><div class="feedback" id="i3-calc-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="i3-perp-score">0</b> / 5.</div>'
+ '<div id="i3-perp-q" style="margin:8px 0"></div><div class="opts-row" id="i3-perp-opts"></div><div class="feedback" id="i3-perp-fb"></div></div>';
html += '<div class="wg"><div class="wg-header"><span class="wg-badge">Инт. 3</span><span class="wg-title">$\\cos$ угла</span></div>'
+ '<div class="wg-help">Решено: <b id="i3-angle-score">0</b> / 4.</div>'
+ '<div id="i3-angle-q" style="margin:8px 0"></div><div class="actions"><input class="tinp" id="i3-angle-inp" placeholder="например 1/2"><button class="btn primary" id="i3-angle-go">Проверить</button></div><div class="feedback" id="i3-angle-fb"></div></div>';
html += '<div id="boss-3-slot"></div>';
html += readButton('p3');
html += secNavFor('p3');
box.innerHTML = html;
ensureStereo(()=>buildDotProduct());
runQuizInput('i3-calc', I3_CALC_ITEMS, 14, 'скаляр');
runQuizMC('i3-perp', I3_PERP_ITEMS, 12, 'перп.');
runQuizInput('i3-angle', I3_ANGLE_ITEMS, 14, 'cos');
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);
}
/* ===== §14 (p4) ===== */
function buildP4(){
const box = document.getElementById('p4-body'); if(!box) return;
let html = '';
html += makeCard('algo', 'Алгоритм координатно-векторного метода', '§ 14.1',
'<ol style="margin-left:22px;line-height:1.7">'
+ '<li>Ввести ПДСК так, чтобы основные точки имели простые координаты.</li>'
+ '<li>Выписать координаты всех нужных точек.</li>'
+ '<li>Найти координаты векторов: $\\vec{AB} = B - A$.</li>'
+ '<li>Применить формулы $|\\vec{AB}|$, $\\vec{a}\\cdot\\vec{b}$, $\\cos\\varphi$.</li>'
+ '</ol>');
html += makeCard('example', 'Куб в координатах', '§ 14.2',
'<p>Поместим куб с ребром 1: $A(0;0;0)$, $B(1;0;0)$, $C(1;1;0)$, $D(0;1;0)$, $A_1(0;0;1)$, $B_1(1;0;1)$, $C_1(1;1;1)$, $D_1(0;1;1)$.</p>'
+ '<div class="viz3d" id="viz14-cube"></div>'
+ '<p>Через координаты легко вычисляются:</p>'
+ '<ul>'
+ '<li>Расстояние между любыми точками куба;</li>'
+ '<li>Угол между любыми векторами (диагоналями, рёбрами);</li>'
+ '<li>Перпендикулярность через $\\vec{a}\\cdot\\vec{b}=0$.</li>'
+ '</ul>'
+ '<p><b>Пример:</b> диагональ $\\vec{AC_1}=(1;1;1)$, $|\\vec{AC_1}|=\\sqrt{3}$.</p>');
html += makeCard('rule', 'Углы и расстояния через координаты', '§ 14.3',
'<div class="formula-plate"><b>Угол между прямыми:</b> $\\cos\\varphi = \\dfrac{|\\vec{a}\\cdot\\vec{b}|}{|\\vec{a}|\\cdot|\\vec{b}|}$<br><b>Угол прямой и плоскости:</b> $\\sin\\varphi = \\dfrac{|\\vec{a}\\cdot\\vec{n}|}{|\\vec{a}|\\cdot|\\vec{n}|}$<br><b>Расст. от точки до плоск.:</b> $\\rho = \\dfrac{|Ax_0+By_0+Cz_0+D|}{\\sqrt{A^2+B^2+C^2}}$</div>');
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-cube-score">0</b> / 5.</div>'
+ '<div id="i4-cube-q" style="margin:8px 0"></div><div class="actions"><input class="tinp" id="i4-cube-inp" placeholder="ответ"><button class="btn primary" id="i4-cube-go">Проверить</button></div><div class="feedback" id="i4-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="i4-ang-score">0</b> / 4.</div>'
+ '<div id="i4-ang-q" style="margin:8px 0"></div><div class="actions"><input class="tinp" id="i4-ang-inp" placeholder="например 1/2 или 90"><button class="btn primary" id="i4-ang-go">Проверить</button></div><div class="feedback" id="i4-ang-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-meth-score">0</b> / 5.</div>'
+ '<div id="i4-meth-q" style="margin:8px 0"></div><div class="opts-row" id="i4-meth-opts"></div><div class="feedback" id="i4-meth-fb"></div></div>';
html += '<div id="boss-4-slot"></div>';
html += readButton('p4');
html += secNavFor('p4');
box.innerHTML = html;
ensureStereo(()=>buildCubeInCoords());
runQuizInput('i4-cube', I4_CUBE_ITEMS, 16, 'куб коорд');
runQuizInput('i4-ang', I4_ANG_ITEMS, 16, 'угол скаляр');
runQuizMC('i4-meth', I4_METH_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 + master ===== */
function buildFinal(){
const box = document.getElementById('final-body'); if(!box) return;
let html = '';
html += '<div class="stub-note"><h3>Финал курса · 4 интегрированных босса</h3><p>Победи 4 финальных босса (координаты, векторы, скаляр., сборная). После — ачивка stereo10_r4_master + 120 XP.</p><p style="margin-top:8px;color:var(--pri2);font-weight:800">&#9733; Если все 4 ачивки разделов получены — выдаётся <b>МЕГА-АЧИВКА stereo10_master</b> + 200 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="celebration" style="display:none;margin-top:18px"></div>';
html += secNavFor('final');
box.innerHTML = html;
['f1','f2','f3','f4'].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'].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';
const ach = JSON.parse(localStorage.getItem('geometry10_achievements')||'[]');
if(ach.indexOf('stereo10_r4_master') < 0){
ach.push('stereo10_r4_master');
localStorage.setItem('geometry10_achievements', JSON.stringify(ach));
addXp(120, 'r4-master');
achievement('r4_done', 'Раздел 4 пройден!');
}
const hasAll = ['stereo10_r1_master','stereo10_r2_master','stereo10_r3_master','stereo10_r4_master'].every(a=>ach.indexOf(a)>=0);
let megaHtml = '';
if(hasAll && ach.indexOf('stereo10_master') < 0){
ach.push('stereo10_master');
localStorage.setItem('geometry10_achievements', JSON.stringify(ach));
addXp(200, 'master');
achievement('master', '&#9733; Геометрия 10 пройдена!');
megaHtml = '<div style="margin-top:18px;padding:24px;background:linear-gradient(135deg,#fef3c7,#fde68a);border:2px solid #f59e0b;border-radius:14px;text-align:center"><div style="font-family:Unbounded,sans-serif;font-size:1.5rem;font-weight:900;color:#92400e;margin-bottom:10px">&#9733;&#9733;&#9733; ГЕОМЕТРИЯ 10 ПРОЙДЕНА! &#9733;&#9733;&#9733;</div><div style="font-size:.95rem;color:#78350f;margin-bottom:14px">Все 4 раздела освоены. Стереометрия — твоя сила. Главная ачивка курса <code style="background:#fbbf24;padding:2px 8px;border-radius:5px">stereo10_master</code> + 200 XP мега-бонус.</div></div>';
} else if(hasAll){
megaHtml = '<div style="margin-top:18px;padding:18px;background:linear-gradient(135deg,#fef3c7,#fde68a);border:2px solid #f59e0b;border-radius:14px;font-family:Unbounded,sans-serif;color:#92400e;font-weight:800">&#9733; Главная ачивка stereo10_master уже получена. Геометрия 10 пройдена!</div>';
} else {
const missing = [];
if(ach.indexOf('stereo10_r1_master')<0) missing.push('R1');
if(ach.indexOf('stereo10_r2_master')<0) missing.push('R2');
if(ach.indexOf('stereo10_r3_master')<0) missing.push('R3');
if(missing.length) megaHtml = '<div style="margin-top:18px;padding:14px 18px;background:var(--card-soft);border:1px dashed var(--pri);border-radius:11px;font-size:.88rem;color:var(--muted)">До главной ачивки stereo10_master осталось пройти финалы разделов: <b>'+missing.join(', ')+'</b>.</div>';
}
cel.innerHTML = '<div class="boss-card solved" style="background:linear-gradient(135deg,#fef3c7,#fde68a);text-align:center"><div style="font-family:Unbounded,sans-serif;font-size:1.4rem;font-weight:900;color:#92400e;margin-bottom:8px">&#9733; Раздел 4 пройден! &#9733;</div><div style="color:var(--text);margin-bottom:14px">Все 4 финальных босса побеждены. Координаты и векторы — освоены.</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">+ 120 XP бонус + ачивка stereo10_r4_master</div></div>' + megaHtml;
bumpProgress('final', 100);
}
function loadBossState(key){ try{ return JSON.parse(localStorage.getItem('geometry10_r4_'+key)||'null'); }catch(e){ return null; } }
function saveBossState(key, state){ try{ localStorage.setItem('geometry10_r4_'+key, JSON.stringify(state)); }catch(e){} }
function runQuizMC(id, items, xpReward){
const state = JSON.parse(localStorage.getItem('geometry10_r4_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_r4_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_r4_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_r4_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 ===== */
/* === §11: ПДСК с тикaми и точкой M(2;1;3) === */
function buildCoordSystem(){
const S = window.STEREO3D; if(!S) return;
const sc = new S.Scene(460,340,{view:'CABINET',scale:48});
/* Оси с длинными стрелками */
sc.addArrow([0,0,0],[3.4,0,0],{color:'#dc2626',width:2.6});
sc.addArrow([0,0,0],[0,3.4,0],{color:'#16a34a',width:2.6});
sc.addArrow([0,0,0],[0,0,3.4],{color:'#1e3a8a',width:2.6});
sc.addLabel('x',[3.4,0,0],{dx:14,dy:8,color:'#dc2626',fontSize:16,anchor:'start'});
sc.addLabel('y',[0,3.4,0],{dx:14,dy:8,color:'#16a34a',fontSize:16,anchor:'start'});
sc.addLabel('z',[0,0,3.4],{dx:-10,dy:-4,color:'#1e3a8a',fontSize:16,anchor:'end'});
/* Тики 1, 2, 3 на каждой оси (короткие штрихи + цифры) */
for(let i = 1; i <= 3; i++){
sc.addEdge([i,-0.08,0],[i,0.08,0],{stroke:'#dc2626',width:1.6});
sc.addLabel(String(i),[i,0,0],{dx:-3,dy:18,color:'#dc2626',fontSize:11,anchor:'middle'});
sc.addEdge([-0.08,i,0],[0.08,i,0],{stroke:'#16a34a',width:1.6});
sc.addLabel(String(i),[0,i,0],{dx:8,dy:16,color:'#16a34a',fontSize:11,anchor:'start'});
sc.addEdge([-0.1,0,i],[0.1,0,i],{stroke:'#1e3a8a',width:1.6});
sc.addLabel(String(i),[0,0,i],{dx:-12,dy:4,color:'#1e3a8a',fontSize:11,anchor:'end'});
}
sc.addVertex([0,0,0],'O',{dx:-20,dy:18,color:'#0b1d33',fontSize:14});
/* Точка M(2;1;3) — числа совпадают с реальными координатами */
const M = [2,1,3];
/* Пунктиры из M к плоскости Oxy (вертикально вниз), затем к осям */
sc.addEdge(M,[2,1,0],{stroke:'#a78bfa',width:1.4,dash:'4 3'});
sc.addEdge([2,1,0],[2,0,0],{stroke:'#a78bfa',width:1.4,dash:'4 3'});
sc.addEdge([2,1,0],[0,1,0],{stroke:'#a78bfa',width:1.4,dash:'4 3'});
sc.addEdge(M,[0,0,3],{stroke:'#a78bfa',width:1.4,dash:'4 3'});
/* Маленькие маркеры на проекциях */
sc.addVertex([2,1,0],'',{r:2.5,fill:'#a78bfa'});
/* Точка M жирная */
sc.addVertex(M,'M (2; 1; 3)',{dx:12,dy:-8,color:'#7c3aed',fontSize:15,r:5,fill:'#7c3aed'});
const el = document.getElementById('viz11-coords'); if(el) el.innerHTML = sc.render();
}
/* === §11: Расстояние между точками (3D-Пифагор) === */
function buildDistanceFormula(){
const S = window.STEREO3D; if(!S) return;
const sc = new S.Scene(460,320,{view:'CABINET',scale:50});
/* Серые оси для контекста */
sc.addArrow([0,0,0],[3.0,0,0],{color:'#cbd5e1',width:1.4});
sc.addArrow([0,0,0],[0,3.0,0],{color:'#cbd5e1',width:1.4});
sc.addArrow([0,0,0],[0,0,2.6],{color:'#cbd5e1',width:1.4});
sc.addLabel('x',[3.0,0,0],{dx:10,dy:8,color:'#94a3b8',fontSize:12,anchor:'start'});
sc.addLabel('y',[0,3.0,0],{dx:10,dy:8,color:'#94a3b8',fontSize:12,anchor:'start'});
sc.addLabel('z',[0,0,2.6],{dx:-8,dy:-2,color:'#94a3b8',fontSize:12,anchor:'end'});
/* A(0;0;0), B(2;2;2) */
const A = [0,0,0];
const B = [2,2,2];
/* Параллелепипед-подсказка: рёбра вдоль осей с цветовой кодировкой */
sc.addEdge(A,[2,0,0],{stroke:'#dc2626',width:2.8});
sc.addEdge([2,0,0],[2,2,0],{stroke:'#16a34a',width:2.8});
sc.addEdge([2,2,0],B,{stroke:'#1e3a8a',width:2.8});
/* Бледные грани куба-подсказки */
sc.addEdge(A,[0,2,0],{stroke:'#94a3b8',width:1,dash:'3 3'});
sc.addEdge([0,2,0],[2,2,0],{stroke:'#94a3b8',width:1,dash:'3 3'});
sc.addEdge(A,[0,0,2],{stroke:'#94a3b8',width:1,dash:'3 3'});
sc.addEdge([0,0,2],B,{stroke:'#94a3b8',width:1,dash:'3 3'});
sc.addEdge([2,0,0],[2,0,2],{stroke:'#94a3b8',width:1,dash:'3 3'});
sc.addEdge([2,0,2],B,{stroke:'#94a3b8',width:1,dash:'3 3'});
/* Главная диагональ */
sc.addEdge(A,B,{stroke:'#7c3aed',width:4});
/* Вершины */
sc.addVertex(A,'A',{dx:-18,dy:16,color:'#0b1d33',fontSize:15,r:5,fill:'#0b1d33'});
sc.addVertex(B,'B',{dx:12,dy:-4,color:'#7c3aed',fontSize:15,r:5,fill:'#7c3aed'});
/* Подписи Δx, Δy, Δz на цветных рёбрах */
sc.addLabel('Δx=2',[1,0,0],{dx:0,dy:18,color:'#dc2626',fontSize:13,anchor:'middle'});
sc.addLabel('Δy=2',[2,1,0],{dx:14,dy:6,color:'#16a34a',fontSize:13,anchor:'start'});
sc.addLabel('Δz=2',[2,2,1],{dx:14,dy:0,color:'#1e3a8a',fontSize:13,anchor:'start'});
/* Маркер прямого угла в B (показывая 3D-Пифагор) */
sc.addRightAngleMark([2,2,0],[1.6,2,0],[2,2,0.4],{color:'#d97706',width:1.6,size:0.20});
const el = document.getElementById('viz11-dist'); if(el) el.innerHTML = sc.render();
}
/* === §12: Сложение векторов — параллелограмм + треугольник === */
function buildVectorAdd(){
const S = window.STEREO3D; if(!S) return;
const sc = new S.Scene(460,260,{view:'CABINET',scale:60});
const O = [0,0,0];
const A = [1.8,0.2,0]; /* конец a */
const B = [0.5,1.5,0]; /* конец b (из O) */
const C = [A[0]+B[0],A[1]+B[1],A[2]+B[2]]; /* конец a+b */
/* Параллелограмм-подсказка: вторая копия b из O и a' из B */
sc.addArrow(O,B,{color:'#16a34a',width:2,label:'',lx:0,ly:0});
sc.addEdge(B,C,{stroke:'#94a3b8',width:1.4,dash:'4 3'});
/* Треугольник: a из O, b' из конца a */
sc.addArrow(O,A,{color:'#dc2626',width:3.2,label:'a',lx:-4,ly:20});
sc.addArrow(A,C,{color:'#16a34a',width:3.2,label:'b',lx:14,ly:0});
/* Сумма a+b — фиолетовая, толстая, диагональ */
sc.addArrow(O,C,{color:'#7c3aed',width:4,label:'a + b',lx:-44,ly:8});
sc.addVertex(O,'O',{dx:-18,dy:18,color:'#0b1d33',fontSize:14,r:4,fill:'#0b1d33'});
/* Подпись правила */
sc.addLabel('правило треугольника',[1.5,0.85,0],{dx:0,dy:30,color:'#475569',fontSize:11,anchor:'middle'});
const el = document.getElementById('viz12-add'); if(el) el.innerHTML = sc.render();
}
/* === §12: Базис i,j,k и разложение вектора === */
function buildVectorBasis(){
const S = window.STEREO3D; if(!S) return;
const sc = new S.Scene(460,320,{view:'CABINET',scale:60});
/* Бледные оси */
sc.addArrow([0,0,0],[2.8,0,0],{color:'#cbd5e1',width:1.2});
sc.addArrow([0,0,0],[0,2.8,0],{color:'#cbd5e1',width:1.2});
sc.addArrow([0,0,0],[0,0,2.8],{color:'#cbd5e1',width:1.2});
sc.addLabel('x',[2.8,0,0],{dx:10,dy:8,color:'#94a3b8',fontSize:12,anchor:'start'});
sc.addLabel('y',[0,2.8,0],{dx:10,dy:8,color:'#94a3b8',fontSize:12,anchor:'start'});
sc.addLabel('z',[0,0,2.8],{dx:-8,dy:-2,color:'#94a3b8',fontSize:12,anchor:'end'});
/* Базисные единичные векторы — яркие */
sc.addArrow([0,0,0],[1,0,0],{color:'#dc2626',width:3.4,label:'i',lx:-4,ly:22});
sc.addArrow([0,0,0],[0,1,0],{color:'#16a34a',width:3.4,label:'j',lx:18,ly:10});
sc.addArrow([0,0,0],[0,0,1],{color:'#1e3a8a',width:3.4,label:'k',lx:-14,ly:-2});
/* Произвольный вектор a = 2i + 1.5j + 1.5k */
const a = [2,1.5,1.5];
/* Покажем разложение через ступенчатую ломаную (2i, потом 1.5j, потом 1.5k) */
sc.addArrow([0,0,0],[2,0,0],{color:'#fda4af',width:2.2,dash:'5 3'}); /* 2i */
sc.addArrow([2,0,0],[2,1.5,0],{color:'#86efac',width:2.2,dash:'5 3'}); /* +1.5j */
sc.addArrow([2,1.5,0],a,{color:'#bfdbfe',width:2.2,dash:'5 3'}); /* +1.5k */
/* Итоговый вектор a — толстый фиолетовый */
sc.addArrow([0,0,0],a,{color:'#7c3aed',width:3.6,label:'a = 2i + 1.5j + 1.5k',lx:14,ly:-8});
/* Метки координат */
sc.addLabel('2',[1,0,0],{dx:0,dy:18,color:'#dc2626',fontSize:11,anchor:'middle'});
sc.addLabel('1.5',[2,0.75,0],{dx:14,dy:8,color:'#16a34a',fontSize:11,anchor:'start'});
sc.addLabel('1.5',[2,1.5,0.75],{dx:14,dy:0,color:'#1e3a8a',fontSize:11,anchor:'start'});
const el = document.getElementById('viz12-basis'); if(el) el.innerHTML = sc.render();
}
/* === §13: Скалярное произведение + проекция b на a === */
function buildDotProduct(){
const S = window.STEREO3D; if(!S) return;
const sc = new S.Scene(460,280,{view:'CABINET',scale:65});
const O = [0,0,0];
/* В плоскости z=0 для наглядности */
const a = [2.4,0.3,0];
const b = [1.3,1.7,0];
/* Векторы */
sc.addArrow(O,a,{color:'#dc2626',width:3.4,label:'a',lx:-4,ly:22});
sc.addArrow(O,b,{color:'#1e3a8a',width:3.4,label:'b',lx:-22,ly:-2});
/* Проекция b на a: проекция конца b на прямую a */
const aLen2 = a[0]*a[0]+a[1]*a[1]+a[2]*a[2];
const dot = a[0]*b[0]+a[1]*b[1]+a[2]*b[2];
const t = dot/aLen2;
const proj = [a[0]*t,a[1]*t,a[2]*t];
/* Линия проекции (b -> proj) */
sc.addEdge(b,proj,{stroke:'#94a3b8',width:1.4,dash:'3 3'});
/* Жирный отрезок |b|cos φ вдоль a */
sc.addEdge(O,proj,{stroke:'#d97706',width:4});
/* Маркер прямого угла на проекции (между линией проекции и направлением a) */
const aDir = [a[0]-proj[0]||-a[0], a[1]-proj[1]||-a[1], 0];
const perpDir = [b[0]-proj[0], b[1]-proj[1], 0];
sc.addRightAngleMark(proj,[proj[0]-0.18,proj[1]+0,0],[proj[0]+perpDir[0]*0.12,proj[1]+perpDir[1]*0.12,0],{color:'#7c3aed',width:1.4,size:0.16});
/* Метка проекции */
sc.addLabel('|b|·cos φ',[proj[0]/2,proj[1]/2,0],{dx:0,dy:22,color:'#d97706',fontSize:12,anchor:'middle'});
/* Угол φ */
sc.addAngleMark(O,a,b,{r:0.5,color:'#f59e0b',width:1.8,label:'φ'});
sc.addVertex(O,'O',{dx:-16,dy:18,color:'#0b1d33',fontSize:13,r:4,fill:'#0b1d33'});
const el = document.getElementById('viz13-dot'); if(el) el.innerHTML = sc.render();
}
/* === §14: Куб ABCDA1B1C1D1 в ПДСК с координатами всех 8 вершин + диагональ === */
function buildCubeInCoords(){
const S = window.STEREO3D; if(!S) return;
const sc = new S.Scene(500,360,{view:'CABINET',scale:75,center:[120,250]});
/* Оси с подписями */
sc.addArrow([0,0,0],[1.7,0,0],{color:'#dc2626',width:1.8});
sc.addArrow([0,0,0],[0,1.7,0],{color:'#16a34a',width:1.8});
sc.addArrow([0,0,0],[0,0,1.7],{color:'#1e3a8a',width:1.8});
sc.addLabel('x',[1.7,0,0],{dx:10,dy:10,color:'#dc2626',fontSize:13,anchor:'start'});
sc.addLabel('y',[0,1.7,0],{dx:12,dy:8,color:'#16a34a',fontSize:13,anchor:'start'});
sc.addLabel('z',[0,0,1.7],{dx:-8,dy:-2,color:'#1e3a8a',fontSize:13,anchor:'end'});
/* Тик "1" на каждой оси */
sc.addEdge([1,-0.06,0],[1,0.06,0],{stroke:'#dc2626',width:1.4});
sc.addLabel('1',[1,0,0],{dx:-3,dy:18,color:'#dc2626',fontSize:11,anchor:'middle'});
sc.addEdge([-0.06,1,0],[0.06,1,0],{stroke:'#16a34a',width:1.4});
sc.addLabel('1',[0,1,0],{dx:8,dy:14,color:'#16a34a',fontSize:11,anchor:'start'});
sc.addEdge([-0.08,0,1],[0.08,0,1],{stroke:'#1e3a8a',width:1.4});
sc.addLabel('1',[0,0,1],{dx:-10,dy:4,color:'#1e3a8a',fontSize:11,anchor:'end'});
/* Куб с ребром 1 */
sc.addBox({center:[0.5,0.5,0.5],size:[1,1,1],labels:true,color:'#fef3c7',opacity:0.30});
/* Главная диагональ AC1 — выделить */
sc.addEdge([0,0,0],[1,1,1],{stroke:'#7c3aed',width:3.4,dash:'6 3'});
sc.addLabel('|AC₁| = √3',[0.5,0.5,0.5],{dx:18,dy:-2,color:'#7c3aed',fontSize:13,anchor:'start'});
/* Координаты всех 8 вершин рядом с буквами */
const vc = [
[[0,0,0],'(0;0;0)',-30,28],
[[1,0,0],'(1;0;0)',-2,28],
[[1,1,0],'(1;1;0)',12,18],
[[0,1,0],'(0;1;0)',-32,18],
[[0,0,1],'(0;0;1)',-44,2],
[[1,0,1],'(1;0;1)',-2,-2],
[[1,1,1],'(1;1;1)',14,-12],
[[0,1,1],'(0;1;1)',-44,-12]
];
vc.forEach(v=>{ sc.addLabel(v[1],v[0],{dx:v[2],dy:v[3],color:'#78350f',fontSize:9,anchor:'start'}); });
const el = document.getElementById('viz14-cube'); if(el) el.innerHTML = sc.render();
}
/* ===== Quiz items ===== */
const I1_COORD_ITEMS = [
{ q:'$M(3;0;0)$. Где?', opts:['Ось $Ox$','Ось $Oy$','Пл-сть $Oxy$','Пр-во'], correct:0, explain:'$x \\neq 0$, остальные $=0$.' },
{ q:'$M(0;5;0)$. Где?', opts:['$Ox$','$Oy$','$Oz$','$Oxy$'], correct:1, explain:'Ось $Oy$.' },
{ q:'$M(2;3;0)$. Где?', opts:['$Ox$','$Oxy$','$Oxz$','Вне'], correct:1, explain:'$z=0$.' },
{ q:'$M(0;0;7)$. Где?', opts:['Пр-во','$Oxy$','$Oz$','$Oyz$'], correct:2, explain:'Ось $Oz$.' },
{ q:'$M(0;3;5)$. Где?', opts:['$Oxy$','$Oxz$','$Oyz$','$Oz$'], correct:2, explain:'$x=0$.' },
{ q:'Сколько октантов в пр-ве?', opts:['4','6','8','12'], correct:2, explain:'8.' }
];
const I1_DIST_ITEMS = [
{ q:'$A(1;2;3), B(4;6;3)$. $|AB|$?', answer:'5', explain:'$\\sqrt{9+16+0}=5$.' },
{ q:'$|OM|$, $M(2;3;6)$?', answer:'7', explain:'$\\sqrt{49}=7$.' },
{ q:'$A(0;0;0), B(1;1;1)$. $|AB|$?', answer:['√3','sqrt(3)','sqrt3','1.73','1.732'], explain:'$\\sqrt{3}$.' },
{ q:'$A(1;0;0), B(0;1;0)$. $|AB|$?', answer:['√2','sqrt(2)','sqrt2','1.41','1.414'], explain:'$\\sqrt{2}$.' },
{ q:'$|OM|$, $M(2;2;2)$?', answer:['2√3','2sqrt(3)','2sqrt3','3.46','3.464'], explain:'$2\\sqrt{3}$.' }
];
const I1_MID_ITEMS = [
{ q:'Середина $A(2;4;6), B(8;6;4)$.', answer:['5,5,5'], explain:'$(5;5;5)$.' },
{ q:'Середина $A(0;0;0), B(4;6;8)$.', answer:['2,3,4'], explain:'$(2;3;4)$.' },
{ q:'Середина $A(-2;3;1), B(4;-1;5)$.', answer:['1,1,3'], explain:'$(1;1;3)$.' },
{ q:'$A(1;1;1), M(2;2;2)$ — середина. $B$?', answer:['3,3,3'], explain:'$B = 2M-A$.' },
{ q:'Середина $A(0;0;6), B(0;0;0)$.', answer:['0,0,3'], explain:'$(0;0;3)$.' }
];
const I2_ADD_ITEMS = [
{ q:'$\\vec{a}=(1;2;3), \\vec{b}=(4;5;6)$. $\\vec{a}+\\vec{b}$?', answer:['5,7,9'], explain:'$(5;7;9)$.' },
{ q:'$\\vec{a}=(3;-1;2), \\vec{b}=(1;4;-2)$. $\\vec{a}+\\vec{b}$?', answer:['4,3,0'], explain:'$(4;3;0)$.' },
{ q:'$\\vec{a}=(2;3;1)$. $3\\vec{a}$?', answer:['6,9,3'], explain:'$(6;9;3)$.' },
{ q:'$\\vec{a}=(1;0;-2), \\vec{b}=(2;3;1)$. $\\vec{a}-\\vec{b}$?', answer:['-1,-3,-3'], explain:'$(-1;-3;-3)$.' },
{ q:'$\\vec{a}=(2;4;6)$. $\\frac{1}{2}\\vec{a}$?', answer:['1,2,3'], explain:'$(1;2;3)$.' }
];
const I2_COORD_ITEMS = [
{ q:'$A(1;2;3), B(4;6;3)$. $\\vec{AB}$?', answer:['3,4,0'], explain:'$(3;4;0)$.' },
{ q:'$A(2;1;0), B(5;1;4)$. $\\vec{AB}$?', answer:['3,0,4'], explain:'$(3;0;4)$.' },
{ q:'$|\\vec{AB}|$, $A(1;2;3), B(4;6;3)$?', answer:'5', explain:'$|(3;4;0)|=5$.' },
{ q:'$\\vec{BA}$, $A(0;0;0), B(2;-1;2)$?', answer:['-2,1,-2'], explain:'$\\vec{BA}=-\\vec{AB}$.' },
{ q:'$|\\vec{a}|$, $\\vec{a}=(2;3;6)$?', answer:'7', explain:'$\\sqrt{49}=7$.' }
];
const I2_COLL_ITEMS = [
{ q:'$\\vec{a}=(1;2;3), \\vec{b}=(2;4;6)$. Коллин.?', opts:['Да','Нет'], correct:0, explain:'$\\vec{b}=2\\vec{a}$.' },
{ q:'$\\vec{a}=(1;2;3), \\vec{b}=(2;4;5)$. Коллин.?', opts:['Да','Нет'], correct:1, explain:'$3/5 \\neq 1/2$.' },
{ q:'$\\vec{a}=(3;0;-6), \\vec{b}=(-1;0;2)$. Коллин.?', opts:['Да','Нет'], correct:0, explain:'$\\vec{a}=-3\\vec{b}$.' },
{ q:'$\\vec{0}$ коллин. любому?', opts:['Да','Нет'], correct:0, explain:'Да.' },
{ q:'$\\vec{a}=(2;0;0), \\vec{b}=(0;3;0)$. Коллин.?', opts:['Да','Нет'], correct:1, explain:'Разные оси.' }
];
const I3_CALC_ITEMS = [
{ q:'$(1;2;3) \\cdot (4;5;6)$?', answer:'32', explain:'$4+10+18=32$.' },
{ q:'$(1;0;-1) \\cdot (2;3;2)$?', answer:'0', explain:'$2-2=0$.' },
{ q:'$\\vec{a}\\cdot\\vec{a}$, $\\vec{a}=(2;3;1)$?', answer:'14', explain:'$4+9+1=14$.' },
{ q:'$(1;-1;2) \\cdot (3;1;-1)$?', answer:'0', explain:'$3-1-2=0$.' },
{ q:'$(1;1;1) \\cdot (1;1;1)$?', answer:'3', explain:'3.' }
];
const I3_PERP_ITEMS = [
{ q:'$(2;3;1), (1;-1;1)$. $\\perp$?', opts:['Да','Нет'], correct:0, explain:'$2-3+1=0$.' },
{ q:'$(1;2;3), (2;-1;0)$. $\\perp$?', opts:['Да','Нет'], correct:0, explain:'$2-2+0=0$.' },
{ q:'$(1;1;1), (1;-2;1)$. $\\perp$?', opts:['Да','Нет'], correct:0, explain:'$1-2+1=0$.' },
{ q:'$(1;0;0), (0;0;1)$. $\\perp$?', opts:['Да','Нет'], correct:0, explain:'0.' },
{ q:'$(1;2;3), (2;4;6)$. $\\perp$?', opts:['Да','Нет'], correct:1, explain:'Коллин., скаляр $=28$.' }
];
const I3_ANGLE_ITEMS = [
{ q:'$\\vec{a}=(1;0;0), \\vec{b}=(1;1;0)$. $\\cos\\varphi$?', answer:['1/√2','1/sqrt(2)','√2/2','sqrt(2)/2','0.707'], explain:'$\\frac{1}{\\sqrt{2}}$.' },
{ q:'$(1;0;0), (0;1;0)$. $\\cos$?', answer:'0', explain:'$\\perp$.' },
{ q:'$(1;1;0), (1;1;0)$. $\\cos$?', answer:'1', explain:'Совпадают.' },
{ q:'$(1;0;0), (-1;0;0)$. $\\cos$?', answer:'-1', explain:'Противопол.' }
];
const I4_CUBE_ITEMS = [
{ q:'Куб ребро 1, $A(0;0;0)$. $C_1$?', answer:['1,1,1'], explain:'$(1;1;1)$.' },
{ q:'Куб 1. $|AC_1|$?', answer:['√3','sqrt(3)','sqrt3','1.73','1.732'], explain:'$\\sqrt{3}$.' },
{ q:'Куб 1. $|\\vec{AB_1}|$?', answer:['√2','sqrt(2)','sqrt2','1.41','1.414'], explain:'$\\sqrt{2}$.' },
{ q:'Куб 2, $A(0;0;0)$. $B_1$?', answer:['2,0,2'], explain:'$(2;0;2)$.' },
{ q:'Куб 1. $\\vec{AC}\\cdot\\vec{AB_1}$, $C(1;1;0), B_1(1;0;1)$?', answer:'1', explain:'$1+0+0=1$.' }
];
const I4_ANG_ITEMS = [
{ q:'Куб 1. $\\cos$ угла $\\vec{AB_1}=(1;0;1), \\vec{AC}=(1;1;0)$?', answer:['1/2','0.5'], explain:'$\\frac{1}{\\sqrt{2}\\sqrt{2}}=1/2$.' },
{ q:'Угол $(1;0;0)$ и $(0;0;1)$ в °?', answer:'90', explain:'$\\perp$.' },
{ q:'Куб: угол $\\vec{AB}=(1;0;0), \\vec{AB_1}=(1;0;1)$ в °?', answer:'45', explain:'$\\cos=1/\\sqrt{2}$.' },
{ q:'$(1;1;1)$ и $(1;1;1)$. Угол в °?', answer:'0', explain:'Совпадают.' }
];
const I4_METH_ITEMS = [
{ q:'Куб: угол между скрещ. диагоналями. Лучший метод?', opts:['Чертёж + теор.','Векторно-координ.','Сечения','ТТП'], correct:1, explain:'Координаты удобнее.' },
{ q:'Доказать $\\perp$ в пирамиде с прямоуг. основ. Метод?', opts:['Координ.','ТТП','Любой','Невозм.'], correct:2, explain:'Оба работают.' },
{ q:'Расст. от точки до пл-сти (3 точки). Метод?', opts:['Чертёж','$\\rho = \\frac{|...|}{\\sqrt{...}}$','Сечения','Не вычисл.'], correct:1, explain:'Формула.' },
{ q:'$\\vec{a}\\cdot\\vec{b}=0$ означает:', opts:['$\\parallel$','$\\perp$','$|\\vec{a}|=|\\vec{b}|$','Ничего'], correct:1, explain:'$\\perp$.' },
{ q:'Угол между плоскостями — через какие векторы?', opts:['Направл.','Нормали','Ребро','Высоту'], correct:1, explain:'Нормали.' }
];
/* ===== Bosses ===== */
const BOSS_DEFS = {
b1: { title:'Босс §11 — Координаты', tag:'§11', xp:70, stages:[
{ q:'$A(1;2;2), B(4;6;2)$. $|AB|$?', type:'input', a:'5', explain:'$\\sqrt{9+16}=5$.' },
{ q:'$M(0;0;5)$. Где?', type:'mc', opts:['$Oz$','$Oy$','$Oxy$','Пр-во'], correct:0, explain:'$Oz$.' },
{ q:'Середина $A(2;0;0), B(0;2;2)$ (формат x;y;z)?', type:'input', a:['1,1,1','1;1;1'], explain:'$(1;1;1)$.' },
{ q:'$|OM|$, $M(2;3;6)$?', type:'input', a:'7', explain:'7.' },
{ q:'Октантов в пр-ве?', type:'input', a:'8', explain:'8.' }
]},
b2: { title:'Босс §12 — Векторы', tag:'§12', xp:70, stages:[
{ q:'$\\vec{a}=(1;2;3), \\vec{b}=(4;5;6)$. $\\vec{a}+\\vec{b}$ (x;y;z)?', type:'input', a:['5,7,9'], explain:'$(5;7;9)$.' },
{ q:'$A(1;0;0), B(2;3;4)$. $\\vec{AB}$?', type:'input', a:['1,3,4'], explain:'$(1;3;4)$.' },
{ q:'$|\\vec{a}|$, $\\vec{a}=(2;3;6)$?', type:'input', a:'7', explain:'7.' },
{ q:'$(1;2;-1), (2;4;-2)$. Коллин.?', type:'mc', opts:['Да','Нет'], correct:0, explain:'Да.' },
{ q:'$3\\vec{a}$, $\\vec{a}=(1;-2;3)$?', type:'input', a:['3,-6,9'], explain:'$(3;-6;9)$.' }
]},
b3: { title:'Босс §13 — Скаляр. произв.', tag:'§13', xp:70, stages:[
{ q:'$(1;2;3) \\cdot (4;5;6)$?', type:'input', a:'32', explain:'32.' },
{ q:'$\\vec{a}\\cdot\\vec{b}=0$ означает:', type:'mc', opts:['$\\parallel$','$\\perp$','Равны'], correct:1, explain:'$\\perp$.' },
{ q:'$(1;0;-1) \\cdot (2;3;2)$?', type:'input', a:'0', explain:'0.' },
{ q:'$\\cos\\varphi$, $\\vec{a}=(1;0;0), \\vec{b}=(1;1;0)$?', type:'input', a:['1/√2','1/sqrt(2)','√2/2','sqrt(2)/2','0.707'], explain:'$\\frac{1}{\\sqrt{2}}$.' },
{ q:'$\\vec{a}\\cdot\\vec{a}$, $\\vec{a}=(1;2;2)$?', type:'input', a:'9', explain:'9.' }
]},
b4: { title:'Босс §14 — Применение', tag:'§14', xp:80, stages:[
{ q:'Куб 1. $|AC_1|$?', type:'input', a:['√3','sqrt(3)','sqrt3','1.73','1.732'], explain:'$\\sqrt{3}$.' },
{ q:'Куб 1. $\\cos$ $\\vec{AB_1}=(1;0;1), \\vec{AC}=(1;1;0)$?', type:'input', a:['1/2','0.5'], explain:'$1/2$.' },
{ q:'Угол между плоск. через какие векторы?', type:'mc', opts:['Направл.','Нормали','Точки','Высоты'], correct:1, explain:'Нормали.' },
{ q:'Куб 2, $A(0;0;0)$. $C_1$?', type:'input', a:['2,2,2'], explain:'$(2;2;2)$.' },
{ q:'$\\vec{a}\\cdot\\vec{b} > 0 \\Rightarrow \\varphi$:', type:'mc', opts:['Острый','Прямой','Тупой','180°'], correct:0, explain:'Острый.' },
{ q:'Куб 1. $\\sin$ угла $AC_1$ и $ABCD$ ($\\vec{n}=(0;0;1), \\vec{AC_1}=(1;1;1)$)?', type:'input', a:['1/√3','1/sqrt(3)','√3/3','sqrt(3)/3','0.577'], explain:'$\\frac{1}{\\sqrt{3}}$.' }
]}
};
const FINAL_BOSS_DEFS = {
f1: { title:'Финал · Координаты и расст.', tag:'Финал R4', xp:30, stages:[
{ q:'$A(0;0;0), B(2;2;1)$. $|AB|$?', type:'input', a:'3', explain:'$\\sqrt{9}=3$.' },
{ q:'$M(0;3;0)$. Где?', type:'mc', opts:['$Ox$','$Oy$','$Oz$','$Oxz$'], correct:1, explain:'$Oy$.' },
{ q:'Середина $A(2;4;0), B(0;0;6)$ (x;y;z)?', type:'input', a:['1,2,3'], explain:'$(1;2;3)$.' },
{ q:'$|OM|$, $M(1;2;2)$?', type:'input', a:'3', explain:'3.' }
]},
f2: { title:'Финал · Векторы', tag:'Финал R4', xp:30, stages:[
{ q:'$\\vec{a}=(2;3;-1), \\vec{b}=(1;-2;4)$. $\\vec{a}+\\vec{b}$?', type:'input', a:['3,1,3'], explain:'$(3;1;3)$.' },
{ q:'$A(1;2;3), B(4;3;5)$. $\\vec{AB}$?', type:'input', a:['3,1,2'], explain:'$(3;1;2)$.' },
{ q:'$|\\vec{a}|$, $\\vec{a}=(2;2;1)$?', type:'input', a:'3', explain:'3.' },
{ q:'$(2;4;6), (1;2;3)$. Коллин.?', type:'mc', opts:['Да','Нет'], correct:0, explain:'Да.' }
]},
f3: { title:'Финал · Скаляр. произв.', tag:'Финал R4', xp:35, stages:[
{ q:'$(1;-2;3) \\cdot (2;1;1)$?', type:'input', a:'3', explain:'$2-2+3=3$.' },
{ q:'$\\vec{a}\\cdot\\vec{b}=0 \\Rightarrow$', type:'mc', opts:['$\\parallel$','$\\perp$','Равны'], correct:1, explain:'$\\perp$.' },
{ q:'$\\cos$, $\\vec{a}=(1;1;0), \\vec{b}=(1;0;1)$?', type:'input', a:['1/2','0.5'], explain:'$1/2$.' },
{ q:'$\\vec{a}\\cdot\\vec{a}$, $\\vec{a}=(3;0;4)$?', type:'input', a:'25', explain:'25.' }
]},
f4: { title:'Финал · Сборная', tag:'Финал R4', xp:55, stages:[
{ q:'Куб 1. $|AC_1|$?', type:'input', a:['√3','sqrt(3)','sqrt3','1.73','1.732'], explain:'$\\sqrt{3}$.' },
{ q:'Куб 1. $\\cos$ $\\vec{AB_1}=(1;0;1), \\vec{AD_1}=(0;1;1)$?', type:'input', a:['1/2','0.5'], explain:'$1/2$.' },
{ q:'Угол в °?', type:'input', a:'60', explain:'$60°$.' },
{ q:'$\\vec{a}\\cdot\\vec{b} < 0 \\Rightarrow \\varphi$:', type:'mc', opts:['Острый','Прямой','Тупой'], correct:2, explain:'Тупой.' },
{ q:'Куб 2, $A(0;0;0)$. $|AC_1|$?', type:'input', a:['2√3','2sqrt(3)','2sqrt3','3.46','3.464'], explain:'$2\\sqrt{3}$.' }
]}
};
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>