Files
Learn_System/frontend/textbooks/geometry_8_ch2.html
T
Maxim Dolgolyov d20f0f933e feat(geom8): Wave 1 Главы 2 — §1-§4 (квадрат, прямоугольник, параллелограмм, треугольник)
§1 Площадь квадрата: SVG-сетка со слайдером a=1..10, калькулятор двусторонний
(a→S, S→√S), конвертер единиц (мм²/см²/дм²/м²/км²), тренажёр, босс.
§2 Прямоугольник: draggable угол (a,b,S=a·b в реалтайме), калькулятор прямой
и обратный, DnD-сортер по S=24, тренажёр, босс.
§3 Параллелограмм: draggable верхнее основание — S=a·h не меняется
(равноплощадные!), 4-шаговая анимация 'разрезаем и переставляем
в прямоугольник', калькулятор, тренажёр, босс.
§4 Треугольник: draggable C по горизонтальной прямой — S=½·a·h постоянна,
анимация 'достраиваем поворотом на 180° в параллелограмм', калькулятор тройной
(a,h→S; S,a→h; S,h→a), тренажёр, босс.

File: 503 → 1675 LOC.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-28 09:08:28 +03:00

1676 lines
146 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>Геометрия 8 · Глава 2 · Площади</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>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<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:#f0fdf4; --text:#0f172a; --ink:#0f172a; --muted:#64748b;
--border:#e2e8f0; --sh:0 1px 3px rgba(0,0,0,.06); --sh2:0 4px 14px rgba(0,0,0,.08);
--pri:#059669; --pri2:#047857; --pri-soft:#d1fae5;
--acc:#10b981; --acc2:#059669; --acc-soft:#a7f3d0;
--ok:#10b981; --ok-bg:#d1fae5; --warn:#f59e0b; --warn-bg:#fef3c7;
--bad:#ef4444; --fail:#dc2626; --fail-bg:#fee2e2;
}
.dark{--bg:#030a06; --card:#071209; --card-soft:#0a1a0e; --text:#ecfdf5; --ink:#ecfdf5; --muted:#6b9e82; --border:#14291a}
*{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,#064e3b 0%,#059669 55%,#34d399 100%);color:#fff;padding:46px 22px 30px;overflow:hidden;border-bottom:2px solid rgba(52,211,153,.2);min-height:130px}
.hdr::before{content:'ГЛАВА 2';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(160,255,200,.12);line-height:1;pointer-events:none;user-select:none;z-index:0}
.hdr-row{position:relative;z-index:1;display:flex;align-items:center;gap:14px;flex-wrap:wrap}
.hdr h1{font-family:'Unbounded',sans-serif;font-size:1.5rem;font-weight:900;letter-spacing:-.01em;line-height:1.3;padding-top:4px}
.hdr-sub{font-size:.85rem;opacity:.88;margin-top:6px;font-weight:500;line-height:1.4}
.hdr-side{margin-left:auto;display:flex;gap:8px;align-items:center;flex-wrap:wrap}
.hdr-btn{padding:7px 12px;border-radius:9px;background:rgba(255,255,255,.14);color:#fff;font-weight:600;font-size:.82rem;display:inline-flex;align-items:center;gap:6px;transition:background .15s;text-decoration:none}
.hdr-btn:hover{background:rgba(255,255,255,.24)}
.main{max-width:1240px;margin:0 auto;padding:22px;width:100%;display:grid;grid-template-columns:1fr 280px;gap:24px}
@media(max-width:980px){.main{grid-template-columns:1fr;padding:14px}}
.col-main{min-width:0}
.hero{background:linear-gradient(135deg,var(--pri-soft) 0%,var(--acc-soft) 50%,var(--pri-soft) 100%);background-size:200% 200%;animation:heroShift 12s ease-in-out infinite;border:1px solid var(--border);border-radius:18px;padding:24px 22px;margin-bottom:24px;position:relative;overflow:hidden}
@keyframes heroShift{0%,100%{background-position:0% 50%}50%{background-position:100% 50%}}
.hero h2{font-family:'Unbounded',sans-serif;font-size:1.55rem;font-weight:800;color:var(--pri2);margin-bottom:10px;letter-spacing:-.01em}
.hero p{font-size:.95rem;color:var(--text);opacity:.88;margin-bottom:14px;max-width:640px}
.hero-row{display:flex;gap:14px;flex-wrap:wrap;align-items:center}
.btn-primary{padding:11px 22px;background:linear-gradient(135deg,var(--pri),var(--pri2));color:#fff;border-radius:11px;font-weight:700;font-size:.92rem;display:inline-flex;align-items:center;gap:8px;box-shadow:var(--sh2);transition:transform .15s,box-shadow .15s}
.btn-primary:hover{transform:translateY(-1px);box-shadow:0 8px 28px rgba(5,150,105,.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(5,150,105,.18);border-radius:5px;overflow:hidden}
.hp-fill{height:100%;background:linear-gradient(90deg,var(--pri),var(--acc));border-radius:5px;width:0%;transition:width .6s cubic-bezier(.16,1,.3,1)}
.hp-text{font-size:.78rem;color:var(--muted);font-weight:700;margin-top:4px;display:block}
.hero-xp-badge{display:inline-flex;align-items:center;gap:6px;padding:6px 12px;background:linear-gradient(135deg,var(--warn,#f59e0b),var(--pri));color:#fff;border-radius:99px;font-size:.82rem;font-weight:800;letter-spacing:.02em;box-shadow:0 4px 12px rgba(5,150,105,.22);font-family:'Unbounded',sans-serif}
.psel{margin-bottom:24px}
.psel-title{font-size:.72rem;font-weight:800;color:var(--muted);text-transform:uppercase;letter-spacing:.08em;margin-bottom:10px}
.psel-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(160px,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:.88rem;font-weight:700;color:var(--text);line-height:1.3;margin-bottom:8px}
.psel-prog{height:4px;background:rgba(5,150,105,.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,#f0fdf4,#d1fae5)}
.psel-card.final .psel-num{color:var(--warn)}
/* SECTION COLORS — emerald/teal/green spectrum */
.sec[id="sec-p1"] { --sec-acc:#059669; --sec-acc-d:#047857; --sec-acc-soft:#d1fae5; }
.sec[id="sec-p2"] { --sec-acc:#10b981; --sec-acc-d:#059669; --sec-acc-soft:#a7f3d0; }
.sec[id="sec-p3"] { --sec-acc:#0d9488; --sec-acc-d:#0f766e; --sec-acc-soft:#ccfbf1; }
.sec[id="sec-p4"] { --sec-acc:#0891b2; --sec-acc-d:#0e7490; --sec-acc-soft:#cffafe; }
.sec[id="sec-p5"] { --sec-acc:#0284c7; --sec-acc-d:#0369a1; --sec-acc-soft:#e0f2fe; }
.sec[id="sec-p6"] { --sec-acc:#6366f1; --sec-acc-d:#4f46e5; --sec-acc-soft:#e0e7ff; }
.sec[id="sec-p7"] { --sec-acc:#7c3aed; --sec-acc-d:#6d28d9; --sec-acc-soft:#ede9fe; }
.sec[id="sec-p8"] { --sec-acc:#059669; --sec-acc-d:#047857; --sec-acc-soft:#d1fae5; }
.sec[id="sec-p9"] { --sec-acc:#10b981; --sec-acc-d:#059669; --sec-acc-soft:#a7f3d0; }
.sec[id="sec-p10"] { --sec-acc:#0d9488; --sec-acc-d:#0f766e; --sec-acc-soft:#ccfbf1; }
.sec[id="sec-p11"] { --sec-acc:#16a34a; --sec-acc-d:#15803d; --sec-acc-soft:#dcfce7; }
.sec[id="sec-p12"] { --sec-acc:#22c55e; --sec-acc-d:#16a34a; --sec-acc-soft:#bbf7d0; }
.sec[id="sec-p13"] { --sec-acc:#84cc16; --sec-acc-d:#65a30d; --sec-acc-soft:#ecfccb; }
.sec[id="sec-p14"] { --sec-acc:#d97706; --sec-acc-d:#b45309; --sec-acc-soft:#fef3c7; }
.sec[id="sec-p15"] { --sec-acc:#059669; --sec-acc-d:#047857; --sec-acc-soft:#d1fae5; }
.sec[id="sec-final2"] { --sec-acc:#059669; --sec-acc-d:#047857; --sec-acc-soft:#d1fae5; }
.sec{display:none;position:relative;animation:fadeIn .35s ease}
.sec.active{display:block}
@keyframes fadeIn{from{opacity:0;transform:translateY(8px)}to{opacity:1;transform:none}}
.sec::before{content:attr(data-watermark);position:absolute;right:-20px;top:10%;font-family:'Unbounded',sans-serif;font-size:clamp(6rem,18vw,14rem);font-weight:900;color:transparent;-webkit-text-stroke:1.5px var(--sec-acc-soft,var(--pri-soft));line-height:1;pointer-events:none;user-select:none;z-index:0;opacity:.35}
.sec-header{margin-bottom:22px;padding-bottom:14px;border-bottom:2px solid var(--sec-acc-soft,var(--pri-soft));position:relative;z-index:1}
.sec-num{display:inline-block;padding:4px 10px;background:linear-gradient(135deg,var(--sec-acc,var(--pri)),var(--sec-acc-d,var(--pri2)));color:#fff;border-radius:7px;font-family:'Unbounded',sans-serif;font-size:.78rem;font-weight:800;letter-spacing:.04em;margin-bottom:8px}
.sec-h{font-family:'Unbounded',sans-serif;font-size:1.7rem;font-weight:800;color:var(--sec-acc-d,var(--pri2));letter-spacing:-.01em;line-height:1.25}
.card{background:var(--card);border:1px solid var(--border);border-radius:14px;padding:18px 20px;margin-bottom:16px;box-shadow:0 1px 3px rgba(0,0,0,.04),0 8px 24px rgba(5,150,105,.06);position:relative;z-index:1;transition:transform .25s cubic-bezier(.16,1,.3,1),box-shadow .25s}
.card:hover{transform:translateY(-2px);box-shadow:0 4px 10px rgba(0,0,0,.06),0 16px 36px rgba(5,150,105,.12)}
.card-header{display:flex;align-items:center;gap:10px;margin-bottom:12px;padding-bottom:10px;border-bottom:1px dashed var(--border)}
.card-icon{width:32px;height:32px;border-radius:9px;display:flex;align-items:center;justify-content:center;flex-shrink:0;color:#fff}
.card-icon.repeat{background:#0ea5e9}.card-icon.theory{background:#8b5cf6}.card-icon.algo{background:#f59e0b}.card-icon.rule{background:#ec4899}.card-icon.example{background:#10b981}.card-icon.oral{background:#06b6d4}.card-icon.class{background:#3b82f6}.card-icon.home{background:#f97316}
.card-icon .ic{width:18px;height:18px}
.card-title{font-family:'Unbounded',sans-serif;font-size:.82rem;font-weight:800;text-transform:uppercase;letter-spacing:.06em;color:var(--muted);flex:1}
.card-num{font-size:.74rem;font-weight:700;color:var(--muted);background:var(--sec-acc-soft,var(--pri-soft));padding:3px 7px;border-radius:5px}
.card-body{font-size:.94rem;line-height:1.65}
.card-body p{margin-bottom:8px}
.wg{background:linear-gradient(135deg,var(--card),var(--sec-acc-soft,var(--pri-soft)));border:1.5px solid var(--sec-acc,var(--pri));border-radius:14px;padding:18px 20px;margin-bottom:18px;box-shadow:var(--sh2);position:relative;z-index:1}
.wg-header{display:flex;align-items:center;gap:8px;margin-bottom:14px}
.wg-badge{padding:4px 9px;background:var(--sec-acc,var(--pri));color:#fff;border-radius:6px;font-family:'Unbounded',sans-serif;font-size:.68rem;font-weight:800;text-transform:uppercase;letter-spacing:.06em}
.wg-title{font-family:'Unbounded',sans-serif;font-size:1.05rem;font-weight:800;color:var(--sec-acc-d,var(--pri2));flex:1}
.wg-help{font-size:.88rem;color:var(--text);margin-bottom:12px;line-height:1.55;background:linear-gradient(135deg,var(--warn-bg,#fef3c7),var(--sec-acc-soft,var(--pri-soft)));border-left:4px solid var(--warn,#f59e0b);padding:9px 14px;border-radius:9px}
.btn{padding:8px 16px;border-radius:8px;background:var(--card);color:var(--text);border:1.5px solid var(--border);font-weight:600;font-size:.88rem;transition:background .15s,border-color .15s,transform .1s}
.btn:hover{background:var(--sec-acc-soft,var(--pri-soft));border-color:var(--sec-acc,var(--pri))}
.btn:active{transform:scale(.96)}
.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))}
.btn.small{padding:5px 11px;font-size:.78rem}
.tinp{padding:8px 12px;border:1.5px solid var(--border);border-radius:8px;background:var(--card);color:var(--text);transition:border-color .15s}
.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}
.sidecard-row:last-child{margin-bottom:0}
@media(max-width:980px){.col-side{position:static;max-height:none}}
.xp-card{background:linear-gradient(135deg,var(--acc-soft),var(--pri-soft));border:1.5px solid var(--acc);border-radius:12px;padding:14px;margin-bottom:14px}
.xp-card-title{font-size:.68rem;font-weight:800;color:var(--acc2);text-transform:uppercase;letter-spacing:.07em;margin-bottom:8px;display:flex;align-items:center;justify-content:space-between}
.xp-level{font-size:1.1rem;font-weight:900;color:var(--acc2);font-family:'Unbounded',sans-serif}
.xp-bar{height:9px;background:rgba(16,185,129,.15);border-radius:6px;overflow:hidden;margin:7px 0}
.xp-fill{height:100%;background:linear-gradient(90deg,var(--acc),var(--pri));border-radius:6px;transition:width .5s cubic-bezier(.4,0,.2,1)}
.xp-nums{font-size:.74rem;color:var(--muted);display:flex;justify-content:space-between}
.spoiler{border:1px solid var(--border);border-radius:10px;background:var(--card);margin:10px 0;overflow:hidden}
.spoiler summary{padding:8px 14px;background:var(--sec-acc-soft,var(--pri-soft));font-weight:700;cursor:pointer;font-size:.88rem;color:var(--sec-acc-d,var(--pri2));list-style:none;display:flex;align-items:center;gap:8px}
.spoiler summary::-webkit-details-marker{display:none}
.spoiler summary::before{content:'+';font-size:1.2rem;font-weight:900;color:var(--sec-acc,var(--pri));width:18px}
.spoiler[open] summary::before{content:'\2212'}
.spoiler-body{padding:10px 14px;font-size:.92rem;line-height:1.6}
.sec-nav{display:flex;gap:10px;margin-top:24px;padding-top:20px;border-top:1px solid var(--border);justify-content:space-between;flex-wrap:wrap}
.foot{text-align:center;padding:30px 16px;color:var(--muted);font-size:.78rem;border-top:1px solid var(--border);margin-top:30px}
.tbl{width:100%;border-collapse:collapse;margin:12px 0;font-size:.88rem}
.tbl th,.tbl td{padding:7px 10px;border:1px solid var(--border);text-align:center}
.tbl th{background:var(--sec-acc-soft,var(--pri-soft));color:var(--sec-acc-d,var(--pri2));font-weight:700}
.ach-popup{position:fixed;top:80px;right:18px;background:linear-gradient(135deg,#059669,#10b981);color:#fff;padding:12px 18px;border-radius:11px;font-weight:700;font-size:.9rem;box-shadow:0 8px 28px rgba(5,150,105,.45);z-index:1002;display:none;align-items:center;gap:8px;animation:achIn .45s cubic-bezier(.34,1.56,.64,1) forwards;max-width:340px}
.ach-popup.show{display:flex}
@keyframes achIn{from{opacity:0;transform:translateX(40px)}to{opacity:1;transform:none}}
.dnd-pool{display:flex;flex-wrap:wrap;gap:8px;margin-bottom:14px;padding:10px;border:1.5px dashed var(--border);border-radius:10px;min-height:54px;transition:border-color .18s,background .18s}
.dnd-pool.over{border-color:var(--sec-acc,var(--pri));background:var(--sec-acc-soft,var(--pri-soft));border-style:solid}
.dnd-pool.col{flex-direction:column;align-items:stretch}
.dnd-chip{display:inline-flex;align-items:center;gap:6px;padding:6px 12px;background:var(--card);border:1.5px solid var(--border);border-radius:10px;cursor:grab;user-select:none;font-size:.92rem;line-height:1.4;transition:transform .12s,box-shadow .12s,border-color .12s;touch-action:none;max-width:100%}
.dnd-chip:hover{transform:translateY(-1px);border-color:var(--sec-acc,var(--pri));box-shadow:var(--sh)}
.dnd-chip.armed{border-color:var(--sec-acc,var(--pri));background:var(--sec-acc-soft,var(--pri-soft));box-shadow:0 0 0 3px rgba(5,150,105,.22);transform:translateY(-1px)}
.dnd-chip.dragging{opacity:.28}
.dnd-chip.placed{background:var(--sec-acc-soft,var(--pri-soft));border-color:var(--sec-acc,var(--pri))}
.dnd-chip .dnd-x{padding:0 5px;color:var(--muted);font-weight:700;font-size:1.05rem;border-radius:4px;cursor:pointer}
.dnd-chip .dnd-x:hover{color:var(--bad,var(--fail));background:var(--fail-bg)}
.drop-box{background:var(--card);border:1.5px dashed var(--border);border-radius:10px;padding:10px;min-height:90px;transition:border-color .15s,background .15s}
.drop-box:hover{border-color:var(--sec-acc,var(--pri));background:var(--sec-acc-soft,var(--pri-soft))}
.drop-box h5{font-family:'Unbounded',sans-serif;font-size:.78rem;color:var(--sec-acc-d,var(--pri2));margin-bottom:8px;text-transform:uppercase;letter-spacing:.05em}
.drop-box.over{border-color:var(--sec-acc,var(--pri));background:var(--sec-acc-soft,var(--pri-soft));border-style:solid;transform:scale(1.015)}
.drop-items{display:flex;flex-wrap:wrap;gap:6px;min-height:32px}
.dnd-hint{font-size:.78rem;color:var(--muted);margin-bottom:8px;display:flex;align-items:center;gap:6px}
.dnd-hint svg{width:14px;height:14px;flex-shrink:0}
.actions{display:flex;gap:8px;flex-wrap:wrap;margin-top:10px}
.sliders{display:grid;grid-template-columns:repeat(auto-fit,minmax(180px,1fr));gap:10px;margin-bottom:10px}
.sliders label{display:block;font-size:.92rem;color:var(--muted);background:var(--card);padding:8px 12px;border-radius:8px;border:1px solid var(--border);line-height:1.5}
.sliders label b{font-family:'JetBrains Mono',monospace;font-size:1.05rem;color:var(--sec-acc-d,var(--pri2));margin-left:4px}
.sliders label input[type="range"]{display:block;width:100%;margin-top:6px;accent-color:var(--sec-acc,var(--pri))}
.score-display{display:flex;gap:14px;flex-wrap:wrap;align-items:center;padding:10px 14px;background:var(--sec-acc-soft,var(--pri-soft));border-radius:10px;margin-bottom:12px}
.score-display b{color:var(--sec-acc-d,var(--pri2));font-size:1.15rem}
.col-side-backdrop{position:fixed;inset:0;background:rgba(0,0,0,.42);z-index:9990;display:none;animation:fadeIn .18s ease}
.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}
}
.gloss-term{border-bottom:1.5px dotted var(--sec-acc,var(--pri));cursor:help;color:var(--sec-acc-d,var(--pri2));font-weight:600;padding:0 1px}
.gloss-term:hover{background:var(--sec-acc-soft,var(--pri-soft));border-radius:3px}
.gloss-tip{position:fixed;max-width:320px;padding:11px 14px;background:var(--card);border:1.5px solid var(--sec-acc,var(--pri));border-radius:11px;font-size:.84rem;line-height:1.55;box-shadow:0 12px 32px rgba(0,0,0,.18);z-index:9994;display:none;pointer-events:none;color:var(--text)}
.gloss-tip.show{display:block;animation:tipIn .15s ease}
.gloss-tip b{color:var(--sec-acc-d,var(--pri2));font-size:.92rem}
@keyframes tipIn{from{opacity:0;transform:translateY(4px)}to{opacity:1;transform:none}}
.search-modal{position:fixed;inset:0;background:rgba(15,23,42,.55);backdrop-filter:blur(4px);z-index:9993;display:none;align-items:flex-start;justify-content:center;padding-top:14vh}
.search-modal.show{display:flex;animation:fadeIn .15s ease}
.search-box{background:var(--bg);border:1px solid var(--border);border-radius:14px;width:560px;max-width:92vw;max-height:70vh;display:flex;flex-direction:column;overflow:hidden;box-shadow:0 24px 64px rgba(0,0,0,.4)}
.search-input{padding:14px 16px;font-size:1rem;border:0;border-bottom:1px solid var(--border);background:transparent;color:var(--text);outline:none}
.search-results{flex:1;overflow-y:auto;padding:6px 0}
.search-row{display:block;padding:8px 16px;cursor:pointer;border-bottom:1px solid var(--border);text-align:left;background:transparent;border-left:0;border-right:0;border-top:0;width:100%;color:var(--text)}
.search-row:hover,.search-row.active{background:var(--sec-acc-soft,var(--pri-soft))}
.search-row .sr-kind{font-size:.7rem;font-weight:800;color:var(--muted);text-transform:uppercase;letter-spacing:.06em;margin-bottom:2px}
.search-row .sr-title{font-weight:700;font-size:.92rem;color:var(--text)}
.search-row .sr-desc{font-size:.8rem;color:var(--muted);margin-top:2px}
.search-empty{padding:20px;text-align:center;color:var(--muted);font-size:.88rem}
.search-foot{padding:8px 14px;border-top:1px solid var(--border);font-size:.74rem;color:var(--muted);display:flex;gap:14px;background:var(--card-soft,transparent)}
.search-foot kbd{padding:2px 6px;background:var(--card);border:1px solid var(--border);border-radius:4px;font-family:'JetBrains Mono',monospace;font-size:.72rem}
</style>
</head>
<body>
<header class="hdr">
<div class="hdr-row">
<div>
<h1>Геометрия 8 · Глава 2</h1>
<div class="hdr-sub">Площади</div>
</div>
<div class="hdr-side">
<a href="/textbook/geometry-8" class="hdr-btn" title="К Геометрии 8 — все главы">
<svg class="ic" viewBox="0 0 24 24"><polyline points="15 18 9 12 15 6"/></svg>
К геометрии 8
</a>
<button id="search-btn" class="hdr-btn" title="Поиск (Ctrl+K)">
<svg class="ic" viewBox="0 0 24 24"><circle cx="11" cy="11" r="7"/><path d="m21 21-4-4"/></svg>
Поиск
</button>
<button id="sidebar-btn" class="hdr-btn" title="Шпаргалка">
<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" title="Сменить тему">
<svg class="ic" viewBox="0 0 24 24"><path d="M21 12.8A9 9 0 1 1 11.2 3a7 7 0 0 0 9.8 9.8z"/></svg>
<span id="theme-lab">Тёмная</span>
</button>
</div>
</div>
</header>
<main class="main">
<div class="col-main">
<section class="hero">
<h2>Площади: от квадрата до теоремы Пифагора</h2>
<p>Площадь — числовая мера размера фигуры. Мы выведем формулы для <b>всех плоских фигур</b>: квадрата, прямоугольника, параллелограмма, треугольника, трапеции, ромба. Главный результат — <b>теорема Пифагора</b> и её доказательство через площади.</p>
<div class="hero-row">
<button class="btn-primary" onclick="goTo('p1')">
<svg class="ic" viewBox="0 0 24 24"><polygon points="6 4 20 12 6 20 6 4" fill="currentColor" stroke="none"/></svg>
Начать § 1
</button>
<div class="hero-progress">
<span class="hp-label">Прогресс по главе</span>
<div class="hp-bar"><div id="hero-hp-fill" class="hp-fill"></div></div>
<span id="hero-hp-text" class="hp-text">0%</span>
</div>
<div id="hero-xp-badge" class="hero-xp-badge" title="Опыт"></div>
</div>
</section>
<section class="psel">
<div class="psel-title">Параграфы главы</div>
<div id="psel-grid" class="psel-grid"></div>
</section>
<section id="sec-p1" class="sec" data-watermark="S"><div class="sec-header"><span class="sec-num">§ 1</span><h2 class="sec-h">Площадь квадрата</h2></div><div id="p1-body"></div></section>
<section id="sec-p2" class="sec" data-watermark="ab"><div class="sec-header"><span class="sec-num">§ 2</span><h2 class="sec-h">Площадь прямоугольника</h2></div><div id="p2-body"></div></section>
<section id="sec-p3" class="sec" data-watermark="bh"><div class="sec-header"><span class="sec-num">§ 3</span><h2 class="sec-h">Площадь параллелограмма</h2></div><div id="p3-body"></div></section>
<section id="sec-p4" class="sec" data-watermark="&#189;bh"><div class="sec-header"><span class="sec-num">§ 4</span><h2 class="sec-h">Площадь треугольника</h2></div><div id="p4-body"></div></section>
<section id="sec-p5" class="sec" data-watermark="&#189;(a+b)h"><div class="sec-header"><span class="sec-num">§ 5</span><h2 class="sec-h">Площадь трапеции</h2></div><div id="p5-body"></div></section>
<section id="sec-p6" class="sec" data-watermark="&#189;dd"><div class="sec-header"><span class="sec-num">§ 6</span><h2 class="sec-h">Площадь ромба</h2></div><div id="p6-body"></div></section>
<section id="sec-p7" class="sec" data-watermark="&#8978;"><div class="sec-header"><span class="sec-num">§ 7</span><h2 class="sec-h">Площадь прямоугольного треугольника</h2></div><div id="p7-body"></div></section>
<section id="sec-p8" class="sec" data-watermark="h"><div class="sec-header"><span class="sec-num">§ 8</span><h2 class="sec-h">Высота, проведённая к гипотенузе</h2></div><div id="p8-body"></div></section>
<section id="sec-p9" class="sec" data-watermark="m"><div class="sec-header"><span class="sec-num">§ 9</span><h2 class="sec-h">Треугольники с общей высотой</h2></div><div id="p9-body"></div></section>
<section id="sec-p10" class="sec" data-watermark="2:1"><div class="sec-header"><span class="sec-num">§ 10</span><h2 class="sec-h">Свойства медианы для площадей</h2></div><div id="p10-body"></div></section>
<section id="sec-p11" class="sec" data-watermark="a&#178;+b&#178;"><div class="sec-header"><span class="sec-num">§ 11</span><h2 class="sec-h">Теорема Пифагора</h2></div><div id="p11-body"></div></section>
<section id="sec-p12" class="sec" data-watermark="&#8730;3"><div class="sec-header"><span class="sec-num">§ 12</span><h2 class="sec-h">S и h равностороннего треугольника</h2></div><div id="p12-body"></div></section>
<section id="sec-p13" class="sec" data-watermark="&#8730;2"><div class="sec-header"><span class="sec-num">§ 13</span><h2 class="sec-h">Диагональ квадрата</h2></div><div id="p13-body"></div></section>
<section id="sec-p14" class="sec" data-watermark="&#8656;"><div class="sec-header"><span class="sec-num">§ 14</span><h2 class="sec-h">Обратная теорема Пифагора</h2></div><div id="p14-body"></div></section>
<section id="sec-p15" class="sec" data-watermark="3-4-5"><div class="sec-header"><span class="sec-num">§ 15</span><h2 class="sec-h">Пифагоровы тройки</h2></div><div id="p15-body"></div></section>
<section id="sec-final2" class="sec" data-watermark="&#9733;"><div class="sec-header"><span class="sec-num" style="background:linear-gradient(135deg,#059669,#10b981)">Финал главы</span><h2 class="sec-h">Итоги. Боссы главы 2</h2></div><div id="final2-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">Интерактивный учебник «Геометрия 8» · Глава 2 · Площади · LearnSpace</footer>
<div id="ach-popup" class="ach-popup"><svg class="ic" viewBox="0 0 24 24" style="width:22px;height:22px"><rect x="3" y="3" width="18" height="14" rx="1"/></svg><span id="ach-text">Достижение!</span></div>
<div id="gloss-tip" class="gloss-tip"></div>
<div id="search-modal" class="search-modal" role="dialog" aria-label="Поиск по главе">
<div class="search-box">
<input type="text" id="search-input" class="search-input" placeholder="Поиск: формула, теорема, параграф…" autocomplete="off">
<div id="search-results" class="search-results"></div>
<div class="search-foot"><span><kbd>&#8593;&#8595;</kbd> навигация</span><span><kbd>Enter</kbd> открыть</span><span><kbd>Esc</kbd> закрыть</span></div>
</div>
</div>
<script>
'use strict';
const STATE = {
current: 'p1',
progress: { p1:0,p2:0,p3:0,p4:0,p5:0,p6:0,p7:0,p8:0,p9:0,p10:0,p11:0,p12:0,p13:0,p14:0,p15:0,final2:0 },
achievements: new Map(), xp: 0, level: 1,
};
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:'Начало главы 2!', ch2_done:'Площади изучены!' };
function loadProgress(){
try{
const s=localStorage.getItem('geometry8_ch2_progress'); if(s) Object.assign(STATE.progress,JSON.parse(s));
const a=localStorage.getItem('geometry8_ch2_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('geometry8_xp')||0); STATE.level=calcLevel(STATE.xp);
}catch(e){}
}
function saveProgress(){
try{ localStorage.setItem('geometry8_ch2_progress',JSON.stringify(STATE.progress)); localStorage.setItem('geometry8_ch2_achievements',JSON.stringify(Object.fromEntries(STATE.achievements))); localStorage.setItem('geometry8_xp',String(STATE.xp)); }catch(e){}
}
function bumpProgress(key,delta){ STATE.progress[key]=Math.max(0,Math.min(100,(STATE.progress[key]||0)+delta)); saveProgress(); refreshProgressUI(); if(STATE.progress[key]>=50) markParaRead(key); }
const _TB_SLUG='geometry-8-ch2'; 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 loadServerReadState(){ const tok=(window.LS&&LS.getToken)?LS.getToken():'';if(!tok)return; fetch('/api/textbooks/'+_TB_SLUG,{headers:{'Authorization':'Bearer '+tok}}).then(r=>r.ok?r.json():null).then(d=>{ if(!d||!d.progress)return; (d.progress.read||[]).forEach(k=>{_markedRead.add(k);if((STATE.progress[k]||0)<50)STATE.progress[k]=100;}); saveProgress();refreshProgressUI(); }).catch(()=>{}); }
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,'geometry8-ch2-'+(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);} if(window.confetti)try{confetti();}catch(e){} } }
const TOTAL_PARAS=16;
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"><rect x="3" y="3" width="18" height="14" rx="1"/></svg> Ур. '+STATE.level+' \xb7 '+(STATE.xp||0)+' XP'; }
if(STATE.current&&document.getElementById('sidebar-content')){ try{buildSidebar(STATE.current);}catch(e){} }
}
function achievement(id,text){ if(STATE.achievements.has(id))return; STATE.achievements.set(id,text||ACH_LABELS[id]||id); saveProgress(); const pop=document.getElementById('ach-popup'); if(pop){document.getElementById('ach-text').textContent=text||ACH_LABELS[id]||id;pop.classList.add('show');setTimeout(()=>pop.classList.remove('show'),3300);} addXp(20,'ach-'+id); }
const PARAS = [
{ id:'p1', num:'§ 1', name:'Площадь квадрата', sub:'S = a²' },
{ id:'p2', num:'§ 2', name:'Площадь прямоугольника', sub:'S = ab' },
{ id:'p3', num:'§ 3', name:'Площадь параллелограмма', sub:'S = bh' },
{ id:'p4', num:'§ 4', name:'Площадь треугольника', sub:'S = ½bh' },
{ id:'p5', num:'§ 5', name:'Площадь трапеции', sub:'S = ½(a+b)h' },
{ id:'p6', num:'§ 6', name:'Площадь ромба', sub:'S = ½d₁d₂' },
{ id:'p7', num:'§ 7', name:'Прямоугольный треугольник', sub:'S = ½ab' },
{ id:'p8', num:'§ 8', name:'Высота к гипотенузе', sub:'h² = ab' },
{ id:'p9', num:'§ 9', name:'Общая высота', sub:'Отношение площадей' },
{ id:'p10', num:'§ 10', name:'Медиана и площадь', sub:'2 равные части' },
{ id:'p11', num:'§ 11', name:'Теорема Пифагора', sub:'a² + b² = c²' },
{ id:'p12', num:'§ 12', name:'Равносторонний треугольник', sub:'h = a√3/2' },
{ id:'p13', num:'§ 13', name:'Диагональ квадрата', sub:'d = a√2' },
{ id:'p14', num:'§ 14', name:'Обратная теорема Пифагора', sub:'Признак прямого угла' },
{ id:'p15', num:'§ 15', name:'Пифагоровы тройки', sub:'3-4-5, 5-12-13, ...' },
{ id:'final2', num:'★', name:'Финал главы', sub:'Итоги · Боссы', final:true },
];
function buildParaSelector(){ const g=document.getElementById('psel-grid');g.innerHTML=''; PARAS.forEach(p=>{ const card=document.createElement('div'); card.className='psel-card'+(p.final?' final':''); card.dataset.id=p.id;card.dataset.progCard=p.id; card.innerHTML=`<div class="psel-num">${p.num}</div><div class="psel-name">${p.name}</div><div class="psel-prog"><div class="psel-prog-fill"></div></div>`; card.addEventListener('click',()=>goTo(p.id)); g.appendChild(card); }); }
const BUILT=new Set();
const BUILDERS={
p1:()=>buildP1(),p2:()=>buildP2(),p3:()=>buildP3(),p4:()=>buildP4(),p5:()=>buildP5stub(),
p6:()=>buildP6stub(),p7:()=>buildP7stub(),p8:()=>buildP8stub(),p9:()=>buildP9stub(),p10:()=>buildP10stub(),
p11:()=>buildP11stub(),p12:()=>buildP12stub(),p13:()=>buildP13stub(),p14:()=>buildP14stub(),p15:()=>buildP15stub(),
final2:()=>buildFinal2stub(),
};
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);
setTimeout(()=>{ try{wrapGlossary(el);}catch(e){} },60);
markLastPara(id);
}
const SIDEBARS={
p1:{ title:'Шпаргалка § 1', rows:[['Площадь квадрата','$S = a^2$'],['Единицы','мм², см², м², км²']] },
p2:{ title:'Шпаргалка § 2', rows:[['Площадь прямоугольника','$S = ab$'],['Основание × высоту','']] },
p3:{ title:'Шпаргалка § 3', rows:[['$S = bh$','основание × высота'],['Высота','расстояние между параллельными сторонами']] },
p4:{ title:'Шпаргалка § 4', rows:[['$S = \\dfrac{1}{2}bh$','половина произведения'],['Основание','любая сторона треугольника']] },
p5:{ title:'Шпаргалка § 5', rows:[['$S = \\dfrac{a+b}{2}\\cdot h$','полусумма оснований × высота']] },
p6:{ title:'Шпаргалка § 6', rows:[['$S = \\dfrac{d_1 d_2}{2}$','полупроизведение диагоналей']] },
p7:{ title:'Шпаргалка § 7', rows:[['$S = \\dfrac{ab}{2}$','полупроизведение катетов'],['Катеты','стороны при прямом угле']] },
p8:{ title:'Шпаргалка § 8', rows:[['$h^2 = a_1 \\cdot a_2$','высота к гипотенузе'],['$a^2 = c \\cdot a_1$','катет и проекция']] },
p9:{ title:'Шпаргалка § 9', rows:[['Общая высота','отношение площадей = отношению оснований']] },
p10:{ title:'Шпаргалка § 10', rows:[['Медиана','делит треугольник на 2 равновеликих']] },
p11:{ title:'Шпаргалка § 11', rows:[['Теорема Пифагора','$a^2 + b^2 = c^2$'],['$c$','гипотенуза'],['$a, b$','катеты']] },
p12:{ title:'Шпаргалка § 12', rows:[['Высота','$h = \\dfrac{a\\sqrt{3}}{2}$'],['Площадь','$S = \\dfrac{a^2\\sqrt{3}}{4}$']] },
p13:{ title:'Шпаргалка § 13', rows:[['Диагональ квадрата','$d = a\\sqrt{2}$'],['Угол','45°']] },
p14:{ title:'Шпаргалка § 14', rows:[['Обратная','если $a^2+b^2=c^2$, то угол прямой']] },
p15:{ title:'Шпаргалка § 15', rows:[['3-4-5','тройка Пифагора'],['5-12-13',''],['8-15-17','']] },
final2:{ title:'Финал главы', rows:[['15 параграфов','площади изучены'],['Главный результат','Теорема Пифагора']] },
};
const TIPS=[
{sec:'p1', html:'$S = a^2$ — отсюда и название «квадрат» числа.'},
{sec:'p2', html:'Площадь прямоугольника $= $ длина $\\times$ ширина. Проверь: $3\\times4=12$ кв.ед.'},
{sec:'p3', html:'Высота параллелограмма — это <b>перпендикуляр</b> между параллельными сторонами, не боковая сторона!'},
{sec:'p4', html:'Площадь треугольника = <b>половине</b> площади параллелограмма на том же основании.'},
{sec:'p5', html:'Трапеция: $S = \\dfrac{a+b}{2}\\cdot h$ = средняя линия × высота.'},
{sec:'p6', html:'Площадь ромба через диагонали: $S = \\dfrac{d_1 d_2}{2}$. Это удобнее, чем $S = ah$.'},
{sec:'p7', html:'У прямоугольного треугольника $S = \\dfrac{ab}{2}$, где $a, b$ — катеты.'},
{sec:'p8', html:'Высота к гипотенузе делит её на два отрезка; $h^2 = a_1 a_2$.'},
{sec:'p9', html:'Два треугольника с общей высотой: их площади относятся как их основания.'},
{sec:'p10', html:'Медиана делит треугольник на два <b>равновеликих</b> треугольника.'},
{sec:'p11', html:'Теорема Пифагора: $a^2 + b^2 = c^2$. Самая важная теорема курса!'},
{sec:'p12', html:'В равностороннем треугольнике $h = \\dfrac{a\\sqrt{3}}{2}$, $S = \\dfrac{a^2\\sqrt{3}}{4}$.'},
{sec:'p13', html:'Диагональ квадрата $d = a\\sqrt{2}$ — применение теоремы Пифагора.'},
{sec:'p14', html:'Обратная теорема: если $a^2+b^2=c^2$, то треугольник прямоугольный.'},
{sec:'p15', html:'Пифагоровы тройки: $(3,4,5)$, $(5,12,13)$, $(8,15,17)$, $(7,24,25)$...'},
{sec:'final2', html:'Знание формул площадей + теорема Пифагора — ключ ко всему курсу.'},
];
function buildSidebar(id){
const box=document.getElementById('sidebar-content'); const sb=SIDEBARS[id]||SIDEBARS.p1; let html='';
const xpForLv=_xpForLevel(STATE.level),xpNext=_xpForLevel(STATE.level+1);
const xpInLv=STATE.xp-xpForLv,xpRange=xpNext-xpForLv,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:#065f46;display:flex;align-items:center;gap:6px"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" style="width:14px;height:14px"><rect x="3" y="3" width="18" height="14" rx="1"/></svg>Подсказка</h4><div class="sidecard-row" style="margin-bottom:0;font-size:.84rem;line-height:1.55">${tip.html}</div></div>`;
if(STATE.achievements.size>0){ html+=`<div class="sidecard"><h4>Достижения <span style="color:var(--warn);float:right">${STATE.achievements.size}</span></h4>`; [...STATE.achievements.values()].slice(-4).forEach(text=>{ html+=`<div class="sidecard-row" style="font-size:.78rem;color:var(--ok)">&#10003; ${text}</div>`; }); html+='</div>'; }
box.innerHTML=html; if(window.renderMathInElement) try{renderMath(box);}catch(e){}
}
function initTheme(){ const t=localStorage.getItem('geometry8_ch2_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('geometry8_ch2_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){ elm.className='feedback '+(ok?'ok':'fail');elm.innerHTML=text||(ok?'&#10003; Верно!':'&#10007; Неверно'); }
function fmt(n){ if(!isFinite(n))return '?';if(Number.isInteger(n))return String(n);return Math.abs(n-Math.round(n))<1e-9?String(Math.round(n)):(+n.toFixed(4)).toString(); }
const ICONS={repeat:'<svg class="ic" viewBox="0 0 24 24"><polyline points="9 11 12 14 22 4"/><path d="M21 12v7a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h11"/></svg>',theory:'<svg class="ic" viewBox="0 0 24 24"><path d="M4 19.5A2.5 2.5 0 0 1 6.5 17H20"/><path d="M6.5 2H20v20H6.5A2.5 2.5 0 0 1 4 19.5v-15A2.5 2.5 0 0 1 6.5 2z"/></svg>',algo:'<svg class="ic" viewBox="0 0 24 24"><polyline points="17 11 21 7 17 3"/><line x1="21" y1="7" x2="9" y2="7"/><polyline points="7 13 3 17 7 21"/><line x1="3" y1="17" x2="15" y2="17"/></svg>',rule:'<svg class="ic" viewBox="0 0 24 24"><path d="M6 8a6 6 0 0 1 12 0c0 7 3 9 3 9H3s3-2 3-9"/><path d="M10.3 21a1.94 1.94 0 0 0 3.4 0"/></svg>',example:'<svg class="ic" viewBox="0 0 24 24"><path d="M9 18h6"/><path d="M10 22h4"/><path d="M12 2a7 7 0 0 0-4 13c1 1 2 2 2 4h4c0-2 1-3 2-4a7 7 0 0 0-4-13z"/></svg>',oral:'<svg class="ic" viewBox="0 0 24 24"><circle cx="12" cy="12" r="10"/><path d="M9.09 9a3 3 0 0 1 5.83 1c0 2-3 3-3 3"/><line x1="12" y1="17" x2="12.01" y2="17"/></svg>',class:'<svg class="ic" viewBox="0 0 24 24"><rect x="3" y="3" width="18" height="14" rx="1"/><line x1="3" y1="21" x2="21" y2="21"/><polyline points="7 14 10 11 13 14 17 10"/></svg>',home:'<svg class="ic" viewBox="0 0 24 24"><path d="M3 9l9-7 9 7v11a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z"/><polyline points="9 22 9 12 15 12 15 22"/></svg>'};
function makeCard(kind,title,num,body){ const labels={repeat:'Повторение',theory:'Теория',algo:'Алгоритм',rule:'Правило',example:'Пример',oral:'Устно',class:'Класс',home:'Домашка'}; return `<div class="card"><div class="card-header"><div class="card-icon ${kind}">${ICONS[kind]}</div><div class="card-title">${labels[kind]||''}${title&&title!==labels[kind]?' \xb7 '+title:''}</div>${num?`<div class="card-num">${num}</div>`:''}</div><div class="card-body">${body}</div></div>`; }
function secNav(prev,next){ const NAMES={p1:'§1',p2:'§2',p3:'§3',p4:'§4',p5:'§5',p6:'§6',p7:'§7',p8:'§8',p9:'§9',p10:'§10',p11:'§11',p12:'§12',p13:'§13',p14:'§14',p15:'§15',final2:'Финал'}; 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; }
let _confettiCanvas=null,_confettiParticles=[],_confettiRaf=null;
function confetti(){ if(!_confettiCanvas){_confettiCanvas=document.createElement('canvas');_confettiCanvas.style.cssText='position:fixed;top:0;left:0;width:100%;height:100%;pointer-events:none;z-index:9999';document.body.appendChild(_confettiCanvas);} const c=_confettiCanvas;c.width=window.innerWidth;c.height=window.innerHeight; const ctx=c.getContext('2d'); const colors=['#059669','#10b981','#34d399','#f59e0b','#0891b2']; for(let i=0;i<80;i++){_confettiParticles.push({x:window.innerWidth/2+(Math.random()-.5)*200,y:window.innerHeight/2,vx:(Math.random()-.5)*14,vy:-10-Math.random()*10,g:.4,life:100,color:colors[i%colors.length],r:4+Math.random()*4,rot:0,vRot:(Math.random()-.5)*.3});} if(_confettiRaf)cancelAnimationFrame(_confettiRaf); function frame(){ctx.clearRect(0,0,c.width,c.height);_confettiParticles=_confettiParticles.filter(p=>{p.x+=p.vx;p.y+=p.vy;p.vy+=p.g;p.life--;p.rot+=p.vRot;ctx.save();ctx.translate(p.x,p.y);ctx.rotate(p.rot);ctx.fillStyle=p.color;ctx.fillRect(-p.r,-p.r/2,p.r*2,p.r);ctx.restore();return p.life>0&&p.y<c.height+50;});if(_confettiParticles.length>0)_confettiRaf=requestAnimationFrame(frame);else{ctx.clearRect(0,0,c.width,c.height);_confettiRaf=null;}} frame(); }
const GLOSSARY=[
{term:'многоугольник',def:'Замкнутая ломаная линия, образующая плоскую фигуру.',sec:'p1',aliases:['многоугольник','многоугольника','многоугольнике','многоугольников','многоугольники']},
{term:'площадь',def:'Числовая характеристика плоской фигуры, выражающая её размер.',sec:'p1',aliases:['площадь','площади','площадью']},
{term:'высота',def:'Перпендикуляр, опущенный из вершины на противоположную сторону (или её продолжение).',sec:'p3',aliases:['высота','высоты','высоте','высоту','высотой']},
{term:'гипотенуза',def:'Сторона прямоугольного треугольника, противоположная прямому углу.',sec:'p7',aliases:['гипотенуза','гипотенузы','гипотенузе','гипотенузу','гипотенузой']},
{term:'катет',def:'Одна из двух сторон прямоугольного треугольника, образующих прямой угол.',sec:'p7',aliases:['катет','катета','катете','катетов','катеты','катетами']},
{term:'теорема Пифагора',def:'В прямоугольном треугольнике сумма квадратов катетов равна квадрату гипотенузы: $a^2 + b^2 = c^2$.',sec:'p11',aliases:['теорема Пифагора','теореме Пифагора','теоремы Пифагора']},
{term:'пифагорова тройка',def:'Набор трёх натуральных чисел $(a, b, c)$, для которых $a^2 + b^2 = c^2$.',sec:'p15',aliases:['пифагорова тройка','пифагоровы тройки','пифагоровых троек','пифагоровой тройки']},
{term:'параллелограмм',def:'Четырёхугольник, у которого противоположные стороны попарно параллельны.',sec:'p3',aliases:['параллелограмм','параллелограмма','параллелограмме','параллелограммов']},
{term:'трапеция',def:'Четырёхугольник, у которого ровно одна пара параллельных сторон.',sec:'p5',aliases:['трапеция','трапеции','трапецию','трапеций']},
{term:'ромб',def:'Параллелограмм с равными сторонами.',sec:'p6',aliases:['ромб','ромба','ромбе','ромбов','ромбы']},
{term:'медиана',def:'Отрезок от вершины треугольника до середины противоположной стороны.',sec:'p10',aliases:['медиана','медианы','медиан','медиану']},
{term:'подобные треугольники',def:'Треугольники, у которых углы равны попарно и стороны пропорциональны.',sec:'p9',aliases:['подобные треугольники','подобных треугольников']},
{term:'касательная',def:'Прямая, имеющая одну общую точку с окружностью.',sec:'p11',aliases:['касательная','касательной','касательную']},
{term:'вписанный угол',def:'Угол с вершиной на окружности, стороны которого — хорды.',sec:'p11',aliases:['вписанный угол','вписанного угла']},
{term:'центральный угол',def:'Угол с вершиной в центре окружности.',sec:'p11',aliases:['центральный угол','центрального угла']},
{term:'коэффициент подобия',def:'Отношение соответственных сторон подобных треугольников.',sec:'p9',aliases:['коэффициент подобия','коэффициента подобия']},
{term:'биссектриса',def:'Луч, делящий угол пополам.',sec:'p9',aliases:['биссектриса','биссектрисы','биссектрису']},
{term:'хорда',def:'Отрезок, соединяющий две точки окружности.',sec:'p11',aliases:['хорда','хорды','хорде','хорд']},
];
function wrapGlossary(root){ if(!root||root.__glossDone)return; const allAliases=[];GLOSSARY.forEach((g,i)=>g.aliases.forEach(a=>allAliases.push({a,i})));allAliases.sort((x,y)=>y.a.length-x.a.length); const re=new RegExp('(?<![\\w\\u0400-\\u04ff-])('+allAliases.map(x=>x.a.replace(/[.*+?^${}()|[\]\\]/g,'\\$&')).join('|')+')(?![\\w\\u0400-\\u04ff-])','iu'); const walker=document.createTreeWalker(root,NodeFilter.SHOW_TEXT,{acceptNode(node){const p=node.parentElement;if(!p)return NodeFilter.FILTER_REJECT;if(p.closest('.katex,.gloss-term,button,input,select,.wg-badge,.card-icon,.sec-num,.psel-num,.hdr,.ach-popup,script,style,.search-modal,.sidecard,.gloss-tip'))return NodeFilter.FILTER_REJECT;if(!re.test(node.nodeValue))return NodeFilter.FILTER_REJECT;return NodeFilter.FILTER_ACCEPT;}}); const nodes=[];let n;while((n=walker.nextNode()))nodes.push(n); nodes.forEach(node=>{const text=node.nodeValue;const out=document.createDocumentFragment();let cursor=0;const global=new RegExp(re.source,'giu');let m;while((m=global.exec(text))!==null){if(m.index>cursor)out.appendChild(document.createTextNode(text.slice(cursor,m.index)));const found=m[0].toLowerCase();const hit=allAliases.find(x=>x.a.toLowerCase()===found);const g=hit?GLOSSARY[hit.i]:null;const sp=document.createElement('span');sp.className='gloss-term';sp.dataset.gloss=g?g.term:'';sp.textContent=m[0];out.appendChild(sp);cursor=m.index+m[0].length;}if(cursor<text.length)out.appendChild(document.createTextNode(text.slice(cursor)));node.parentNode.replaceChild(out,node);}); root.__glossDone=true; }
function initGlossaryTip(){ const tip=document.getElementById('gloss-tip');if(!tip)return;let lockOpen=null; function show(elm){const g=GLOSSARY.find(x=>x.term===elm.dataset.gloss);if(!g)return;tip.innerHTML='<b>'+g.term[0].toUpperCase()+g.term.slice(1)+'</b><div style="margin-top:4px">'+g.def+'</div><div style="margin-top:6px;font-size:.72rem;color:var(--muted);text-transform:uppercase;letter-spacing:.06em">См. § '+g.sec.replace('p','')+'</div>';if(window.renderMathInElement)renderMath(tip);const r=elm.getBoundingClientRect();tip.classList.add('show');const tw=tip.offsetWidth,th=tip.offsetHeight;let left=r.left,top=r.bottom+8;if(left+tw>window.innerWidth-12)left=window.innerWidth-tw-12;if(top+th>window.innerHeight-12)top=r.top-th-8;tip.style.left=Math.max(8,left)+'px';tip.style.top=Math.max(8,top)+'px';} function hide(){tip.classList.remove('show');} document.addEventListener('mouseover',e=>{const elm=e.target.closest&&e.target.closest('.gloss-term');if(elm&&!lockOpen)show(elm);}); document.addEventListener('mouseout',e=>{const elm=e.target.closest&&e.target.closest('.gloss-term');if(elm&&!lockOpen)hide();}); document.addEventListener('click',e=>{const elm=e.target.closest&&e.target.closest('.gloss-term');if(elm){if(lockOpen===elm){lockOpen=null;hide();}else{lockOpen=elm;show(elm);}}else if(lockOpen&&!e.target.closest('.gloss-tip')){lockOpen=null;hide();}}); }
const SEARCH_INDEX=(function(){ const arr=[]; PARAS.forEach(p=>arr.push({kind:'Параграф',title:p.num+' '+p.name,desc:p.sub||'',sec:p.id})); GLOSSARY.forEach(g=>arr.push({kind:'Понятие',title:g.term,desc:g.def.replace(/\$/g,''),sec:g.sec,gloss:g.term})); [['Формула','S = a² (квадрат)','§1','p1'],['Формула','S = ab (прямоугольник)','§2','p2'],['Формула','S = bh (параллелограмм)','§3','p3'],['Формула','S = ½bh (треугольник)','§4','p4'],['Формула','a² + b² = c² (Пифагор)','§11','p11']].forEach(([k,t,d,s])=>arr.push({kind:k,title:t,desc:d,sec:s})); return arr; })();
function initSearch(){ const modal=document.getElementById('search-modal'),inp=document.getElementById('search-input'),out=document.getElementById('search-results'),btn=document.getElementById('search-btn'); if(!modal||!inp||!out)return; let cur=0,rows=[]; function score(q,it){const t=(it.title+' '+it.desc).toLowerCase();if(t.includes(q))return 100+(it.title.toLowerCase().startsWith(q)?50:0);let s=0;q.split(/\s+/).forEach(w=>{if(w&&t.includes(w))s+=10;});return s;} function rank(q){q=q.trim().toLowerCase();if(!q)return SEARCH_INDEX.slice(0,12);return SEARCH_INDEX.map(it=>({it,s:score(q,it)})).filter(x=>x.s>0).sort((a,b)=>b.s-a.s).slice(0,20).map(x=>x.it);} function render(){cur=0;if(!rows.length){out.innerHTML='<div class="search-empty">Ничего не найдено</div>';return;}out.innerHTML=rows.map((r,i)=>`<button class="search-row${i===0?' active':''}" data-i="${i}"><div class="sr-kind">${r.kind}</div><div class="sr-title">${r.title}</div>${r.desc?`<div class="sr-desc">${r.desc.length>90?r.desc.slice(0,90)+'…':r.desc}</div>`:''}</button>`).join('');out.querySelectorAll('.search-row').forEach(b=>b.addEventListener('click',()=>{cur=+b.dataset.i;pick();}));} function pick(){const r=rows[cur];if(!r)return;close();goTo(r.sec);if(r.gloss){setTimeout(()=>{const sec=document.getElementById('sec-'+r.sec);const elm=sec&&sec.querySelector('[data-gloss="'+r.gloss+'"]');if(elm){elm.scrollIntoView({behavior:'smooth',block:'center'});elm.style.transition='background .3s';elm.style.background='var(--warn,#f59e0b)';setTimeout(()=>{elm.style.background='';},1400);}},400);}} function move(d){const items=out.querySelectorAll('.search-row');if(!items.length)return;items[cur]&&items[cur].classList.remove('active');cur=(cur+d+items.length)%items.length;items[cur].classList.add('active');items[cur].scrollIntoView({block:'nearest'});} function open(){modal.classList.add('show');inp.value='';rows=rank('');render();setTimeout(()=>inp.focus(),50);} function close(){modal.classList.remove('show');} btn&&btn.addEventListener('click',open); modal.addEventListener('click',e=>{if(e.target===modal)close();}); inp.addEventListener('input',()=>{rows=rank(inp.value);render();}); inp.addEventListener('keydown',e=>{if(e.key==='ArrowDown'){e.preventDefault();move(1);}else if(e.key==='ArrowUp'){e.preventDefault();move(-1);}else if(e.key==='Enter'){e.preventDefault();pick();}else if(e.key==='Escape'){e.preventDefault();close();}}); document.addEventListener('keydown',e=>{if((e.ctrlKey||e.metaKey)&&(e.key==='k'||e.key==='K')){e.preventDefault();if(modal.classList.contains('show'))close();else open();}}); }
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();initGlossaryTip();initSearch();buildParaSelector();refreshProgressUI();loadServerReadState();goTo('p1');setTimeout(()=>achievement('start','Начало главы 2!'),600); if(window.LS&&window.LS.xp){window.LS.xp.load().then(function(s){if(s&&s.xp>STATE.xp){STATE.xp=s.xp;STATE.level=calcLevel(STATE.xp);saveProgress();refreshProgressUI();if(STATE.current)buildSidebar(STATE.current);}});} }
document.addEventListener('DOMContentLoaded',init);
/* ============================================================
HELPER: setupSorter (drag-and-drop chip sorter)
============================================================ */
function setupSorter(cfg){
const placed={}; const pool=document.getElementById(cfg.poolId); const scope=document.querySelector(cfg.scopeSelector);
if(!pool||!scope) return {placed,render:()=>{},reset:()=>{}};
pool.classList.add('dnd-pool'); if(cfg.columnLayout) pool.classList.add('col');
let armed=null;
function buildChip(it,isPlaced){ const e=document.createElement('div'); e.className='dnd-chip'+(isPlaced?' placed':''); e.dataset.id=it.id; e.innerHTML='<span class="dnd-txt">'+it.html+'</span><span class="dnd-x" title="Убрать">\xd7</span>'; attach(e,it.id); return e; }
function attach(elm,itId){ let ghost=null,dragging=false,sx=0,sy=0; elm.addEventListener('pointerdown',ev=>{ if(ev.button!==undefined&&ev.button!==0) return; if(ev.target.classList&&ev.target.classList.contains('dnd-x')){ ev.stopPropagation(); if(placed[itId]){delete placed[itId];render();}else if(armed===itId){armed=null;render();} return; } sx=ev.clientX;sy=ev.clientY; const r=elm.getBoundingClientRect(); const ox=ev.clientX-r.left,oy=ev.clientY-r.top; try{elm.setPointerCapture(ev.pointerId);}catch(e){} function onMove(e){ const dx=e.clientX-sx,dy=e.clientY-sy; if(!dragging&&Math.hypot(dx,dy)>8){ dragging=true; ghost=elm.cloneNode(true); ghost.classList.remove('armed'); ghost.style.cssText='position:fixed;z-index:9999;pointer-events:none;opacity:.9;transform:rotate(-2.5deg);box-shadow:0 14px 36px rgba(0,0,0,.32);width:'+r.width+'px;left:'+(e.clientX-ox)+'px;top:'+(e.clientY-oy)+'px'; document.body.appendChild(ghost); elm.classList.add('dragging'); } if(dragging&&ghost){ ghost.style.left=(e.clientX-ox)+'px';ghost.style.top=(e.clientY-oy)+'px'; const under=document.elementsFromPoint(e.clientX,e.clientY); scope.querySelectorAll('.drop-box.over,.dnd-pool.over').forEach(n=>n.classList.remove('over')); const tgt=under.find(n=>n.classList&&(n.classList.contains('drop-box')||n.classList.contains('dnd-pool'))); if(tgt)tgt.classList.add('over'); } } function onUp(e){ elm.removeEventListener('pointermove',onMove);elm.removeEventListener('pointerup',onUp);elm.removeEventListener('pointercancel',onUp);elm.classList.remove('dragging'); if(ghost){ghost.remove();ghost=null;} scope.querySelectorAll('.drop-box.over,.dnd-pool.over').forEach(n=>n.classList.remove('over')); if(dragging){ const under=document.elementsFromPoint(e.clientX,e.clientY); const box=under.find(n=>n.classList&&n.classList.contains('drop-box')); const pl=under.find(n=>n.classList&&n.classList.contains('dnd-pool')); if(box){const di=box.querySelector('[data-cat]');if(di){placed[itId]=di.dataset.cat;armed=null;render();return;}}else if(pl){delete placed[itId];armed=null;render();return;} }else{ if(placed[itId]){delete placed[itId];armed=null;render();}else{armed=(armed===itId)?null:itId;render();} } dragging=false; } elm.addEventListener('pointermove',onMove);elm.addEventListener('pointerup',onUp);elm.addEventListener('pointercancel',onUp); }); }
function attachBoxTaps(){ scope.querySelectorAll('.drop-box').forEach(box=>{ box.addEventListener('click',ev=>{ if(!armed)return; if(ev.target.closest('.dnd-chip'))return; const di=box.querySelector('[data-cat]'); if(di){placed[armed]=di.dataset.cat;armed=null;render();} }); }); }
function render(){ pool.innerHTML=''; cfg.items.forEach(it=>{if(placed[it.id])return;const c=buildChip(it,false);if(armed===it.id)c.classList.add('armed');pool.appendChild(c);}); cfg.cats.forEach(cat=>{const box=scope.querySelector('.drop-items[data-cat="'+cat+'"]');if(!box)return;box.innerHTML='';cfg.items.forEach(it=>{if(placed[it.id]!==cat)return;box.appendChild(buildChip(it,true));});}); if(window.renderMathInElement)try{renderMath(scope);}catch(_){} }
attachBoxTaps(); render();
return {placed,render,reset(){ for(const k in placed)delete placed[k];armed=null;render(); }};
}
/* ============================================================
§1 — ПЛОЩАДЬ КВАДРАТА
============================================================ */
function buildP1(){
const box = document.getElementById('p1-body');
let html = '';
html += makeCard('theory','Понятие площади','1.1',`
<p><b>Площадь</b> — числовая характеристика плоской фигуры, выражающая её размер. Единица площади — единичный квадрат (квадрат со стороной 1).</p>
<p><b>Аксиомы площади:</b></p>
<ul style="margin-left:18px;margin-top:6px;line-height:1.8">
<li><b>Положительность:</b> площадь любой фигуры $> 0$.</li>
<li><b>Равенство:</b> у равных (конгруэнтных) фигур площади равны.</li>
<li><b>Аддитивность:</b> площадь фигуры равна сумме площадей частей, на которые она разбита.</li>
</ul>`);
html += makeCard('rule','Площадь квадрата','1.2',`
<p>Если квадрат имеет сторону $a$, то его площадь:</p>
$$S = a^2$$
<p><b>Доказательство (идея):</b> квадрат разбивается на $a \\times a = a^2$ единичных квадратов. По аксиоме аддитивности $S = a^2$.</p>
<p style="margin-top:8px;font-size:.88rem;color:var(--muted)">Именно поэтому в математике «возведение в квадрат» называется так — это площадь квадрата.</p>`);
html += makeCard('rule','Единицы измерения площади','1.3',`
<table class="tbl">
<thead><tr><th>Единица</th><th>Равна</th></tr></thead>
<tbody>
<tr><td>$1\\,\\text{км}^2$</td><td>$10^6\\,\\text{м}^2$</td></tr>
<tr><td>$1\\,\\text{м}^2$</td><td>$10^4\\,\\text{см}^2 = 10^6\\,\\text{мм}^2$</td></tr>
<tr><td>$1\\,\\text{дм}^2$</td><td>$100\\,\\text{см}^2$</td></tr>
<tr><td>$1\\,\\text{см}^2$</td><td>$100\\,\\text{мм}^2$</td></tr>
</tbody>
</table>
<p style="margin-top:8px;font-size:.88rem">При переводе из крупной единицы в мелкую — умножать, из мелкой в крупную — делить.</p>`);
html += makeCard('example','Пример','1.4',`
<p><b>Сторона квадрата 7 см. Площадь?</b><br>$S = 7^2 = 49\\,\\text{см}^2$.</p>
<p style="margin-top:8px"><b>Площадь квадрата 36 дм². Найти сторону.</b><br>$a = \\sqrt{36} = 6\\,\\text{дм}$.</p>
<p style="margin-top:8px"><b>Перевести 2,5 м² в см².</b><br>$2{,}5 \\cdot 10^4 = 25000\\,\\text{см}^2$.</p>`);
/* --- INTERACTIVE 1: SVG-квадрат с сеткой --- */
html += `<div class="wg" id="p1-grid-wg">
<div class="wg-header"><span class="wg-badge">ИНТЕРАКТИВ 1</span><div class="wg-title">Квадрат с сеткой</div></div>
<div class="wg-help">Двигай слайдер — квадрат заполняется единичными клетками. Количество клеток = $a^2$ — это и есть площадь!</div>
<div class="sliders">
<label>Сторона $a$ = <b id="p1-grid-a-val">4</b>
<input type="range" min="1" max="10" value="4" id="p1-grid-a-sl">
</label>
</div>
<div style="display:flex;gap:18px;flex-wrap:wrap;align-items:flex-start;justify-content:center">
<div id="p1-grid-svg-wrap" style="flex-shrink:0"></div>
<div id="p1-grid-info" style="padding:14px;background:var(--card);border-radius:10px;border:1px solid var(--border);min-width:160px;font-size:.95rem;line-height:2"></div>
</div>
</div>`;
/* --- INTERACTIVE 2: Калькулятор площади --- */
html += `<div class="wg">
<div class="wg-header"><span class="wg-badge">ИНТЕРАКТИВ 2</span><div class="wg-title">Калькулятор площади квадрата</div></div>
<div class="wg-help">Введи сторону — получи площадь; или введи площадь — получи сторону.</div>
<div style="display:grid;grid-template-columns:1fr 1fr;gap:14px;flex-wrap:wrap">
<div style="background:var(--card);border:1px solid var(--border);border-radius:10px;padding:14px">
<div style="font-weight:700;margin-bottom:8px;color:var(--sec-acc-d,var(--pri2))">Сторона → Площадь</div>
<div style="display:flex;gap:8px;flex-wrap:wrap;align-items:center">
<input type="number" id="p1-calc-a" class="tinp" placeholder="a" style="width:90px" min="0.001">
<button class="btn primary" id="p1-calc-go-a">= S</button>
</div>
<div id="p1-calc-out-a" style="margin-top:8px;font-size:.95rem"></div>
</div>
<div style="background:var(--card);border:1px solid var(--border);border-radius:10px;padding:14px">
<div style="font-weight:700;margin-bottom:8px;color:var(--sec-acc-d,var(--pri2))">Площадь → Сторона</div>
<div style="display:flex;gap:8px;flex-wrap:wrap;align-items:center">
<input type="number" id="p1-calc-s" class="tinp" placeholder="S" style="width:90px" min="0.001">
<button class="btn primary" id="p1-calc-go-s">= a</button>
</div>
<div id="p1-calc-out-s" style="margin-top:8px;font-size:.95rem"></div>
</div>
</div>
</div>`;
/* --- INTERACTIVE 3: Конвертер единиц --- */
html += `<div class="wg">
<div class="wg-header"><span class="wg-badge">ИНТЕРАКТИВ 3</span><div class="wg-title">Конвертер единиц площади</div></div>
<div class="wg-help">Введи число и выбери единицу — получишь перевод во все остальные единицы.</div>
<div style="display:flex;gap:8px;flex-wrap:wrap;align-items:center;margin-bottom:10px">
<input type="number" id="p1-conv-val" class="tinp" style="width:120px" value="1" min="0">
<select id="p1-conv-unit" class="tinp" style="width:80px">
<option value="mm2">мм²</option>
<option value="cm2">см²</option>
<option value="dm2">дм²</option>
<option value="m2" selected>м²</option>
<option value="km2">км²</option>
</select>
<button class="btn primary" id="p1-conv-go">Перевести</button>
</div>
<div id="p1-conv-out" style="padding:12px;background:var(--sec-acc-soft,var(--pri-soft));border-radius:10px;display:none;line-height:2;font-size:.95rem"></div>
</div>`;
/* --- INTERACTIVE 4: Тренажёр --- */
html += `<div class="wg">
<div class="wg-header"><span class="wg-badge">ИНТЕРАКТИВ 4</span><div class="wg-title">Тренажёр §1</div></div>
<div class="wg-help">Реши задачи на площадь квадрата и конвертацию единиц. Введи ответ и нажми «Проверить».</div>
<div class="score-display"><span>Задача <b id="p1-tr-i">1</b> / 5</span><span>Очки: <b id="p1-tr-score">0</b></span></div>
<div id="p1-tr-task" style="padding:14px;background:var(--sec-acc-soft,var(--pri-soft));border-radius:10px;font-size:1.05rem;margin-bottom:10px"></div>
<div style="display:flex;gap:8px;align-items:center;flex-wrap:wrap">
<input type="number" id="p1-tr-ans" class="tinp" placeholder="Ответ" style="width:120px">
<button class="btn primary" id="p1-tr-go">Проверить</button>
<button class="btn" id="p1-tr-start">Начать</button>
</div>
<div class="feedback" id="p1-tr-fb" style="display:none"></div>
</div>`;
/* --- INTERACTIVE 5: Босс §1 --- */
html += `<div class="wg" style="border-color:var(--sec-acc-d,var(--pri2))">
<div class="wg-header"><span class="wg-badge" style="background:var(--sec-acc-d,var(--pri2))">БОСС §1</span><div class="wg-title">Итоговые задачи</div></div>
<div class="wg-help">Реши все 4 задачи — каждая верная даёт +5 XP.</div>
<div id="p1-boss-tasks"></div>
</div>`;
html += `<div style="margin-top:18px;display:flex;justify-content:center">
<button class="btn primary" id="p1-read-btn" onclick="addXp(10,'p1-read');bumpProgress('p1',40);this.textContent='Прочитано!';this.disabled=true;">
<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>
Я прочитал §1 (+10 XP)
</button>
</div>`;
html += secNav(null,'p2');
box.innerHTML = html;
if(window.renderMathInElement) setTimeout(()=>renderMath(box),0);
/* == INIT: SVG-сетка == */
(function(){
const sl=document.getElementById('p1-grid-a-sl');
const valEl=document.getElementById('p1-grid-a-val');
const wrap=document.getElementById('p1-grid-svg-wrap');
const info=document.getElementById('p1-grid-info');
function draw(){
const a=+sl.value;
valEl.textContent=a;
const cell=34, pad=14;
const W=a*cell+pad*2, H=a*cell+pad*2;
let s='<svg viewBox="0 0 '+W+' '+H+'" style="width:'+Math.min(W,300)+'px;background:var(--card);border:1px solid var(--border);border-radius:12px">';
// fill cells
for(let r=0;r<a;r++) for(let c=0;c<a;c++){
const x=pad+c*cell, y=pad+r*cell;
s+='<rect x="'+x+'" y="'+y+'" width="'+(cell-1)+'" height="'+(cell-1)+'" rx="3" fill="rgba(5,150,105,.18)" stroke="#059669" stroke-width="1"/>';
}
// side label bottom
const midX=pad+a*cell/2, botY=pad+a*cell+11;
s+='<text x="'+midX+'" y="'+botY+'" text-anchor="middle" font-size="13" font-weight="700" fill="#047857" font-family="JetBrains Mono,monospace">a='+a+'</text>';
// side label left
const midY=pad+a*cell/2;
s+='<text x="8" y="'+midY+'" text-anchor="middle" dominant-baseline="middle" font-size="13" font-weight="700" fill="#047857" font-family="JetBrains Mono,monospace" transform="rotate(-90,8,'+midY+')">a='+a+'</text>';
s+='</svg>';
wrap.innerHTML=s;
info.innerHTML='<div><b>a</b> = '+a+'</div><div><b>a²</b> = '+a+'² = '+(a*a)+'</div><div style="color:var(--sec-acc-d,var(--pri2));font-size:1.05rem;font-weight:800">S = '+(a*a)+' кл.</div>';
}
sl.addEventListener('input',draw);
draw();
})();
/* == INIT: Калькулятор == */
(function(){
document.getElementById('p1-calc-go-a').addEventListener('click',()=>{
const a=parseFloat(document.getElementById('p1-calc-a').value);
const out=document.getElementById('p1-calc-out-a');
if(!isFinite(a)||a<=0){out.innerHTML='<span style="color:var(--bad)">Введи положительное число.</span>';return;}
const S=a*a;
out.innerHTML='$S = '+a+'^2 = '+fmt(S)+'$';
renderMath(out); addXp(1,'p1-calc');
});
document.getElementById('p1-calc-go-s').addEventListener('click',()=>{
const S=parseFloat(document.getElementById('p1-calc-s').value);
const out=document.getElementById('p1-calc-out-s');
if(!isFinite(S)||S<=0){out.innerHTML='<span style="color:var(--bad)">Введи положительное число.</span>';return;}
const a=Math.sqrt(S);
out.innerHTML='$a = \\sqrt{'+fmt(S)+'} = '+fmt(a)+'$';
renderMath(out); addXp(1,'p1-calc-inv');
});
document.getElementById('p1-calc-a').addEventListener('keydown',e=>{if(e.key==='Enter')document.getElementById('p1-calc-go-a').click();});
document.getElementById('p1-calc-s').addEventListener('keydown',e=>{if(e.key==='Enter')document.getElementById('p1-calc-go-s').click();});
})();
/* == INIT: Конвертер == */
(function(){
const factors={mm2:1,cm2:100,dm2:1e4,m2:1e6,km2:1e12};
const labels={mm2:'мм²',cm2:'см²',dm2:'дм²',m2:'м²',km2:'км²'};
document.getElementById('p1-conv-go').addEventListener('click',()=>{
const v=parseFloat(document.getElementById('p1-conv-val').value);
const unit=document.getElementById('p1-conv-unit').value;
const out=document.getElementById('p1-conv-out');
if(!isFinite(v)||v<0){out.style.display='none';return;}
const inMm2=v*factors[unit];
let rows='';
for(const [u,f] of Object.entries(factors)){
const res=inMm2/f;
rows+='<div><b>'+labels[u]+':</b> '+fmt(res)+(u===unit?' <span style="color:var(--sec-acc-d,var(--pri2));font-weight:800">(исходное)</span>':'')+'</div>';
}
out.innerHTML=rows; out.style.display='block'; addXp(2,'p1-conv');
});
document.getElementById('p1-conv-val').addEventListener('keydown',e=>{if(e.key==='Enter')document.getElementById('p1-conv-go').click();});
})();
/* == INIT: Тренажёр == */
(function(){
const tasks=[
{q:'Сторона квадрата <b>9 см</b>. Найди площадь (в см²).', ans:81, hint:'S = 9² = 81 см².'},
{q:'Площадь квадрата <b>64 м²</b>. Найди сторону (в м).', ans:8, hint:'a = √64 = 8 м.'},
{q:'Переведи <b>3 м²</b> в см². Ответ в см².', ans:30000, hint:'1 м² = 10 000 см², 3·10 000 = 30 000 см².'},
{q:'Сторона квадрата <b>12 дм</b>. Найди площадь в дм².', ans:144, hint:'S = 12² = 144 дм².'},
{q:'Площадь квадрата <b>225 мм²</b>. Найди сторону в мм.', ans:15, hint:'a = √225 = 15 мм.'},
];
let idx=0,score=0;
function show(){
document.getElementById('p1-tr-i').textContent=idx+1;
document.getElementById('p1-tr-task').innerHTML=tasks[idx].q;
document.getElementById('p1-tr-ans').value='';
document.getElementById('p1-tr-fb').style.display='none';
}
document.getElementById('p1-tr-start').addEventListener('click',()=>{idx=0;score=0;document.getElementById('p1-tr-score').textContent=0;show();});
document.getElementById('p1-tr-go').addEventListener('click',()=>{
if(idx>=tasks.length)return;
const ans=+document.getElementById('p1-tr-ans').value;
const fb=document.getElementById('p1-tr-fb');
if(Math.abs(ans-tasks[idx].ans)<0.01){
score++;document.getElementById('p1-tr-score').textContent=score;
addXp(3,'p1-tr-'+idx);bumpProgress('p1',5);
if(idx<tasks.length-1){feedback(fb,true,'Верно! +3 XP');idx++;setTimeout(()=>show(),900);}
else{feedback(fb,true,'Все задачи решены! +5 XP');addXp(5,'p1-tr-all');bumpProgress('p1',10);}
} else {
feedback(fb,false,'Неверно. '+tasks[idx].hint);
}
});
document.getElementById('p1-tr-ans').addEventListener('keydown',e=>{if(e.key==='Enter')document.getElementById('p1-tr-go').click();});
show();
})();
/* == INIT: Босс §1 == */
(function(){
const tasks=[
{q:'Сторона квадрата <b>13 см</b>. Площадь (в см²)?', ans:169, hint:'13² = 169.'},
{q:'Площадь квадрата <b>196 м²</b>. Сторона (в м)?', ans:14, hint:'√196 = 14.'},
{q:'Переведи <b>50 000 см²</b> в м². Ответ в м².', ans:5, hint:'50 000 / 10 000 = 5 м².'},
{q:'Периметр квадрата <b>28 дм</b>. Площадь в дм²?', ans:49, hint:'a = 28/4 = 7; S = 7² = 49.'},
];
const bossBox=document.getElementById('p1-boss-tasks');
bossBox.innerHTML=tasks.map((t,i)=>`
<div style="padding:14px;background:var(--card);border-radius:10px;border:1px solid var(--border);margin-bottom:10px">
<div style="margin-bottom:8px;font-size:.95rem">${t.q}</div>
<div style="display:flex;gap:8px;flex-wrap:wrap;align-items:center">
<input type="number" class="tinp" id="p1b-a${i}" placeholder="Ответ" style="width:110px">
<button class="btn primary small" onclick="(function(){
const v=+document.getElementById('p1b-a${i}').value;
const fb=document.getElementById('p1b-fb${i}');
if(Math.abs(v-${t.ans})<0.01){
feedback(fb,true,'Верно! +5 XP');
if(!p1BossSolved.has(${i})){ p1BossSolved.add(${i}); addXp(5,'p1-boss${i}'); bumpProgress('p1',10); }
} else feedback(fb,false,'Неверно. ${t.hint}');
})()">Проверить</button>
</div>
<div class="feedback" id="p1b-fb${i}" style="display:none;margin-top:8px"></div>
</div>`).join('');
window.p1BossSolved=new Set();
})();
}
/* ============================================================
§2 — ПЛОЩАДЬ ПРЯМОУГОЛЬНИКА
============================================================ */
function buildP2(){
const box = document.getElementById('p2-body');
let html = '';
html += makeCard('theory','Площадь прямоугольника','2.1',`
<p><b>Теорема.</b> Площадь прямоугольника со сторонами $a$ и $b$:</p>
$$S = a \\cdot b$$
<p><b>Доказательство:</b> прямоугольник $a \\times b$ разбивается на $a \\cdot b$ единичных квадратов. По аксиоме аддитивности $S = a \\cdot b$.</p>
<p style="margin-top:8px">Квадрат — частный случай прямоугольника при $a = b$: $S = a \\cdot a = a^2$.</p>`);
html += makeCard('rule','Периметр и площадь','2.2',`
<p>Прямоугольник со сторонами $a$ и $b$:</p>
$$S = a \\cdot b, \\quad P = 2(a + b)$$
<p>Связь: зная $P$ и одну сторону, можно найти вторую: $b = \\dfrac{P}{2} - a$.</p>`);
html += makeCard('example','Примеры','2.3',`
<p><b>Прямоугольник 6 × 4. Площадь?</b><br>$S = 6 \\cdot 4 = 24\\,\\text{см}^2$.</p>
<p style="margin-top:8px"><b>Площадь прямоугольника 36 м², одна сторона 9 м. Найти вторую.</b><br>$b = 36 / 9 = 4\\,\\text{м}$.</p>
<p style="margin-top:8px"><b>Периметр 26 см, одна сторона 3 см. Площадь?</b><br>$b = 26/2 - 3 = 10\\,\\text{см}$. $S = 3 \\cdot 10 = 30\\,\\text{см}^2$.</p>`);
/* --- INTERACTIVE 1: Draggable прямоугольник --- */
html += `<div class="wg" id="p2-rect-wg">
<div class="wg-header"><span class="wg-badge">ИНТЕРАКТИВ 1</span><div class="wg-title">Прямоугольник: тяни угол</div></div>
<div class="wg-help">Тащи правый нижний угол мышью или пальцем — меняются стороны и площадь. Сетка показывает разбиение на единичные клетки.</div>
<div id="p2-rect-svg-wrap" style="display:flex;justify-content:center"></div>
<div id="p2-rect-info" style="display:grid;grid-template-columns:repeat(auto-fit,minmax(120px,1fr));gap:8px;margin-top:10px"></div>
</div>`;
/* --- INTERACTIVE 2: Калькулятор --- */
html += `<div class="wg">
<div class="wg-header"><span class="wg-badge">ИНТЕРАКТИВ 2</span><div class="wg-title">Калькулятор прямоугольника</div></div>
<div class="wg-help">Введи известные величины и нажми кнопку для нахождения неизвестной.</div>
<div style="display:grid;grid-template-columns:repeat(auto-fit,minmax(200px,1fr));gap:12px">
<div style="background:var(--card);border:1px solid var(--border);border-radius:10px;padding:14px">
<div style="font-weight:700;margin-bottom:8px;color:var(--sec-acc-d,var(--pri2))">a, b → S и P</div>
<div style="display:flex;gap:6px;flex-wrap:wrap;margin-bottom:8px">
<input type="number" id="p2-calc-a" class="tinp" placeholder="a" style="width:75px">
<input type="number" id="p2-calc-b" class="tinp" placeholder="b" style="width:75px">
<button class="btn primary" id="p2-calc-go">Найти</button>
</div>
<div id="p2-calc-out" style="font-size:.92rem"></div>
</div>
<div style="background:var(--card);border:1px solid var(--border);border-radius:10px;padding:14px">
<div style="font-weight:700;margin-bottom:8px;color:var(--sec-acc-d,var(--pri2))">S, a → b</div>
<div style="display:flex;gap:6px;flex-wrap:wrap;margin-bottom:8px">
<input type="number" id="p2-calc-s" class="tinp" placeholder="S" style="width:75px">
<input type="number" id="p2-calc-a2" class="tinp" placeholder="a" style="width:75px">
<button class="btn primary" id="p2-calc-go2">Найти b</button>
</div>
<div id="p2-calc-out2" style="font-size:.92rem"></div>
</div>
</div>
</div>`;
/* --- INTERACTIVE 3: DnD — разложить пары (a,b) для S=24 --- */
html += `<div class="wg" id="p2-dnd-wg">
<div class="wg-header"><span class="wg-badge">ИНТЕРАКТИВ 3</span><div class="wg-title">Какие пары дают площадь 24?</div></div>
<div class="wg-help">Разложи пары $(a, b)$ на «Площадь = 24» и «Площадь ≠ 24». Нажми карточку, затем нужный ящик.</div>
<div id="p2-dnd-pool"></div>
<div style="display:grid;grid-template-columns:1fr 1fr;gap:10px;margin-top:8px">
<div class="drop-box"><h5>S = 24</h5><div class="drop-items" data-cat="yes"></div></div>
<div class="drop-box"><h5>S ≠ 24</h5><div class="drop-items" data-cat="no"></div></div>
</div>
<div class="actions"><button class="btn primary" id="p2-dnd-check">Проверить</button><button class="btn" id="p2-dnd-reset">Сначала</button></div>
<div class="feedback" id="p2-dnd-fb" style="display:none"></div>
</div>`;
/* --- INTERACTIVE 4: Тренажёр --- */
html += `<div class="wg">
<div class="wg-header"><span class="wg-badge">ИНТЕРАКТИВ 4</span><div class="wg-title">Тренажёр §2</div></div>
<div class="wg-help">5 задач на площадь и периметр прямоугольника.</div>
<div class="score-display"><span>Задача <b id="p2-tr-i">1</b> / 5</span><span>Очки: <b id="p2-tr-score">0</b></span></div>
<div id="p2-tr-task" style="padding:14px;background:var(--sec-acc-soft,var(--pri-soft));border-radius:10px;font-size:1.05rem;margin-bottom:10px"></div>
<div style="display:flex;gap:8px;align-items:center;flex-wrap:wrap">
<input type="number" id="p2-tr-ans" class="tinp" placeholder="Ответ" style="width:120px">
<button class="btn primary" id="p2-tr-go">Проверить</button>
<button class="btn" id="p2-tr-start">Начать</button>
</div>
<div class="feedback" id="p2-tr-fb" style="display:none"></div>
</div>`;
/* --- INTERACTIVE 5: Босс §2 --- */
html += `<div class="wg" style="border-color:var(--sec-acc-d,var(--pri2))">
<div class="wg-header"><span class="wg-badge" style="background:var(--sec-acc-d,var(--pri2))">БОСС §2</span><div class="wg-title">Итоговые задачи</div></div>
<div class="wg-help">4 задачи — +5 XP каждая.</div>
<div id="p2-boss-tasks"></div>
</div>`;
html += `<div style="margin-top:18px;display:flex;justify-content:center">
<button class="btn primary" id="p2-read-btn" onclick="addXp(10,'p2-read');bumpProgress('p2',40);this.textContent='Прочитано!';this.disabled=true;">
<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>
Я прочитал §2 (+10 XP)
</button>
</div>`;
html += secNav('p1','p3');
box.innerHTML = html;
if(window.renderMathInElement) setTimeout(()=>renderMath(box),0);
/* == INIT: Draggable прямоугольник == */
(function(){
const W=340, H=300;
const MIN_CELLS=1, MAX_CELLS=9;
const PAD=30, CELL=24;
let cols=6, rows=4;
function draw(){
const rw=cols*CELL, rh=rows*CELL;
const x0=PAD, y0=PAD;
let s='<svg id="p2-rect-svg" viewBox="0 0 '+W+' '+H+'" style="width:100%;max-width:340px;background:var(--card);border:1px solid var(--border);border-radius:14px;touch-action:none">';
// grid cells
for(let r=0;r<rows;r++) for(let c=0;c<cols;c++){
s+='<rect x="'+(x0+c*CELL)+'" y="'+(y0+r*CELL)+'" width="'+(CELL-1)+'" height="'+(CELL-1)+'" rx="2" fill="rgba(16,185,129,.15)" stroke="#10b981" stroke-width=".8"/>';
}
// border
s+='<rect x="'+x0+'" y="'+y0+'" width="'+rw+'" height="'+rh+'" fill="none" stroke="#059669" stroke-width="2.5" rx="3"/>';
// labels
const midX=x0+rw/2, midY=y0+rh/2;
s+='<text x="'+midX+'" y="'+(y0+rh+18)+'" text-anchor="middle" font-size="13" font-weight="700" fill="#047857" font-family="JetBrains Mono,monospace">a = '+cols+'</text>';
s+='<text x="'+(x0-14)+'" y="'+midY+'" text-anchor="middle" dominant-baseline="middle" font-size="13" font-weight="700" fill="#047857" font-family="JetBrains Mono,monospace" transform="rotate(-90,'+(x0-14)+','+midY+')">b = '+rows+'</text>';
// drag handle (bottom-right corner)
const hx=x0+rw, hy=y0+rh;
s+='<circle cx="'+hx+'" cy="'+hy+'" r="11" fill="rgba(5,150,105,.25)" id="p2-drag-bg"/>';
s+='<circle cx="'+hx+'" cy="'+hy+'" r="7" fill="#059669" stroke="#fff" stroke-width="2" id="p2-drag-handle" style="cursor:nwse-resize"/>';
s+='</svg>';
const svg=document.getElementById('p2-rect-svg');
const wrap=document.getElementById('p2-rect-svg-wrap');
wrap.innerHTML=s;
updateInfo();
const handle=wrap.querySelector('#p2-drag-handle');
if(handle){
handle.addEventListener('pointerdown',ev=>{
function onMove(e){
const svgEl=wrap.querySelector('#p2-rect-svg');
if(!svgEl) return;
const rect=svgEl.getBoundingClientRect();
const sx=W/rect.width, sy=H/rect.height;
const nx=Math.round(Math.max(MIN_CELLS,Math.min(MAX_CELLS,((e.clientX-rect.left)*sx-PAD)/CELL)));
const ny=Math.round(Math.max(MIN_CELLS,Math.min(MAX_CELLS,((e.clientY-rect.top)*sy-PAD)/CELL)));
if(nx!==cols||ny!==rows){cols=nx;rows=ny;draw();}
}
function onUp(){window.removeEventListener('pointermove',onMove);window.removeEventListener('pointerup',onUp);window.removeEventListener('pointercancel',onUp);}
window.addEventListener('pointermove',onMove);
window.addEventListener('pointerup',onUp);
window.addEventListener('pointercancel',onUp);
});
}
}
function updateInfo(){
const S=cols*rows, P=2*(cols+rows);
document.getElementById('p2-rect-info').innerHTML=`
<div style="padding:8px 12px;background:var(--card);border-radius:8px;border:1px solid var(--border);font-size:.88rem"><div style="color:var(--muted);font-size:.72rem;font-weight:700;text-transform:uppercase;margin-bottom:4px">a</div><b>${cols}</b></div>
<div style="padding:8px 12px;background:var(--card);border-radius:8px;border:1px solid var(--border);font-size:.88rem"><div style="color:var(--muted);font-size:.72rem;font-weight:700;text-transform:uppercase;margin-bottom:4px">b</div><b>${rows}</b></div>
<div style="padding:8px 12px;background:var(--card);border-radius:8px;border:1px solid var(--border);font-size:.88rem"><div style="color:var(--muted);font-size:.72rem;font-weight:700;text-transform:uppercase;margin-bottom:4px">S = a·b</div><b style="color:var(--sec-acc-d,var(--pri2))">${S}</b></div>
<div style="padding:8px 12px;background:var(--card);border-radius:8px;border:1px solid var(--border);font-size:.88rem"><div style="color:var(--muted);font-size:.72rem;font-weight:700;text-transform:uppercase;margin-bottom:4px">P = 2(a+b)</div><b>${P}</b></div>`;
}
draw();
})();
/* == INIT: Калькулятор == */
(function(){
document.getElementById('p2-calc-go').addEventListener('click',()=>{
const a=parseFloat(document.getElementById('p2-calc-a').value);
const b=parseFloat(document.getElementById('p2-calc-b').value);
const out=document.getElementById('p2-calc-out');
if(!isFinite(a)||!isFinite(b)||a<=0||b<=0){out.innerHTML='<span style="color:var(--bad)">Введи положительные числа.</span>';return;}
out.innerHTML='$S = '+fmt(a)+'\\cdot'+fmt(b)+' = '+fmt(a*b)+'$<br>$P = 2('+fmt(a)+'+'+fmt(b)+') = '+fmt(2*(a+b))+'$';
renderMath(out); addXp(1,'p2-calc');
});
document.getElementById('p2-calc-go2').addEventListener('click',()=>{
const S=parseFloat(document.getElementById('p2-calc-s').value);
const a=parseFloat(document.getElementById('p2-calc-a2').value);
const out=document.getElementById('p2-calc-out2');
if(!isFinite(S)||!isFinite(a)||S<=0||a<=0){out.innerHTML='<span style="color:var(--bad)">Введи положительные числа.</span>';return;}
const b=S/a;
out.innerHTML='$b = S/a = '+fmt(S)+'/'+fmt(a)+' = '+fmt(b)+'$';
renderMath(out); addXp(1,'p2-calc-inv');
});
})();
/* == INIT: DnD == */
(function(){
const items=[
{id:'p1x24',html:'1 × 24',ans:'yes'},{id:'p2x12',html:'2 × 12',ans:'yes'},
{id:'p3x8', html:'3 × 8', ans:'yes'},{id:'p4x6', html:'4 × 6', ans:'yes'},
{id:'p5x5', html:'5 × 5', ans:'no'},{id:'p3x9', html:'3 × 9', ans:'no'},
{id:'p6x5', html:'6 × 5', ans:'no'},
];
const sorter2=setupSorter({poolId:'p2-dnd-pool',scopeSelector:'#p2-dnd-wg',items,cats:['yes','no']});
document.getElementById('p2-dnd-reset').addEventListener('click',()=>{sorter2.reset();document.getElementById('p2-dnd-fb').style.display='none';});
document.getElementById('p2-dnd-check').addEventListener('click',()=>{
let ok=0;
items.forEach(it=>{if(sorter2.placed[it.id]===it.ans)ok++;});
const fb=document.getElementById('p2-dnd-fb');
if(ok===items.length){feedback(fb,true,'Все верно! +5 XP');addXp(5,'p2-dnd');bumpProgress('p2',15);}
else feedback(fb,false,'Верно: '+ok+' из '+items.length+'. Перемножь числа и проверь: = 24?');
});
})();
/* == INIT: Тренажёр == */
(function(){
const tasks=[
{q:'Прямоугольник <b>7 × 5</b>. Площадь?', ans:35, hint:'S = 7·5 = 35.'},
{q:'Площадь прямоугольника <b>48 м²</b>, одна сторона <b>6 м</b>. Вторая сторона?', ans:8, hint:'b = 48/6 = 8.'},
{q:'Периметр прямоугольника <b>24 см</b>, одна сторона <b>4 см</b>. Площадь?', ans:32, hint:'b = 24/2 - 4 = 8; S = 4·8 = 32.'},
{q:'Прямоугольник <b>11 × 3</b>. Периметр?', ans:28, hint:'P = 2(11+3) = 28.'},
{q:'Площадь комнаты <b>5 × 4 м</b>. Количество плиток <b>50 × 50 см</b>?', ans:80, hint:'S = 20 м² = 200 000 см². Плитка: 50×50=2500 см². 200 000/2500 = 80.'},
];
let idx=0,score=0;
function show(){
document.getElementById('p2-tr-i').textContent=idx+1;
document.getElementById('p2-tr-task').innerHTML=tasks[idx].q;
document.getElementById('p2-tr-ans').value='';
document.getElementById('p2-tr-fb').style.display='none';
}
document.getElementById('p2-tr-start').addEventListener('click',()=>{idx=0;score=0;document.getElementById('p2-tr-score').textContent=0;show();});
document.getElementById('p2-tr-go').addEventListener('click',()=>{
if(idx>=tasks.length)return;
const ans=+document.getElementById('p2-tr-ans').value;
const fb=document.getElementById('p2-tr-fb');
if(Math.abs(ans-tasks[idx].ans)<0.5){
score++;document.getElementById('p2-tr-score').textContent=score;
addXp(3,'p2-tr-'+idx);bumpProgress('p2',5);
if(idx<tasks.length-1){feedback(fb,true,'Верно! +3 XP');idx++;setTimeout(()=>show(),900);}
else{feedback(fb,true,'Все задачи решены! +5 XP');addXp(5,'p2-tr-all');bumpProgress('p2',10);}
} else {
feedback(fb,false,'Неверно. '+tasks[idx].hint);
}
});
document.getElementById('p2-tr-ans').addEventListener('keydown',e=>{if(e.key==='Enter')document.getElementById('p2-tr-go').click();});
show();
})();
/* == INIT: Босс §2 == */
(function(){
const tasks=[
{q:'Прямоугольник <b>13 × 8</b>. Площадь?', ans:104, hint:'13·8 = 104.'},
{q:'Площадь участка <b>360 м²</b>, ширина <b>15 м</b>. Длина?', ans:24, hint:'360/15 = 24.'},
{q:'Периметр прямоугольника <b>34 дм</b>, одна сторона <b>9 дм</b>. Площадь?', ans:72, hint:'b = 34/2 - 9 = 8; S = 9·8 = 72.'},
{q:'Квадрат и прямоугольник <b>9 × 4</b> имеют равные площади. Сторона квадрата?', ans:6, hint:'S = 36, a = √36 = 6.'},
];
const bossBox=document.getElementById('p2-boss-tasks');
bossBox.innerHTML=tasks.map((t,i)=>`
<div style="padding:14px;background:var(--card);border-radius:10px;border:1px solid var(--border);margin-bottom:10px">
<div style="margin-bottom:8px;font-size:.95rem">${t.q}</div>
<div style="display:flex;gap:8px;flex-wrap:wrap;align-items:center">
<input type="number" class="tinp" id="p2b-a${i}" placeholder="Ответ" style="width:110px">
<button class="btn primary small" onclick="(function(){
const v=+document.getElementById('p2b-a${i}').value;
const fb=document.getElementById('p2b-fb${i}');
if(Math.abs(v-${t.ans})<0.5){
feedback(fb,true,'Верно! +5 XP');
if(!p2BossSolved.has(${i})){ p2BossSolved.add(${i}); addXp(5,'p2-boss${i}'); bumpProgress('p2',10); }
} else feedback(fb,false,'Неверно. ${t.hint}');
})()">Проверить</button>
</div>
<div class="feedback" id="p2b-fb${i}" style="display:none;margin-top:8px"></div>
</div>`).join('');
window.p2BossSolved=new Set();
})();
}
/* ============================================================
§3 — ПЛОЩАДЬ ПАРАЛЛЕЛОГРАММА
============================================================ */
function buildP3(){
const box = document.getElementById('p3-body');
let html = '';
html += makeCard('theory','Площадь параллелограмма','3.1',`
<p><b>Высота параллелограмма</b> — перпендикуляр, опущенный из любой точки одной стороны на другую (параллельную) сторону.</p>
<p><b>Теорема.</b> Площадь параллелограмма равна произведению основания на высоту:</p>
$$S = a \\cdot h$$
<p>где $a$ — основание (любая сторона), $h$ — высота, опущенная на это основание.</p>`);
html += makeCard('rule','Доказательство через прямоугольник','3.2',`
<p><b>Идея:</b> от параллелограмма отрезаем боковой прямоугольный треугольник и переносим его на другой конец — получается прямоугольник с теми же основанием $a$ и высотой $h$.</p>
<p>По аксиоме аддитивности площади параллелограмм и прямоугольник равновелики:</p>
$$S_{\\text{пар}} = S_{\\text{прямоуг}} = a \\cdot h$$
<p style="margin-top:8px;font-size:.88rem;color:var(--muted)">Ключевое следствие: два параллелограмма с одним основанием и одинаковой высотой имеют <b>равные площади</b>, даже если выглядят по-разному!</p>`);
html += makeCard('example','Примеры','3.3',`
<p><b>Параллелограмм, основание 8 см, высота 5 см. Площадь?</b><br>$S = 8 \\cdot 5 = 40\\,\\text{см}^2$.</p>
<p style="margin-top:8px"><b>Площадь 60 м², основание 12 м. Высота?</b><br>$h = 60 / 12 = 5\\,\\text{м}$.</p>`);
/* --- INTERACTIVE 1: Draggable параллелограмм (сдвигаем верх) --- */
html += `<div class="wg" id="p3-pgram-wg">
<div class="wg-header"><span class="wg-badge">ИНТЕРАКТИВ 1</span><div class="wg-title">Равноплощадные параллелограммы</div></div>
<div class="wg-help">Тащи верхнее основание влево–вправо. Основание $a$ и высота $h$ не меняются — площадь $S = a \\cdot h$ постоянна!</div>
<div id="p3-pgram-svg-wrap" style="display:flex;justify-content:center"></div>
<div id="p3-pgram-info" style="display:grid;grid-template-columns:repeat(auto-fit,minmax(110px,1fr));gap:8px;margin-top:10px"></div>
</div>`;
/* --- INTERACTIVE 2: Анимация доказательства --- */
html += `<div class="wg" id="p3-proof-wg">
<div class="wg-header"><span class="wg-badge">ИНТЕРАКТИВ 2</span><div class="wg-title">Доказательство: разрезаем и переставляем</div></div>
<div class="wg-help">Нажимай «Далее» чтобы пройти шаги доказательства формулы $S = a \\cdot h$.</div>
<div id="p3-proof-svg-wrap" style="display:flex;justify-content:center;margin-bottom:10px"></div>
<div id="p3-proof-desc" style="padding:10px 14px;background:var(--sec-acc-soft,var(--pri-soft));border-radius:10px;font-size:.95rem;line-height:1.6;margin-bottom:10px"></div>
<div style="display:flex;gap:8px">
<button class="btn primary" id="p3-proof-next">Далее</button>
<button class="btn" id="p3-proof-reset">Сначала</button>
</div>
</div>`;
/* --- INTERACTIVE 3: Калькулятор --- */
html += `<div class="wg">
<div class="wg-header"><span class="wg-badge">ИНТЕРАКТИВ 3</span><div class="wg-title">Калькулятор параллелограмма</div></div>
<div class="wg-help">Задай два из трёх параметров — найди третий.</div>
<div style="display:grid;grid-template-columns:repeat(auto-fit,minmax(200px,1fr));gap:12px">
<div style="background:var(--card);border:1px solid var(--border);border-radius:10px;padding:14px">
<div style="font-weight:700;margin-bottom:8px;color:var(--sec-acc-d,var(--pri2))">a, h → S</div>
<div style="display:flex;gap:6px;flex-wrap:wrap;margin-bottom:8px">
<input type="number" id="p3-calc-a" class="tinp" placeholder="a" style="width:75px">
<input type="number" id="p3-calc-h" class="tinp" placeholder="h" style="width:75px">
<button class="btn primary" id="p3-calc-go1">= S</button>
</div>
<div id="p3-calc-out1" style="font-size:.92rem"></div>
</div>
<div style="background:var(--card);border:1px solid var(--border);border-radius:10px;padding:14px">
<div style="font-weight:700;margin-bottom:8px;color:var(--sec-acc-d,var(--pri2))">S, h → a</div>
<div style="display:flex;gap:6px;flex-wrap:wrap;margin-bottom:8px">
<input type="number" id="p3-calc-s" class="tinp" placeholder="S" style="width:75px">
<input type="number" id="p3-calc-h2" class="tinp" placeholder="h" style="width:75px">
<button class="btn primary" id="p3-calc-go2">= a</button>
</div>
<div id="p3-calc-out2" style="font-size:.92rem"></div>
</div>
</div>
</div>`;
/* --- INTERACTIVE 4: Тренажёр --- */
html += `<div class="wg">
<div class="wg-header"><span class="wg-badge">ИНТЕРАКТИВ 4</span><div class="wg-title">Тренажёр §3</div></div>
<div class="wg-help">5 задач на площадь параллелограмма.</div>
<div class="score-display"><span>Задача <b id="p3-tr-i">1</b> / 5</span><span>Очки: <b id="p3-tr-score">0</b></span></div>
<div id="p3-tr-task" style="padding:14px;background:var(--sec-acc-soft,var(--pri-soft));border-radius:10px;font-size:1.05rem;margin-bottom:10px"></div>
<div style="display:flex;gap:8px;align-items:center;flex-wrap:wrap">
<input type="number" id="p3-tr-ans" class="tinp" placeholder="Ответ" style="width:120px">
<button class="btn primary" id="p3-tr-go">Проверить</button>
<button class="btn" id="p3-tr-start">Начать</button>
</div>
<div class="feedback" id="p3-tr-fb" style="display:none"></div>
</div>`;
/* --- INTERACTIVE 5: Босс §3 --- */
html += `<div class="wg" style="border-color:var(--sec-acc-d,var(--pri2))">
<div class="wg-header"><span class="wg-badge" style="background:var(--sec-acc-d,var(--pri2))">БОСС §3</span><div class="wg-title">Итоговые задачи</div></div>
<div class="wg-help">4 задачи — +5 XP каждая.</div>
<div id="p3-boss-tasks"></div>
</div>`;
html += `<div style="margin-top:18px;display:flex;justify-content:center">
<button class="btn primary" id="p3-read-btn" onclick="addXp(10,'p3-read');bumpProgress('p3',40);this.textContent='Прочитано!';this.disabled=true;">
<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>
Я прочитал §3 (+10 XP)
</button>
</div>`;
html += secNav('p2','p4');
box.innerHTML = html;
if(window.renderMathInElement) setTimeout(()=>renderMath(box),0);
/* == INIT: Draggable параллелограмм == */
(function(){
const W=380, H=240;
const BASE_X=50, BASE_Y=190, BASE_W=200, HEIGHT_PX=100;
let shiftX=40;
function draw(){
const bx=BASE_X, by=BASE_Y, bw=BASE_W, h=HEIGHT_PX;
const x1=bx, x2=bx+bw, x3=bx+bw+shiftX, x4=bx+shiftX;
const y1=by, y2=by, y3=by-h, y4=by-h;
const cx=(x1+x2+x3+x4)/4, cy=(y1+y2+y3+y4)/4;
let s='<svg id="p3-pgram-svg" viewBox="0 0 '+W+' '+H+'" style="width:100%;max-width:400px;background:var(--card);border:1px solid var(--border);border-radius:14px;touch-action:none">';
// height line
const hBaseX=x4+(x1-x4)*0;
s+='<line x1="'+x4+'" y1="'+y4+'" x2="'+x4+'" y2="'+y1+'" stroke="#f59e0b" stroke-width="1.5" stroke-dasharray="5 3"/>';
s+='<text x="'+(x4-16)+'" y="'+((y4+y1)/2)+'" text-anchor="middle" dominant-baseline="middle" font-size="12" font-weight="700" fill="#b45309" font-family="JetBrains Mono,monospace">h</text>';
// right angle mark
s+='<polyline points="'+(x4+8)+','+(y1)+' '+(x4+8)+','+(y1-8)+' '+x4+','+(y1-8)+'" fill="none" stroke="#f59e0b" stroke-width="1.5"/>';
// polygon
s+='<polygon points="'+x1+','+y1+' '+x2+','+y2+' '+x3+','+y3+' '+x4+','+y4+'" fill="rgba(13,148,136,.15)" stroke="#0d9488" stroke-width="2.5" stroke-linejoin="round"/>';
// base label
s+='<text x="'+((x1+x2)/2)+'" y="'+(y1+18)+'" text-anchor="middle" font-size="12" font-weight="700" fill="#047857" font-family="JetBrains Mono,monospace">a</text>';
// S label
s+='<text x="'+cx+'" y="'+cy+'" text-anchor="middle" dominant-baseline="middle" font-size="13" font-weight="700" fill="#0f766e" font-family="Unbounded,sans-serif">S=a·h</text>';
// drag handle (upper base midpoint)
const hmx=(x4+x3)/2, hmy=(y4+y3)/2;
s+='<circle cx="'+hmx+'" cy="'+hmy+'" r="12" fill="rgba(13,148,136,.2)" id="p3-pgram-bg"/>';
s+='<circle cx="'+hmx+'" cy="'+hmy+'" r="7" fill="#0d9488" stroke="#fff" stroke-width="2" id="p3-drag-upper" style="cursor:ew-resize"/>';
s+='</svg>';
document.getElementById('p3-pgram-svg-wrap').innerHTML=s;
document.getElementById('p3-pgram-info').innerHTML=`
<div style="padding:8px 12px;background:var(--card);border-radius:8px;border:1px solid var(--border);font-size:.88rem"><div style="color:var(--muted);font-size:.72rem;font-weight:700;text-transform:uppercase;margin-bottom:4px">Основание a</div><b>${BASE_W} px</b></div>
<div style="padding:8px 12px;background:var(--card);border-radius:8px;border:1px solid var(--border);font-size:.88rem"><div style="color:var(--muted);font-size:.72rem;font-weight:700;text-transform:uppercase;margin-bottom:4px">Высота h</div><b>${HEIGHT_PX} px</b></div>
<div style="padding:8px 12px;background:var(--card);border-radius:8px;border:1px solid var(--border);font-size:.88rem"><div style="color:var(--muted);font-size:.72rem;font-weight:700;text-transform:uppercase;margin-bottom:4px">Сдвиг</div><b>${shiftX}</b></div>
<div style="padding:8px 12px;background:var(--card);border-radius:8px;border:1px solid var(--border);font-size:.88rem"><div style="color:var(--muted);font-size:.72rem;font-weight:700;text-transform:uppercase;margin-bottom:4px">S = a·h</div><b style="color:var(--sec-acc-d,var(--pri2))">${BASE_W*HEIGHT_PX}</b></div>`;
const handle=document.getElementById('p3-drag-upper');
if(handle){
handle.addEventListener('pointerdown',ev=>{
const startX=ev.clientX, startShift=shiftX;
function onMove(e){
const svgEl=document.getElementById('p3-pgram-svg');
if(!svgEl) return;
const rect=svgEl.getBoundingClientRect();
const scale=W/rect.width;
const delta=Math.round((e.clientX-startX)*scale);
shiftX=Math.max(-80,Math.min(180,startShift+delta));
draw();
}
function onUp(){window.removeEventListener('pointermove',onMove);window.removeEventListener('pointerup',onUp);window.removeEventListener('pointercancel',onUp);}
window.addEventListener('pointermove',onMove);
window.addEventListener('pointerup',onUp);
window.addEventListener('pointercancel',onUp);
});
}
}
draw();
})();
/* == INIT: Анимация доказательства == */
(function(){
let step=0;
const W=360, H=200;
const bx=40, by=160, bw=200, H2=100, sh=60;
const steps=[
{
desc:'Исходный параллелограмм ABCD. Основание AB = a, высота h — перпендикуляр от D к AB.',
draw(){
const x1=bx,x2=bx+bw,x3=bx+bw+sh,x4=bx+sh;
const y1=by,y2=by,y3=by-H2,y4=by-H2;
return '<polygon points="'+[x1,y1,x2,y2,x3,y3,x4,y4].join(' ')+'" fill="rgba(13,148,136,.15)" stroke="#0d9488" stroke-width="2.2"/>'
+'<line x1="'+x4+'" y1="'+y4+'" x2="'+x4+'" y2="'+y1+'" stroke="#f59e0b" stroke-width="1.5" stroke-dasharray="4 3"/>'
+'<text x="'+((x1+x2)/2)+'" y="'+(y1+16)+'" text-anchor="middle" font-size="12" font-weight="700" fill="#047857" font-family="JetBrains Mono,monospace">a</text>'
+'<text x="'+(x4-14)+'" y="'+(by-H2/2)+'" text-anchor="middle" font-size="12" font-weight="700" fill="#b45309" font-family="JetBrains Mono,monospace">h</text>'
+'<text x="'+x1+'" y="'+(y1+14)+'" font-size="11" fill="#0f766e" font-weight="700">A</text>'
+'<text x="'+x2+'" y="'+(y2+14)+'" font-size="11" fill="#0f766e" font-weight="700">B</text>'
+'<text x="'+(x3+2)+'" y="'+(y3-4)+'" font-size="11" fill="#0f766e" font-weight="700">C</text>'
+'<text x="'+(x4-14)+'" y="'+(y4-4)+'" font-size="11" fill="#0f766e" font-weight="700">D</text>';
}
},
{
desc:'Проводим высоту DE — перпендикуляр из D на AB. Отрезаем правый треугольник BCE.',
draw(){
const x1=bx,x2=bx+bw,x3=bx+bw+sh,x4=bx+sh;
const y1=by,y2=by,y3=by-H2,y4=by-H2;
return '<polygon points="'+[x1,y1,x2,y2,x3,y3,x4,y4].join(' ')+'" fill="rgba(13,148,136,.12)" stroke="#0d9488" stroke-width="2.2"/>'
+'<polygon points="'+x2+','+y2+' '+x3+','+y3+' '+x4+','+y4+'" fill="rgba(239,68,68,.25)" stroke="#ef4444" stroke-width="2" stroke-dasharray="5 3"/>'
+'<line x1="'+x4+'" y1="'+y4+'" x2="'+x4+'" y2="'+y1+'" stroke="#f59e0b" stroke-width="2"/>'
+'<text x="'+((x1+x4)/2)+'" y="'+(by-H2/2)+'" text-anchor="middle" font-size="11" fill="#0f766e">Оставшийся трапеция</text>'
+'<text x="'+((x2+x3+x4)/3)+'" y="'+(by-H2/3)+'" text-anchor="middle" font-size="11" fill="#ef4444">Треугольник</text>';
}
},
{
desc:'Переносим выделенный треугольник на левую сторону. Он встаёт точно на место треугольника ADE.',
draw(){
const x1=bx,x2=bx+bw,x4=bx+sh;
const y1=by,y2=by,y4=by-H2;
return '<rect x="'+x1+'" y="'+(by-H2)+'" width="'+bw+'" height="'+H2+'" fill="rgba(13,148,136,.15)" stroke="#0d9488" stroke-width="2.2"/>'
+'<polygon points="'+x1+','+y1+' '+bx+','+(by-H2)+' '+(bx-sh)+','+(by-H2)+'" fill="rgba(239,68,68,.25)" stroke="#ef4444" stroke-width="2"/>'
+'<polygon points="'+bx+','+(by-H2)+' '+(bx-sh)+','+(by-H2)+' '+x1+','+y1+'" fill="rgba(16,185,129,.3)" stroke="#10b981" stroke-width="2"/>'
+'<text x="'+((x1+x2)/2)+'" y="'+(by-H2/2)+'" text-anchor="middle" font-size="11" fill="#0f766e">Получается прямоугольник!</text>';
}
},
{
desc:'Получился прямоугольник со сторонами a и h. Его площадь S = a·h. Значит, и у исходного параллелограмма S = a·h!',
draw(){
const x1=bx;
return '<rect x="'+x1+'" y="'+(by-H2)+'" width="'+bw+'" height="'+H2+'" fill="rgba(16,185,129,.22)" stroke="#059669" stroke-width="2.5"/>'
+'<text x="'+(x1+bw/2)+'" y="'+(by-H2-10)+'" text-anchor="middle" font-size="12" font-weight="700" fill="#047857" font-family="JetBrains Mono,monospace">a = '+bw+' px</text>'
+'<text x="'+(x1-12)+'" y="'+(by-H2/2)+'" text-anchor="middle" dominant-baseline="middle" font-size="12" font-weight="700" fill="#047857" font-family="JetBrains Mono,monospace" transform="rotate(-90,'+(x1-12)+','+(by-H2/2)+')">h = '+H2+' px</text>'
+'<text x="'+(x1+bw/2)+'" y="'+(by-H2/2)+'" text-anchor="middle" dominant-baseline="middle" font-size="15" font-weight="800" fill="#047857" font-family="Unbounded,sans-serif">S = a·h</text>';
}
},
];
function render(){
const s=steps[step];
const svgHtml='<svg viewBox="0 0 '+W+' '+H+'" style="width:100%;max-width:380px;background:var(--card);border:1px solid var(--border);border-radius:14px">'+s.draw()+'</svg>';
document.getElementById('p3-proof-svg-wrap').innerHTML=svgHtml;
document.getElementById('p3-proof-desc').innerHTML='<b>Шаг '+(step+1)+' / '+steps.length+'.</b> '+s.desc;
document.getElementById('p3-proof-next').textContent=step<steps.length-1?'Далее':'Готово!';
}
document.getElementById('p3-proof-next').addEventListener('click',()=>{
if(step<steps.length-1){step++;render();addXp(1,'p3-proof-step');}
else{addXp(3,'p3-proof-done');bumpProgress('p3',15);}
});
document.getElementById('p3-proof-reset').addEventListener('click',()=>{step=0;render();});
render();
})();
/* == INIT: Калькулятор == */
(function(){
document.getElementById('p3-calc-go1').addEventListener('click',()=>{
const a=parseFloat(document.getElementById('p3-calc-a').value);
const h=parseFloat(document.getElementById('p3-calc-h').value);
const out=document.getElementById('p3-calc-out1');
if(!isFinite(a)||!isFinite(h)||a<=0||h<=0){out.innerHTML='<span style="color:var(--bad)">Введи положительные числа.</span>';return;}
out.innerHTML='$S = '+fmt(a)+'\\cdot'+fmt(h)+' = '+fmt(a*h)+'$';
renderMath(out); addXp(1,'p3-calc');
});
document.getElementById('p3-calc-go2').addEventListener('click',()=>{
const S=parseFloat(document.getElementById('p3-calc-s').value);
const h=parseFloat(document.getElementById('p3-calc-h2').value);
const out=document.getElementById('p3-calc-out2');
if(!isFinite(S)||!isFinite(h)||S<=0||h<=0){out.innerHTML='<span style="color:var(--bad)">Введи положительные числа.</span>';return;}
out.innerHTML='$a = S/h = '+fmt(S)+'/'+fmt(h)+' = '+fmt(S/h)+'$';
renderMath(out); addXp(1,'p3-calc-inv');
});
})();
/* == INIT: Тренажёр == */
(function(){
const tasks=[
{q:'Параллелограмм, основание <b>10 см</b>, высота <b>7 см</b>. Площадь?', ans:70, hint:'S = 10·7 = 70.'},
{q:'Площадь параллелограмма <b>84 м²</b>, высота <b>6 м</b>. Основание?', ans:14, hint:'a = 84/6 = 14.'},
{q:'Основание параллелограмма <b>15 дм</b>, площадь <b>120 дм²</b>. Высота?', ans:8, hint:'h = 120/15 = 8.'},
{q:'Параллелограмм, сторона <b>9 см</b>, высота к этой стороне <b>4 см</b>. Площадь?', ans:36, hint:'S = 9·4 = 36.'},
{q:'Два параллелограмма: у обоих основание <b>8</b>, высоты <b>5</b> и <b>5</b>. Какую площадь имеет каждый?', ans:40, hint:'S = 8·5 = 40 — равные площади!'},
];
let idx=0,score=0;
function show(){
document.getElementById('p3-tr-i').textContent=idx+1;
document.getElementById('p3-tr-task').innerHTML=tasks[idx].q;
document.getElementById('p3-tr-ans').value='';
document.getElementById('p3-tr-fb').style.display='none';
}
document.getElementById('p3-tr-start').addEventListener('click',()=>{idx=0;score=0;document.getElementById('p3-tr-score').textContent=0;show();});
document.getElementById('p3-tr-go').addEventListener('click',()=>{
if(idx>=tasks.length)return;
const ans=+document.getElementById('p3-tr-ans').value;
const fb=document.getElementById('p3-tr-fb');
if(Math.abs(ans-tasks[idx].ans)<0.5){
score++;document.getElementById('p3-tr-score').textContent=score;
addXp(3,'p3-tr-'+idx);bumpProgress('p3',5);
if(idx<tasks.length-1){feedback(fb,true,'Верно! +3 XP');idx++;setTimeout(()=>show(),900);}
else{feedback(fb,true,'Все задачи решены! +5 XP');addXp(5,'p3-tr-all');bumpProgress('p3',10);}
} else {
feedback(fb,false,'Неверно. '+tasks[idx].hint);
}
});
document.getElementById('p3-tr-ans').addEventListener('keydown',e=>{if(e.key==='Enter')document.getElementById('p3-tr-go').click();});
show();
})();
/* == INIT: Босс §3 == */
(function(){
const tasks=[
{q:'Параллелограмм, основание <b>16 дм</b>, высота <b>9 дм</b>. Площадь?', ans:144, hint:'16·9 = 144.'},
{q:'Площадь параллелограмма <b>195 м²</b>, основание <b>15 м</b>. Высота?', ans:13, hint:'195/15 = 13.'},
{q:'Два параллелограмма с основанием 12 имеют площади <b>96</b> и <b>60</b>. Найди сумму их высот.', ans:13, hint:'h1=96/12=8, h2=60/12=5, сумма=13.'},
{q:'Параллелограмм, боковая сторона <b>10 см</b>, высота к ней <b>6 см</b>. Площадь?', ans:60, hint:'S = 10·6 = 60.'},
];
const bossBox=document.getElementById('p3-boss-tasks');
bossBox.innerHTML=tasks.map((t,i)=>`
<div style="padding:14px;background:var(--card);border-radius:10px;border:1px solid var(--border);margin-bottom:10px">
<div style="margin-bottom:8px;font-size:.95rem">${t.q}</div>
<div style="display:flex;gap:8px;flex-wrap:wrap;align-items:center">
<input type="number" class="tinp" id="p3b-a${i}" placeholder="Ответ" style="width:110px">
<button class="btn primary small" onclick="(function(){
const v=+document.getElementById('p3b-a${i}').value;
const fb=document.getElementById('p3b-fb${i}');
if(Math.abs(v-${t.ans})<0.5){
feedback(fb,true,'Верно! +5 XP');
if(!p3BossSolved.has(${i})){ p3BossSolved.add(${i}); addXp(5,'p3-boss${i}'); bumpProgress('p3',10); }
} else feedback(fb,false,'Неверно. ${t.hint}');
})()">Проверить</button>
</div>
<div class="feedback" id="p3b-fb${i}" style="display:none;margin-top:8px"></div>
</div>`).join('');
window.p3BossSolved=new Set();
})();
}
/* ============================================================
§4 — ПЛОЩАДЬ ТРЕУГОЛЬНИКА
============================================================ */
function buildP4(){
const box = document.getElementById('p4-body');
let html = '';
html += makeCard('theory','Площадь треугольника','4.1',`
<p><b>Высота треугольника</b> — перпендикуляр, опущенный из вершины на противоположную сторону (основание) или её продолжение.</p>
<p><b>Теорема.</b> Площадь треугольника равна половине произведения основания на высоту:</p>
$$S = \\dfrac{1}{2} \\cdot a \\cdot h$$
<p>где $a$ — основание, $h$ — высота к нему.</p>`);
html += makeCard('rule','Доказательство','4.2',`
<p><b>Идея:</b> из треугольника $ABC$ строим параллелограмм — копируем треугольник и прикладываем к стороне $AB$. Получается параллелограмм $ABDC$ с тем же основанием $a$ и той же высотой $h$.</p>
<p>Площадь параллелограмма: $S_{\\text{пар}} = a \\cdot h$.</p>
<p>Треугольник — ровно <b>половина</b> параллелограмма (диагональ делит его на два равных треугольника):</p>
$$S_{\\triangle} = \\dfrac{1}{2} \\cdot a \\cdot h$$
<p style="margin-top:8px;font-size:.88rem;color:var(--muted)">Важное следствие: треугольники с одним основанием и одинаковой высотой (вершина лежит на прямой, параллельной основанию) имеют <b>равные площади</b>.</p>`);
html += makeCard('example','Примеры','4.3',`
<p><b>Треугольник, основание 12 см, высота 8 см. Площадь?</b><br>$S = \\dfrac{1}{2} \\cdot 12 \\cdot 8 = 48\\,\\text{см}^2$.</p>
<p style="margin-top:8px"><b>Площадь 30 м², основание 10 м. Высота?</b><br>$h = \\dfrac{2S}{a} = \\dfrac{60}{10} = 6\\,\\text{м}$.</p>
<p style="margin-top:8px"><b>Площадь 18 дм², высота 6 дм. Основание?</b><br>$a = \\dfrac{2S}{h} = \\dfrac{36}{6} = 6\\,\\text{дм}$.</p>`);
/* --- INTERACTIVE 1: Draggable вершина C --- */
html += `<div class="wg" id="p4-tri-wg">
<div class="wg-header"><span class="wg-badge">ИНТЕРАКТИВ 1</span><div class="wg-title">Равноплощадные треугольники</div></div>
<div class="wg-help">Тащи вершину C вдоль прямой. Основание AB фиксировано, высота не меняется — площадь $S = \\frac{1}{2} \\cdot AB \\cdot h$ постоянна!</div>
<div id="p4-tri-svg-wrap" style="display:flex;justify-content:center"></div>
<div id="p4-tri-info" style="display:grid;grid-template-columns:repeat(auto-fit,minmax(110px,1fr));gap:8px;margin-top:10px"></div>
</div>`;
/* --- INTERACTIVE 2: Анимация "достраиваем до параллелограмма" --- */
html += `<div class="wg" id="p4-proof-wg">
<div class="wg-header"><span class="wg-badge">ИНТЕРАКТИВ 2</span><div class="wg-title">Доказательство: достраиваем до параллелограмма</div></div>
<div class="wg-help">Нажимай «Далее» и смотри, как треугольник превращается в половину параллелограмма.</div>
<div id="p4-proof-svg-wrap" style="display:flex;justify-content:center;margin-bottom:10px"></div>
<div id="p4-proof-desc" style="padding:10px 14px;background:var(--sec-acc-soft,var(--pri-soft));border-radius:10px;font-size:.95rem;line-height:1.6;margin-bottom:10px"></div>
<div style="display:flex;gap:8px">
<button class="btn primary" id="p4-proof-next">Далее</button>
<button class="btn" id="p4-proof-reset">Сначала</button>
</div>
</div>`;
/* --- INTERACTIVE 3: Калькулятор --- */
html += `<div class="wg">
<div class="wg-header"><span class="wg-badge">ИНТЕРАКТИВ 3</span><div class="wg-title">Калькулятор треугольника</div></div>
<div class="wg-help">Введи два из трёх параметров — найди третий.</div>
<div style="display:grid;grid-template-columns:repeat(auto-fit,minmax(190px,1fr));gap:12px">
<div style="background:var(--card);border:1px solid var(--border);border-radius:10px;padding:14px">
<div style="font-weight:700;margin-bottom:8px;color:var(--sec-acc-d,var(--pri2))">a, h → S</div>
<div style="display:flex;gap:6px;flex-wrap:wrap;margin-bottom:8px">
<input type="number" id="p4-calc-a" class="tinp" placeholder="a" style="width:72px">
<input type="number" id="p4-calc-h" class="tinp" placeholder="h" style="width:72px">
<button class="btn primary" id="p4-calc-go1">= S</button>
</div>
<div id="p4-calc-out1" style="font-size:.92rem"></div>
</div>
<div style="background:var(--card);border:1px solid var(--border);border-radius:10px;padding:14px">
<div style="font-weight:700;margin-bottom:8px;color:var(--sec-acc-d,var(--pri2))">S, a → h</div>
<div style="display:flex;gap:6px;flex-wrap:wrap;margin-bottom:8px">
<input type="number" id="p4-calc-s2" class="tinp" placeholder="S" style="width:72px">
<input type="number" id="p4-calc-a2" class="tinp" placeholder="a" style="width:72px">
<button class="btn primary" id="p4-calc-go2">= h</button>
</div>
<div id="p4-calc-out2" style="font-size:.92rem"></div>
</div>
<div style="background:var(--card);border:1px solid var(--border);border-radius:10px;padding:14px">
<div style="font-weight:700;margin-bottom:8px;color:var(--sec-acc-d,var(--pri2))">S, h → a</div>
<div style="display:flex;gap:6px;flex-wrap:wrap;margin-bottom:8px">
<input type="number" id="p4-calc-s3" class="tinp" placeholder="S" style="width:72px">
<input type="number" id="p4-calc-h3" class="tinp" placeholder="h" style="width:72px">
<button class="btn primary" id="p4-calc-go3">= a</button>
</div>
<div id="p4-calc-out3" style="font-size:.92rem"></div>
</div>
</div>
</div>`;
/* --- INTERACTIVE 4: Тренажёр --- */
html += `<div class="wg">
<div class="wg-header"><span class="wg-badge">ИНТЕРАКТИВ 4</span><div class="wg-title">Тренажёр §4</div></div>
<div class="wg-help">5 задач на площадь треугольника.</div>
<div class="score-display"><span>Задача <b id="p4-tr-i">1</b> / 5</span><span>Очки: <b id="p4-tr-score">0</b></span></div>
<div id="p4-tr-task" style="padding:14px;background:var(--sec-acc-soft,var(--pri-soft));border-radius:10px;font-size:1.05rem;margin-bottom:10px"></div>
<div style="display:flex;gap:8px;align-items:center;flex-wrap:wrap">
<input type="number" id="p4-tr-ans" class="tinp" placeholder="Ответ" style="width:120px">
<button class="btn primary" id="p4-tr-go">Проверить</button>
<button class="btn" id="p4-tr-start">Начать</button>
</div>
<div class="feedback" id="p4-tr-fb" style="display:none"></div>
</div>`;
/* --- INTERACTIVE 5: Босс §4 --- */
html += `<div class="wg" style="border-color:var(--sec-acc-d,var(--pri2))">
<div class="wg-header"><span class="wg-badge" style="background:var(--sec-acc-d,var(--pri2))">БОСС §4</span><div class="wg-title">Итоговые задачи</div></div>
<div class="wg-help">4 задачи — +5 XP каждая.</div>
<div id="p4-boss-tasks"></div>
</div>`;
html += `<div style="margin-top:18px;display:flex;justify-content:center">
<button class="btn primary" id="p4-read-btn" onclick="addXp(10,'p4-read');bumpProgress('p4',40);this.textContent='Прочитано!';this.disabled=true;">
<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>
Я прочитал §4 (+10 XP)
</button>
</div>`;
html += secNav('p3','p5');
box.innerHTML = html;
if(window.renderMathInElement) setTimeout(()=>renderMath(box),0);
/* == INIT: Draggable вершина C == */
(function(){
const W=380, H=240;
const Ax=60, Ay=190, Bx=280, By=190;
const BASE=Bx-Ax, H_FIX=100;
let Cx=170;
const Cy=Ay-H_FIX;
function draw(){
const S=0.5*BASE*H_FIX;
let s='<svg id="p4-tri-svg" viewBox="0 0 '+W+' '+H+'" style="width:100%;max-width:400px;background:var(--card);border:1px solid var(--border);border-radius:14px;touch-action:none">';
// parallel line (locus of C)
s+='<line x1="10" y1="'+Cy+'" x2="'+(W-10)+'" y2="'+Cy+'" stroke="#f59e0b" stroke-width="1" stroke-dasharray="6 4" opacity=".7"/>';
s+='<text x="'+(W-8)+'" y="'+(Cy-8)+'" text-anchor="end" font-size="10" fill="#b45309">Прямая C</text>';
// height line
s+='<line x1="'+Cx+'" y1="'+Cy+'" x2="'+Cx+'" y2="'+Ay+'" stroke="#f59e0b" stroke-width="1.5" stroke-dasharray="4 3" opacity=".8"/>';
// right angle mark
s+='<polyline points="'+(Cx+8)+','+Ay+' '+(Cx+8)+','+(Ay-8)+' '+Cx+','+(Ay-8)+'" fill="none" stroke="#f59e0b" stroke-width="1.5"/>';
s+='<text x="'+(Cx+16)+'" y="'+(Ay-H_FIX/2)+'" font-size="11" fill="#b45309">h</text>';
// triangle
s+='<polygon points="'+Ax+','+Ay+' '+Bx+','+By+' '+Cx+','+Cy+'" fill="rgba(8,145,178,.18)" stroke="#0891b2" stroke-width="2.2" stroke-linejoin="round"/>';
// AB label
s+='<text x="'+((Ax+Bx)/2)+'" y="'+(Ay+18)+'" text-anchor="middle" font-size="12" font-weight="700" fill="#047857" font-family="JetBrains Mono,monospace">a = AB = '+BASE+'</text>';
// S label — cx and cy both from triangle centroid
const lx=(Ax+Bx+Cx)/3, ly=(Ay+By+Cy)/3;
s+='<text x="'+lx+'" y="'+ly+'" text-anchor="middle" dominant-baseline="middle" font-size="13" font-weight="700" fill="#0e7490" font-family="Unbounded,sans-serif">S = '+S+'</text>';
// vertex labels
s+='<text x="'+(Ax-12)+'" y="'+(Ay+4)+'" font-size="12" font-weight="700" fill="#0891b2">A</text>';
s+='<text x="'+(Bx+4)+'" y="'+(By+4)+'" font-size="12" font-weight="700" fill="#0891b2">B</text>';
s+='<text x="'+(Cx)+'" y="'+(Cy-12)+'" text-anchor="middle" font-size="12" font-weight="700" fill="#0891b2">C</text>';
// C drag handle
s+='<circle cx="'+Cx+'" cy="'+Cy+'" r="12" fill="rgba(8,145,178,.2)"/>';
s+='<circle cx="'+Cx+'" cy="'+Cy+'" r="7" fill="#0891b2" stroke="#fff" stroke-width="2" id="p4-drag-c" style="cursor:ew-resize"/>';
s+='</svg>';
document.getElementById('p4-tri-svg-wrap').innerHTML=s;
document.getElementById('p4-tri-info').innerHTML=`
<div style="padding:8px 12px;background:var(--card);border-radius:8px;border:1px solid var(--border);font-size:.88rem"><div style="color:var(--muted);font-size:.72rem;font-weight:700;text-transform:uppercase;margin-bottom:4px">Основание a</div><b>${BASE}</b></div>
<div style="padding:8px 12px;background:var(--card);border-radius:8px;border:1px solid var(--border);font-size:.88rem"><div style="color:var(--muted);font-size:.72rem;font-weight:700;text-transform:uppercase;margin-bottom:4px">Высота h</div><b>${H_FIX}</b></div>
<div style="padding:8px 12px;background:var(--card);border-radius:8px;border:1px solid var(--border);font-size:.88rem"><div style="color:var(--muted);font-size:.72rem;font-weight:700;text-transform:uppercase;margin-bottom:4px">S = ½·a·h</div><b style="color:var(--sec-acc-d,var(--pri2))">${S}</b></div>`;
const handle=document.getElementById('p4-drag-c');
if(handle){
handle.addEventListener('pointerdown',ev=>{
function onMove(e){
const svgEl=document.getElementById('p4-tri-svg');
if(!svgEl) return;
const rect=svgEl.getBoundingClientRect();
const scale=W/rect.width;
const nx=Math.max(10,Math.min(W-10,(e.clientX-rect.left)*scale));
Cx=Math.round(nx);
draw();
}
function onUp(){window.removeEventListener('pointermove',onMove);window.removeEventListener('pointerup',onUp);window.removeEventListener('pointercancel',onUp);}
window.addEventListener('pointermove',onMove);
window.addEventListener('pointerup',onUp);
window.addEventListener('pointercancel',onUp);
});
}
}
draw();
})();
/* == INIT: Анимация доказательства == */
(function(){
let step=0;
const W=380, H=230;
const Ax=60,Ay=190,Bx=260,By=190,Cx=160,Cy=90;
// mirror of C relative to midpoint of AB gives D
const Dx=Ax+Bx-Cx, Dy=Ay+By-Cy;
const steps=[
{
desc:'Исходный треугольник ABC. Основание AB = a, высота h из вершины C.',
draw(){
return '<polygon points="'+Ax+','+Ay+' '+Bx+','+By+' '+Cx+','+Cy+'" fill="rgba(8,145,178,.18)" stroke="#0891b2" stroke-width="2.2"/>'
+'<line x1="'+Cx+'" y1="'+Cy+'" x2="'+Cx+'" y2="'+Ay+'" stroke="#f59e0b" stroke-width="1.5" stroke-dasharray="4 3"/>'
+'<text x="'+((Ax+Bx)/2)+'" y="'+(Ay+16)+'" text-anchor="middle" font-size="12" font-weight="700" fill="#047857" font-family="JetBrains Mono,monospace">a</text>'
+'<text x="'+(Cx+10)+'" y="'+((Ay+Cy)/2)+'" font-size="11" fill="#b45309">h</text>'
+'<text x="'+(Ax-12)+'" y="'+(Ay+4)+'" font-size="12" font-weight="700" fill="#0891b2">A</text>'
+'<text x="'+(Bx+4)+'" y="'+(By+4)+'" font-size="12" font-weight="700" fill="#0891b2">B</text>'
+'<text x="'+Cx+'" y="'+(Cy-12)+'" text-anchor="middle" font-size="12" font-weight="700" fill="#0891b2">C</text>';
}
},
{
desc:'Берём копию треугольника ABC и поворачиваем её на 180° вокруг середины AB. Вершина C переходит в точку D.',
draw(){
return '<polygon points="'+Ax+','+Ay+' '+Bx+','+By+' '+Cx+','+Cy+'" fill="rgba(8,145,178,.18)" stroke="#0891b2" stroke-width="2.2"/>'
+'<polygon points="'+Ax+','+Ay+' '+Bx+','+By+' '+Dx+','+Dy+'" fill="rgba(239,68,68,.2)" stroke="#ef4444" stroke-width="2" stroke-dasharray="5 3"/>'
+'<circle cx="'+((Ax+Bx)/2)+'" cy="'+((Ay+By)/2)+'" r="5" fill="#f59e0b"/>'
+'<text x="'+(Dx)+'" y="'+(Dy-12)+'" text-anchor="middle" font-size="12" font-weight="700" fill="#ef4444">D</text>'
+'<text x="'+Cx+'" y="'+(Cy-12)+'" text-anchor="middle" font-size="12" font-weight="700" fill="#0891b2">C</text>';
}
},
{
desc:'Треугольники ABC и ABD вместе образуют параллелограмм ACBD. Обе его диагонали — это AB и CD.',
draw(){
return '<polygon points="'+Ax+','+Ay+' '+Cx+','+Cy+' '+Bx+','+By+' '+Dx+','+Dy+'" fill="rgba(16,185,129,.18)" stroke="#10b981" stroke-width="2.2"/>'
+'<line x1="'+Ax+'" y1="'+Ay+'" x2="'+Bx+'" y2="'+By+'" stroke="#059669" stroke-width="2" stroke-dasharray="5 3"/>'
+'<line x1="'+Cx+'" y1="'+Cy+'" x2="'+Dx+'" y2="'+Dy+'" stroke="#059669" stroke-width="2" stroke-dasharray="5 3"/>'
+'<text x="'+((Ax+Cx+Bx+Dx)/4)+'" y="'+((Ay+Cy+By+Dy)/4)+'" text-anchor="middle" dominant-baseline="middle" font-size="12" font-weight="700" fill="#047857">Параллелограмм</text>';
}
},
{
desc:'Параллелограмм = два равных треугольника. S(пар) = a·h. Значит S(△) = ½·a·h!',
draw(){
return '<polygon points="'+Ax+','+Ay+' '+Cx+','+Cy+' '+Bx+','+By+' '+Dx+','+Dy+'" fill="rgba(16,185,129,.22)" stroke="#059669" stroke-width="2.5"/>'
+'<polygon points="'+Ax+','+Ay+' '+Bx+','+By+' '+Cx+','+Cy+'" fill="rgba(8,145,178,.35)" stroke="#0891b2" stroke-width="2"/>'
+'<text x="'+((Ax+Bx+Cx)/3)+'" y="'+((Ay+By+Cy)/3)+'" text-anchor="middle" dominant-baseline="middle" font-size="13" font-weight="800" fill="#0e7490" font-family="Unbounded,sans-serif">½·a·h</text>'
+'<text x="'+((Ax+Bx+Dx)/3)+'" y="'+((Ay+By+Dy)/3)+'" text-anchor="middle" dominant-baseline="middle" font-size="13" font-weight="800" fill="#047857" font-family="Unbounded,sans-serif">½·a·h</text>';
}
},
];
function render(){
const s=steps[step];
document.getElementById('p4-proof-svg-wrap').innerHTML='<svg viewBox="0 0 '+W+' '+H+'" style="width:100%;max-width:400px;background:var(--card);border:1px solid var(--border);border-radius:14px">'+s.draw()+'</svg>';
document.getElementById('p4-proof-desc').innerHTML='<b>Шаг '+(step+1)+' / '+steps.length+'.</b> '+s.desc;
document.getElementById('p4-proof-next').textContent=step<steps.length-1?'Далее':'Готово!';
}
document.getElementById('p4-proof-next').addEventListener('click',()=>{
if(step<steps.length-1){step++;render();addXp(1,'p4-proof-step');}
else{addXp(3,'p4-proof-done');bumpProgress('p4',15);}
});
document.getElementById('p4-proof-reset').addEventListener('click',()=>{step=0;render();});
render();
})();
/* == INIT: Калькулятор == */
(function(){
document.getElementById('p4-calc-go1').addEventListener('click',()=>{
const a=parseFloat(document.getElementById('p4-calc-a').value);
const h=parseFloat(document.getElementById('p4-calc-h').value);
const out=document.getElementById('p4-calc-out1');
if(!isFinite(a)||!isFinite(h)||a<=0||h<=0){out.innerHTML='<span style="color:var(--bad)">Введи положительные числа.</span>';return;}
out.innerHTML='$S = \\frac{1}{2}\\cdot'+fmt(a)+'\\cdot'+fmt(h)+' = '+fmt(0.5*a*h)+'$';
renderMath(out); addXp(1,'p4-calc');
});
document.getElementById('p4-calc-go2').addEventListener('click',()=>{
const S=parseFloat(document.getElementById('p4-calc-s2').value);
const a=parseFloat(document.getElementById('p4-calc-a2').value);
const out=document.getElementById('p4-calc-out2');
if(!isFinite(S)||!isFinite(a)||S<=0||a<=0){out.innerHTML='<span style="color:var(--bad)">Введи положительные числа.</span>';return;}
out.innerHTML='$h = \\frac{2S}{a} = \\frac{2\\cdot'+fmt(S)+'}{'+fmt(a)+'} = '+fmt(2*S/a)+'$';
renderMath(out); addXp(1,'p4-calc-h');
});
document.getElementById('p4-calc-go3').addEventListener('click',()=>{
const S=parseFloat(document.getElementById('p4-calc-s3').value);
const h=parseFloat(document.getElementById('p4-calc-h3').value);
const out=document.getElementById('p4-calc-out3');
if(!isFinite(S)||!isFinite(h)||S<=0||h<=0){out.innerHTML='<span style="color:var(--bad)">Введи положительные числа.</span>';return;}
out.innerHTML='$a = \\frac{2S}{h} = \\frac{2\\cdot'+fmt(S)+'}{'+fmt(h)+'} = '+fmt(2*S/h)+'$';
renderMath(out); addXp(1,'p4-calc-a');
});
})();
/* == INIT: Тренажёр == */
(function(){
const tasks=[
{q:'Треугольник, основание <b>14 см</b>, высота <b>6 см</b>. Площадь?', ans:42, hint:'S = ½·14·6 = 42.'},
{q:'Площадь треугольника <b>45 м²</b>, высота <b>9 м</b>. Основание?', ans:10, hint:'a = 2·45/9 = 10.'},
{q:'Площадь треугольника <b>36 дм²</b>, основание <b>12 дм</b>. Высота?', ans:6, hint:'h = 2·36/12 = 6.'},
{q:'Прямоугольный треугольник, катеты <b>8 см</b> и <b>6 см</b>. Площадь?', ans:24, hint:'S = ½·8·6 = 24.'},
{q:'Площадь параллелограмма <b>80 м²</b>. Площадь треугольника на том же основании и высоте?', ans:40, hint:'S(△) = S(пар)/2 = 40.'},
];
let idx=0,score=0;
function show(){
document.getElementById('p4-tr-i').textContent=idx+1;
document.getElementById('p4-tr-task').innerHTML=tasks[idx].q;
document.getElementById('p4-tr-ans').value='';
document.getElementById('p4-tr-fb').style.display='none';
}
document.getElementById('p4-tr-start').addEventListener('click',()=>{idx=0;score=0;document.getElementById('p4-tr-score').textContent=0;show();});
document.getElementById('p4-tr-go').addEventListener('click',()=>{
if(idx>=tasks.length)return;
const ans=+document.getElementById('p4-tr-ans').value;
const fb=document.getElementById('p4-tr-fb');
if(Math.abs(ans-tasks[idx].ans)<0.5){
score++;document.getElementById('p4-tr-score').textContent=score;
addXp(3,'p4-tr-'+idx);bumpProgress('p4',5);
if(idx<tasks.length-1){feedback(fb,true,'Верно! +3 XP');idx++;setTimeout(()=>show(),900);}
else{feedback(fb,true,'Все задачи решены! +5 XP');addXp(5,'p4-tr-all');bumpProgress('p4',10);}
} else {
feedback(fb,false,'Неверно. '+tasks[idx].hint);
}
});
document.getElementById('p4-tr-ans').addEventListener('keydown',e=>{if(e.key==='Enter')document.getElementById('p4-tr-go').click();});
show();
})();
/* == INIT: Босс §4 == */
(function(){
const tasks=[
{q:'Треугольник, основание <b>18 дм</b>, высота <b>10 дм</b>. Площадь?', ans:90, hint:'½·18·10 = 90.'},
{q:'Площадь треугольника <b>56 м²</b>, основание <b>14 м</b>. Высота?', ans:8, hint:'h = 2·56/14 = 8.'},
{q:'Квадрат со стороной <b>6 см</b>. Диагональ делит его на 2 треугольника. Площадь каждого?', ans:18, hint:'S(кв) = 36; S(△) = 36/2 = 18.'},
{q:'Параллелограмм, основание <b>20 м</b>, высота <b>7 м</b>. Площадь вписанного треугольника (того же основания и высоты)?', ans:70, hint:'S(пар) = 140; S(△) = 70.'},
];
const bossBox=document.getElementById('p4-boss-tasks');
bossBox.innerHTML=tasks.map((t,i)=>`
<div style="padding:14px;background:var(--card);border-radius:10px;border:1px solid var(--border);margin-bottom:10px">
<div style="margin-bottom:8px;font-size:.95rem">${t.q}</div>
<div style="display:flex;gap:8px;flex-wrap:wrap;align-items:center">
<input type="number" class="tinp" id="p4b-a${i}" placeholder="Ответ" style="width:110px">
<button class="btn primary small" onclick="(function(){
const v=+document.getElementById('p4b-a${i}').value;
const fb=document.getElementById('p4b-fb${i}');
if(Math.abs(v-${t.ans})<0.5){
feedback(fb,true,'Верно! +5 XP');
if(!p4BossSolved.has(${i})){ p4BossSolved.add(${i}); addXp(5,'p4-boss${i}'); bumpProgress('p4',10); }
} else feedback(fb,false,'Неверно. ${t.hint}');
})()">Проверить</button>
</div>
<div class="feedback" id="p4b-fb${i}" style="display:none;margin-top:8px"></div>
</div>`).join('');
window.p4BossSolved=new Set();
})();
}
function buildP5stub(){ document.getElementById('p5-body').innerHTML='<div class="card"><div class="card-body"><p><b>§5 — Волна 1</b>: содержимое появится в следующем обновлении.</p></div></div>'+secNav('p4','p6'); }
function buildP6stub(){ document.getElementById('p6-body').innerHTML='<div class="card"><div class="card-body"><p><b>§6 — Волна 1</b>: содержимое появится в следующем обновлении.</p></div></div>'+secNav('p5','p7'); }
function buildP7stub(){ document.getElementById('p7-body').innerHTML='<div class="card"><div class="card-body"><p><b>§7 — Волна 1</b>: содержимое появится в следующем обновлении.</p></div></div>'+secNav('p6','p8'); }
function buildP8stub(){ document.getElementById('p8-body').innerHTML='<div class="card"><div class="card-body"><p><b>§8 — Волна 1</b>: содержимое появится в следующем обновлении.</p></div></div>'+secNav('p7','p9'); }
function buildP9stub(){ document.getElementById('p9-body').innerHTML='<div class="card"><div class="card-body"><p><b>§9 — Волна 1</b>: содержимое появится в следующем обновлении.</p></div></div>'+secNav('p8','p10'); }
function buildP10stub(){ document.getElementById('p10-body').innerHTML='<div class="card"><div class="card-body"><p><b>§10 — Волна 1</b>: содержимое появится в следующем обновлении.</p></div></div>'+secNav('p9','p11'); }
function buildP11stub(){ document.getElementById('p11-body').innerHTML='<div class="card"><div class="card-body"><p><b>§11 — Волна 1</b>: содержимое появится в следующем обновлении.</p></div></div>'+secNav('p10','p12'); }
function buildP12stub(){ document.getElementById('p12-body').innerHTML='<div class="card"><div class="card-body"><p><b>§12 — Волна 1</b>: содержимое появится в следующем обновлении.</p></div></div>'+secNav('p11','p13'); }
function buildP13stub(){ document.getElementById('p13-body').innerHTML='<div class="card"><div class="card-body"><p><b>§13 — Волна 1</b>: содержимое появится в следующем обновлении.</p></div></div>'+secNav('p12','p14'); }
function buildP14stub(){ document.getElementById('p14-body').innerHTML='<div class="card"><div class="card-body"><p><b>§14 — Волна 1</b>: содержимое появится в следующем обновлении.</p></div></div>'+secNav('p13','p15'); }
function buildP15stub(){ document.getElementById('p15-body').innerHTML='<div class="card"><div class="card-body"><p><b>§15 — Волна 1</b>: содержимое появится в следующем обновлении.</p></div></div>'+secNav('p14','final2'); }
function buildFinal2stub(){ document.getElementById('final2-body').innerHTML='<div class="card"><div class="card-body"><p><b>Финал главы 2 — Волна 1</b>: боссы и итоги появятся в следующем обновлении.</p></div></div>'+secNav('p15',null); }
</script>
</body>
</html>