Files
Learn_System/frontend/textbooks/geometry_7_ch4.html
T
Maxim Dolgolyov 7fbbfad0fe fix(geom7 ch4): переделаны рисунки §21, §22, §25 + добавлены §24
- §21: треугольник перестроен — цветовая кодировка
  (красная сторона = длиннейшая, зелёная = короткая) +
  углы напротив окрашены в тон стороне. Исправлена легенда
  (теперь корректно: c>a>b ⇒ ∠C>∠A>∠B).
- §22: 'возможный' треугольник 4-5-6 с точными
  координатами вершины (решена система уравнений);
  'невозможный' 3-4-8 показан как 2 дуги от A и B радиусов
  3 и 4 (в масштабе 25px/ед.) с явным красным 'зазором'.
- §24: добавлены 4 SVG-панели — по одной на каждый признак
  с цветовой подсветкой выделенных элементов
  (катеты / катет+угол / гипот+угол / гипот+катет).
- §25: рисунок биссектрисы пересчитан по углу — стороны
  угла идут под углом ±25° от биссектрисы, K, F₁, F₂
  вычисляются проекцией. Добавлены подписи d=d и
  одинаковые штрихи KF₁ = KF₂.
2026-05-29 09:16:52 +03:00

1683 lines
125 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 · Глава 4 · Сумма углов треугольника</title>
<meta name="viewport" content="width=device-width,initial-scale=1">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/katex@0.16.9/dist/katex.min.css">
<script defer src="https://cdn.jsdelivr.net/npm/katex@0.16.9/dist/katex.min.js"></script>
<script defer src="https://cdn.jsdelivr.net/npm/katex@0.16.9/dist/contrib/auto-render.min.js"
onload="renderMathInElement(document.body,{delimiters:[{left:'$$',right:'$$',display:true},{left:'$',right:'$',display:false},{left:'\\[',right:'\\]',display:true},{left:'\\(',right:'\\)',display:false}],throwOnError:false})"></script>
<script src="/js/api.js" defer></script>
<script src="/js/xp.js" defer></script>
<script src="/js/geom7_svg.js?v=6" 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:#0891b2; --pri2:#0e7490; --pri-soft:#cffafe;
--acc:#06b6d4; --acc2:#0891b2; --acc-soft:#a5f3fc;
--ok:#10b981; --ok-bg:#d1fae5; --warn:#f59e0b; --warn-bg:#fef3c7;
--bad:#ef4444; --fail:#dc2626; --fail-bg:#fee2e2;
}
.dark{--bg:#08121a; --card:#0a1620; --card-soft:#0e1d2a; --text:#e0f7ff; --muted:#7090a0; --border:#1a2a38}
*{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,#164e63 0%,#0891b2 55%,#22d3ee 100%);color:#fff;padding:46px 22px 30px;overflow:hidden;border-bottom:2px solid rgba(165,243,252,.2);min-height:130px}
.hdr::before{content:'ГЛАВА 4';position:absolute;right:-12px;top:50%;transform:translateY(-50%);font-family:'Unbounded',sans-serif;font-size:clamp(5rem,15vw,11rem);font-weight:900;letter-spacing:-.04em;color:transparent;-webkit-text-stroke:1.5px rgba(220,250,255,.12);line-height:1;pointer-events: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:'\2220';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}
.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)}
.btn-primary:hover{transform:translateY(-1px);box-shadow:0 8px 28px rgba(8,145,178,.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(8,145,178,.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;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(8,145,178,.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-p19"] { --sec-acc:#0891b2; --sec-acc-d:#0e7490; --sec-acc-soft:#cffafe; }
.sec[id="sec-p20"] { --sec-acc:#0d9488; --sec-acc-d:#0f766e; --sec-acc-soft:#ccfbf1; }
.sec[id="sec-p21"] { --sec-acc:#059669; --sec-acc-d:#047857; --sec-acc-soft:#d1fae5; }
.sec[id="sec-p22"] { --sec-acc:#65a30d; --sec-acc-d:#4d7c0f; --sec-acc-soft:#ecfccb; }
.sec[id="sec-p23"] { --sec-acc:#d97706; --sec-acc-d:#b45309; --sec-acc-soft:#fef3c7; }
.sec[id="sec-p24"] { --sec-acc:#dc2626; --sec-acc-d:#b91c1c; --sec-acc-soft:#fee2e2; }
.sec[id="sec-p25"] { --sec-acc:#7c3aed; --sec-acc-d:#6d28d9; --sec-acc-soft:#ede9fe; }
.sec[id="sec-p26"] { --sec-acc:#0891b2; --sec-acc-d:#0e7490; --sec-acc-soft:#cffafe; }
.sec[id="sec-final4"]{ --sec-acc:#0891b2; --sec-acc-d:#0e7490; --sec-acc-soft:#cffafe; }
.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;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));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(8,145,178,.06);position:relative;z-index:1}
.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}
.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(8,145,178,.18);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,#0891b2,#06b6d4);color:#fff;padding:12px 18px;border-radius:11px;font-weight:700;font-size:.9rem;box-shadow:0 8px 28px rgba(8,145,178,.45);z-index:1002;display:none;align-items:center;gap:8px;max-width:340px}
.ach-popup.show{display:flex}
.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}
.svg-host-row{display:flex;gap:8px;flex-wrap:wrap;justify-content:center;margin:12px 0}
</style>
</head>
<body>
<header class="hdr">
<div class="hdr-row">
<div>
<h1>Геометрия 7 · Глава 4</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> и о том, как стороны и углы влияют друг на друга. Доказываем главное равенство геометрии ($180°$), разбираем <b>внешний угол</b>, <b>неравенство треугольника</b>, изучаем <b>прямоугольные треугольники</b> и красивое свойство <b>катета против $30°$</b>.</p>
<div class="hero-row">
<button class="btn-primary" onclick="goTo('p19')"><svg class="ic" viewBox="0 0 24 24"><polygon points="6 4 20 12 6 20 6 4" fill="currentColor" stroke="none"/></svg> Начать § 19</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-p19" class="sec" data-watermark="180°"><div class="sec-header"><span class="sec-num">§ 19</span><h2 class="sec-h">Сумма углов треугольника</h2></div><div id="p19-body"></div></section>
<section id="sec-p20" class="sec" data-watermark="∠"><div class="sec-header"><span class="sec-num">§ 20</span><h2 class="sec-h">Внешний угол треугольника</h2></div><div id="p20-body"></div></section>
<section id="sec-p21" class="sec" data-watermark="&gt;"><div class="sec-header"><span class="sec-num">§ 21</span><h2 class="sec-h">Соотношения между сторонами и углами</h2></div><div id="p21-body"></div></section>
<section id="sec-p22" class="sec" data-watermark="&Delta;"><div class="sec-header"><span class="sec-num">§ 22</span><h2 class="sec-h">Неравенство треугольника</h2></div><div id="p22-body"></div></section>
<section id="sec-p23" class="sec" data-watermark="⌐"><div class="sec-header"><span class="sec-num">§ 23</span><h2 class="sec-h">Прямоугольные треугольники</h2></div><div id="p23-body"></div></section>
<section id="sec-p24" class="sec" data-watermark="=⌐"><div class="sec-header"><span class="sec-num">§ 24</span><h2 class="sec-h">Признаки равенства прямоугольных треугольников</h2></div><div id="p24-body"></div></section>
<section id="sec-p25" class="sec" data-watermark="↗"><div class="sec-header"><span class="sec-num">§ 25</span><h2 class="sec-h">Биссектриса угла как ГМТ</h2></div><div id="p25-body"></div></section>
<section id="sec-p26" class="sec" data-watermark="30°"><div class="sec-header"><span class="sec-num">§ 26</span><h2 class="sec-h">Свойство катета против угла 30°</h2></div><div id="p26-body"></div></section>
<section id="sec-final4" class="sec" data-watermark="★"><div class="sec-header"><span class="sec-num" style="background:linear-gradient(135deg,#0891b2,#22d3ee)">Финал главы</span><h2 class="sec-h">Итоги. 5 боссов главы 4</h2></div><div id="final4-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» · Глава 4 · Сумма углов треугольника · LearnSpace</footer>
<div id="ach-popup" class="ach-popup"><svg class="ic" viewBox="0 0 24 24" style="width:22px;height:22px"><polygon points="12,2 22,20 2,20"/></svg><span id="ach-text">Достижение!</span></div>
<script>
'use strict';
const STATE = { current:'p19', progress:{p19:0,p20:0,p21:0,p22:0,p23:0,p24:0,p25:0,p26:0,final4:0}, achievements:new Map(), xp:0, level:1 };
const TOTAL_PARAS = 9;
const _TB_SLUG = 'geometry-7-ch4';
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:'Начало главы 4!',
p19_done:'Сумма $180°$ — освоена!',
p22_done:'Неравенство треугольника — твоё!',
p23_done:'Прямоугольный треугольник — мастер!',
p26_done:'Свойство $30°$ — знаешь!',
ch4_done:'Глава 4 пройдена!',
};
function loadProgress(){
try{
const s=localStorage.getItem('geometry7_ch4_progress'); if(s) Object.assign(STATE.progress, JSON.parse(s));
const a=localStorage.getItem('geometry7_ch4_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_ch4_progress', JSON.stringify(STATE.progress));
localStorage.setItem('geometry7_ch4_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==='p19') achievement('p19_done');
else if(key==='p22') achievement('p22_done');
else if(key==='p23') achievement('p23_done');
else if(key==='p26') achievement('p26_done');
else if(key==='final4') achievement('ch4_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-ch4-'+(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:'p19', num:'§ 19', name:'Сумма углов', sub:'$= 180°$' },
{ id:'p20', num:'§ 20', name:'Внешний угол', sub:'$= $ сумме 2 внутр.' },
{ id:'p21', num:'§ 21', name:'Стороны vs углы', sub:'больше → напротив больше' },
{ id:'p22', num:'§ 22', name:'Неравенство треуг.', sub:'$|a-b| < c < a+b$' },
{ id:'p23', num:'§ 23', name:'Прямоугольные', sub:'катеты + гипотенуза' },
{ id:'p24', num:'§ 24', name:'Признаки рав. прям.', sub:'5 признаков' },
{ id:'p25', num:'§ 25', name:'Биссектриса как ГМТ', sub:'равноуд. от сторон' },
{ id:'p26', num:'§ 26', name:'Катет против $30°$', sub:'$= \\frac{c}{2}$' },
{ id:'final4', 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 = { p19:()=>buildP19(), p20:()=>buildP20(), p21:()=>buildP21(), p22:()=>buildP22(), p23:()=>buildP23(), p24:()=>buildP24(), p25:()=>buildP25(), p26:()=>buildP26(), final4:()=>buildFinal4() };
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 = {
p19:{title:'Шпаргалка \xA719',rows:[
['Сумма','$\\angle A + \\angle B + \\angle C = 180°$'],
['Доказательство','через прямую $\\parallel$ стороне'],
['Равносторонний','каждый угол $= 60°$'],
['Равнобедренный','$\\angle$ при основании $= \\frac{180°-\\alpha}{2}$'],
['Прямоуг.','$\\angle A + \\angle B = 90°$'],
]},
p20:{title:'Шпаргалка \xA720',rows:[
['Внешний','смежен с внутренним'],
['Свойство','$\\angle$ внеш. $= \\angle B + \\angle C$ (не смежные)'],
['Сумма','внутр. + внеш. = $180°$'],
['Польза','быстро находим неизвестный угол'],
]},
p21:{title:'Шпаргалка \xA721',rows:[
['Свойство','больше сторона → больше угол напротив'],
['Обратно','больше угол → больше сторона напротив'],
['Равные','$a = b \\Leftrightarrow \\angle A = \\angle B$'],
]},
p22:{title:'Шпаргалка \xA722',rows:[
['Неравенство','$|a-b| < c < a+b$'],
['Каждая сторона','$<$ сумме двух других'],
['Проверка','$3, 4, 8$ — НЕ треуг. ($3+4 < 8$)'],
]},
p23:{title:'Шпаргалка \xA723',rows:[
['Катеты','две стороны при прямом угле'],
['Гипотенуза','против прямого угла, самая длинная'],
['Сумма острых','$= 90°$'],
['Обозн.','$\\angle C = 90°$, $c$ — гипотенуза'],
]},
p24:{title:'Шпаргалка \xA724',rows:[
['По 2 катетам','равны'],
['По катету + остр. углу','равны'],
['По гипот. + остр. углу','равны'],
['По гипот. + катету','равны'],
]},
p25:{title:'Шпаргалка \xA725',rows:[
['Биссектриса','делит угол пополам'],
['ГМТ','точки, равноуд. от сторон угла'],
['Свойство','если $K \\in $ бис. $\\to$ расст. до сторон равны'],
['Обратно','равноуд. $\\to$ на бис.'],
]},
p26:{title:'Шпаргалка \xA726',rows:[
['Угол $= 30°$','катет напротив $= \\frac{c}{2}$ (полугипотенуза)'],
['Обратно','если катет $= \\frac{c}{2} \\to$ угол $= 30°$'],
['Пример','$c=10$, угол $30°$ → катет $=5$'],
]},
final4:{title:'Финал главы',rows:[
['\xA719\xA726','теория главы 4'],
['Боссов','5'],
['Награда','+100 XP за полное прохождение'],
]},
};
const TIPS=[
{sec:'p19',html:'<b>Сумма углов треугольника = $180°$</b> — главная формула геометрии. Запомни как «$180$» и применяй везде.'},
{sec:'p20',html:'<b>Внешний угол</b> = сумме двух <b>не смежных с ним</b> внутренних. Удобно для быстрого вычисления.'},
{sec:'p21',html:'Запомни: <b>против бо́льшей стороны лежит бо́льший угол</b>, и наоборот. Никаких исключений.'},
{sec:'p22',html:'<b>Каждая сторона $<$ сумме двух других</b>. Если $a+b \\le c$ — такого треугольника не бывает.'},
{sec:'p23',html:'В прямоугольном: $\\angle A + \\angle B = 90°$ (острые), $\\angle C = 90°$, $c$ — гипотенуза.'},
{sec:'p24',html:'<b>4 признака</b> равенства прямоугольных треугольников — все требуют меньше условий, чем для обычных.'},
{sec:'p25',html:'<b>Биссектриса</b> угла — это ГМТ точек, равноудалённых от сторон угла (как серединный $\\perp$ — для отрезка).'},
{sec:'p26',html:'<b>«$30°$ → половина гипотенузы»</b> — топ-свойство для прямоуг. треугольника. Используется во всей геометрии.'},
{sec:'final4',html:'5 боссов проверяют всё: $180°$, неравенство, прямоуг. тр-ки, $30°$. Удачи!'},
];
function buildSidebar(id){
const box=document.getElementById('sidebar-content');
const sb=SIDEBARS[id]||SIDEBARS.p19;
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_ch4_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_ch4_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={p19:'\xA719',p20:'\xA720',p21:'\xA721',p22:'\xA722',p23:'\xA723',p24:'\xA724',p25:'\xA725',p26:'\xA726',final4:'Финал'};
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 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('p19');
setTimeout(()=>achievement('start','Начало главы 4!'), 600);
}
document.addEventListener('DOMContentLoaded', init);
/* ============================================================
Хелпер: треугольник с заданными углами (грубо) + подписи
============================================================ */
function drawTriangleAngles(opts){
const G=window.GEOM7; if(!G) return '';
opts=opts||{};
const W=opts.W||280, H=opts.H||180;
const b=G.svgBox(W,H,{id:opts.id||'tri-ang',cell:20});
const A={x:40,y:H-30}, B={x:W-40,y:H-30};
/* Position C так, чтобы углы были близки к заданным */
const C=opts.C||{x:W/2,y:30};
let s=b.open
+ G.polygon([A,B,C],{color:'#0891b2',fill:'rgba(8,145,178,.08)'})
+ G.angle(A,B,C,{color:'#7c3aed',r:24,label:opts.labelA||'∠ A',fontSize:11,labelOffset:12})
+ G.angle(B,A,C,{color:'#dc2626',r:24,label:opts.labelB||'∠ B',fontSize:11,labelOffset:12})
+ G.angle(C,A,B,{color:'#059669',r:24,label:opts.labelC||'∠ C',fontSize:11,labelOffset:12})
+ G.point(A.x,A.y,'A',{color:'#1e293b',dx:-14,dy:14})
+ G.point(B.x,B.y,'B',{color:'#1e293b',dx:8,dy:14})
+ G.point(C.x,C.y,'C',{color:'#1e293b',dx:-4,dy:-8})
+ b.close;
return s;
}
/* ============================================================
\xA7 19 — Сумма углов треугольника
============================================================ */
function buildP19(){
const box = document.getElementById('p19-body');
const G = window.GEOM7;
let html = '';
/* Главная картинка: треугольник + прямая через вершину параллельно противоположной стороне */
let svgProof='';
if(G){
const b=G.svgBox(320,200,{id:'p19-proof',cell:20});
const A={x:60,y:160}, B={x:260,y:160}, C={x:170,y:50};
/* Прямая m через C, параллельная AB */
const M1={x:20,y:50}, M2={x:300,y:50};
svgProof = b.open
+ G.segment(M1,M2,{color:'#7c3aed',width:2,dash:'5 3'})
+ G.parallelMark({x:60,y:50},{x:120,y:50},{color:'#7c3aed'})
+ G.parallelMark({x:120,y:160},{x:180,y:160},{color:'#7c3aed'})
+ G.polygon([A,B,C],{color:'#0891b2',fill:'rgba(8,145,178,.08)'})
+ G.angle(C,A,B,{color:'#059669',r:24,label:'∠ C',fontSize:11,labelOffset:12})
+ G.angle(A,B,C,{color:'#dc2626',r:24,label:'∠ A',fontSize:11,labelOffset:12})
+ G.angle(B,A,C,{color:'#7c3aed',r:24,label:'∠ B',fontSize:11,labelOffset:12})
/* Метки на прямой m — две части, накрест лежащие с A и B */
+ '<text x="100" y="40" font-size="11" font-family="Unbounded,Inter,sans-serif" font-weight="700" fill="#7c3aed">α</text>'
+ '<text x="230" y="40" font-size="11" font-family="Unbounded,Inter,sans-serif" font-weight="700" fill="#dc2626">β</text>'
+ '<text x="295" y="44" font-size="12" font-family="Unbounded,Inter,sans-serif" font-weight="700" fill="#7c3aed">m</text>'
+ G.point(A.x,A.y,'A',{color:'#1e293b',dx:-14,dy:14})
+ G.point(B.x,B.y,'B',{color:'#1e293b',dx:8,dy:14})
+ G.point(C.x,C.y,'C',{color:'#1e293b',dx:-4,dy:-8})
+ b.close;
}
html += makeCard('rule', 'Главная теорема', '19.1', `
<p style="background:var(--sec-acc-soft);padding:10px 14px;border-radius:8px;font-size:1.05rem"><b>Теорема.</b> Сумма углов любого треугольника равна $180°$:</p>
<p style="text-align:center;font-size:1.2rem;margin:10px 0">$\\angle A + \\angle B + \\angle C = 180°$</p>
<p>Это <b>самое важное равенство</b> в школьной геометрии. Им пользуются буквально везде.</p>`);
html += makeCard('theory', 'Доказательство', '19.2', `
<p>Через вершину $C$ проведём прямую $m$, <b>параллельную</b> стороне $AB$. Это возможно по аксиоме параллельных.</p>
<div class="svg-host">`+svgProof+`</div>
<p>Прямая $AC$ — секущая для $m$ и $AB$ $\\Rightarrow$ накрест лежащие $\\angle A = \\alpha$ (на прямой $m$).</p>
<p>Прямая $BC$ — секущая для $m$ и $AB$ $\\Rightarrow$ накрест лежащие $\\angle B = \\beta$ (на прямой $m$).</p>
<p>Углы $\\alpha$, $\\angle C$ и $\\beta$ лежат вдоль прямой $m$ и в сумме дают <b>развёрнутый угол</b>: $\\alpha + \\angle C + \\beta = 180°$.</p>
<p>Подставляем $\\alpha = \\angle A$, $\\beta = \\angle B$: $\\angle A + \\angle B + \\angle C = 180°$. ■</p>`);
html += makeCard('example', 'Частные случаи', '19.3', `
<p><b>Равносторонний треугольник.</b> Все углы равны: $3\\alpha = 180° \\Rightarrow \\alpha = 60°$. Каждый угол $= 60°$.</p>
<p><b>Равнобедренный с углом при вершине $\\alpha$.</b> Углы при основании равны: $\\beta = \\frac{180° - \\alpha}{2}$.</p>
<p><b>Прямоугольный треугольник.</b> $\\angle C = 90°$ $\\Rightarrow$ $\\angle A + \\angle B = 90°$. Острые углы дополняют друг друга до $90°$.</p>
<p><b>Не может быть двух прямых или двух тупых углов</b> в одном треугольнике — сумма превысит $180°$.</p>`);
/* ИНТЕРАКТИВ 1 — найди 3-й угол */
html += '<div class="wg" id="p19-iv1">'
+'<div class="wg-header"><span class="wg-badge">ИНТЕРАКТИВ 1</span><div class="wg-title">Найди третий угол</div></div>'
+'<div class="wg-help">Используй: $\\angle A + \\angle B + \\angle C = 180°$.</div>'
+trainerHTML('p19-iv1', 6, 'градусы')
+'</div>';
/* ИНТЕРАКТИВ 2 — частные случаи */
html += '<div class="wg" id="p19-iv2">'
+'<div class="wg-header"><span class="wg-badge">ИНТЕРАКТИВ 2</span><div class="wg-title">Особые треугольники</div></div>'
+'<div class="wg-help">Равносторонний, равнобедренный, прямоугольный.</div>'
+trainerHTML('p19-iv2', 5, 'градусы')
+'</div>';
html += secNav(null, 'p20') + readButton('p19');
box.innerHTML = html; renderMath(box);
makeTrainer({
idPrefix:'p19-iv1',
questions:[
{ q:'$\\angle A = 50°$, $\\angle B = 60°$. Найди $\\angle C$.', a:70 },
{ q:'$\\angle A = 90°$, $\\angle B = 45°$. Найди $\\angle C$.', a:45 },
{ q:'$\\angle A = 30°$, $\\angle B = 100°$. Найди $\\angle C$.', a:50 },
{ q:'$\\angle A = \\angle B = 70°$. Найди $\\angle C$.', a:40 },
{ q:'$\\angle A = 25°$, $\\angle B = 25°$. Найди $\\angle C$.', a:130 },
{ q:'$\\angle A = 80°$, $\\angle B = 80°$. Найди $\\angle C$.', a:20 },
],
onComplete:(s,n)=>{ if(s===n){addXp(18,'p19-iv1');bumpProgress('p19',30);} else if(s>=4){addXp(9,'p19-iv1');bumpProgress('p19',15);} }
});
makeTrainer({
idPrefix:'p19-iv2',
questions:[
{ q:'Равносторонний треугольник. Найди любой угол.', a:60 },
{ q:'Равнобедренный, $\\angle$ при вершине $= 40°$. Найди угол при основании.', a:70 },
{ q:'Равнобедренный, угол при основании $= 65°$. Найди угол при вершине.', a:50 },
{ q:'Прямоугольный, один острый угол $= 35°$. Найди второй острый.', a:55 },
{ q:'Углы относятся как $1:2:3$. Сумма $= 180$. Найди наибольший угол.', a:90 },
],
onComplete:(s,n)=>{ if(s===n){addXp(18,'p19-iv2');bumpProgress('p19',30);} else if(s>=3){addXp(9,'p19-iv2');bumpProgress('p19',14);} }
});
wireReadBtn('p19');
}
/* ============================================================
\xA7 20 — Внешний угол треугольника
============================================================ */
function buildP20(){
const box = document.getElementById('p20-body');
const G = window.GEOM7;
let html = '';
let svgExt='';
if(G){
const b=G.svgBox(320,180,{id:'p20-ext',cell:20});
const A={x:50,y:140}, B={x:200,y:140}, C={x:140,y:40};
/* Продолжение AB за B */
const B2={x:290,y:140};
svgExt = b.open
+ G.polygon([A,B,C],{color:'#0891b2',fill:'rgba(8,145,178,.08)'})
+ G.segment(B,B2,{color:'#dc2626',width:2.5,dash:'5 3'})
+ G.angle(B,C,B2,{color:'#f59e0b',r:30,label:'∠ внеш',fontSize:11,labelOffset:14})
+ G.angle(B,A,C,{color:'#7c3aed',r:24,label:'∠ B',fontSize:11,labelOffset:12})
+ G.angle(A,B,C,{color:'#059669',r:24,label:'∠ A',fontSize:11,labelOffset:12})
+ G.angle(C,A,B,{color:'#dc2626',r:24,label:'∠ C',fontSize:11,labelOffset:12})
+ G.point(A.x,A.y,'A',{color:'#1e293b',dx:-14,dy:12})
+ G.point(B.x,B.y,'B',{color:'#1e293b',dx:-2,dy:14})
+ G.point(C.x,C.y,'C',{color:'#1e293b',dx:-4,dy:-8})
+ b.close;
}
html += makeCard('rule', 'Определение', '20.1', `
<p><b>Внешний угол треугольника</b> при вершине — угол, <b>смежный</b> со внутренним углом этой вершины. Получается продолжением одной из сторон за вершину.</p>
<div class="svg-host">`+svgExt+`</div>
<p>Внешний и соответствующий внутренний угол вместе образуют <b>развёрнутый угол</b>: $\\angle_{\\text{внутр}} + \\angle_{\\text{внеш}} = 180°$.</p>`);
html += makeCard('rule', 'Теорема о внешнем угле', '20.2', `
<p style="background:var(--sec-acc-soft);padding:10px 14px;border-radius:8px"><b>Теорема.</b> Внешний угол треугольника равен <b>сумме двух внутренних углов, не смежных с ним</b>.</p>
<p>Доказательство: пусть $\\angle$ внешн. при $B$ — это угол, смежный с $\\angle B$. Тогда $\\angle$ внеш. $= 180° - \\angle B$. По теореме о сумме: $\\angle A + \\angle B + \\angle C = 180°$ $\\Rightarrow$ $180° - \\angle B = \\angle A + \\angle C$. Значит $\\angle$ внеш. $= \\angle A + \\angle C$. ■</p>
<p><b>Польза:</b> часто проще посчитать внешний угол через 2 не смежных, чем сначала находить внутренний, потом вычитать.</p>`);
html += makeCard('example', 'Пример', '20.3', `
<p><b>Задача.</b> В $\\triangle ABC$: $\\angle A = 40°$, $\\angle C = 70°$. Найди внешний угол при вершине $B$.</p>
<p><b>Решение.</b> $\\angle$ внеш. $B$ $= \\angle A + \\angle C = 40° + 70° = 110°$.</p>
<p><b>Проверка.</b> $\\angle B = 180° - 40° - 70° = 70°$. Внешний $= 180° - 70° = 110°$. Совпадает.</p>`);
/* ИНТЕРАКТИВ 1 — найди внешний угол */
html += '<div class="wg" id="p20-iv1">'
+'<div class="wg-header"><span class="wg-badge">ИНТЕРАКТИВ 1</span><div class="wg-title">Внешний угол = сумма двух</div></div>'
+'<div class="wg-help">Внешний $=$ сумма двух не смежных с ним внутренних.</div>'
+trainerHTML('p20-iv1', 5, 'градусы')
+'</div>';
/* ИНТЕРАКТИВ 2 — обратные задачи */
html += '<div class="wg" id="p20-iv2">'
+'<div class="wg-header"><span class="wg-badge">ИНТЕРАКТИВ 2</span><div class="wg-title">Найди недостающий внутренний</div></div>'
+'<div class="wg-help">Если знаешь внешний и один внутренний из противоположной стороны.</div>'
+trainerHTML('p20-iv2', 5, 'градусы')
+'</div>';
html += secNav('p19', 'p21') + readButton('p20');
box.innerHTML = html; renderMath(box);
makeTrainer({
idPrefix:'p20-iv1',
questions:[
{ q:'$\\angle A = 50°$, $\\angle C = 60°$. Найди внешний при $B$.', a:110 },
{ q:'$\\angle A = 30°$, $\\angle B = 80°$. Найди внешний при $C$.', a:110 },
{ q:'$\\angle A = 90°$, $\\angle C = 40°$. Найди внешний при $B$.', a:130 },
{ q:'$\\angle A = 20°$, $\\angle C = 25°$. Найди внешний при $B$.', a:45 },
{ q:'$\\angle B = 70°$, $\\angle C = 60°$. Найди внешний при $A$.', a:130 },
],
onComplete:(s,n)=>{ if(s===n){addXp(15,'p20-iv1');bumpProgress('p20',30);} else if(s>=3){addXp(8,'p20-iv1');bumpProgress('p20',15);} }
});
makeTrainer({
idPrefix:'p20-iv2',
questions:[
{ q:'Внешний при $B = 120°$, $\\angle A = 50°$. Найди $\\angle C$.', a:70 },
{ q:'Внешний при $C = 100°$, $\\angle B = 35°$. Найди $\\angle A$.', a:65 },
{ q:'Внешний при $A = 140°$, $\\angle B = 80°$. Найди $\\angle C$.', a:60 },
{ q:'Внешний при $B = 150°$. Найди $\\angle B$.', a:30 },
{ q:'Внутренний $\\angle A = 30°$. Найди внешний при $A$.', a:150 },
],
onComplete:(s,n)=>{ if(s===n){addXp(15,'p20-iv2');bumpProgress('p20',30);} else if(s>=3){addXp(8,'p20-iv2');bumpProgress('p20',15);} }
});
wireReadBtn('p20');
}
/* ============================================================
\xA7 21 — Соотношения между сторонами и углами
============================================================ */
function buildP21(){
const box = document.getElementById('p21-body');
const G = window.GEOM7;
let html = '';
let svg21='';
if(G){
const b=G.svgBox(300,180,{id:'p21-tri',cell:20});
/* Скалянный треугольник: AB ≈ 240 (самая длинная), BC ≈ 184, AC ≈ 170 (самая короткая)
Тогда ∠C — самый большой (напротив AB), ∠B — самый маленький (напротив AC) */
const A={x:30,y:160}, B={x:270,y:160}, C={x:140,y:30};
svg21 = b.open
+ G.polygon([A,B,C],{color:'#0891b2',fill:'rgba(8,145,178,.08)'})
/* Цветовая кодировка: красный = самый большой, оранж. = средний, зелёный = самый маленький */
+ G.segment(A,B,{color:'#dc2626',width:3,label:'c — самая длин.',labelOffset:18,labelColor:'#dc2626'})
+ G.segment(B,C,{color:'#f59e0b',width:2.5,label:'a',labelOffset:-16,labelColor:'#f59e0b'})
+ G.segment(A,C,{color:'#059669',width:2,label:'b — самая корот.',labelOffset:-16,labelColor:'#059669'})
+ G.angle(C,A,B,{color:'#dc2626',r:28,label:'∠C — наиб.',fontSize:11,labelOffset:14})
+ G.angle(A,B,C,{color:'#f59e0b',r:22,label:'∠A',fontSize:11,labelOffset:12})
+ G.angle(B,A,C,{color:'#059669',r:18,label:'∠B — наим.',fontSize:11,labelOffset:12})
+ G.point(A.x,A.y,'A',{color:'#1e293b',dx:-14,dy:12})
+ G.point(B.x,B.y,'B',{color:'#1e293b',dx:8,dy:12})
+ G.point(C.x,C.y,'C',{color:'#1e293b',dx:-4,dy:-8})
+ b.close;
}
html += makeCard('rule', 'Прямое свойство', '21.1', `
<p style="background:var(--sec-acc-soft);padding:10px 14px;border-radius:8px"><b>Теорема.</b> В треугольнике <b>против бо́льшей стороны лежит бо́льший угол</b>.</p>
<div class="svg-host">`+svg21+`</div>
<p>На рисунке: $AB > BC > AC$ (стороны $c > a > b$) $\\Rightarrow$ $\\angle C > \\angle A > \\angle B$.</p>
<p style="background:var(--warn-bg);padding:9px 13px;border-radius:8px;border-left:4px solid var(--warn)"><b>Цвет = размер.</b> Красным выделены самая длинная сторона и самый большой угол напротив неё. Зелёным — самая короткая и самый маленький.</p>
<p><b>Правило большого пальца.</b> Самая длинная сторона «смотрит» на самый большой угол.</p>`);
html += makeCard('rule', 'Обратное свойство', '21.2', `
<p style="background:var(--sec-acc-soft);padding:10px 14px;border-radius:8px"><b>Теорема (обратная).</b> Против бо́льшего угла лежит бо́льшая сторона.</p>
<p>Получается, что <b>отношение размера стороны и противоположного угла — взаимно однозначное</b>: больше одно — больше другое.</p>
<p>Следствие: $a = b \\Leftrightarrow \\angle A = \\angle B$. То есть равные углы $\\Leftrightarrow$ равные стороны напротив них.</p>`);
html += makeCard('example', 'Применение', '21.3', `
<p><b>Задача 1.</b> В $\\triangle ABC$: $\\angle A = 80°$, $\\angle B = 60°$, $\\angle C = 40°$. Какая сторона самая длинная?</p>
<p><b>Решение.</b> Наибольший угол $\\angle A$ $\\Rightarrow$ против него лежит <b>$BC$</b> — самая длинная.</p>
<p><b>Задача 2.</b> В $\\triangle$: $AB = 7$, $BC = 5$, $AC = 6$. Какой угол самый большой?</p>
<p><b>Решение.</b> Самая длинная сторона $AB = 7$ $\\Rightarrow$ напротив неё лежит $\\angle C$ — самый большой.</p>`);
/* ИНТЕРАКТИВ 1 — какая сторона/угол */
html += '<div class="wg" id="p21-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="p21-iv1-i">1</b> / 6</span><span>Очки: <b id="p21-iv1-s">0</b> / 6</span></div>'
+'<div id="p21-iv1-q" style="padding:14px;background:var(--sec-acc-soft);border-radius:10px;font-size:1.02rem;text-align:center;margin-bottom:10px"></div>'
+'<div style="display:flex;gap:8px;justify-content:center;flex-wrap:wrap"><button class="btn primary" id="p21-iv1-A">$A$</button><button class="btn primary" id="p21-iv1-B">$B$</button><button class="btn primary" id="p21-iv1-C">$C$</button></div>'
+'<div class="feedback" id="p21-iv1-fb"></div></div>';
/* ИНТЕРАКТИВ 2 — сравни 2 элемента */
html += '<div class="wg" id="p21-iv2">'
+'<div class="wg-header"><span class="wg-badge">ИНТЕРАКТИВ 2</span><div class="wg-title">Сравни два элемента</div></div>'
+'<div class="wg-help">Введи знак: <b>&gt;</b>, <b>&lt;</b> или <b>=</b>.</div>'
+trainerHTML('p21-iv2', 5, 'знак')
+'</div>';
html += secNav('p20', 'p22') + readButton('p21');
box.innerHTML = html; renderMath(box);
(function(){
const Q=[
{ e:'$\\angle A = 80°$, $\\angle B = 60°$, $\\angle C = 40°$. Какая сторона <b>самая длинная</b>?', map:{A:'BC',B:'AC',C:'AB'}, ans:'A', extra:'BC (напротив $\\angle A$)' },
{ e:'$AB = 7$, $BC = 5$, $AC = 6$. Какой угол <b>самый большой</b>?', map:{A:'A',B:'B',C:'C'}, ans:'C', extra:'$\\angle C$ (напротив $AB = 7$)' },
{ e:'$\\angle A = 30°$, $\\angle B = 100°$, $\\angle C = 50°$. Самая длинная сторона?', map:{A:'BC',B:'AC',C:'AB'}, ans:'B', extra:'$AC$ (напротив $\\angle B$)' },
{ e:'$AB = 4$, $BC = 9$, $AC = 7$. Какой угол самый большой?', map:{A:'A',B:'B',C:'C'}, ans:'A', extra:'$\\angle A$ (напротив $BC = 9$)' },
{ e:'$\\angle A = 70°$, $\\angle B = 70°$, $\\angle C = 40°$. Какие стороны равны?', map:{A:'BC=AC',B:'AB=AC',C:'AB=BC'}, ans:'A', extra:'$BC = AC$ (углы $A$ и $B$ равны)' },
{ e:'$AB = AC$, $\\angle B = 50°$. Какой угол равен $50°$?', map:{A:'A',B:'B',C:'C'}, ans:'C', extra:'$\\angle C = 50°$ (равноб. с осн. $BC$)' },
];
let i=0,score=0;
function show(){
if(i>=Q.length){ document.getElementById('p21-iv1-q').innerHTML='<b>Готово!</b> '+score+' / '+Q.length; if(score===Q.length){addXp(15,'p21-iv1');bumpProgress('p21',25);} else if(score>=4){addXp(8,'p21-iv1');bumpProgress('p21',12);} return; }
document.getElementById('p21-iv1-i').textContent=(i+1);
document.getElementById('p21-iv1-s').textContent=score;
document.getElementById('p21-iv1-q').innerHTML=Q[i].e;
renderMath(document.getElementById('p21-iv1-q'));
document.getElementById('p21-iv1-fb').style.display='none';
}
function ans(a){
if(i>=Q.length) return;
const fb=document.getElementById('p21-iv1-fb');
if(a===Q[i].ans){ score++; feedback(fb,true,'&#10003; Верно! '+(Q[i].extra||'')); }
else feedback(fb,false,'&#10007; Правильно: <b>'+Q[i].extra+'</b>');
document.getElementById('p21-iv1-s').textContent=score;
i++; setTimeout(show,1500);
}
document.getElementById('p21-iv1-A').addEventListener('click',()=>ans('A'));
document.getElementById('p21-iv1-B').addEventListener('click',()=>ans('B'));
document.getElementById('p21-iv1-C').addEventListener('click',()=>ans('C'));
show();
})();
makeTrainer({
idPrefix:'p21-iv2',
parser:(v)=>v,
questions:[
{ q:'$\\angle A = 80°$, $\\angle B = 60°$. Сравни $BC$ и $AC$.', a:(v)=>String(v).trim().startsWith('>'), show:'$>$ ($BC > AC$, т.к. $\\angle A > \\angle B$)' },
{ q:'$AB = 5$, $BC = 8$. Сравни $\\angle C$ и $\\angle A$.', a:(v)=>String(v).trim().startsWith('<'), show:'$<$ ($\\angle C < \\angle A$, т.к. $AB < BC$)' },
{ q:'$\\angle A = \\angle B$. Сравни $BC$ и $AC$.', a:(v)=>String(v).trim().startsWith('='), show:'$=$ (равные углы → равные стороны)' },
{ q:'$\\angle A = 30°$, $\\angle B = 100°$. Сравни $AC$ и $BC$.', a:(v)=>String(v).trim().startsWith('>'), show:'$>$ ($AC > BC$, т.к. $\\angle B > \\angle A$)' },
{ q:'$AC = AB$. Сравни $\\angle B$ и $\\angle C$.', a:(v)=>String(v).trim().startsWith('='), show:'$=$ (равные стороны → равные углы)' },
],
onComplete:(s,n)=>{ if(s===n){addXp(15,'p21-iv2');bumpProgress('p21',25);} else if(s>=3){addXp(8,'p21-iv2');bumpProgress('p21',12);} }
});
wireReadBtn('p21');
}
/* ============================================================
\xA7 22 — Неравенство треугольника
============================================================ */
function buildP22(){
const box = document.getElementById('p22-body');
const G = window.GEOM7;
let html = '';
let svgOK='', svgBAD='';
if(G){
/* === Валидный треугольник 4, 5, 6 ===
Масштаб: 1 ед = 25 px. AB = 6 ед = 150 px (база)
Решая систему AC=5*25=125, BC=4*25=100 от A={20,130}, B={170,130}:
C ≈ {114, 47} */
const b1=G.svgBox(220,160,{id:'p22-ok',cell:20});
const A={x:20,y:130}, B={x:170,y:130}, C={x:114,y:47};
svgOK = b1.open
+ G.polygon([A,B,C],{color:'#059669',width:2.5,fill:'rgba(16,185,129,.12)'})
+ G.segment(A,B,{color:'#065f46',width:2,label:'6',labelOffset:14,labelColor:'#065f46'})
+ G.segment(A,C,{color:'#065f46',width:2,label:'5',labelOffset:-14,labelColor:'#065f46'})
+ G.segment(B,C,{color:'#065f46',width:2,label:'4',labelOffset:-14,labelColor:'#065f46'})
+ G.point(A.x,A.y,'A',{color:'#1e293b',dx:-12,dy:12,fontSize:11})
+ G.point(B.x,B.y,'B',{color:'#1e293b',dx:6,dy:12,fontSize:11})
+ G.point(C.x,C.y,'C',{color:'#1e293b',dx:-4,dy:-8,fontSize:11})
+ '<text x="180" y="35" font-size="16" font-weight="900" fill="#10b981">✓</text>'
+ '<text x="110" y="155" text-anchor="middle" font-size="10" fill="#065f46" font-weight="700">4 + 5 &gt; 6 ✓ существует</text>'
+ b1.close;
/* === Невалидный: 3, 4, 8 ===
Масштаб: 1 ед = 25 px. AB = 8*25 = 200 px
Дуга от A: r = 3*25 = 75 (всё, до чего достаёт «3-я сторона»)
Дуга от B: r = 4*25 = 100 (всё, до чего достаёт «4-я сторона»)
Зазор = 200 - 75 - 100 = 25 px → дуги не пересекаются */
const b2=G.svgBox(240,160,{id:'p22-bad',cell:20});
const A2={x:20,y:130}, B2={x:220,y:130};
/* Дуги сверху над прямой AB */
svgBAD = b2.open
+ G.segment(A2,B2,{color:'#dc2626',width:2.5,label:'8',labelOffset:14,labelColor:'#7f1d1d'})
/* Дуга от A (радиус 75, верхняя полуокружность) */
+ '<path d="M '+(A2.x-75)+' '+A2.y+' A 75 75 0 0 1 '+(A2.x+75)+' '+A2.y+'" fill="none" stroke="#7c3aed" stroke-width="2" stroke-dasharray="4 3"/>'
/* Дуга от B (радиус 100, верхняя полуокружность) */
+ '<path d="M '+(B2.x-100)+' '+B2.y+' A 100 100 0 0 1 '+(B2.x+100)+' '+B2.y+'" fill="none" stroke="#f59e0b" stroke-width="2" stroke-dasharray="4 3"/>'
/* Линии-радиусы (показать длины 3 и 4) */
+ G.segment(A2,{x:A2.x,y:A2.y-75},{color:'#7c3aed',width:1.2})
+ G.segment(B2,{x:B2.x,y:B2.y-100},{color:'#f59e0b',width:1.2})
+ '<text x="'+(A2.x+5)+'" y="'+(A2.y-40)+'" font-size="11" font-weight="700" fill="#7c3aed">3</text>'
+ '<text x="'+(B2.x+5)+'" y="'+(B2.y-55)+'" font-size="11" font-weight="700" fill="#f59e0b">4</text>'
/* Двойная стрелка «зазор» */
+ '<line x1="'+(A2.x+75)+'" y1="'+A2.y+'" x2="'+(B2.x-100)+'" y2="'+A2.y+'" stroke="#dc2626" stroke-width="2.5"/>'
+ '<text x="'+((A2.x+75+B2.x-100)/2)+'" y="'+(A2.y-6)+'" text-anchor="middle" font-size="11" font-weight="800" fill="#dc2626">зазор</text>'
+ G.point(A2.x,A2.y,'A',{color:'#1e293b',dx:-12,dy:14,fontSize:11})
+ G.point(B2.x,B2.y,'B',{color:'#1e293b',dx:6,dy:14,fontSize:11})
+ '<text x="200" y="35" font-size="16" font-weight="900" fill="#dc2626">✗</text>'
+ '<text x="120" y="155" text-anchor="middle" font-size="10" fill="#7f1d1d" font-weight="700">3 + 4 &lt; 8 ✗ не существует</text>'
+ b2.close;
}
html += makeCard('rule', 'Неравенство треугольника', '22.1', `
<p style="background:var(--sec-acc-soft);padding:10px 14px;border-radius:8px"><b>Теорема.</b> <b>Каждая сторона треугольника меньше суммы двух других:</b></p>
<p style="text-align:center;font-size:1.15rem;margin:8px 0">$a < b + c, \\quad b < a + c, \\quad c < a + b$</p>
<p>Иначе говоря, треугольник с тремя данными длинами сторон <b>существует тогда и только тогда</b>, когда максимальная сторона меньше суммы двух других.</p>
<p><b>Усиленная форма:</b> $|b - c| < a < b + c$ — длина стороны зажата между разностью и суммой двух других.</p>
<div class="svg-host-row">`+svgOK+svgBAD+`</div>`);
html += makeCard('theory', 'Идея доказательства', '22.2', `
<p>Пусть в $\\triangle ABC$ требуется доказать $AC < AB + BC$.</p>
<p>На стороне $AB$ за $B$ отложим $BD = BC$. Получим равнобедренный $\\triangle BDC$ ($BD = BC$).</p>
<p>$\\angle BDC = \\angle BCD$ (углы при основании). В $\\triangle ACD$: $\\angle ACD > \\angle BCD = \\angle ADC$ $\\Rightarrow$ по свойству §21 $AD > AC$.</p>
<p>А $AD = AB + BD = AB + BC$. Значит $AB + BC > AC$. ■</p>`);
html += makeCard('example', 'Когда треугольник возможен?', '22.3', `
<p><b>Задача 1.</b> Можно ли построить треугольник со сторонами $3, 4, 5$? <br>Проверка: $3+4 > 5$ ✓, $3+5 > 4$ ✓, $4+5 > 3$ ✓. <b>Можно</b>.</p>
<p><b>Задача 2.</b> Можно ли со сторонами $3, 4, 8$? <br>Проверка: $3+4 = 7 < 8$ ✗. <b>Нельзя</b>.</p>
<p><b>Задача 3.</b> В $\\triangle$: $a = 5$, $b = 7$. Какие значения может принимать $c$? <br>$|7-5| < c < 7+5$ $\\Rightarrow$ $2 < c < 12$.</p>`);
/* ИНТЕРАКТИВ 1 — да/нет: возможен треугольник */
html += '<div class="wg" id="p22-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="p22-iv1-i">1</b> / 6</span><span>Очки: <b id="p22-iv1-s">0</b> / 6</span></div>'
+'<div id="p22-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"><button class="btn primary" id="p22-iv1-y" style="background:#10b981;border-color:#10b981">Да</button><button class="btn primary" id="p22-iv1-n" style="background:#dc2626;border-color:#dc2626">Нет</button></div>'
+'<div class="feedback" id="p22-iv1-fb"></div></div>';
/* ИНТЕРАКТИВ 2 — диапазон 3-й стороны */
html += '<div class="wg" id="p22-iv2">'
+'<div class="wg-header"><span class="wg-badge">ИНТЕРАКТИВ 2</span><div class="wg-title">Найди границу</div></div>'
+'<div class="wg-help">$|b-c| < a < b+c$ — найди наибольшее/наименьшее целое значение.</div>'
+trainerHTML('p22-iv2', 5, 'число')
+'</div>';
html += secNav('p21', 'p23') + readButton('p22');
box.innerHTML = html; renderMath(box);
(function(){
const Q=[
{ e:'Стороны $3, 4, 5$', ok:true, why:'$3+4>5$, $3+5>4$, $4+5>3$.' },
{ e:'Стороны $3, 4, 8$', ok:false, why:'$3+4 < 8$, нарушено.' },
{ e:'Стороны $7, 7, 7$', ok:true, why:'Равносторонний, всё ок.' },
{ e:'Стороны $1, 2, 3$', ok:false, why:'$1+2 = 3$, не строго больше — вырожденный.' },
{ e:'Стороны $5, 12, 13$', ok:true, why:'$5+12>13$, прямоугольный (3-4-5)·.' },
{ e:'Стороны $6, 6, 13$', ok:false, why:'$6+6 = 12 < 13$.' },
];
let i=0,score=0;
function show(){
if(i>=Q.length){ document.getElementById('p22-iv1-q').innerHTML='<b>Готово!</b> '+score+' / '+Q.length; if(score===Q.length){addXp(18,'p22-iv1');bumpProgress('p22',30);} else if(score>=4){addXp(9,'p22-iv1');bumpProgress('p22',15);} return; }
document.getElementById('p22-iv1-i').textContent=(i+1);
document.getElementById('p22-iv1-s').textContent=score;
document.getElementById('p22-iv1-q').innerHTML=Q[i].e;
renderMath(document.getElementById('p22-iv1-q'));
document.getElementById('p22-iv1-fb').style.display='none';
}
function ans(yes){
if(i>=Q.length) return;
const fb=document.getElementById('p22-iv1-fb');
if(yes===Q[i].ok){ score++; feedback(fb,true,'&#10003; Верно! '+Q[i].why); }
else feedback(fb,false,'&#10007; '+(Q[i].ok?'Существует: ':'Не существует: ')+Q[i].why);
document.getElementById('p22-iv1-s').textContent=score;
i++; setTimeout(show,1500);
}
document.getElementById('p22-iv1-y').addEventListener('click',()=>ans(true));
document.getElementById('p22-iv1-n').addEventListener('click',()=>ans(false));
show();
})();
makeTrainer({
idPrefix:'p22-iv2',
questions:[
{ q:'Стороны $a = 5$, $b = 7$. Найди <b>наименьшее</b> целое $c$.', a:3 },
{ q:'Стороны $a = 5$, $b = 7$. Найди <b>наибольшее</b> целое $c$.', a:11 },
{ q:'Стороны $a = 4$, $b = 4$. Найди <b>наименьшее</b> целое $c$.', a:1 },
{ q:'Стороны $a = 4$, $b = 4$. Найди <b>наибольшее</b> целое $c$.', a:7 },
{ q:'Стороны $a = 10$, $b = 6$. Сколько <b>целых</b> значений может принимать $c$?', a:11 },
],
onComplete:(s,n)=>{ if(s===n){addXp(18,'p22-iv2');bumpProgress('p22',30);} else if(s>=3){addXp(9,'p22-iv2');bumpProgress('p22',15);} }
});
wireReadBtn('p22');
}
/* ============================================================
\xA7 23 — Прямоугольные треугольники
============================================================ */
function buildP23(){
const box = document.getElementById('p23-body');
const G = window.GEOM7;
let html = '';
let svgRT='';
if(G){
const b=G.svgBox(240,180,{id:'p23-rt',cell:20});
const C={x:50,y:140}, A={x:50,y:30}, B={x:200,y:140};
svgRT = b.open
+ G.polygon([A,B,C],{color:'#0891b2',fill:'rgba(8,145,178,.08)'})
+ G.rightAngleMark(C,A,B,{color:'#dc2626',size:14})
+ G.segment(C,A,{color:'#059669',width:2.5,label:'b',labelOffset:-16})
+ G.segment(C,B,{color:'#7c3aed',width:2.5,label:'a',labelOffset:18})
+ G.segment(A,B,{color:'#dc2626',width:2.5,label:'c (гипот.)',labelOffset:-18})
+ G.angle(A,B,C,{color:'#f59e0b',r:24,label:'∠A',fontSize:11,labelOffset:12})
+ G.angle(B,A,C,{color:'#f59e0b',r:24,label:'∠B',fontSize:11,labelOffset:12})
+ G.point(A.x,A.y,'A',{color:'#1e293b',dx:8,dy:-4})
+ G.point(B.x,B.y,'B',{color:'#1e293b',dx:8,dy:12})
+ G.point(C.x,C.y,'C',{color:'#1e293b',dx:-14,dy:14})
+ b.close;
}
html += makeCard('theory', 'Определения', '23.1', `
<p><b>Прямоугольный треугольник</b> — треугольник, у которого один из углов прямой ($= 90°$).</p>
<p>Сторона, лежащая против прямого угла, называется <b>гипотенузой</b>. Две стороны, образующие прямой угол, — <b>катеты</b>.</p>
<div class="svg-host">`+svgRT+`</div>
<p>На рисунке: $\\angle C = 90°$, $a$ и $b$ — катеты, $c$ — гипотенуза.</p>`);
html += makeCard('rule', 'Свойства', '23.2', `
<p style="background:var(--sec-acc-soft);padding:10px 14px;border-radius:8px"><b>Свойство 1.</b> В прямоугольном треугольнике <b>сумма острых углов равна $90°$</b>: $\\angle A + \\angle B = 90°$.</p>
<p>(Действительно: $\\angle A + \\angle B + 90° = 180°$ $\\Rightarrow$ $\\angle A + \\angle B = 90°$.)</p>
<p style="background:var(--sec-acc-soft);padding:10px 14px;border-radius:8px"><b>Свойство 2.</b> <b>Гипотенуза — самая длинная сторона</b> прямоугольного треугольника.</p>
<p>(Действительно: против бо́льшего угла лежит бо́льшая сторона. Прямой угол — самый большой.)</p>`);
html += makeCard('example', 'Применение', '23.3', `
<p><b>Задача.</b> В прямоугольном $\\triangle$ один острый угол равен $35°$. Найди другой.</p>
<p><b>Решение.</b> $\\angle A + \\angle B = 90°$ $\\Rightarrow$ другой $= 90° - 35° = 55°$.</p>
<p><b>Задача 2.</b> В прямоугольном $\\triangle$ один острый угол на $10°$ больше другого. Найди углы.</p>
<p><b>Решение.</b> $\\alpha + (\\alpha + 10°) = 90°$ $\\Rightarrow$ $2\\alpha = 80°$ $\\Rightarrow$ $\\alpha = 40°$. Углы $40°$ и $50°$.</p>`);
/* ИНТЕРАКТИВ 1 — найди острый */
html += '<div class="wg" id="p23-iv1">'
+'<div class="wg-header"><span class="wg-badge">ИНТЕРАКТИВ 1</span><div class="wg-title">Найди острый угол</div></div>'
+'<div class="wg-help">$\\angle A + \\angle B = 90°$.</div>'
+trainerHTML('p23-iv1', 6, 'градусы')
+'</div>';
/* ИНТЕРАКТИВ 2 — терминология */
html += '<div class="wg" id="p23-iv2">'
+'<div class="wg-header"><span class="wg-badge">ИНТЕРАКТИВ 2</span><div class="wg-title">Катет или гипотенуза?</div></div>'
+'<div class="wg-help">Ответь словом: <b>«катет»</b> или <b>«гипотенуза»</b>.</div>'
+trainerHTML('p23-iv2', 5, 'катет/гипотенуза')
+'</div>';
html += secNav('p22', 'p24') + readButton('p23');
box.innerHTML = html; renderMath(box);
makeTrainer({
idPrefix:'p23-iv1',
questions:[
{ q:'Прямоугольный $\\triangle$, один острый $= 30°$. Другой?', a:60 },
{ q:'Прямоугольный $\\triangle$, один острый $= 45°$. Другой?', a:45 },
{ q:'Прямоугольный $\\triangle$, один острый $= 20°$. Другой?', a:70 },
{ q:'Прямоугольный $\\triangle$, $\\angle A = 3 \\cdot \\angle B$. Найди $\\angle A$.', a:67.5 },
{ q:'Прямоугольный $\\triangle$, $\\angle A - \\angle B = 30°$. Найди $\\angle A$.', a:60 },
{ q:'В равнобедренном прямоугольном $\\triangle$ острый угол $= ?$', a:45 },
],
onComplete:(s,n)=>{ if(s===n){addXp(15,'p23-iv1');bumpProgress('p23',28);} else if(s>=4){addXp(8,'p23-iv1');bumpProgress('p23',14);} }
});
makeTrainer({
idPrefix:'p23-iv2',
parser:(v)=>v,
questions:[
{ q:'Сторона, лежащая против прямого угла, — это ...', a:(v)=>String(v).trim().toLowerCase().startsWith('гипот'), show:'гипотенуза' },
{ q:'Сторона, образующая прямой угол, — это ...', a:(v)=>String(v).trim().toLowerCase().startsWith('катет'), show:'катет' },
{ q:'Самая длинная сторона прямоугольного треугольника — это ...', a:(v)=>String(v).trim().toLowerCase().startsWith('гипот'), show:'гипотенуза' },
{ q:'Сколько катетов в прямоугольном треугольнике? (число)', a:(v)=>+v===2, show:'2' },
{ q:'Сколько гипотенуз в прямоугольном треугольнике? (число)', a:(v)=>+v===1, show:'1' },
],
onComplete:(s,n)=>{ if(s===n){addXp(12,'p23-iv2');bumpProgress('p23',22);} else if(s>=3){addXp(6,'p23-iv2');bumpProgress('p23',12);} }
});
wireReadBtn('p23');
}
/* ============================================================
\xA7 24 — Признаки равенства прямоугольных треугольников
============================================================ */
function buildP24(){
const box = document.getElementById('p24-body');
const G = window.GEOM7;
let html = '';
/* === Хелпер: рисует пару прямоугольных треугольников с подсветкой ===
mark — какие элементы выделены: 'legs', 'leg-angle', 'hyp-angle', 'hyp-leg' */
function drawRTPair(id, mark, title){
if(!G) return '';
const b = G.svgBox(220, 130, {id:id, cell:20, bg:'#fff'});
/* Левый треугольник: C={20,100}, A={20,30}, B={100,100} */
/* Правый треугольник: C2={120,100}, A2={120,30}, B2={200,100} */
const C1={x:20,y:100}, A1={x:20,y:30}, B1={x:100,y:100};
const C2={x:130,y:100}, A2={x:130,y:30}, B2={x:210,y:100};
function tri(C,A,B,opts){
opts = opts || {};
let s = G.polygon([A,B,C],{color:'#0891b2',fill:'rgba(8,145,178,.08)',width:1.8});
s += G.rightAngleMark(C,A,B,{color:'#475569',size:9});
/* Катет 1 (вертикальный CA) */
const c1Color = (mark==='legs' || mark==='leg-angle' || mark==='hyp-leg') ? '#dc2626' : '#475569';
const c1Tick = (mark==='legs' || mark==='leg-angle' || mark==='hyp-leg') ? 1 : 0;
s += G.segment(C,A,{color:c1Color,width:2.5,ticks:c1Tick,tickLen:5});
/* Катет 2 (горизонтальный CB) */
const c2Color = (mark==='legs') ? '#7c3aed' : '#475569';
const c2Tick = (mark==='legs') ? 2 : 0;
s += G.segment(C,B,{color:c2Color,width:2.5,ticks:c2Tick,tickLen:5});
/* Гипотенуза AB */
const hColor = (mark==='hyp-angle' || mark==='hyp-leg') ? '#dc2626' : '#475569';
const hTick = (mark==='hyp-angle' || mark==='hyp-leg') ? 1 : 0;
s += G.segment(A,B,{color:hColor,width:2.5,ticks:hTick,tickLen:5});
/* Острый угол при A */
if(mark==='leg-angle' || mark==='hyp-angle'){
s += G.angle(A,B,C,{color:'#f59e0b',r:20,width:2});
/* Маленький маркер дуги: 1 штрих */
s += '<circle cx="'+(A.x+8)+'" cy="'+(A.y+18)+'" r="2" fill="#f59e0b"/>';
}
return s;
}
let s = b.open + tri(C1,A1,B1) + tri(C2,A2,B2)
+ '<text x="60" y="125" text-anchor="middle" font-size="9" fill="#0891b2" font-weight="700">△ 1</text>'
+ '<text x="170" y="125" text-anchor="middle" font-size="9" fill="#0891b2" font-weight="700">△ 2</text>'
+ '<text x="110" y="65" text-anchor="middle" font-size="20" fill="#10b981" font-weight="900">=</text>'
+ b.close;
return '<div style="text-align:center"><div style="font-size:.78rem;font-weight:700;color:var(--sec-acc-d);margin-bottom:4px">'+title+'</div>'+s+'</div>';
}
const svgP1 = drawRTPair('p24-pr1', 'legs', 'Признак 1 · 2 катета');
const svgP2 = drawRTPair('p24-pr2', 'leg-angle', 'Признак 2 · катет + угол');
const svgP3 = drawRTPair('p24-pr3', 'hyp-angle', 'Признак 3 · гипот. + угол');
const svgP4 = drawRTPair('p24-pr4', 'hyp-leg', 'Признак 4 · гипот. + катет');
html += makeCard('rule', '4 признака равенства прямоугольных $\\triangle$', '24.1', `
<p>Для прямоугольных треугольников <b>дополнительно к</b> 3 признакам обычных треугольников действуют упрощённые формулировки. <b>Цвет = выделенный элемент.</b></p>
<div style="display:grid;grid-template-columns:repeat(auto-fit,minmax(220px,1fr));gap:10px;margin:14px 0">`+svgP1+svgP2+svgP3+svgP4+`</div>
<p style="background:var(--sec-acc-soft);padding:10px 14px;border-radius:8px"><b>Признак 1.</b> <b>По двум катетам.</b> Если 2 катета одного $\\triangle$ равны 2 катетам другого — треугольники равны. (По 1-му признаку обычных $\\triangle$: 2 стороны + угол $90°$ между ними.)</p>
<p style="background:var(--sec-acc-soft);padding:10px 14px;border-radius:8px"><b>Признак 2.</b> <b>По катету и прилежащему острому углу.</b> Если катет и прилежащий к нему острый угол одного $\\triangle$ равны катету и прилежащему углу другого — треугольники равны.</p>
<p style="background:var(--sec-acc-soft);padding:10px 14px;border-radius:8px"><b>Признак 3.</b> <b>По гипотенузе и острому углу.</b> Если гипотенуза и острый угол одного $\\triangle$ равны гипотенузе и острому углу другого — треугольники равны.</p>
<p style="background:var(--sec-acc-soft);padding:10px 14px;border-radius:8px"><b>Признак 4.</b> <b>По гипотенузе и катету.</b> Если гипотенуза и один катет одного $\\triangle$ равны гипотенузе и катету другого — треугольники равны.</p>`);
html += makeCard('theory', 'Почему именно 4', '24.2', `
<p>В обычных треугольниках 2 стороны и угол <b>не между ними</b> — НЕ признак (бывают разные треугольники с такими данными).</p>
<p>Но если этот угол <b>прямой</b> (т.е. треугольник прямоугольный) — то такие данные задают треугольник <b>однозначно</b>. Это и есть <b>4-й признак</b> (гипотенуза + катет).</p>
<p>Таким образом, у прямоугольных треугольников есть <b>«бонусный» признак</b>, которого нет у обычных.</p>`);
html += makeCard('example', 'Доказательство равенства', '24.3', `
<p><b>Задача.</b> На основании равнобедренного $\\triangle ABC$ ($AB = AC$) опущена высота $AH$. Докажи, что $\\triangle AHB = \\triangle AHC$.</p>
<p><b>Решение.</b> $\\triangle AHB$ и $\\triangle AHC$ — прямоугольные ($\\angle AHB = \\angle AHC = 90°$).</p>
<ul style="padding-left:22px;line-height:1.85">
<li>$AB = AC$ — гипотенузы равны (по условию);</li>
<li>$AH$ — общий катет.</li>
</ul>
<p>По <b>4-му признаку</b> (гипотенуза + катет) $\\triangle AHB = \\triangle AHC$.</p>
<p>Отсюда $BH = HC$, то есть высота к основанию равнобедренного $\\triangle$ — одновременно медиана.</p>`);
/* ИНТЕРАКТИВ 1 — выбери признак */
html += '<div class="wg" id="p24-iv1">'
+'<div class="wg-header"><span class="wg-badge">ИНТЕРАКТИВ 1</span><div class="wg-title">Какой признак применить?</div></div>'
+'<div class="wg-help">Введи номер признака от 1 до 4 (или «0» если данных недостаточно).</div>'
+trainerHTML('p24-iv1', 6, 'номер')
+'</div>';
/* ИНТЕРАКТИВ 2 — да/нет можно ли утверждать равенство */
html += '<div class="wg" id="p24-iv2">'
+'<div class="wg-header"><span class="wg-badge">ИНТЕРАКТИВ 2</span><div class="wg-title">Достаточно данных?</div></div>'
+'<div class="wg-help">Реши, гарантирует ли указанная пара элементов равенство треугольников.</div>'
+'<div class="score-display"><span>Задача <b id="p24-iv2-i">1</b> / 5</span><span>Очки: <b id="p24-iv2-s">0</b> / 5</span></div>'
+'<div id="p24-iv2-q" style="padding:14px;background:var(--sec-acc-soft);border-radius:10px;font-size:1.02rem;text-align:center;margin-bottom:10px"></div>'
+'<div style="display:flex;gap:8px;justify-content:center"><button class="btn primary" id="p24-iv2-y" style="background:#10b981;border-color:#10b981">Да</button><button class="btn primary" id="p24-iv2-n" style="background:#dc2626;border-color:#dc2626">Нет</button></div>'
+'<div class="feedback" id="p24-iv2-fb"></div></div>';
html += secNav('p23', 'p25') + readButton('p24');
box.innerHTML = html; renderMath(box);
makeTrainer({
idPrefix:'p24-iv1',
questions:[
{ q:'Оба катета равны соответствующим катетам другого $\\triangle$.', a:1 },
{ q:'Гипотенузы равны и по одному катету равны.', a:4 },
{ q:'Гипотенузы равны и по одному острому углу равны.', a:3 },
{ q:'Один катет и прилежащий острый угол равны.', a:2 },
{ q:'Только гипотенузы равны.', a:0 },
{ q:'Один катет и противолежащий острый угол равны. Это какой признак? (см. свойство суммы $= 90°$)', a:2 },
],
onComplete:(s,n)=>{ if(s===n){addXp(18,'p24-iv1');bumpProgress('p24',32);} else if(s>=4){addXp(9,'p24-iv1');bumpProgress('p24',16);} }
});
(function(){
const Q=[
{ e:'Два прямоугольных $\\triangle$: гипотенузы $= 10$ и по одному катету $= 6$.', ok:true, why:'4-й признак: гипотенуза + катет.' },
{ e:'Два прямоугольных $\\triangle$: только гипотенузы равны.', ok:false, why:'Только одного элемента мало.' },
{ e:'Два прямоугольных $\\triangle$: 2 катета $= 3$ и $4$.', ok:true, why:'1-й признак: 2 катета.' },
{ e:'Два прямоугольных $\\triangle$: только острые углы равны (без сторон).', ok:false, why:'Стороны могут отличаться.' },
{ e:'Два прямоугольных $\\triangle$: гипотенузы и по одному острому углу равны.', ok:true, why:'3-й признак: гипотенуза + острый.' },
];
let i=0,score=0;
function show(){
if(i>=Q.length){ document.getElementById('p24-iv2-q').innerHTML='<b>Готово!</b> '+score+' / '+Q.length; if(score===Q.length){addXp(15,'p24-iv2');bumpProgress('p24',28);} else if(score>=3){addXp(8,'p24-iv2');bumpProgress('p24',14);} return; }
document.getElementById('p24-iv2-i').textContent=(i+1);
document.getElementById('p24-iv2-s').textContent=score;
document.getElementById('p24-iv2-q').innerHTML=Q[i].e;
renderMath(document.getElementById('p24-iv2-q'));
document.getElementById('p24-iv2-fb').style.display='none';
}
function ans(yes){
if(i>=Q.length) return;
const fb=document.getElementById('p24-iv2-fb');
if(yes===Q[i].ok){ score++; feedback(fb,true,'&#10003; Верно! '+Q[i].why); }
else feedback(fb,false,'&#10007; '+(Q[i].ok?'Да: ':'Нет: ')+Q[i].why);
document.getElementById('p24-iv2-s').textContent=score;
i++; setTimeout(show,1500);
}
document.getElementById('p24-iv2-y').addEventListener('click',()=>ans(true));
document.getElementById('p24-iv2-n').addEventListener('click',()=>ans(false));
show();
})();
wireReadBtn('p24');
}
/* ============================================================
\xA7 25 — Биссектриса угла как ГМТ
============================================================ */
function buildP25(){
const box = document.getElementById('p25-body');
const G = window.GEOM7;
let html = '';
let svgBis='';
if(G){
/* Чистый рисунок: вершина угла O слева, биссектриса вправо вдоль оси x.
Угол ∠AOB = 50° (по 25° от биссектрисы вверх и вниз). */
const b=G.svgBox(300,200,{id:'p25-bis',cell:20});
const O={x:40,y:100};
const ang=25*Math.PI/180; /* половина угла */
const len=220;
/* OA — верхняя сторона: направление (cos(-25°), sin(-25°)) */
const Aend={x:O.x+len*Math.cos(-ang), y:O.y+len*Math.sin(-ang)};
/* OB — нижняя сторона */
const Bend={x:O.x+len*Math.cos(ang), y:O.y+len*Math.sin(ang)};
/* Биссектриса — горизонталь от O */
const Bs={x:O.x+len,y:O.y};
/* Точка K на биссектрисе */
const K={x:O.x+150,y:O.y};
/* F1, F2 — основания перпендикуляров из K на OA и OB */
function foot(P, P1, P2){
const v={x:P2.x-P1.x, y:P2.y-P1.y};
const w={x:P.x-P1.x, y:P.y-P1.y};
const t=(v.x*w.x+v.y*w.y)/(v.x*v.x+v.y*v.y);
return {x:P1.x+t*v.x, y:P1.y+t*v.y};
}
const F1=foot(K,O,Aend);
const F2=foot(K,O,Bend);
svgBis = b.open
/* Стороны угла */
+ G.segment(O,Aend,{color:'#7c3aed',width:2.5})
+ G.segment(O,Bend,{color:'#7c3aed',width:2.5})
/* Биссектриса */
+ G.segment(O,Bs,{color:'#dc2626',width:2,dash:'6 3'})
/* Дуга, показывающая равенство углов (две одинаковые) */
+ G.arc(O,28,-ang,0,{color:'#dc2626',width:1.5})
+ G.arc(O,28,0,ang,{color:'#dc2626',width:1.5})
/* Перпендикуляры от K */
+ G.segment(K,F1,{color:'#0891b2',width:2})
+ G.segment(K,F2,{color:'#0891b2',width:2})
/* Прямые углы у оснований */
+ G.rightAngleMark(F1,K,O,{color:'#0891b2',size:10})
+ G.rightAngleMark(F2,K,O,{color:'#0891b2',size:10})
/* Метки KF1 = KF2 — тики на перпендикулярах */
+ G.segment(K,F1,{color:'#0891b2',width:0.01,ticks:1,tickLen:5})
+ G.segment(K,F2,{color:'#0891b2',width:0.01,ticks:1,tickLen:5})
/* Точки */
+ G.point(O.x,O.y,'O',{color:'#1e293b',dx:-14,dy:5,fontSize:13})
+ G.point(K.x,K.y,'K',{color:'#dc2626',dx:-10,dy:-8,fontSize:13,r:3.5})
+ G.point(Bs.x,Bs.y,'',{r:2,color:'#dc2626'})
+ G.point(F1.x,F1.y,'F₁',{color:'#0891b2',dx:6,dy:-4,fontSize:11,r:3})
+ G.point(F2.x,F2.y,'F₂',{color:'#0891b2',dx:6,dy:12,fontSize:11,r:3})
/* Подписи */
+ '<text x="'+(Bs.x-10)+'" y="'+(Bs.y-6)+'" font-size="11" font-family="Unbounded,Inter,sans-serif" font-weight="700" fill="#dc2626">бис.</text>'
+ '<text x="'+(Aend.x-2)+'" y="'+(Aend.y-2)+'" font-size="12" font-family="Unbounded,Inter,sans-serif" font-weight="700" fill="#7c3aed">A</text>'
+ '<text x="'+(Bend.x-2)+'" y="'+(Bend.y+14)+'" font-size="12" font-family="Unbounded,Inter,sans-serif" font-weight="700" fill="#7c3aed">B</text>'
/* Равенство расстояний */
+ '<text x="'+((K.x+F1.x)/2-22)+'" y="'+((K.y+F1.y)/2-2)+'" font-size="11" font-family="JetBrains Mono,monospace" font-weight="700" fill="#0891b2">d</text>'
+ '<text x="'+((K.x+F2.x)/2-22)+'" y="'+((K.y+F2.y)/2+10)+'" font-size="11" font-family="JetBrains Mono,monospace" font-weight="700" fill="#0891b2">d</text>'
+ '<text x="'+(O.x+150)+'" y="195" text-anchor="middle" font-size="11" fill="#065f46" font-weight="700">KF₁ = KF₂ = d</text>'
+ b.close;
}
html += makeCard('theory', 'Расстояние от точки до прямой', '25.1', `
<p><b>Расстоянием от точки до прямой</b> называется длина <b>перпендикуляра</b>, опущенного из этой точки на прямую. Это самое короткое расстояние.</p>
<p>Если точка лежит на прямой, расстояние равно $0$.</p>`);
html += makeCard('rule', 'Биссектриса как ГМТ', '25.2', `
<p style="background:var(--sec-acc-soft);padding:10px 14px;border-radius:8px"><b>Теорема.</b> Любая точка биссектрисы угла <b>равноудалена от его сторон</b>. Обратно: точка, лежащая внутри угла и равноудалённая от его сторон, лежит на биссектрисе.</p>
<div class="svg-host">`+svgBis+`</div>
<p>Таким образом, биссектриса угла — <b>геометрическое место точек</b>, равноудалённых от сторон этого угла. (Аналогично серединному перпендикуляру для отрезка.)</p>
<details class="spoiler"><summary>Доказательство (прямая часть)</summary>
<div class="spoiler-body">
<p>Пусть $K$ — на биссектрисе $\\angle AOB$. Опустим перпендикуляры $KF_1 \\perp OA$ и $KF_2 \\perp OB$.</p>
<p>Рассмотрим прямоугольные $\\triangle OKF_1$ и $\\triangle OKF_2$:</p>
<ul style="padding-left:22px;line-height:1.85">
<li>$OK$ — общая гипотенуза;</li>
<li>$\\angle KOF_1 = \\angle KOF_2$ (бис. делит пополам).</li>
</ul>
<p>По <b>3-му признаку</b> прямоугольных $\\triangle$ (гипот. + остр. угол) $\\triangle OKF_1 = \\triangle OKF_2$. Отсюда $KF_1 = KF_2$. ■</p>
</div></details>`);
html += makeCard('rule', 'Центр вписанной окружности', '25.3', `
<p>В треугольнике <b>3 биссектрисы</b> пересекаются в одной точке (доказательство аналогично серединным перпендикулярам).</p>
<p>Эта точка равноудалена от всех 3 сторон $\\triangle$ $\\Rightarrow$ она — <b>центр окружности, вписанной</b> в треугольник (касается всех сторон). Эту точку называют <b>инцентром</b>.</p>`);
/* ИНТЕРАКТИВ 1 — расстояние / биссектриса */
html += '<div class="wg" id="p25-iv1">'
+'<div class="wg-header"><span class="wg-badge">ИНТЕРАКТИВ 1</span><div class="wg-title">Свойство биссектрисы</div></div>'
+'<div class="wg-help">$K \\in$ бис. $\\Leftrightarrow$ $KF_1 = KF_2$.</div>'
+trainerHTML('p25-iv1', 5, 'число')
+'</div>';
/* ИНТЕРАКТИВ 2 — да/нет утверждения */
html += '<div class="wg" id="p25-iv2">'
+'<div class="wg-header"><span class="wg-badge">ИНТЕРАКТИВ 2</span><div class="wg-title">Верно или нет?</div></div>'
+'<div class="wg-help">Свойство биссектрисы и центр вписанной окружности.</div>'
+'<div class="score-display"><span>Задача <b id="p25-iv2-i">1</b> / 5</span><span>Очки: <b id="p25-iv2-s">0</b> / 5</span></div>'
+'<div id="p25-iv2-q" style="padding:14px;background:var(--sec-acc-soft);border-radius:10px;font-size:1.02rem;text-align:center;margin-bottom:10px"></div>'
+'<div style="display:flex;gap:8px;justify-content:center"><button class="btn primary" id="p25-iv2-y" style="background:#10b981;border-color:#10b981">Да</button><button class="btn primary" id="p25-iv2-n" style="background:#dc2626;border-color:#dc2626">Нет</button></div>'
+'<div class="feedback" id="p25-iv2-fb"></div></div>';
html += secNav('p24', 'p26') + readButton('p25');
box.innerHTML = html; renderMath(box);
makeTrainer({
idPrefix:'p25-iv1',
questions:[
{ q:'$K$ — на биссектрисе $\\angle AOB$. Расст. от $K$ до $OA = 5$ см. Расст. до $OB$?', a:5 },
{ q:'$K$ — на бис. $\\angle AOB$. $KF_1 + KF_2 = 14$ ($F_i$ — основания перп.). Найди $KF_1$.', a:7 },
{ q:'$M$ внутри $\\angle$, расст. до сторон равны $8$ и $8$. Лежит ли $M$ на биссектрисе? Введи 1=да, 0=нет.', a:1 },
{ q:'$M$ внутри $\\angle$, расст. до сторон $= 5$ и $7$. На бис.? Введи 1=да, 0=нет.', a:0 },
{ q:'Сколько биссектрис в треугольнике? (число)', a:3 },
],
onComplete:(s,n)=>{ if(s===n){addXp(15,'p25-iv1');bumpProgress('p25',28);} else if(s>=3){addXp(8,'p25-iv1');bumpProgress('p25',14);} }
});
(function(){
const Q=[
{ e:'Любая точка биссектрисы угла равноудалена от его сторон.', ok:true },
{ e:'Биссектриса делит противоположную сторону пополам.', ok:false },
{ e:'3 биссектрисы треугольника пересекаются в одной точке.', ok:true },
{ e:'Точка пересечения биссектрис — центр описанной окружности.', ok:false },
{ e:'Центр вписанной окружности равноудалён от всех сторон $\\triangle$.', ok:true },
];
let i=0,score=0;
function show(){
if(i>=Q.length){ document.getElementById('p25-iv2-q').innerHTML='<b>Готово!</b> '+score+' / '+Q.length; if(score===Q.length){addXp(12,'p25-iv2');bumpProgress('p25',22);} else if(score>=3){addXp(6,'p25-iv2');bumpProgress('p25',12);} return; }
document.getElementById('p25-iv2-i').textContent=(i+1);
document.getElementById('p25-iv2-s').textContent=score;
document.getElementById('p25-iv2-q').innerHTML=Q[i].e;
renderMath(document.getElementById('p25-iv2-q'));
document.getElementById('p25-iv2-fb').style.display='none';
}
function ans(yes){
if(i>=Q.length) return;
const fb=document.getElementById('p25-iv2-fb');
if(yes===Q[i].ok){ score++; feedback(fb,true,'&#10003; Верно!'); }
else feedback(fb,false,'&#10007; '+(Q[i].ok?'Верно.':'Это неверно.'));
document.getElementById('p25-iv2-s').textContent=score;
i++; setTimeout(show,1300);
}
document.getElementById('p25-iv2-y').addEventListener('click',()=>ans(true));
document.getElementById('p25-iv2-n').addEventListener('click',()=>ans(false));
show();
})();
wireReadBtn('p25');
}
/* ============================================================
\xA7 26 — Свойство катета против угла 30°
============================================================ */
function buildP26(){
const box = document.getElementById('p26-body');
const G = window.GEOM7;
let html = '';
let svg30='';
if(G){
const b=G.svgBox(260,200,{id:'p26-30',cell:20});
const C={x:50,y:170}, A={x:50,y:30}, B={x:220,y:170};
/* Дополним до равностороннего: отразим A в B относительно прямой CB */
/* Достаточно простого равностор. треугольника ABA' где A' — отражение A через прямую CB */
const A2={x:50+2*(220-50),y:30}; /* не строго, схема */
svg30 = b.open
+ G.polygon([A,B,C],{color:'#0891b2',fill:'rgba(8,145,178,.10)'})
+ G.rightAngleMark(C,A,B,{color:'#dc2626',size:12})
+ G.segment(A,C,{color:'#059669',width:2.5,label:'b',labelOffset:-16})
+ G.segment(C,B,{color:'#7c3aed',width:2.5,label:'a (= c/2)',labelOffset:18})
+ G.segment(A,B,{color:'#dc2626',width:2.5,label:'c (гипот.)',labelOffset:-18})
+ G.angle(A,B,C,{color:'#f59e0b',r:24,label:'60°',fontSize:11,labelOffset:14})
+ G.angle(B,A,C,{color:'#f59e0b',r:30,label:'30°',fontSize:12,labelOffset:14})
+ G.point(A.x,A.y,'A',{color:'#1e293b',dx:8,dy:-4})
+ G.point(B.x,B.y,'B',{color:'#1e293b',dx:8,dy:12})
+ G.point(C.x,C.y,'C',{color:'#1e293b',dx:-14,dy:14})
+ b.close;
}
html += makeCard('rule', 'Теорема о катете против $30°$', '26.1', `
<p style="background:var(--sec-acc-soft);padding:10px 14px;border-radius:8px;font-size:1.05rem"><b>Теорема.</b> В прямоугольном треугольнике катет, лежащий <b>против угла $30°$</b>, равен <b>половине гипотенузы</b>:</p>
<p style="text-align:center;font-size:1.2rem;margin:10px 0">$a = \\dfrac{c}{2}$</p>
<div class="svg-host">`+svg30+`</div>
<p>На рисунке: $\\angle A = 30°$, $\\angle B = 60°$, $\\angle C = 90°$. Катет $a = BC$ напротив $\\angle A = 30°$, и $a = \\frac{c}{2}$.</p>`);
html += makeCard('theory', 'Доказательство', '26.2', `
<p>В прямоугольном $\\triangle ABC$: $\\angle A = 30°$, $\\angle C = 90°$, $\\angle B = 60°$.</p>
<p>Отразим $\\triangle ABC$ относительно прямой $BC$ — получим $\\triangle A'BC$, в котором $\\angle A' = 30°$, $A'B = AB = c$, $A'C = AC = b$.</p>
<p>Получился больший $\\triangle ABA'$ с $\\angle A = \\angle A' = 30°$ и $\\angle ABA' = 60° + 60° = 120°$. Это <b>равнобедренный</b> $\\triangle$ ($AB = A'B = c$).</p>
<p>Но сумма $30° + 30° + 120° = 180°$ ✓. А теперь приглядимся: в $\\triangle ABA'$ при углах $30°$ и $30°$ стороны $AA'$ и $AB$... — на самом деле проще.</p>
<p><b>Простой вариант.</b> Достроим до равностороннего $\\triangle$: отразим $A$ через прямую $CB$, получим $A'$. Тогда $\\triangle ABA'$ — равносторонний (все стороны $= c$, все углы $60°$). $AA' = c$. Но $AA' = 2 \\cdot AC = 2b$... — нет, $AA' = 2 \\cdot BC = 2a$. Значит $2a = c$ $\\Rightarrow$ $a = \\frac{c}{2}$. ■</p>`);
html += makeCard('rule', 'Обратное свойство', '26.3', `
<p style="background:var(--sec-acc-soft);padding:10px 14px;border-radius:8px"><b>Обратная теорема.</b> Если в прямоугольном треугольнике катет равен половине гипотенузы, то <b>угол против этого катета равен $30°$</b>.</p>`);
html += makeCard('example', 'Применение', '26.4', `
<p><b>Задача 1.</b> Гипотенуза прямоугольного $\\triangle$ равна $10$ см, один из острых углов равен $30°$. Найди катет напротив этого угла.</p>
<p><b>Решение.</b> Катет $= \\frac{10}{2} = 5$ см.</p>
<p><b>Задача 2.</b> В прямоугольном $\\triangle$ катет $= 4$, гипотенуза $= 8$. Найди угол против этого катета.</p>
<p><b>Решение.</b> $\\frac{4}{8} = \\frac{1}{2}$ $\\Rightarrow$ по обратной теореме угол $= 30°$.</p>
<p><b>Задача 3.</b> В равнобедренном $\\triangle ABC$ ($AB = AC$) угол при вершине $A = 120°$. Высота $BH$ к стороне $AC$. Если $AC = 6$, найди $BH$.</p>
<p><b>Решение.</b> Углы при основании $= 30°$. В $\\triangle ABH$: $\\angle BAH = 60°$ (внешний к $120°$? нет, $\\angle BAH = 180° - 120° = 60°$, т.к. $H$ за $A$). $\\angle ABH = 30°$, $BH$ — катет напротив $30°$. Используем свойство...</p>`);
/* ИНТЕРАКТИВ 1 — найди катет/гипотенузу */
html += '<div class="wg" id="p26-iv1">'
+'<div class="wg-header"><span class="wg-badge">ИНТЕРАКТИВ 1</span><div class="wg-title">$30°$ → половина</div></div>'
+'<div class="wg-help">Катет против $30°$ $= \\frac{c}{2}$, и наоборот.</div>'
+trainerHTML('p26-iv1', 6, 'число')
+'</div>';
/* ИНТЕРАКТИВ 2 — комбо задачи */
html += '<div class="wg" id="p26-iv2">'
+'<div class="wg-header"><span class="wg-badge">ИНТЕРАКТИВ 2</span><div class="wg-title">Связки</div></div>'
+'<div class="wg-help">Используй сумму острых $= 90°$, признак $30°$, прочее.</div>'
+trainerHTML('p26-iv2', 5, 'число')
+'</div>';
html += secNav('p25', 'final4') + readButton('p26');
box.innerHTML = html; renderMath(box);
makeTrainer({
idPrefix:'p26-iv1',
questions:[
{ q:'Прямоуг. $\\triangle$, гипот. $= 10$, один остр. $= 30°$. Катет против $30°$?', a:5 },
{ q:'Прямоуг. $\\triangle$, гипот. $= 14$, угол $30°$. Катет напротив?', a:7 },
{ q:'Прямоуг. $\\triangle$, угол $30°$, катет напротив $= 6$. Гипотенуза?', a:12 },
{ q:'Прямоуг. $\\triangle$, катет $= 4$, гипот. $= 8$. Угол напротив катета?', a:30 },
{ q:'Прямоуг. $\\triangle$, катет $= 3$, гипот. $= 6$. Угол напротив?', a:30 },
{ q:'Прямоуг. $\\triangle$, гипот. $= 20$, угол $30°$. Катет против $60°$ (другой острый)? Это $b$, а $a = c/2 = 10$. Найди $a$.', a:10 },
],
onComplete:(s,n)=>{ if(s===n){addXp(18,'p26-iv1');bumpProgress('p26',30);} else if(s>=4){addXp(9,'p26-iv1');bumpProgress('p26',15);} }
});
makeTrainer({
idPrefix:'p26-iv2',
questions:[
{ q:'Прямоуг. $\\triangle$, один остр. $= 60°$. Другой?', a:30 },
{ q:'Прямоуг. $\\triangle$, остр. углы относятся как $1:2$. Меньший острый угол?', a:30 },
{ q:'Прямоуг. $\\triangle$, гипот. $= 12$, остр. угол $60°$. Катет против $30°$?', a:6 },
{ q:'Катет против $30°$ равен $7$. Гипотенуза?', a:14 },
{ q:'Равнобедр. прямоуг. $\\triangle$: гипот. $= 10$. Острые углы $= ?$ (введи один из них)', a:45 },
],
onComplete:(s,n)=>{ if(s===n){addXp(15,'p26-iv2');bumpProgress('p26',28);} else if(s>=3){addXp(8,'p26-iv2');bumpProgress('p26',14);} }
});
wireReadBtn('p26');
}
/* ============================================================
FINAL 4 — 5 БОССОВ
============================================================ */
const BOSSES = [
{
n:1, title:'Босс \xA719-20 — Сумма и внешний угол', color:'#0891b2',
steps:[
{ q:'Сумма углов треугольника $= ?$ градусов.', verify:(v)=>+v===180, hint:'Главная теорема.' },
{ q:'$\\angle A = 60°$, $\\angle B = 70°$. Найди $\\angle C$.', verify:(v)=>+v===50, hint:'$180 - 60 - 70$.' },
{ q:'Равносторонний $\\triangle$: один угол $= ?$', verify:(v)=>+v===60, hint:'$180/3$.' },
{ q:'Внешний угол при $B = 110°$. Найди $\\angle B$.', verify:(v)=>+v===70, hint:'Смежный.' },
{ q:'$\\angle A = 40°$, $\\angle C = 60°$. Внешний при $B = ?$', verify:(v)=>+v===100, hint:'$A + C$.' },
]
},
{
n:2, title:'Босс \xA721-22 — Стороны, углы, неравенство', color:'#65a30d',
steps:[
{ q:'$\\angle A = 80°$, $\\angle B = 60°$, $\\angle C = 40°$. Самая <b>длинная</b> сторона напротив угла $?$ (введи букву)', verify:(v)=>String(v).trim().toUpperCase().startsWith('A'), hint:'Напротив наибольшего угла.' },
{ q:'$AB = 4$, $BC = 9$, $AC = 7$. Самый большой угол при вершине $?$', verify:(v)=>String(v).trim().toUpperCase().startsWith('A'), hint:'Напротив $BC=9$.' },
{ q:'Стороны $3, 4, 8$. Существует ли треугольник? «да» или «нет»', verify:(v)=>String(v).trim().toLowerCase().startsWith('н'), hint:'$3+4 < 8$.' },
{ q:'$a = 5$, $b = 9$. Наибольшее целое $c = ?$', verify:(v)=>+v===13, hint:'$c < 14$.' },
{ q:'$a = 5$, $b = 9$. Наименьшее целое $c = ?$', verify:(v)=>+v===5, hint:'$c > 4$.' },
]
},
{
n:3, title:'Босс \xA723-24 — Прямоугольные', color:'#d97706',
steps:[
{ q:'В прямоуг. $\\triangle$ один острый $= 40°$. Другой?', verify:(v)=>+v===50, hint:'Сумма $90°$.' },
{ q:'Сторона, лежащая против прямого угла, — это $?$ Введи «гипотенуза» или «катет».', verify:(v)=>String(v).trim().toLowerCase().startsWith('гипот'), hint:'Самая длинная.' },
{ q:'Сколько признаков равенства прямоугольных $\\triangle$? (число)', verify:(v)=>+v===4, hint:'2 катета / катет+угол / гипот+угол / гипот+катет.' },
{ q:'Два прям. $\\triangle$: гипотенузы и по 1 катету равны. Это $?$-й признак.', verify:(v)=>+v===4, hint:'Гипотенуза + катет — 4-й.' },
{ q:'В прямоугольном равнобедренном $\\triangle$ острые углы $= ?$', verify:(v)=>+v===45, hint:'$90/2$.' },
]
},
{
n:4, title:'Босс \xA725-26 — Биссектриса и $30°$', color:'#7c3aed',
steps:[
{ q:'Биссектриса угла — ГМТ точек, равноудалённых от $?$ Введи слово.', verify:(v)=>String(v).trim().toLowerCase().startsWith('стор'), hint:'Сторон угла.' },
{ q:'Сколько биссектрис в треугольнике? (число)', verify:(v)=>+v===3, hint:'По одной к каждой вершине.' },
{ q:'Точка пересечения биссектрис — центр $?$ окружности (введи «вписанной» или «описанной»)', verify:(v)=>String(v).trim().toLowerCase().startsWith('впис'), hint:'Вписанной.' },
{ q:'Прямоуг. $\\triangle$, гипот. $= 16$, один остр. $= 30°$. Катет против $30°$?', verify:(v)=>+v===8, hint:'Половина гипот.' },
{ q:'Прямоуг. $\\triangle$, катет $= 5$, гипот. $= 10$. Угол против катета?', verify:(v)=>+v===30, hint:'Катет $= c/2$ $\\Rightarrow 30°$.' },
]
},
{
n:5, title:'Финальный босс — Сборная задача', color:'#dc2626',
steps:[
{ q:'Углы $\\triangle$ относятся как $1:3:5$. Сумма $= 180$. Найди наибольший угол.', verify:(v)=>+v===100, hint:'$1+3+5=9$, $5 \\cdot 20 = 100$.' },
{ q:'В прямоуг. $\\triangle$ один остр. в 2 раза больше другого. Найди больший острый.', verify:(v)=>+v===60, hint:'$\\alpha + 2\\alpha = 90$, $\\alpha = 30$, $2\\alpha = 60$.' },
{ q:'Стороны $\\triangle$: $a$, $a+2$, $a+4$. При $a=3$: существует? «да»/«нет»', verify:(v)=>String(v).trim().toLowerCase().startsWith('д'), hint:'$3,5,7$: $3+5>7$ ✓.' },
{ q:'Прямоуг. $\\triangle$: один остр. $30°$, катет напротив него $= 6$. Гипотенуза?', verify:(v)=>+v===12, hint:'$c = 2 \\cdot 6$.' },
{ q:'Гипотенуза прямоуг. $\\triangle = 20$. Один из острых $= 60°$. Катет против $30°$?', verify:(v)=>+v===10, hint:'$c/2 = 10$.' },
]
},
];
function buildFinal4(){
const box = document.getElementById('final4-body');
let html = '';
html += makeCard('theory', 'Что мы изучили', 'Итог', `
<p>Глава 4 — про углы, стороны и прямоугольные треугольники:</p>
<ul style="padding-left:22px;line-height:1.85">
<li>главная теорема: <b>сумма углов $= 180°$</b>;</li>
<li><b>внешний угол</b> $=$ сумме двух не смежных внутренних;</li>
<li>связка <b>«сторона ↔ угол»</b> (больше одно — больше другое);</li>
<li><b>неравенство треугольника</b>: $|b-c| < a < b+c$;</li>
<li><b>прямоугольный $\\triangle$</b>: катеты, гипотенуза, сумма острых $= 90°$;</li>
<li><b>4 признака</b> равенства прямоугольных;</li>
<li>биссектриса как <b>ГМТ</b> + центр <b>вписанной</b> окружности;</li>
<li>волшебное <b>«катет против $30°$ $=$ половине гипотенузы»</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(8,145,178,.12);border-radius:9px;overflow:hidden"><div id="boss-overall-fill" style="height:100%;width:0%;background:linear-gradient(90deg,#0891b2,#22d3ee);transition:width .5s"></div></div>'
+'</div>';
html += secNav('p26', null);
box.innerHTML = html; renderMath(box);
const cont = document.getElementById('bosses-container');
const BOSS_STATE = (function(){
try{ const s=localStorage.getItem('geometry7_ch4_bosses'); if(s) return JSON.parse(s); }catch(e){}
return BOSSES.map(()=>({stage:0,defeated:false}));
})();
function saveBosses(){ try{ localStorage.setItem('geometry7_ch4_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('final4',60); achievement('ch4_done','Глава 4 пройдена!'); }
}
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('final4',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>