Files
Learn_System/frontend/textbooks/geometry_7_ch1.html
T
Maxim Dolgolyov 2ffe376b2d feat(geom7 ch1): Wave 5 — Глава 1 «Начальные понятия геометрии» (§1-§7 + Финал)
Главное приобретение волны: библиотека geom7_svg.js — задел на ВСЮ
геометрию 7. 14 функций-хелперов:
- point, segment, ray, line — базовые примитивы с подписями/тиками
- circle с опц. центром, радиусом, подписью R
- arc, angle — дуги углов через atan2; кратчайший путь
- rightAngleMark — L-форма ВНУТРЬ угла (полилиния по двум направлениям)
- protractor — полукруглый транспортир с делениями каждые 10°
- polyline, polygon — ломаная/замкнутый полигон
- parallelMark — стрелочки на отрезках
- svgBox — обёртка с сеткой и фоном
- distance, midPoint, vec, unit, perp, rotate — математика
- renderMath — KaTeX с правильными делимитерами

Глава 1 — 7 § + Финал по учебнику Казакова 2022 (стр. 8-50):
- §1 Повторение 5-6 классов (длина, единицы, точка между двумя)
- §2 Предмет геометрии (аксиомы vs теоремы, планиметрия/стереометрия)
- §3 Прямая, луч, отрезок, ломаная (SVG-иллюстрации каждого)
- §4 Окружность и круг (свойство точки относительно окружности)
- §5 Угол и виды углов (острый/прямой/тупой/развёрнутый — SVG)
   + биссектриса
- §6 Смежные и вертикальные углы (с SVG: дополнительные лучи + пересечение)
- §7 Перпендикулярные прямые (теоремы единственности)

Интерактивы: 2-3 на §, всего 17:
- викторины с цветными кнопками (тип угла, аксиома/теорема, верно/нет)
- тренажёры (длины, углы, биссектрисы, перпендикуляры)

Финал: 5 боссов × 5 этапов = 25 этапов. Темы: §1-2, §3-4, §5, §6, §7.

amber-тема, KaTeX, sidebar-шпаргалка с формулами,
прогресс/XP, /api/textbooks/geometry-7-ch1/progress.

JS парсится OK (75 КБ), HTTP 200, 113 КБ.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-29 07:53:10 +03:00

1513 lines
110 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>Геометрия 7 · Глава 1 · Начальные понятия</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/geom7_svg.js" defer></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; --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:#fef9c3;
--ok:#10b981; --ok-bg:#d1fae5; --warn:#f59e0b; --warn-bg:#fef3c7;
--bad:#ef4444; --fail:#dc2626; --fail-bg:#fee2e2;
}
.dark{--bg:#0a0a0e; --card:#13120a; --card-soft:#18160a; --text:#fef9e7; --muted:#a39070; --border:#2a2512}
*{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,#92400e 0%,#d97706 55%,#fbbf24 100%);color:#fff;padding:46px 22px 30px;overflow:hidden;border-bottom:2px solid rgba(251,191,36,.2);min-height:130px}
.hdr::before{content:'ГЛАВА 1';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,235,180,.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:'\25CF';position:absolute;right:-10px;top:-20px;font-size:clamp(2rem,8vw,5.5rem);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(217,119,6,.32)}
.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(217,119,6,.18);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(217,119,6,.22);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(170px,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(217,119,6,.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,#fff5e1,#fef3c7)}
.sec[id="sec-p1"]{ --sec-acc:#d97706; --sec-acc-d:#b45309; --sec-acc-soft:#fef3c7; }
.sec[id="sec-p2"]{ --sec-acc:#0891b2; --sec-acc-d:#0e7490; --sec-acc-soft:#cffafe; }
.sec[id="sec-p3"]{ --sec-acc:#059669; --sec-acc-d:#047857; --sec-acc-soft:#d1fae5; }
.sec[id="sec-p4"]{ --sec-acc:#7c3aed; --sec-acc-d:#6d28d9; --sec-acc-soft:#ede9fe; }
.sec[id="sec-p5"]{ --sec-acc:#db2777; --sec-acc-d:#9d174d; --sec-acc-soft:#fce7f3; }
.sec[id="sec-p6"]{ --sec-acc:#dc2626; --sec-acc-d:#b91c1c; --sec-acc-soft:#fee2e2; }
.sec[id="sec-p7"]{ --sec-acc:#ea580c; --sec-acc-d:#c2410c; --sec-acc-soft:#ffedd5; }
.sec[id="sec-final1"]{ --sec-acc:#d97706; --sec-acc-d:#b45309; --sec-acc-soft:#fef3c7; }
.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(--sec-acc-soft,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(--sec-acc-soft,var(--pri-soft));position:relative;z-index:1}
.sec-num{display:inline-block;padding:4px 10px;background:linear-gradient(135deg,var(--sec-acc,var(--pri)),var(--sec-acc-d,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.55rem;font-weight:800;color:var(--sec-acc-d,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(217,119,6,.06);position:relative;z-index:1;transition:transform .25s cubic-bezier(.16,1,.3,1),box-shadow .25s}
.card:hover{transform:translateY(-2px)}
.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.theory{background:#8b5cf6}.card-icon.rule{background:#ec4899}.card-icon.algo{background:#f59e0b}.card-icon.example{background:#10b981}
.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(--sec-acc-soft,var(--pri-soft));padding:3px 7px;border-radius:5px}
.card-body{font-size:.94rem;line-height:1.65}
.card-body p{margin-bottom:8px}
.wg{background:linear-gradient(135deg,var(--card),var(--sec-acc-soft,var(--pri-soft)));border:1.5px solid var(--sec-acc,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(--sec-acc,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(--sec-acc-d,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,#fef3c7),var(--sec-acc-soft,var(--pri-soft)));border-left:4px solid var(--warn,#f59e0b);padding:9px 14px;border-radius:9px}
.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(--sec-acc-soft,var(--pri-soft));border-color:var(--sec-acc,var(--pri))}
.btn.primary{background:var(--sec-acc,var(--pri));color:#fff;border-color:var(--sec-acc,var(--pri))}
.btn.primary:hover{background:var(--sec-acc-d,var(--pri2));border-color:var(--sec-acc-d,var(--pri2))}
.tinp{padding:8px 12px;border:1.5px solid var(--border);border-radius:8px;background:var(--card);color:var(--text);font-family:'JetBrains Mono',monospace}
.tinp:focus{outline:0;border-color:var(--sec-acc,var(--pri));box-shadow:0 0 0 3px var(--sec-acc-soft,var(--pri-soft))}
.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)}
.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}
@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(245,158,11,.15);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}
.spoiler{border:1px solid var(--border);border-radius:10px;background:var(--card);margin:10px 0;overflow:hidden}
.spoiler summary{padding:8px 14px;background:var(--sec-acc-soft,var(--pri-soft));font-weight:700;cursor:pointer;font-size:.88rem;color:var(--sec-acc-d,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(--sec-acc,var(--pri));width:18px}
.spoiler[open] summary::before{content:'\2212'}
.spoiler-body{padding:10px 14px;font-size:.92rem;line-height:1.6}
.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,#d97706,#f59e0b);color:#fff;padding:12px 18px;border-radius:11px;font-weight:700;font-size:.9rem;box-shadow:0 8px 28px rgba(217,119,6,.45);z-index:1002;display:none;align-items:center;gap:8px;max-width:340px}
.ach-popup.show{display:flex}
.dnd-pool{display:flex;flex-wrap:wrap;gap:8px;margin-bottom:14px;padding:10px;border:1.5px dashed var(--border);border-radius:10px;min-height:54px}
.dnd-pool.col{flex-direction:column;align-items:stretch}
.dnd-chip{display:inline-flex;align-items:center;gap:6px;padding:6px 12px;background:var(--card);border:1.5px solid var(--border);border-radius:10px;cursor:grab;user-select:none;font-size:.92rem;touch-action:none}
.dnd-chip:hover{border-color:var(--sec-acc,var(--pri))}
.dnd-chip.armed{border-color:var(--sec-acc,var(--pri));background:var(--sec-acc-soft,var(--pri-soft));box-shadow:0 0 0 3px rgba(217,119,6,.22)}
.dnd-chip.placed{background:var(--sec-acc-soft,var(--pri-soft));border-color:var(--sec-acc,var(--pri))}
.dnd-chip .dnd-x{padding:0 5px;color:var(--muted);font-weight:700;cursor:pointer}
.drop-box{background:var(--card);border:1.5px dashed var(--border);border-radius:10px;padding:10px;min-height:90px}
.drop-box.over{border-color:var(--sec-acc,var(--pri));background:var(--sec-acc-soft,var(--pri-soft));border-style:solid}
.drop-box h5{font-family:'Unbounded',sans-serif;font-size:.78rem;color:var(--sec-acc-d,var(--pri2));margin-bottom:8px;text-transform:uppercase}
.drop-items{display:flex;flex-wrap:wrap;gap:6px;min-height:32px}
.actions{display:flex;gap:8px;flex-wrap:wrap;margin-top:10px}
.sliders{display:grid;grid-template-columns:repeat(auto-fit,minmax(180px,1fr));gap:10px;margin-bottom:10px}
.sliders label{display:block;font-size:.92rem;color:var(--muted);background:var(--card);padding:8px 12px;border-radius:8px;border:1px solid var(--border)}
.sliders label b{font-family:'JetBrains Mono',monospace;font-size:1.05rem;color:var(--sec-acc-d,var(--pri2));margin-left:4px}
.sliders label input[type="range"]{display:block;width:100%;margin-top:6px;accent-color:var(--sec-acc,var(--pri))}
.score-display{display:flex;gap:14px;flex-wrap:wrap;align-items:center;padding:10px 14px;background:var(--sec-acc-soft,var(--pri-soft));border-radius:10px;margin-bottom:12px}
.score-display b{color:var(--sec-acc-d,var(--pri2));font-size:1.15rem}
.hp-boss{height:14px;background:rgba(220,38,38,.12);border-radius:9px;overflow:hidden;border:1px solid #fecaca;margin:8px 0}
.hp-boss-fill{height:100%;background:linear-gradient(90deg,#dc2626,#f59e0b);border-radius:9px;transition:width .5s cubic-bezier(.4,0,.2,1)}
.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}
}
.boss-card{padding:16px;background:var(--card);border-radius:12px;border:2px solid var(--bad,#dc2626);margin-bottom:14px}
.boss-head{display:flex;align-items:center;gap:10px;margin-bottom:10px}
.boss-title{font-family:'Unbounded',sans-serif;font-weight:800;color:#7f1d1d;font-size:1.04rem;flex:1}
.boss-stage{font-size:.85rem;color:var(--muted)}
.boss-q{font-size:1rem;line-height:1.55;padding:11px 13px;background:var(--card-soft);border-radius:8px;margin-bottom:9px;border-left:3px solid var(--bad,#dc2626)}
.svg-host{display:flex;justify-content:center;margin:12px 0}
.viz-info{text-align:center;font-size:.92rem;color:var(--muted);margin-top:8px;font-family:'JetBrains Mono',monospace}
</style>
</head>
<body>
<header class="hdr">
<div class="hdr-row">
<div>
<h1>Геометрия 7 · Глава 1</h1>
<div class="hdr-sub">Начальные понятия геометрии · точка, прямая, луч, отрезок, угол, перпендикуляр</div>
</div>
<div class="hdr-side">
<a href="/textbook/geometry-7" class="hdr-btn"><svg class="ic" viewBox="0 0 24 24"><polyline points="15 18 9 12 15 6"/></svg> К геометрии 7</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>Здесь мы знакомимся с <b>основными фигурами</b> (точка, прямая, плоскость), учимся различать <b>прямую, луч и отрезок</b>, измерять <b>углы</b>, изучаем <b>смежные и вертикальные углы</b>, <b>окружность и круг</b>, заканчиваем <b>перпендикулярами</b>. Этот фундамент — основа всего курса геометрии.</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> Начать § 1</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"></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="5-6"><div class="sec-header"><span class="sec-num">§ 1</span><h2 class="sec-h">Повторение материала 5-6 классов</h2></div><div id="p1-body"></div></section>
<section id="sec-p2" class="sec" data-watermark="●"><div class="sec-header"><span class="sec-num">§ 2</span><h2 class="sec-h">Предмет геометрии</h2></div><div id="p2-body"></div></section>
<section id="sec-p3" class="sec" data-watermark="—"><div class="sec-header"><span class="sec-num">§ 3</span><h2 class="sec-h">Прямая. Луч. Отрезок. Ломаная</h2></div><div id="p3-body"></div></section>
<section id="sec-p4" class="sec" data-watermark="○"><div class="sec-header"><span class="sec-num">§ 4</span><h2 class="sec-h">Окружность и круг</h2></div><div id="p4-body"></div></section>
<section id="sec-p5" class="sec" data-watermark="∠"><div class="sec-header"><span class="sec-num">§ 5</span><h2 class="sec-h">Угол. Виды углов</h2></div><div id="p5-body"></div></section>
<section id="sec-p6" class="sec" data-watermark="≃"><div class="sec-header"><span class="sec-num">§ 6</span><h2 class="sec-h">Смежные углы. Вертикальные углы</h2></div><div id="p6-body"></div></section>
<section id="sec-p7" class="sec" data-watermark="⊥"><div class="sec-header"><span class="sec-num">§ 7</span><h2 class="sec-h">Перпендикулярные прямые</h2></div><div id="p7-body"></div></section>
<section id="sec-final1" class="sec" data-watermark="★"><div class="sec-header"><span class="sec-num" style="background:linear-gradient(135deg,#d97706,#f59e0b)">Финал главы</span><h2 class="sec-h">Итоги. 5 боссов главы 1</h2></div><div id="final1-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">Интерактивный учебник «Геометрия 7» · Глава 1 · Начальные понятия · 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:{p1:0,p2:0,p3:0,p4:0,p5:0,p6:0,p7:0,final1:0}, achievements:new Map(), xp:0, level:1 };
const TOTAL_PARAS = 8;
const _TB_SLUG = 'geometry-7-ch1';
function calcLevel(xp){ return Math.floor(Math.sqrt((xp||0)/100))+1; }
function _xpForLevel(lv){ return (lv-1)*(lv-1)*100; }
const ACH_LABELS = {
start:'Начало главы 1!',
p5_done:'Углы изучены!',
p6_done:'Смежные и вертикальные — освоены!',
p7_done:'Перпендикуляр — твой инструмент!',
ch1_done:'Глава 1 пройдена!',
};
function loadProgress(){
try{
const s=localStorage.getItem('geometry7_ch1_progress'); if(s) Object.assign(STATE.progress, JSON.parse(s));
const a=localStorage.getItem('geometry7_ch1_achievements');
if(a){ const p=JSON.parse(a); if(Array.isArray(p)) p.forEach(id=>STATE.achievements.set(id, ACH_LABELS[id]||id)); else 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('geometry7_xp')||0); STATE.level=calcLevel(STATE.xp);
}catch(e){}
}
function saveProgress(){
try{
localStorage.setItem('geometry7_ch1_progress', JSON.stringify(STATE.progress));
localStorage.setItem('geometry7_ch1_achievements', JSON.stringify(Object.fromEntries(STATE.achievements)));
localStorage.setItem('geometry7_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);
if(STATE.progress[key]>=100){
if(key==='p5') achievement('p5_done');
else if(key==='p6') achievement('p6_done');
else if(key==='p7') achievement('p7_done');
else if(key==='final1') achievement('ch1_done');
}
}
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,'geometry7-ch1-'+(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)+'%'; });
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);
}
const PARAS = [
{ id:'p1', num:'§ 1', name:'Повторение 5-6', sub:'Точки, отрезки, углы' },
{ id:'p2', num:'§ 2', name:'Предмет геометрии', sub:'Аксиомы и теоремы' },
{ id:'p3', num:'§ 3', name:'Прямая, луч, отрезок', sub:'+ ломаная и многоугольники' },
{ id:'p4', num:'§ 4', name:'Окружность и круг', sub:'Центр, радиус' },
{ id:'p5', num:'§ 5', name:'Угол. Виды углов', sub:'Острый, прямой, тупой' },
{ id:'p6', num:'§ 6', name:'Смежные и вертикальные', sub:'Сумма = 180°' },
{ id:'p7', num:'§ 7', name:'Перпендикулярные прямые', sub:'$a \\perp b$' },
{ id:'final1', num:'★', name:'Финал главы', sub:'Итоги \xB7 5 боссов', final:true },
];
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>';
card.addEventListener('click', ()=>goTo(p.id));
g.appendChild(card);
});
}
const BUILT=new Set();
const BUILDERS = { p1:()=>buildP1(), p2:()=>buildP2(), p3:()=>buildP3(), p4:()=>buildP4(), p5:()=>buildP5(), p6:()=>buildP6(), p7:()=>buildP7(), final1:()=>buildFinal1() };
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:'Шпаргалка \xA71',rows:[
['Точка','обозначается прописной латинской буквой $A, B, C$'],
['Отрезок','часть прямой между двумя точками'],
['Длина','измеряется в мм, см, дм, м, км'],
['Сравнение','наложением'],
]},
p2:{title:'Шпаргалка \xA72',rows:[
['3 основные','точка, прямая, плоскость (без определения)'],
['Планиметрия','свойства плоских фигур'],
['Стереометрия','свойства фигур в пространстве'],
['Аксиома','без доказательства'],
['Теорема','с доказательством'],
]},
p3:{title:'Шпаргалка \xA73',rows:[
['Аксиома прямой','через 2 точки — одна прямая'],
['Луч','часть прямой с одним концом'],
['Отрезок','часть прямой с двумя концами'],
['Аксиома измерения','$AC + CB = AB$'],
['Ломаная','отрезки соединены концами'],
]},
p4:{title:'Шпаргалка \xA74',rows:[
['Окружность','замкнутая линия, все точки на равном расстоянии от центра'],
['Круг','часть плоскости, ограниченная окружностью'],
['Радиус','отрезок центр-окружность'],
['Диаметр','$d = 2R$'],
]},
p5:{title:'Шпаргалка \xA75',rows:[
['Угол','две стороны (лучи) + вершина'],
['Острый','$< 90°$'],
['Прямой','$= 90°$'],
['Тупой','$90° < \\alpha < 180°$'],
['Развёрнутый','$= 180°$'],
['Полный','$= 360°$'],
['Биссектриса','делит угол пополам'],
]},
p6:{title:'Шпаргалка \xA76',rows:[
['Смежные','общая сторона + дополнительные'],
['Сумма смежных','$= 180°$'],
['Вертикальные','стороны $\\to$ дополнительные'],
['Вертикальные','<b>равны</b>'],
]},
p7:{title:'Шпаргалка \xA77',rows:[
['$a \\perp b$','две прямые, пересекаются под прямым углом'],
['4 угла','все по 90°'],
['Через точку','одна перпендикуляр'],
['$a \\perp c$ и $b \\perp c$','$\\Rightarrow$ $a \\parallel b$'],
]},
final1:{title:'Финал главы',rows:[
['§1–§7','теория главы 1'],
['Боссов','5'],
['Награда','+100 XP за полное прохождение'],
]},
};
const TIPS=[
{sec:'p1',html:'Длина отрезка <b>положительное</b> число. Равные отрезки $\\Leftrightarrow$ равные длины.'},
{sec:'p2',html:'<b>Аксиома</b> — фундамент. <b>Теорема</b> — доказывается через аксиомы и уже доказанные теоремы.'},
{sec:'p3',html:'Через одну точку — <b>бесконечно много</b> прямых. Через две — <b>одна</b>. Через три не на одной прямой — <b>ни одной</b>.'},
{sec:'p4',html:'Окружность — <b>линия</b>. Круг — <b>часть плоскости</b>. Обруч ↔ окружность, крышка люка ↔ круг.'},
{sec:'p5',html:'Аксиома измерения углов: если луч проходит внутри угла, он разбивает его на два угла, сумма градусных мер которых равна градусной мере исходного.'},
{sec:'p6',html:'<b>Смежные</b>: сумма = 180°. <b>Вертикальные</b>: равны между собой. При пересечении двух прямых — 4 угла попарно вертикальны.'},
{sec:'p7',html:'$a \\perp b$ значит угол между прямыми = 90°. Через любую точку плоскости можно провести <b>только одну</b> прямую, перпендикулярную данной.'},
{sec:'final1',html:'5 боссов проверяют все темы главы 1.'},
];
function buildSidebar(id){
const box=document.getElementById('sidebar-content');
const sb=SIDEBARS[id]||SIDEBARS.p1;
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"><div class="xp-card-title"><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];
html+='<div class="sidecard" style="background:linear-gradient(135deg,var(--warn-bg,#fef3c7),var(--pri-soft));border-color:var(--warn,#f59e0b)"><h4 style="color:#92400e">Подсказка</h4><div class="sidecard-row" style="font-size:.84rem;line-height:1.55">'+tip.html+'</div></div>';
if(STATE.achievements.size>0){
html+='<div class="sidecard"><h4>Достижения</h4>';
[...STATE.achievements.values()].slice(-4).forEach(text=>{ html+='<div class="sidecard-row" style="font-size:.78rem;color:var(--ok)">✓ '+text+'</div>'; });
html+='</div>';
}
box.innerHTML=html;
if(window.renderMathInElement) try{ renderMath(box); }catch(e){}
}
function initTheme(){
const t=localStorage.getItem('geometry7_ch1_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('geometry7_ch1_theme', dark?'dark':'light');
document.getElementById('theme-lab').textContent=dark?'Светлая':'Тёмная';
});
}
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 = {
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>',
};
function makeCard(kind, title, num, body){
const labels={theory:'Теория',algo:'Алгоритм',rule:'Правило',example:'Пример'};
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 secNav(prev, next){
const NAMES={p1:'\xA71',p2:'\xA72',p3:'\xA73',p4:'\xA74',p5:'\xA75',p6:'\xA76',p7:'\xA77',final1:'Финал'};
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 setupSorter(cfg){
const placed={}; const pool=document.getElementById(cfg.poolId); const scope=document.querySelector(cfg.scopeSelector);
if(!pool||!scope) return {placed,render:()=>{},reset:()=>{}};
pool.classList.add('dnd-pool'); if(cfg.columnLayout) pool.classList.add('col');
let armed=null;
function buildChip(it,isPlaced){ const e=document.createElement('div'); e.className='dnd-chip'+(isPlaced?' placed':''); e.dataset.id=it.id; e.innerHTML='<span class="dnd-txt">'+it.html+'</span><span class="dnd-x" title="Убрать">\xd7</span>'; attach(e,it.id); return e; }
function attach(elm,itId){
elm.addEventListener('click', ev=>{
if(ev.target.classList && ev.target.classList.contains('dnd-x')){ ev.stopPropagation(); if(placed[itId]){delete placed[itId];render();}else if(armed===itId){armed=null;render();} return; }
if(placed[itId]){delete placed[itId];armed=null;render();} else {armed=(armed===itId)?null:itId;render();}
});
}
function attachBoxTaps(){ scope.querySelectorAll('.drop-box').forEach(box=>{ box.addEventListener('click',ev=>{ if(!armed)return; if(ev.target.closest('.dnd-chip'))return; const di=box.querySelector('[data-cat]'); if(di){placed[armed]=di.dataset.cat;armed=null;render();} }); }); }
function render(){ pool.innerHTML=''; cfg.items.forEach(it=>{if(placed[it.id])return;const c=buildChip(it,false);if(armed===it.id)c.classList.add('armed');pool.appendChild(c);}); cfg.cats.forEach(cat=>{const box=scope.querySelector('.drop-items[data-cat="'+cat+'"]');if(!box)return;box.innerHTML='';cfg.items.forEach(it=>{if(placed[it.id]!==cat)return;box.appendChild(buildChip(it,true));});}); if(window.renderMathInElement)try{renderMath(scope);}catch(_){} }
attachBoxTaps(); render();
return {placed,render,reset(){ for(const k in placed) delete placed[k]; armed=null; render(); }};
}
function makeTrainer(opts){
let i=0, score=0;
const Q=opts.questions;
const parser = opts.parser || (v => parseFloat(String(v).replace(',','.')));
function show(){
if(i >= Q.length){
document.getElementById(opts.idPrefix+'-q').innerHTML = '<b>Готово!</b> Результат: '+score+' / '+Q.length;
if(opts.onComplete) opts.onComplete(score, Q.length);
return;
}
document.getElementById(opts.idPrefix+'-i').textContent = (i+1);
document.getElementById(opts.idPrefix+'-s').textContent = score;
document.getElementById(opts.idPrefix+'-q').innerHTML = Q[i].q;
document.getElementById(opts.idPrefix+'-ans').value = '';
renderMath(document.getElementById(opts.idPrefix+'-q'));
document.getElementById(opts.idPrefix+'-fb').style.display = 'none';
}
function go(){
if(i >= Q.length) return;
const fb = document.getElementById(opts.idPrefix+'-fb');
const raw = document.getElementById(opts.idPrefix+'-ans').value.trim();
if(raw === ''){ feedback(fb, false, '&#10007; Введи ответ.'); return; }
const expected = Q[i].a;
let ok = false;
if(typeof expected === 'function') ok = expected(raw);
else { const got = parser(raw); ok = !isNaN(got) && Math.abs(got - expected) < 1e-6; }
if(ok){ score++; feedback(fb, true, '&#10003; Верно! Дальше ▶'); }
else feedback(fb, false, '&#10007; Неверно. Правильно: <b>'+(Q[i].show||expected)+'</b>. Дальше ▶');
document.getElementById(opts.idPrefix+'-s').textContent = score;
i++; setTimeout(show, 1100);
}
document.getElementById(opts.idPrefix+'-go').addEventListener('click', go);
document.getElementById(opts.idPrefix+'-ans').addEventListener('keydown', e=>{ if(e.key==='Enter') go(); });
const restart = document.getElementById(opts.idPrefix+'-start');
if(restart) restart.addEventListener('click', ()=>{ i=0; score=0; show(); });
show();
}
function trainerHTML(idPrefix, total, placeholder){
return '<div class="score-display"><span>Задача <b id="'+idPrefix+'-i">1</b> / '+total+'</span><span>Очки: <b id="'+idPrefix+'-s">0</b> / '+total+'</span></div>'
+'<div id="'+idPrefix+'-q" style="padding:14px;background:var(--sec-acc-soft);border-radius:10px;font-size:1.05rem;margin-bottom:10px;text-align:center"></div>'
+'<div style="display:flex;gap:10px;align-items:center;flex-wrap:wrap;justify-content:center">'
+'<input type="text" id="'+idPrefix+'-ans" class="tinp" placeholder="'+(placeholder||'Ответ')+'" style="width:140px;text-align:center">'
+'<button class="btn primary" id="'+idPrefix+'-go">Проверить</button>'
+'<button class="btn" id="'+idPrefix+'-start">Заново</button>'
+'</div><div class="feedback" id="'+idPrefix+'-fb"></div>';
}
function readButton(paraId){
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>'
+' Я прочитал \xA7'+paraId.replace('p','')+' (+10 XP)'
+'</button></div>';
}
function wireReadBtn(paraId){
document.getElementById(paraId+'-read-btn').addEventListener('click', ()=>{
addXp(10, paraId+'-read'); bumpProgress(paraId, 30);
const b=document.getElementById(paraId+'-read-btn'); b.textContent='Прочитано! +10 XP'; b.disabled=true; b.style.opacity=.6;
});
}
function initSidebarToggle(){
const side=document.getElementById('col-side'),back=document.getElementById('col-side-backdrop'),btn=document.getElementById('sidebar-btn');
if(!side||!btn) return;
function open(){ side.classList.add('open'); back.classList.add('show'); }
function close(){ side.classList.remove('open'); back.classList.remove('show'); }
btn.addEventListener('click',()=>{ if(side.classList.contains('open')) close(); else open(); });
back.addEventListener('click',close);
document.addEventListener('keydown',e=>{ if(e.key==='Escape') close(); });
}
function init(){
loadProgress(); initTheme(); initSidebarToggle();
buildParaSelector(); refreshProgressUI(); goTo('p1');
setTimeout(()=>achievement('start','Начало главы 1!'), 600);
}
document.addEventListener('DOMContentLoaded', init);
/* ============================================================
\xA7 1 — Повторение материала 5-6 классов
============================================================ */
function buildP1(){
const box = document.getElementById('p1-body');
const G = window.GEOM7;
let html = '';
/* SVG-иллюстрация: 3 точки на прямой A, C, B */
let svgLine = '';
if(G){
const box1 = G.svgBox(300, 80, { id:'p1-1', cell:20 });
svgLine = box1.open
+ G.line({x:30,y:40},{x:270,y:40},{color:'#0891b2',width:2})
+ G.point(80, 40, 'A', { color:'#dc2626' })
+ G.point(160, 40, 'C', { color:'#dc2626' })
+ G.point(230, 40, 'B', { color:'#dc2626' })
+ box1.close;
}
html += makeCard('theory', 'Геометрия начинается с точки', '1.1', `
<p>В начальных классах вы уже знакомились с <b>точкой</b>, <b>прямой</b>, <b>лучом</b>, <b>отрезком</b>, <b>углом</b>, <b>окружностью</b>. Теперь — повторим главное.</p>
<p>Точки обозначают <b>прописными латинскими буквами</b>: $A, B, C, D, \\ldots$. Их рисуют как маленькие круги/жирные точки.</p>
<p>Через две точки можно провести <b>прямую</b> (бесконечную) или <b>отрезок</b> (с этими двумя точками-концами).</p>
<div class="svg-host">`+svgLine+`</div>
<p style="text-align:center;font-size:.85rem;color:var(--muted)">Прямая с тремя точками $A, C, B$. Точка $C$ лежит <b>между</b> $A$ и $B$.</p>`);
html += makeCard('rule', 'Длина отрезка', '1.2', `
<p>Каждый отрезок имеет <b>длину</b>, выраженную <b>положительным числом</b>.</p>
<p>Единицы длины: мм, см, дм, м, км. Длина $AB$ записывается как <b>$AB = 12$ см</b> или просто <b>$AB = 12$</b> (если единицы не указаны).</p>
<p>Часто вместо «длина отрезка равна 12 см» говорят «отрезок равен 12 см».</p>
<p>Свойство: равным отрезкам соответствуют равные длины. И наоборот.</p>
<p>Чтобы <b>сравнить</b> два отрезка, можно их <b>совместить наложением</b>.</p>`);
html += makeCard('example', 'Пример: длина в разных единицах', '1.3', `
<p>В отрезке $AB$ укладывается 3 отрезка по 1 дм, 5 отрезков по 1 см и 2 отрезка по 1 мм.</p>
<p>Тогда: $AB = 3$ дм $5$ см $2$ мм $= 352$ мм $= 35{,}2$ см $= 3{,}52$ дм.</p>`);
html += '<div class="wg" id="p1-iv1">'
+'<div class="wg-header"><span class="wg-badge">ИНТЕРАКТИВ 1</span><div class="wg-title">Перевод единиц длины</div></div>'
+'<div class="wg-help">Переведи длину в указанные единицы. Ответ — число (десятичная дробь через точку или запятую).</div>'
+trainerHTML('p1-iv1', 5, 'число')
+'</div>';
html += '<div class="wg" id="p1-iv2">'
+'<div class="wg-header"><span class="wg-badge">ИНТЕРАКТИВ 2</span><div class="wg-title">Точка между двумя другими</div></div>'
+'<div class="wg-help">На прямой лежат три точки. Какая лежит <b>между</b> двумя другими? Используй свойство: точка $C$ между $A$ и $B$, если $AC + CB = AB$.</div>'
+trainerHTML('p1-iv2', 4, 'буква')
+'</div>';
html += secNav(null, 'p2') + readButton('p1');
box.innerHTML = html; renderMath(box);
makeTrainer({
idPrefix:'p1-iv1',
questions:[
{ q:'Сколько см в 1 дм?', a:10 },
{ q:'Сколько мм в 25,4 см?', a:254 },
{ q:'$AB = 2$ м 3 дм. Сколько это см?', a:230 },
{ q:'$CD = 1500$ мм. Сколько это дм?', a:15 },
{ q:'$3{,}52$ дм = ? см', a:35.2 },
],
onComplete:(s,n)=>{ if(s===n){addXp(12,'p1-iv1');bumpProgress('p1',25);} else if(s>=3){addXp(6,'p1-iv1');bumpProgress('p1',12);} }
});
makeTrainer({
idPrefix:'p1-iv2',
parser:(v)=>v,
questions:[
{ q:'$AC = 5$, $CB = 7$, $AB = 12$. Какая точка между двумя другими?', a:(v)=>String(v).trim().toUpperCase()==='C', show:'C' },
{ q:'$AB = 4$, $BC = 6$, $AC = 10$. Какая точка между?', a:(v)=>String(v).trim().toUpperCase()==='B', show:'B' },
{ q:'$MN = 3$, $MK = 8$, $NK = 5$. Какая точка между?', a:(v)=>String(v).trim().toUpperCase()==='N', show:'N' },
{ q:'$PQ = 9$, $QR = 4$, $PR = 5$. Какая точка между?', a:(v)=>String(v).trim().toUpperCase()==='R', show:'R' },
],
onComplete:(s,n)=>{ if(s===n){addXp(12,'p1-iv2');bumpProgress('p1',25);} else if(s>=2){addXp(6,'p1-iv2');bumpProgress('p1',12);} }
});
wireReadBtn('p1');
}
/* ============================================================
\xA7 2 — Предмет геометрии
============================================================ */
function buildP2(){
const box = document.getElementById('p2-body');
let html = '';
html += makeCard('theory', 'Основные фигуры', '2.1', `
<p>Три основные геометрические фигуры — <b>точка</b>, <b>прямая</b> и <b>плоскость</b>. Это <b>абстрактные понятия</b>, которые принимаются <b>без определения</b>.</p>
<ul style="padding-left:22px;line-height:1.85">
<li><b>Точка</b> — обозначается прописной латинской буквой ($A, B, C$).</li>
<li><b>Прямая</b> — обозначается двумя прописными буквами ($BC$) или одной малой ($b$).</li>
<li><b>Плоскость</b> — обозначается одной малой греческой буквой ($\\alpha, \\beta$) или тремя прописными.</li>
</ul>`);
html += makeCard('theory', 'Планиметрия и стереометрия', '2.2', `
<p>Школьный курс геометрии делится на <b>планиметрию</b> и <b>стереометрию</b>.</p>
<p>В <b>планиметрии</b> изучаются свойства <b>плоских фигур</b> — тех, которые целиком лежат на плоскости (треугольник, квадрат, окружность).</p>
<p>В <b>стереометрии</b> рассматриваются свойства <b>пространственных фигур</b> — куб, параллелепипед, пирамида, шар.</p>
<p>В 7-9 классах мы изучаем <b>планиметрию</b>.</p>`);
html += makeCard('rule', 'Определения, аксиомы, теоремы', '2.3', `
<p>Все геометрические фигуры (кроме точки, прямой и плоскости) имеют <b>определения</b>.</p>
<p style="background:var(--sec-acc-soft);padding:10px 14px;border-radius:8px"><b>Определение.</b> <i>Отрезком называется часть прямой, ограниченная двумя точками.</i></p>
<p>Свойства фигур формулируются как <b>аксиомы</b> и <b>теоремы</b>.</p>
<ul style="padding-left:22px;line-height:1.85">
<li><b>Аксиома</b> — утверждение, которое принимается <b>без доказательства</b> (очевидно).</li>
<li><b>Теорема</b> — утверждение, истинность которого устанавливается <b>доказательством</b>.</li>
</ul>
<p>Пример аксиомы: <i>через любые две точки плоскости можно провести прямую, и притом только одну</i>.</p>
<p>Пример теоремы: <i>сумма углов треугольника равна $180°$</i>.</p>`);
html += makeCard('rule', 'Три типа задач', '2.4', `
<p>В геометрии выделяют три основных типа задач:</p>
<ol style="padding-left:22px;line-height:1.85">
<li><b>На доказательство</b> — нужно доказать утверждение.</li>
<li><b>На вычисление</b> — найти длину, площадь, угол.</li>
<li><b>На построение</b> — построить фигуру при помощи линейки и циркуля.</li>
</ol>`);
html += '<div class="wg" id="p2-iv1">'
+'<div class="wg-header"><span class="wg-badge">ИНТЕРАКТИВ 1</span><div class="wg-title">Аксиома или теорема?</div></div>'
+'<div class="wg-help">Определи, является ли утверждение аксиомой (без док-ва) или теоремой (с док-вом).</div>'
+'<div class="score-display"><span>Задача <b id="p2-iv1-i">1</b> / 6</span><span>Очки: <b id="p2-iv1-s">0</b> / 6</span></div>'
+'<div id="p2-iv1-q" style="padding:14px;background:var(--sec-acc-soft);border-radius:10px;font-size:1.05rem;text-align:center;margin-bottom:10px"></div>'
+'<div style="display:flex;gap:10px;justify-content:center"><button class="btn primary" id="p2-iv1-ax" style="background:#0891b2;border-color:#0891b2">Аксиома</button><button class="btn primary" id="p2-iv1-th" style="background:#7c3aed;border-color:#7c3aed">Теорема</button></div>'
+'<div class="feedback" id="p2-iv1-fb"></div></div>';
html += secNav('p1', 'p3') + readButton('p2');
box.innerHTML = html; renderMath(box);
(function(){
const Q=[
{ e:'Через любые две точки можно провести прямую, и притом только одну.', isAx:true },
{ e:'Сумма углов треугольника равна $180°$.', isAx:false },
{ e:'От любого луча в данную полуплоскость можно отложить угол данной градусной меры.', isAx:true },
{ e:'Вертикальные углы равны.', isAx:false },
{ e:'Сумма смежных углов равна $180°$.', isAx:false },
{ e:'Если на отрезке взять точку, то она разобьёт его на два отрезка, сумма длин которых равна длине исходного.', isAx:true },
];
let i=0,score=0;
function show(){
if(i>=Q.length){ document.getElementById('p2-iv1-q').innerHTML='<b>Готово!</b> '+score+' / '+Q.length; if(score===Q.length){addXp(12,'p2-iv1');bumpProgress('p2',30);} else if(score>=4){addXp(6,'p2-iv1');bumpProgress('p2',15);} return; }
document.getElementById('p2-iv1-i').textContent=(i+1);
document.getElementById('p2-iv1-s').textContent=score;
document.getElementById('p2-iv1-q').innerHTML=Q[i].e;
renderMath(document.getElementById('p2-iv1-q'));
document.getElementById('p2-iv1-fb').style.display='none';
}
function ans(isAx){
if(i>=Q.length) return;
const fb=document.getElementById('p2-iv1-fb');
if(isAx===Q[i].isAx){ score++; feedback(fb,true,'&#10003; Верно!'); }
else feedback(fb,false,'&#10007; Это <b>'+(Q[i].isAx?'аксиома':'теорема')+'</b>');
document.getElementById('p2-iv1-s').textContent=score;
i++; setTimeout(show,1300);
}
document.getElementById('p2-iv1-ax').addEventListener('click',()=>ans(true));
document.getElementById('p2-iv1-th').addEventListener('click',()=>ans(false));
show();
})();
wireReadBtn('p2');
}
/* ============================================================
\xA7 3 — Прямая. Луч. Отрезок. Ломаная
============================================================ */
function buildP3(){
const box = document.getElementById('p3-body');
const G = window.GEOM7;
let html = '';
/* SVG: 3 рисунка — прямая, луч, отрезок */
let svgLine='', svgRay='', svgSeg='';
if(G){
const b1=G.svgBox(220,80,{id:'p3-l',cell:20});
svgLine = b1.open + G.line({x:30,y:40},{x:190,y:40},{color:'#0891b2'}) + G.point(80,40,'A',{color:'#dc2626'}) + G.point(140,40,'B',{color:'#dc2626'}) + '<text x="110" y="20" text-anchor="middle" font-size="12" fill="#475569" font-weight="700" font-family="Unbounded">Прямая AB</text>' + b1.close;
const b2=G.svgBox(220,80,{id:'p3-r',cell:20});
svgRay = b2.open + G.ray({x:50,y:40},{x:200,y:40},{color:'#059669'}) + G.point(50,40,'A',{color:'#dc2626'}) + G.point(120,40,'B',{color:'#dc2626'}) + '<text x="110" y="20" text-anchor="middle" font-size="12" fill="#475569" font-weight="700" font-family="Unbounded">Луч AB</text>' + b2.close;
const b3=G.svgBox(220,80,{id:'p3-s',cell:20});
svgSeg = b3.open + G.segment({x:50,y:40},{x:170,y:40},{color:'#7c3aed'}) + G.point(50,40,'A',{color:'#dc2626'}) + G.point(170,40,'B',{color:'#dc2626'}) + '<text x="110" y="20" text-anchor="middle" font-size="12" fill="#475569" font-weight="700" font-family="Unbounded">Отрезок AB</text>' + b3.close;
}
html += makeCard('theory', 'Прямая', '3.1', `
<p><b>Прямая</b> бесконечна в обе стороны и разбивает плоскость на две <b>полуплоскости</b>.</p>
<p><b>Аксиома прямой.</b> Через любые две точки плоскости можно провести прямую, и притом только одну.</p>
<p>Следствие: если две прямые имеют общую точку — это <b>единственная</b> общая точка (иначе через две общие точки прошли бы две разные прямые).</p>
<p>Через одну точку можно провести <b>бесконечно много</b> прямых. Через три точки — не всегда можно (только если они лежат на одной прямой).</p>
<div class="svg-host">`+svgLine+`</div>`);
html += makeCard('rule', 'Луч', '3.2', `
<p><b>Лучом</b> называется часть прямой, ограниченная <b>одной точкой</b> (началом луча).</p>
<p>Луч бесконечен в одну сторону. Обозначается двумя прописными буквами (начало <b>всегда первое</b>): <b>$AB$</b> — луч с началом $A$, проходящий через $B$.</p>
<p>Два луча с общим началом, лежащие на одной прямой, называются <b>дополнительными</b> (или противоположными).</p>
<div class="svg-host">`+svgRay+`</div>`);
html += makeCard('rule', 'Отрезок', '3.3', `
<p><b>Отрезком</b> называется часть прямой, ограниченная <b>двумя точками</b> (концами).</p>
<p>Длина отрезка — положительное число. Два отрезка <b>равны</b>, если их можно совместить наложением.</p>
<p><b>Аксиома измерения отрезков.</b> Если на отрезке взять точку, она разобьёт его на два отрезка, сумма длин которых равна длине исходного: $AC + CB = AB$.</p>
<p><b>Аксиома откладывания.</b> На любом луче от его начала можно отложить отрезок данной длины, и притом только один.</p>
<p><b>Серединой</b> отрезка $EF$ называется точка $M$, делящая его на два равных отрезка: $EM = MF$.</p>
<div class="svg-host">`+svgSeg+`</div>`);
html += makeCard('rule', 'Ломаная и многоугольник', '3.4', `
<p><b>Ломаной</b> называется фигура из последовательно соединённых отрезков (звеньев), причём никакие два соседних звена не лежат на одной прямой.</p>
<p>Ломаная <b>замкнутая</b>, если её начало совпадает с концом; иначе — <b>незамкнутая</b>. Ломаная <b>простая</b>, если не имеет самопересечений.</p>
<p><b>Длиной</b> ломаной называется сумма длин её звеньев.</p>
<p><b>Простая замкнутая</b> ломаная — это <b>многоугольник</b>. Её звенья — стороны, точки излома — вершины. Сумма длин сторон — <b>периметр</b> $P$.</p>
<p>Многоугольники: 3 стороны — треугольник, 4 — четырёхугольник, 5 — пятиугольник, ...</p>`);
html += '<div class="wg" id="p3-iv1">'
+'<div class="wg-header"><span class="wg-badge">ИНТЕРАКТИВ 1</span><div class="wg-title">Прямая, луч или отрезок?</div></div>'
+'<div class="wg-help">Различай: <b>прямая</b> — бесконечна в обе стороны; <b>луч</b> — в одну; <b>отрезок</b> — ограничен двумя точками.</div>'
+'<div class="score-display"><span>Задача <b id="p3-iv1-i">1</b> / 6</span><span>Очки: <b id="p3-iv1-s">0</b> / 6</span></div>'
+'<div id="p3-iv1-q" style="padding:14px;background:var(--sec-acc-soft);border-radius:10px;font-size:1.05rem;text-align:center;margin-bottom:10px"></div>'
+'<div style="display:flex;gap:8px;justify-content:center;flex-wrap:wrap"><button class="btn primary" id="p3-iv1-line" style="background:#0891b2;border-color:#0891b2">Прямая</button><button class="btn primary" id="p3-iv1-ray" style="background:#059669;border-color:#059669">Луч</button><button class="btn primary" id="p3-iv1-seg" style="background:#7c3aed;border-color:#7c3aed">Отрезок</button></div>'
+'<div class="feedback" id="p3-iv1-fb"></div></div>';
html += '<div class="wg" id="p3-iv2">'
+'<div class="wg-header"><span class="wg-badge">ИНТЕРАКТИВ 2</span><div class="wg-title">Длина отрезка</div></div>'
+'<div class="wg-help">По аксиоме измерения: $AC + CB = AB$. Найди недостающую длину.</div>'
+trainerHTML('p3-iv2', 5, 'длина')
+'</div>';
html += secNav('p2', 'p4') + readButton('p3');
box.innerHTML = html; renderMath(box);
(function(){
const Q=[
{ e:'Бесконечна в обе стороны', ans:'line' },
{ e:'Имеет начало, бесконечна в одну сторону', ans:'ray' },
{ e:'Имеет два конца, ограничена', ans:'seg' },
{ e:'Можно измерить (имеет длину)', ans:'seg' },
{ e:'Дополнительные две: общее начало + одна прямая', ans:'ray' },
{ e:'Через 2 точки — единственная', ans:'line' },
];
let i=0,score=0;
function show(){
if(i>=Q.length){ document.getElementById('p3-iv1-q').innerHTML='<b>Готово!</b> '+score+' / '+Q.length; if(score===Q.length){addXp(12,'p3-iv1');bumpProgress('p3',25);} else if(score>=4){addXp(6,'p3-iv1');bumpProgress('p3',12);} return; }
document.getElementById('p3-iv1-i').textContent=(i+1);
document.getElementById('p3-iv1-s').textContent=score;
document.getElementById('p3-iv1-q').innerHTML=Q[i].e;
document.getElementById('p3-iv1-fb').style.display='none';
}
function ans(a){
if(i>=Q.length) return;
const fb=document.getElementById('p3-iv1-fb');
if(a===Q[i].ans){ score++; feedback(fb,true,'&#10003; Верно!'); }
else{ const lab={line:'прямая',ray:'луч',seg:'отрезок'}; feedback(fb,false,'&#10007; Правильно: <b>'+lab[Q[i].ans]+'</b>'); }
document.getElementById('p3-iv1-s').textContent=score;
i++; setTimeout(show,1100);
}
document.getElementById('p3-iv1-line').addEventListener('click',()=>ans('line'));
document.getElementById('p3-iv1-ray').addEventListener('click',()=>ans('ray'));
document.getElementById('p3-iv1-seg').addEventListener('click',()=>ans('seg'));
show();
})();
makeTrainer({
idPrefix:'p3-iv2',
questions:[
{ q:'$AC = 5$, $CB = 7$. Найди $AB$.', a:12 },
{ q:'$AB = 24$, $CB = 9$. Найди $AC$.', a:15 },
{ q:'$AB = 20$ см. $C$ — середина $AB$. Найди $AC$.', a:10 },
{ q:'$AB = 24$, $C$ на $AB$, $AC$ на 6 больше $CB$. Найди $AC$.', a:15 },
{ q:'Периметр квадрата $P = 28$ см. Найди сторону.', a:7 },
],
onComplete:(s,n)=>{ if(s===n){addXp(15,'p3-iv2');bumpProgress('p3',28);} else if(s>=3){addXp(8,'p3-iv2');bumpProgress('p3',14);} }
});
wireReadBtn('p3');
}
/* ============================================================
\xA7 4 — Окружность и круг
============================================================ */
function buildP4(){
const box = document.getElementById('p4-body');
const G = window.GEOM7;
let html = '';
let svgCircle='';
if(G){
const b=G.svgBox(280,200,{id:'p4-c',cell:20});
svgCircle = b.open
+ G.circle({x:140,y:100}, 70, { color:'#7c3aed', center:true, centerLabel:'O', radius:true, radiusAngle:Math.PI*0.25, radiusLabel:'R' })
+ G.point(140 + 70*Math.cos(-Math.PI*0.25), 100 - 70*Math.sin(-Math.PI*0.25), 'A', { color:'#dc2626' })
+ b.close;
}
html += makeCard('theory', 'Окружность и круг — разные понятия', '4.1', `
<p><b>Окружностью</b> называется замкнутая линия на плоскости, все точки которой находятся на одинаковом расстоянии от одной точки — <b>центра</b> окружности.</p>
<p><b>Кругом</b> называется внутренняя часть плоскости, ограниченная окружностью.</p>
<p>Окружность — это <b>линия</b> (граница). Круг — это <b>часть плоскости</b> (поверхность).</p>
<ul style="padding-left:22px;line-height:1.8">
<li>Обруч → модель <b>окружности</b></li>
<li>Крышка люка → модель <b>круга</b></li>
</ul>`);
html += makeCard('rule', 'Радиус и диаметр', '4.2', `
<p><b>Радиус</b> $R$ — отрезок, соединяющий центр окружности с точкой на окружности.</p>
<p><b>Диаметр</b> $d$ — отрезок, соединяющий две точки окружности и проходящий через центр. $d = 2R$.</p>
<p>Длина окружности: $C = 2\\pi R$. Площадь круга: $S = \\pi R^2$ (это вы узнаете подробнее в 9 классе).</p>
<div class="svg-host">`+svgCircle+`</div>
<p style="text-align:center;font-size:.85rem;color:var(--muted)">Окружность с центром $O$ и радиусом $OA = R$.</p>`);
html += makeCard('rule', 'Положение точки относительно окружности', '4.3', `
<p>Точка $A$ с расстоянием $OA$ от центра $O$:</p>
<ul style="padding-left:22px;line-height:1.85">
<li>$OA < R$ — точка <b>внутри</b> окружности;</li>
<li>$OA = R$ — точка <b>на</b> окружности;</li>
<li>$OA > R$ — точка <b>вне</b> окружности.</li>
</ul>`);
html += '<div class="wg" id="p4-iv1">'
+'<div class="wg-header"><span class="wg-badge">ИНТЕРАКТИВ 1</span><div class="wg-title">Точка внутри / на / вне окружности</div></div>'
+'<div class="wg-help">Дано: радиус $R$ и расстояние $OA$. Где находится точка $A$?</div>'
+'<div class="score-display"><span>Задача <b id="p4-iv1-i">1</b> / 6</span><span>Очки: <b id="p4-iv1-s">0</b> / 6</span></div>'
+'<div id="p4-iv1-q" style="padding:14px;background:var(--sec-acc-soft);border-radius:10px;font-size:1.1rem;text-align:center;margin-bottom:10px"></div>'
+'<div style="display:flex;gap:8px;justify-content:center;flex-wrap:wrap"><button class="btn primary" id="p4-iv1-in" style="background:#10b981;border-color:#10b981">Внутри</button><button class="btn primary" id="p4-iv1-on" style="background:#7c3aed;border-color:#7c3aed">На</button><button class="btn primary" id="p4-iv1-out" style="background:#dc2626;border-color:#dc2626">Вне</button></div>'
+'<div class="feedback" id="p4-iv1-fb"></div></div>';
html += '<div class="wg" id="p4-iv2">'
+'<div class="wg-header"><span class="wg-badge">ИНТЕРАКТИВ 2</span><div class="wg-title">Радиус и диаметр</div></div>'
+'<div class="wg-help">$d = 2R$. Найди недостающую величину.</div>'
+trainerHTML('p4-iv2', 4, 'число')
+'</div>';
html += secNav('p3', 'p5') + readButton('p4');
box.innerHTML = html; renderMath(box);
(function(){
const Q=[
{ e:'$R = 5$ см, $OA = 3$ см', ans:'in' },
{ e:'$R = 4$ см, $OA = 4$ см', ans:'on' },
{ e:'$R = 6$ см, $OA = 9$ см', ans:'out' },
{ e:'$R = 10$ см, $OA = 10$ см', ans:'on' },
{ e:'$R = 7$ см, $OA = 2$ см', ans:'in' },
{ e:'$R = 3$ см, $OA = 5$ см', ans:'out' },
];
let i=0,score=0;
function show(){
if(i>=Q.length){ document.getElementById('p4-iv1-q').innerHTML='<b>Готово!</b> '+score+' / '+Q.length; if(score===Q.length){addXp(12,'p4-iv1');bumpProgress('p4',28);} else if(score>=4){addXp(6,'p4-iv1');bumpProgress('p4',14);} return; }
document.getElementById('p4-iv1-i').textContent=(i+1);
document.getElementById('p4-iv1-s').textContent=score;
document.getElementById('p4-iv1-q').innerHTML=Q[i].e;
renderMath(document.getElementById('p4-iv1-q'));
document.getElementById('p4-iv1-fb').style.display='none';
}
function ans(a){
if(i>=Q.length) return;
const fb=document.getElementById('p4-iv1-fb');
if(a===Q[i].ans){ score++; feedback(fb,true,'&#10003; Верно!'); }
else{ const lab={in:'внутри',on:'на окружности',out:'вне'}; feedback(fb,false,'&#10007; Правильно: <b>'+lab[Q[i].ans]+'</b>'); }
document.getElementById('p4-iv1-s').textContent=score;
i++; setTimeout(show,1100);
}
document.getElementById('p4-iv1-in').addEventListener('click',()=>ans('in'));
document.getElementById('p4-iv1-on').addEventListener('click',()=>ans('on'));
document.getElementById('p4-iv1-out').addEventListener('click',()=>ans('out'));
show();
})();
makeTrainer({
idPrefix:'p4-iv2',
questions:[
{ q:'$R = 8$ см. Найди $d$.', a:16 },
{ q:'$d = 24$ см. Найди $R$.', a:12 },
{ q:'$R = 5{,}5$ см. Найди $d$.', a:11 },
{ q:'$d = 7$ см. Найди $R$.', a:3.5 },
],
onComplete:(s,n)=>{ if(s===n){addXp(10,'p4-iv2');bumpProgress('p4',22);} else if(s>=2){addXp(5,'p4-iv2');bumpProgress('p4',10);} }
});
wireReadBtn('p4');
}
/* ============================================================
\xA7 5 — Угол. Виды углов
============================================================ */
function buildP5(){
const box = document.getElementById('p5-body');
const G = window.GEOM7;
let html = '';
/* SVG: 4 угла — острый, прямой, тупой, развёрнутый */
function angleViz(deg, color, label){
if(!G) return '';
const b=G.svgBox(140,120,{id:'p5-a-'+deg,cell:20});
const V={x:30,y:90};
const A={x:120,y:90};
const rad=deg*Math.PI/180;
const B={x:V.x+85*Math.cos(-rad), y:V.y-85*Math.sin(rad)};
let s=b.open;
s += G.segment(V,A,{color:'#0891b2',width:2});
s += G.segment(V,B,{color:'#0891b2',width:2});
if(deg===90) s += G.rightAngleMark(V,A,B,{color:'#dc2626',size:12});
else s += G.angle(V,A,B,{color:color,r:22,label:deg+'°',fontSize:11,labelOffset:14});
s += G.point(V.x,V.y,'',{r:3,color:'#0891b2'});
s += '<text x="70" y="115" text-anchor="middle" font-size="11" fill="#475569" font-weight="700" font-family="Unbounded">'+label+'</text>';
s += b.close;
return s;
}
const svgAngles =
'<div style="display:flex;gap:8px;flex-wrap:wrap;justify-content:center">'
+ '<div>'+angleViz(45,'#10b981','Острый')+'</div>'
+ '<div>'+angleViz(90,'#dc2626','Прямой')+'</div>'
+ '<div>'+angleViz(120,'#f59e0b','Тупой')+'</div>'
+ '<div>'+angleViz(180,'#7c3aed','Развёрн.')+'</div>'
+ '</div>';
html += makeCard('theory', 'Что такое угол', '5.1', `
<p>Если из точки провести <b>два луча</b>, получится <b>угол</b>.</p>
<p>Эти лучи называются <b>сторонами</b> угла, а их общая точка — <b>вершиной</b>.</p>
<p>Обозначение тремя буквами: $\\angle BAC$ — вершина $A$ записывается <b>посередине</b>. Если по рисунку понятно, какой угол — обозначают одной буквой: $\\angle A$.</p>
<p>Часто углы обозначают малыми греческими буквами: $\\alpha, \\beta, \\gamma, \\varphi$.</p>`);
html += makeCard('rule', 'Измерение углов. Градус', '5.2', `
<p>Углы измеряют в <b>градусах</b>. Развёрнутый угол (стороны на одной прямой) равен <b>$180°$</b>.</p>
<p>$1°$ — это $\\dfrac{1}{180}$ часть развёрнутого угла.</p>
<p>Углы измеряют <b>транспортиром</b>. Транспортир позволяет также построить угол данной градусной меры.</p>
<p><b>Аксиома измерения углов.</b> Если внутри угла из его вершины провести луч, он разобьёт угол на два, сумма градусных мер которых равна градусной мере исходного.</p>
<p><b>Аксиома откладывания углов.</b> От любого луча в данную полуплоскость можно отложить угол данной градусной меры, и притом только один.</p>`);
html += makeCard('rule', 'Виды углов', '5.3', `
<p>В зависимости от градусной меры:</p>
<ul style="padding-left:22px;line-height:1.85">
<li><b>Острый</b> — меньше $90°$;</li>
<li><b>Прямой</b> — равен $90°$;</li>
<li><b>Тупой</b> — больше $90°$, но меньше $180°$;</li>
<li><b>Развёрнутый</b> — равен $180°$ (стороны — дополнительные лучи);</li>
<li><b>Полный</b> — равен $360°$ (стороны совпадают).</li>
</ul>
<div class="svg-host">`+svgAngles+`</div>`);
html += makeCard('rule', 'Биссектриса угла', '5.4', `
<p><b>Биссектрисой</b> угла называется луч, исходящий из его вершины и делящий угол на два <b>равных</b> угла.</p>
<p>Если $\\angle BAC = 70°$, а $AD$ — биссектриса, то $\\angle BAD = \\angle DAC = 35°$.</p>`);
html += '<div class="wg" id="p5-iv1">'
+'<div class="wg-header"><span class="wg-badge">ИНТЕРАКТИВ 1</span><div class="wg-title">Какой угол?</div></div>'
+'<div class="wg-help">По градусной мере определи тип угла.</div>'
+'<div class="score-display"><span>Задача <b id="p5-iv1-i">1</b> / 6</span><span>Очки: <b id="p5-iv1-s">0</b> / 6</span></div>'
+'<div id="p5-iv1-q" style="padding:14px;background:var(--sec-acc-soft);border-radius:10px;font-size:1.3rem;text-align:center;margin-bottom:10px;font-family:JetBrains Mono,monospace"></div>'
+'<div style="display:flex;gap:6px;justify-content:center;flex-wrap:wrap"><button class="btn primary" id="p5-iv1-acute" style="background:#10b981;border-color:#10b981">Острый</button><button class="btn primary" id="p5-iv1-right" style="background:#dc2626;border-color:#dc2626">Прямой</button><button class="btn primary" id="p5-iv1-obtuse" style="background:#f59e0b;border-color:#f59e0b">Тупой</button><button class="btn primary" id="p5-iv1-straight" style="background:#7c3aed;border-color:#7c3aed">Развёрн.</button></div>'
+'<div class="feedback" id="p5-iv1-fb"></div></div>';
html += '<div class="wg" id="p5-iv2">'
+'<div class="wg-header"><span class="wg-badge">ИНТЕРАКТИВ 2</span><div class="wg-title">Аксиома измерения углов</div></div>'
+'<div class="wg-help">$\\angle BAC + \\angle CAD = \\angle BAD$, где луч $AC$ внутри угла $BAD$. Найди недостающий угол.</div>'
+trainerHTML('p5-iv2', 5, 'градусы')
+'</div>';
html += '<div class="wg" id="p5-iv3">'
+'<div class="wg-header"><span class="wg-badge">ИНТЕРАКТИВ 3</span><div class="wg-title">Биссектриса</div></div>'
+'<div class="wg-help">Биссектриса делит угол пополам.</div>'
+trainerHTML('p5-iv3', 4, 'градусы')
+'</div>';
html += secNav('p4', 'p6') + readButton('p5');
box.innerHTML = html; renderMath(box);
(function(){
const Q=[
{ e:'60°', ans:'acute' },
{ e:'90°', ans:'right' },
{ e:'120°', ans:'obtuse' },
{ e:'180°', ans:'straight' },
{ e:'45°', ans:'acute' },
{ e:'179°', ans:'obtuse' },
];
let i=0,score=0;
function show(){
if(i>=Q.length){ document.getElementById('p5-iv1-q').innerHTML='<b>Готово!</b> '+score+' / '+Q.length; if(score===Q.length){addXp(12,'p5-iv1');bumpProgress('p5',22);} else if(score>=4){addXp(6,'p5-iv1');bumpProgress('p5',12);} return; }
document.getElementById('p5-iv1-i').textContent=(i+1);
document.getElementById('p5-iv1-s').textContent=score;
document.getElementById('p5-iv1-q').innerHTML=Q[i].e;
document.getElementById('p5-iv1-fb').style.display='none';
}
function ans(a){
if(i>=Q.length) return;
const fb=document.getElementById('p5-iv1-fb');
if(a===Q[i].ans){ score++; feedback(fb,true,'&#10003; Верно!'); }
else{ const lab={acute:'острый',right:'прямой',obtuse:'тупой',straight:'развёрнутый'}; feedback(fb,false,'&#10007; Правильно: <b>'+lab[Q[i].ans]+'</b>'); }
document.getElementById('p5-iv1-s').textContent=score;
i++; setTimeout(show,1000);
}
document.getElementById('p5-iv1-acute').addEventListener('click',()=>ans('acute'));
document.getElementById('p5-iv1-right').addEventListener('click',()=>ans('right'));
document.getElementById('p5-iv1-obtuse').addEventListener('click',()=>ans('obtuse'));
document.getElementById('p5-iv1-straight').addEventListener('click',()=>ans('straight'));
show();
})();
makeTrainer({
idPrefix:'p5-iv2',
questions:[
{ q:'$\\angle BAC = 40°$, $\\angle CAD = 25°$. Найди $\\angle BAD$.', a:65 },
{ q:'$\\angle BAD = 80°$, $\\angle CAD = 30°$. Найди $\\angle BAC$.', a:50 },
{ q:'$\\angle BAD = 114°$, $\\angle BAE = 2 \\angle EAD$. Найди $\\angle BAE$.', a:76 },
{ q:'$\\angle ABC = 109°$, $\\angle ABF = 95°$, $\\angle CBD = 54°$ ($BD, BF$ внутри). Найди $\\angle DBF$.', a:40 },
{ q:'Один из смежных углов равен $40°$. Найди другой.', a:140 },
],
onComplete:(s,n)=>{ if(s===n){addXp(15,'p5-iv2');bumpProgress('p5',28);} else if(s>=3){addXp(8,'p5-iv2');bumpProgress('p5',14);} }
});
makeTrainer({
idPrefix:'p5-iv3',
questions:[
{ q:'$\\angle BAC = 80°$, $AM$ — биссектриса. Найди $\\angle BAM$.', a:40 },
{ q:'$\\angle BAC = 68°$, $AM$ — биссектриса $\\angle BAC$, $AK$ — биссектриса $\\angle MAC$. Найди $\\angle BAK$.', a:51 },
{ q:'Внутри прямого угла $KMN$ проведены лучи $MA$ и $MB$: $\\angle KMB = 72°$, $\\angle AMN = 48°$. Найди $\\angle AMB$.', a:30 },
{ q:'$\\angle BOC = 70°$, $OM, OK$ — биссектрисы смежных углов $AOC$ и $BOC$. Найди $\\angle MOK$.', a:90 },
],
onComplete:(s,n)=>{ if(s===n){addXp(12,'p5-iv3');bumpProgress('p5',22);} else if(s>=2){addXp(6,'p5-iv3');bumpProgress('p5',10);} }
});
wireReadBtn('p5');
}
/* ============================================================
\xA7 6 — Смежные углы. Вертикальные углы
============================================================ */
function buildP6(){
const box = document.getElementById('p6-body');
const G = window.GEOM7;
let html = '';
/* SVG для смежных углов */
let svgAdj='', svgVert='';
if(G){
const b=G.svgBox(260,160,{id:'p6-adj',cell:20});
const V={x:130,y:120};
svgAdj = b.open
+ G.segment({x:20,y:120},{x:240,y:120},{color:'#0891b2',width:2})
+ G.segment(V,{x:170,y:50},{color:'#0891b2',width:2})
+ G.angle(V,{x:20,y:120},{x:170,y:50},{color:'#dc2626',r:30,label:'α',fontSize:13})
+ G.angle(V,{x:170,y:50},{x:240,y:120},{color:'#0891b2',r:30,label:'β',fontSize:13})
+ G.point(20,120,'A',{color:'#475569',dx:-12,dy:-6})
+ G.point(240,120,'B',{color:'#475569',dx:8,dy:-6})
+ G.point(170,50,'C',{color:'#475569',dx:8,dy:-6})
+ G.point(V.x,V.y,'O',{color:'#475569',dx:-12,dy:18})
+ '<text x="130" y="20" text-anchor="middle" font-size="11" fill="#475569" font-weight="700" font-family="Unbounded">α + β = 180\xB0</text>'
+ b.close;
const b2=G.svgBox(260,180,{id:'p6-vert',cell:20});
const O={x:130,y:90};
svgVert = b2.open
+ G.line({x:30,y:30},{x:230,y:150},{color:'#0891b2',width:2})
+ G.line({x:30,y:150},{x:230,y:30},{color:'#7c3aed',width:2})
+ G.angle(O,{x:230,y:150},{x:230,y:30},{color:'#dc2626',r:24,label:'1',fontSize:12})
+ G.angle(O,{x:30,y:150},{x:30,y:30},{color:'#dc2626',r:24,label:'2',fontSize:12})
+ G.angle(O,{x:230,y:30},{x:30,y:30},{color:'#f59e0b',r:36,label:'3',fontSize:12})
+ G.angle(O,{x:230,y:150},{x:30,y:150},{color:'#f59e0b',r:36,label:'4',fontSize:12})
+ G.point(O.x,O.y,'O',{color:'#475569',dx:8,dy:-6})
+ '<text x="130" y="172" text-anchor="middle" font-size="11" fill="#475569" font-weight="700" font-family="Unbounded">∠1 = ∠2 (вертик.)</text>'
+ b2.close;
}
html += makeCard('theory', 'Смежные углы', '6.1', `
<p>Два угла называются <b>смежными</b>, если у них <b>одна сторона общая</b>, а две другие стороны являются <b>дополнительными лучами</b> (лежат на одной прямой).</p>
<div class="svg-host">`+svgAdj+`</div>`);
html += makeCard('rule', 'Свойство смежных углов', '6.2', `
<p><b>Теорема.</b> Сумма смежных углов равна $180°$.</p>
<p style="background:var(--sec-acc-soft);padding:10px 14px;border-radius:8px">Из определения: дополнительные лучи образуют развёрнутый угол $= 180°$. Луч между ними разбивает его на два смежных, сумма градусных мер которых равна $180°$.</p>
<p>Следствия:</p>
<ul style="padding-left:22px;line-height:1.85">
<li>Если смежные углы <b>равны</b>, то каждый из них <b>прямой</b> ($90°$).</li>
<li>Если два угла равны, то равны и смежные с ними углы.</li>
</ul>`);
html += makeCard('theory', 'Вертикальные углы', '6.3', `
<p>Два угла называются <b>вертикальными</b>, если стороны одного угла являются <b>дополнительными лучами</b> к сторонам другого.</p>
<p>Когда две прямые пересекаются в точке $O$ — образуются 4 угла, попарно вертикальные.</p>
<div class="svg-host">`+svgVert+`</div>`);
html += makeCard('rule', 'Свойство вертикальных углов', '6.4', `
<p><b>Теорема.</b> Вертикальные углы <b>равны</b>.</p>
<p><b>Доказательство.</b> Пусть $\\angle 1$ и $\\angle 2$ — вертикальные. Тогда углы 1 и 3 смежные ($\\angle 1 + \\angle 3 = 180°$), а углы 2 и 3 тоже смежные ($\\angle 2 + \\angle 3 = 180°$). Отсюда $\\angle 1 = 180° - \\angle 3 = \\angle 2$. Ч. т. д.</p>
<p>При пересечении двух прямых образуются 4 угла; если один из них равен $90°$, то и остальные равны $90°$.</p>`);
html += '<div class="wg" id="p6-iv1">'
+'<div class="wg-header"><span class="wg-badge">ИНТЕРАКТИВ 1</span><div class="wg-title">Сумма смежных = 180°</div></div>'
+'<div class="wg-help">Найди смежный к данному угол.</div>'
+trainerHTML('p6-iv1', 5, 'градусы')
+'</div>';
html += '<div class="wg" id="p6-iv2">'
+'<div class="wg-header"><span class="wg-badge">ИНТЕРАКТИВ 2</span><div class="wg-title">Углы при пересечении прямых</div></div>'
+'<div class="wg-help">При пересечении двух прямых известен один угол. Найди указанный.</div>'
+trainerHTML('p6-iv2', 5, 'градусы')
+'</div>';
html += '<div class="wg" id="p6-iv3">'
+'<div class="wg-header"><span class="wg-badge">ИНТЕРАКТИВ 3</span><div class="wg-title">Сложные задачи</div></div>'
+'<div class="wg-help">Применяй и смежные, и вертикальные углы. Иногда — пропорции.</div>'
+trainerHTML('p6-iv3', 4, 'градусы')
+'</div>';
html += secNav('p5', 'p7') + readButton('p6');
box.innerHTML = html; renderMath(box);
makeTrainer({
idPrefix:'p6-iv1',
questions:[
{ q:'Один из смежных равен $40°$. Найди другой.', a:140 },
{ q:'Один из смежных равен $75°$. Найди другой.', a:105 },
{ q:'Один из смежных равен $90°$. Найди другой.', a:90 },
{ q:'Угол $\\angle MON = 100°$, $\\angle KON$ — смежный с ним. Найди $\\angle KON$.', a:80 },
{ q:'$\\angle AOP = 130°$. Смежный с ним?', a:50 },
],
onComplete:(s,n)=>{ if(s===n){addXp(12,'p6-iv1');bumpProgress('p6',22);} else if(s>=3){addXp(6,'p6-iv1');bumpProgress('p6',12);} }
});
makeTrainer({
idPrefix:'p6-iv2',
questions:[
{ q:'Один из углов при пересечении = $20°$. Найди вертикальный.', a:20 },
{ q:'Один из углов при пересечении = $20°$. Найди смежный.', a:160 },
{ q:'$\\angle DOB = 30°$ (при пересечении). Найди $\\angle AOD$.', a:150 },
{ q:'Один из углов = $110°$. Найди смежный.', a:70 },
{ q:'Один угол при пересечении = $90°$. Чему равны остальные 3?', a:90 },
],
onComplete:(s,n)=>{ if(s===n){addXp(15,'p6-iv2');bumpProgress('p6',28);} else if(s>=3){addXp(8,'p6-iv2');bumpProgress('p6',14);} }
});
makeTrainer({
idPrefix:'p6-iv3',
questions:[
{ q:'Смежные углы относятся $2 : 3$. Найди меньший.', a:72 },
{ q:'Смежные углы: один на $40°$ больше другого. Найди меньший.', a:70 },
{ q:'$\\angle BOC = 70°$, $OM, OK$ — биссектрисы смежных $\\angle AOC, \\angle BOC$. Найди $\\angle MOK$.', a:90 },
{ q:'Из 4 углов при пересечении: $\\angle 1 - \\angle 2 = 28°$ (смежные). Найди $\\angle 1$.', a:104 },
],
onComplete:(s,n)=>{ if(s===n){addXp(15,'p6-iv3');bumpProgress('p6',28);} else if(s>=2){addXp(8,'p6-iv3');bumpProgress('p6',14);} }
});
wireReadBtn('p6');
}
/* ============================================================
\xA7 7 — Перпендикулярные прямые
============================================================ */
function buildP7(){
const box = document.getElementById('p7-body');
const G = window.GEOM7;
let html = '';
let svgPerp='';
if(G){
const b=G.svgBox(280,180,{id:'p7-perp',cell:20});
const O={x:140,y:90};
svgPerp = b.open
+ G.line({x:30,y:90},{x:250,y:90},{color:'#0891b2',width:2})
+ G.line({x:140,y:20},{x:140,y:160},{color:'#7c3aed',width:2})
+ G.rightAngleMark(O,{x:250,y:90},{x:140,y:20},{color:'#dc2626',size:14})
+ G.point(O.x,O.y,'O',{color:'#475569',dx:-14,dy:14})
+ '<text x="250" y="86" font-size="13" fill="#0891b2" font-family="JetBrains Mono" font-weight="700">a</text>'
+ '<text x="146" y="20" font-size="13" fill="#7c3aed" font-family="JetBrains Mono" font-weight="700">b</text>'
+ '<text x="140" y="174" text-anchor="middle" font-size="11" fill="#475569" font-weight="700" font-family="Unbounded">a ⊥ b</text>'
+ b.close;
}
html += makeCard('theory', 'Перпендикулярные прямые', '7.1', `
<p>Две прямые называются <b>перпендикулярными</b>, если они пересекаются <b>под прямым углом</b>.</p>
<p>Обозначение: $a \\perp b$ — прямые $a$ и $b$ перпендикулярны.</p>
<p>При пересечении двух перпендикулярных прямых образуются <b>4 прямых угла</b>.</p>
<p>Отрезки и лучи называются перпендикулярными, если они лежат на перпендикулярных прямых.</p>
<div class="svg-host">`+svgPerp+`</div>`);
html += makeCard('rule', 'Перпендикуляр', '7.2', `
<p><b>Перпендикуляром</b> к данной прямой называется <b>отрезок</b>, лежащий на прямой, перпендикулярной данной, один из концов которого (<b>основание перпендикуляра</b>) — точка пересечения этих прямых.</p>
<p>Из точки $M$, не лежащей на прямой $a$, можно провести <b>перпендикуляр $MK$, опущенный</b> на прямую $a$.</p>
<p>Из точки $P$ на прямой $a$ можно <b>восстановить</b> перпендикуляр $PE$ к прямой $a$.</p>`);
html += makeCard('rule', 'Теоремы о единственности', '7.3', `
<p><b>Теорема 1.</b> Через точку, лежащую на данной прямой, можно провести прямую, перпендикулярную этой прямой, <b>и только одну</b>.</p>
<p><b>Теорема 2.</b> Через точку, не лежащую на данной прямой, можно провести прямую, перпендикулярную этой прямой, <b>и притом только одну</b>.</p>
<p>Следствие: на плоскости через любую точку можно провести <b>ровно одну</b> прямую, перпендикулярную данной.</p>`);
html += makeCard('rule', 'Две прямые, перпендикулярные третьей', '7.4', `
<p><b>Теорема.</b> На плоскости две прямые, перпендикулярные третьей, <b>параллельны</b> между собой.</p>
<p>Если $a \\perp c$ и $b \\perp c$, то $a \\parallel b$.</p>
<p><b>Доказательство.</b> Если бы $a$ и $b$ пересекались в точке $M$, то через $M$ прошли бы две прямые, перпендикулярные $c$ — это противоречит единственности перпендикуляра.</p>`);
html += '<div class="wg" id="p7-iv1">'
+'<div class="wg-header"><span class="wg-badge">ИНТЕРАКТИВ 1</span><div class="wg-title">$a \\perp b$ — что это значит?</div></div>'
+'<div class="wg-help">Проверь утверждение: верно или нет.</div>'
+'<div class="score-display"><span>Задача <b id="p7-iv1-i">1</b> / 5</span><span>Очки: <b id="p7-iv1-s">0</b> / 5</span></div>'
+'<div id="p7-iv1-q" style="padding:14px;background:var(--sec-acc-soft);border-radius:10px;font-size:1.05rem;text-align:center;margin-bottom:10px"></div>'
+'<div style="display:flex;gap:10px;justify-content:center"><button class="btn primary" id="p7-iv1-yes" style="background:#10b981;border-color:#10b981">Верно</button><button class="btn primary" id="p7-iv1-no" style="background:#dc2626;border-color:#dc2626">Неверно</button></div>'
+'<div class="feedback" id="p7-iv1-fb"></div></div>';
html += '<div class="wg" id="p7-iv2">'
+'<div class="wg-header"><span class="wg-badge">ИНТЕРАКТИВ 2</span><div class="wg-title">Задачи с перпендикулярами</div></div>'
+'<div class="wg-help">Применяй: $a \\perp b$ → угол $= 90°$; используй смежные/вертикальные.</div>'
+trainerHTML('p7-iv2', 4, 'градусы')
+'</div>';
html += secNav('p6', 'final1') + readButton('p7');
box.innerHTML = html; renderMath(box);
(function(){
const Q=[
{ e:'Если $a \\perp b$, то углы между ними равны $90°$.', ok:true },
{ e:'Через точку, не лежащую на прямой, можно провести 2 перпендикуляра.', ok:false },
{ e:'Если $a \\perp c$ и $b \\perp c$, то $a \\parallel b$.', ok:true },
{ e:'При пересечении двух перпендикулярных прямых образуются 4 прямых угла.', ok:true },
{ e:'Через точку на прямой можно провести два перпендикуляра.', ok:false },
];
let i=0,score=0;
function show(){
if(i>=Q.length){ document.getElementById('p7-iv1-q').innerHTML='<b>Готово!</b> '+score+' / '+Q.length; if(score===Q.length){addXp(12,'p7-iv1');bumpProgress('p7',28);} else if(score>=3){addXp(6,'p7-iv1');bumpProgress('p7',14);} return; }
document.getElementById('p7-iv1-i').textContent=(i+1);
document.getElementById('p7-iv1-s').textContent=score;
document.getElementById('p7-iv1-q').innerHTML=Q[i].e;
renderMath(document.getElementById('p7-iv1-q'));
document.getElementById('p7-iv1-fb').style.display='none';
}
function ans(isYes){
if(i>=Q.length) return;
const fb=document.getElementById('p7-iv1-fb');
if(isYes===Q[i].ok){ score++; feedback(fb,true,'&#10003; Верно!'); }
else feedback(fb,false,'&#10007; '+(Q[i].ok?'Это верно':'Это неверно'));
document.getElementById('p7-iv1-s').textContent=score;
i++; setTimeout(show,1100);
}
document.getElementById('p7-iv1-yes').addEventListener('click',()=>ans(true));
document.getElementById('p7-iv1-no').addEventListener('click',()=>ans(false));
show();
})();
makeTrainer({
idPrefix:'p7-iv2',
questions:[
{ q:'$AB \\perp CD$, $\\angle KOB = 48°$. Найди $\\angle COM$.', a:42 },
{ q:'$AB \\perp CD$, $\\angle KOB = 48°$. Найди $\\angle MOD$.', a:132 },
{ q:'Прямые $a \\perp b$. Сколько острых углов образуется?', a:0 },
{ q:'$\\angle BAC = 40°$, $AK \\perp AB$. Найди угол между $AK$ и биссектрисой $\\angle BAC$.', a:70 },
],
onComplete:(s,n)=>{ if(s===n){addXp(12,'p7-iv2');bumpProgress('p7',22);} else if(s>=2){addXp(6,'p7-iv2');bumpProgress('p7',10);} }
});
wireReadBtn('p7');
}
/* ============================================================
FINAL 1 — 5 БОССОВ
============================================================ */
const BOSSES = [
{
n:1, title:'Босс \xA71-2 — Основные понятия', color:'#d97706',
steps:[
{ q:'Через сколько точек на плоскости можно провести одну прямую?', verify:(v)=>+v===2, hint:'Аксиома прямой.' },
{ q:'$AB = 12$. $C$ между $A$ и $B$, $AC = 5$. Найди $CB$.', verify:(v)=>+v===7, hint:'$AC + CB = AB$.' },
{ q:'Является ли утверждение «Сумма углов треугольника равна 180°» аксиомой? «да»/«нет»', verify:(v)=>['нет','no','н'].includes(String(v).trim().toLowerCase()), hint:'Это теорема (доказывается).' },
{ q:'$3$ дм $5$ см $= ?$ см', verify:(v)=>+v===35, hint:'1 дм = 10 см.' },
{ q:'Сколько прописных букв нужно для обозначения отрезка?', verify:(v)=>+v===2, hint:'Две — на концах.' },
]
},
{
n:2, title:'Босс \xA73-4 — Прямая, луч, окружность', color:'#059669',
steps:[
{ q:'Луч имеет $?$ конца (число)', verify:(v)=>+v===1, hint:'Только начало.' },
{ q:'Точка $A$ на расстоянии 8 см от центра окружности радиусом 6 см. Внутри (1), на (2) или вне (3)?', verify:(v)=>+v===3, hint:'$OA > R$.' },
{ q:'Диаметр окружности $= 24$ см. Чему равен радиус?', verify:(v)=>+v===12, hint:'$d = 2R$.' },
{ q:'Длина замкнутой ломаной из 4 равных звеньев по 3 см?', verify:(v)=>+v===12, hint:'$P = 4 \\cdot 3$.' },
{ q:'Аксиома измерения отрезков: $AC + CB = ?$', verify:(v)=>{const t=String(v).trim().toUpperCase();return t==='AB';}, hint:'Концы отрезка.' },
]
},
{
n:3, title:'Босс \xA75 — Углы и их виды', color:'#db2777',
steps:[
{ q:'Угол $89°$ — острый, прямой, тупой или развёрнутый? Введи слово.', verify:(v)=>String(v).trim().toLowerCase().startsWith('остр'), hint:'$< 90°$.' },
{ q:'$\\angle BAC = 100°$, $AM$ — биссектриса. Найди $\\angle BAM$.', verify:(v)=>+v===50, hint:'Биссектриса делит пополам.' },
{ q:'$\\angle BAD = 80°$, $\\angle BAC = 50°$ ($AC$ внутри $\\angle BAD$). Найди $\\angle CAD$.', verify:(v)=>+v===30, hint:'$\\angle BAC + \\angle CAD = \\angle BAD$.' },
{ q:'Развёрнутый угол $= ?$ градусов', verify:(v)=>+v===180, hint:'По определению.' },
{ q:'Полный угол $= ?$ градусов', verify:(v)=>+v===360, hint:'Стороны совпадают.' },
]
},
{
n:4, title:'Босс \xA76 — Смежные и вертикальные', color:'#dc2626',
steps:[
{ q:'Один из смежных углов = $35°$. Найди другой.', verify:(v)=>+v===145, hint:'Сумма = 180°.' },
{ q:'Один из 4 углов при пересечении прямых = $40°$. Чему равен вертикальный к нему?', verify:(v)=>+v===40, hint:'Вертикальные равны.' },
{ q:'Смежные углы относятся как $4 : 5$. Найди меньший.', verify:(v)=>+v===80, hint:'$4x + 5x = 180°$.' },
{ q:'Если смежные углы равны, то каждый из них равен $?$', verify:(v)=>+v===90, hint:'$x + x = 180°$.' },
{ q:'$\\angle 1 + \\angle 3 = 250°$ (рис. с пересечением). Найди $\\angle 1$.', verify:(v)=>+v===125, hint:'Они вертикальные → равны → $2x = 250°$.' },
]
},
{
n:5, title:'Финальный босс — Перпендикулярные прямые', color:'#ea580c',
steps:[
{ q:'$a \\perp b$. Чему равен угол между ними?', verify:(v)=>+v===90, hint:'По определению.' },
{ q:'Через точку, не на прямой $a$, можно провести $?$ перпендикуляров к $a$.', verify:(v)=>+v===1, hint:'Только один.' },
{ q:'Если $a \\perp c$ и $b \\perp c$, то $a \\: ? \\: b$. Введи «параллельны» или «перпендикулярны»', verify:(v)=>String(v).trim().toLowerCase().startsWith('парал'), hint:'Теорема о двух перпендикулярах к третьей.' },
{ q:'$AB \\perp CD$, $\\angle KOB = 48°$ ($K$ на $CD$). Найди $\\angle KOA$.', verify:(v)=>+v===132, hint:'Смежный к $\\angle KOB$.' },
{ q:'Сколько острых углов образуется при пересечении двух перпендикулярных прямых?', verify:(v)=>+v===0, hint:'Все 4 угла — прямые.' },
]
},
];
function buildFinal1(){
const box = document.getElementById('final1-body');
let html = '';
html += makeCard('theory', 'Что мы изучили', 'Итог', `
<p>Глава 1 — фундамент геометрии. Мы:</p>
<ul style="padding-left:22px;line-height:1.85">
<li>познакомились с <b>точкой, прямой, плоскостью</b> и узнали, что такое <b>аксиома</b> и <b>теорема</b>;</li>
<li>различили <b>прямую, луч, отрезок</b> и научились применять аксиому измерения $AC + CB = AB$;</li>
<li>узнали, что <b>окружность</b> — это линия, а <b>круг</b> — часть плоскости;</li>
<li>выучили <b>виды углов</b> (острый, прямой, тупой, развёрнутый) и <b>биссектрису</b>;</li>
<li>доказали свойства <b>смежных</b> ($\\alpha + \\beta = 180°$) и <b>вертикальных</b> углов (равны);</li>
<li>освоили <b>перпендикулярные прямые</b>: $a \\perp b$, единственность перпендикуляра.</li>
</ul>
<p>5 боссов — последнее испытание главы.</p>`);
html += '<div id="bosses-container"></div>';
html += '<div style="margin-top:22px;padding:18px 20px;background:linear-gradient(135deg,var(--pri-soft),var(--acc-soft));border-radius:14px;border:1.5px solid var(--pri);text-align:center">'
+'<div style="font-family:\'Unbounded\',sans-serif;font-weight:800;color:var(--pri2);font-size:1.1rem;margin-bottom:6px">Прогресс по боссам</div>'
+'<div id="boss-overall" style="font-size:.95rem;color:var(--text);margin-bottom:10px">0 / 5 боссов побеждено</div>'
+'<div style="height:14px;background:rgba(217,119,6,.12);border-radius:9px;overflow:hidden"><div id="boss-overall-fill" style="height:100%;width:0%;background:linear-gradient(90deg,#d97706,#f59e0b);transition:width .5s"></div></div>'
+'</div>';
html += secNav('p7', null);
box.innerHTML = html; renderMath(box);
const cont = document.getElementById('bosses-container');
const BOSS_STATE = (function(){
try{ const s=localStorage.getItem('geometry7_ch1_bosses'); if(s) return JSON.parse(s); }catch(e){}
return BOSSES.map(()=>({stage:0,defeated:false}));
})();
function saveBosses(){ try{ localStorage.setItem('geometry7_ch1_bosses', JSON.stringify(BOSS_STATE)); }catch(e){} }
function refreshOverall(){
const won=BOSS_STATE.filter(b=>b.defeated).length;
const txt=document.getElementById('boss-overall'); if(txt) txt.textContent=won+' / '+BOSSES.length+' боссов побеждено';
const fill=document.getElementById('boss-overall-fill'); if(fill) fill.style.width=(won*100/BOSSES.length)+'%';
if(won>=BOSSES.length){ bumpProgress('final1',60); achievement('ch1_done','Глава 1 пройдена!'); }
}
cont.innerHTML = BOSSES.map((b,idx)=>{
return '<div class="boss-card" id="boss-card-'+idx+'" style="border-color:'+b.color+'">'
+'<div class="boss-head">'
+'<svg viewBox="0 0 24 24" fill="none" stroke="'+b.color+'" stroke-width="2.2" style="width:28px;height:28px"><polygon points="12,2 22,20 2,20"/></svg>'
+'<div class="boss-title" style="color:'+b.color+'">'+b.title+'</div>'
+'<div class="boss-stage" id="boss-'+idx+'-stage">Этап 1 / '+b.steps.length+'</div>'
+'</div>'
+'<div class="hp-boss" style="border-color:'+b.color+'66;background:'+b.color+'1a"><div class="hp-boss-fill" id="boss-'+idx+'-fill" style="width:0%;background:linear-gradient(90deg,'+b.color+',#f59e0b)"></div></div>'
+'<div class="boss-q" id="boss-'+idx+'-q" style="border-color:'+b.color+'"></div>'
+'<div style="display:flex;gap:8px;align-items:center;flex-wrap:wrap">'
+'<input type="text" id="boss-'+idx+'-input" class="tinp" placeholder="Ответ" style="width:160px;text-align:center">'
+'<button class="btn primary" id="boss-'+idx+'-go" style="background:'+b.color+';border-color:'+b.color+'">Атака</button>'
+'<button class="btn" id="boss-'+idx+'-hint">Подсказка</button>'
+'<button class="btn" id="boss-'+idx+'-restart">↻</button>'
+'</div>'
+'<div class="feedback" id="boss-'+idx+'-fb"></div>'
+'</div>';
}).join('');
BOSSES.forEach((b,idx)=>{
function show(){
const st=BOSS_STATE[idx];
const stageEl=document.getElementById('boss-'+idx+'-stage');
const fill=document.getElementById('boss-'+idx+'-fill');
const q=document.getElementById('boss-'+idx+'-q');
const fb=document.getElementById('boss-'+idx+'-fb');
if(st.defeated){
stageEl.textContent='✓ Побеждён'; fill.style.width='100%';
q.innerHTML='<b style="color:'+b.color+'">Босс повержен!</b>';
document.getElementById('boss-'+idx+'-go').disabled=true;
document.getElementById('boss-'+idx+'-go').style.opacity=.5;
return;
}
stageEl.textContent='Этап '+(st.stage+1)+' / '+b.steps.length;
fill.style.width=(st.stage*100/b.steps.length)+'%';
q.innerHTML=b.steps[st.stage].q;
document.getElementById('boss-'+idx+'-input').value='';
fb.style.display='none';
renderMath(q);
}
document.getElementById('boss-'+idx+'-go').addEventListener('click',()=>{
const st=BOSS_STATE[idx]; if(st.defeated) return;
const step=b.steps[st.stage];
const val=document.getElementById('boss-'+idx+'-input').value;
const fb=document.getElementById('boss-'+idx+'-fb');
if(!val.trim()){ feedback(fb,false,'&#10007; Введи ответ.'); return; }
if(step.verify(val)){
st.stage++;
if(st.stage>=b.steps.length){
st.defeated=true; saveBosses();
feedback(fb,true,'&#10003; Босс '+b.n+' побеждён! +20 XP');
addXp(20,'boss-'+b.n); bumpProgress('final1',18); refreshOverall();
setTimeout(show,1400);
}else{
saveBosses(); feedback(fb,true,'&#10003; Верно! +3 XP'); addXp(3,'boss-step'); setTimeout(show,1100);
}
}else{ feedback(fb,false,'&#10007; Промах.'); }
});
document.getElementById('boss-'+idx+'-hint').addEventListener('click',()=>{
const st=BOSS_STATE[idx]; if(st.defeated) return;
const fb=document.getElementById('boss-'+idx+'-fb');
fb.className='feedback ok';
fb.innerHTML='<span style="color:#92400e">\u{1F4A1} Подсказка:</span> '+b.steps[st.stage].hint;
fb.style.display='block';
fb.style.background='var(--warn-bg)'; fb.style.color='#92400e'; fb.style.borderLeftColor='var(--warn)';
renderMath(fb);
});
document.getElementById('boss-'+idx+'-restart').addEventListener('click',()=>{
BOSS_STATE[idx]={stage:0,defeated:false}; saveBosses();
document.getElementById('boss-'+idx+'-go').disabled=false;
document.getElementById('boss-'+idx+'-go').style.opacity=1;
show(); refreshOverall();
});
show();
});
refreshOverall();
}
</script>
</body>
</html>