Files
Learn_System/frontend/textbooks/geometry_9_ch1.html
T

1243 lines
95 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>Геометрия 9 · Глава 1 · Соотношения в прямоугольном треугольнике</title>
<meta name="viewport" content="width=device-width,initial-scale=1">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/katex@0.16.9/dist/katex.min.css">
<script defer src="https://cdn.jsdelivr.net/npm/katex@0.16.9/dist/katex.min.js"></script>
<script defer src="https://cdn.jsdelivr.net/npm/katex@0.16.9/dist/contrib/auto-render.min.js"
onload="renderMathInElement(document.body,{delimiters:[{left:'$$',right:'$$',display:true},{left:'$',right:'$',display:false},{left:'\\[',right:'\\]',display:true},{left:'\\(',right:'\\)',display:false}],throwOnError:false})"></script>
<script src="/js/api.js" defer></script>
<script src="/js/xp.js" defer></script>
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800&family=Manrope:wght@600;700;800;900&family=Unbounded:wght@700;800;900&family=JetBrains+Mono:wght@500;700&display=swap" rel="stylesheet">
<style>
:root{
--bg:#fafafa; --card:#fff; --card-soft:#f8fafc; --text:#0f172a; --ink:#0f172a; --muted:#64748b;
--border:#e2e8f0; --sh:0 1px 3px rgba(0,0,0,.06); --sh2:0 4px 14px rgba(0,0,0,.08);
--pri:#d97706; --pri2:#b45309; --pri-soft:#fef3c7;
--acc:#fbbf24; --acc2:#d97706; --acc-soft:#fffbeb;
--ok:#10b981; --ok-bg:#d1fae5; --warn:#f59e0b; --warn-bg:#fef3c7;
--bad:#ef4444; --fail:#dc2626; --fail-bg:#fee2e2;
}
.dark{--bg:#1c1208; --card:#251a0a; --card-soft:#2b1f0d; --text:#fef3c7; --ink:#fef3c7; --muted:#a89070; --border:#3d2d18}
*{margin:0;padding:0;box-sizing:border-box;-webkit-tap-highlight-color:transparent}
html,body{font-family:'Inter',system-ui,sans-serif;background:var(--bg);color:var(--text);line-height:1.55;font-size:15px}
button,input,select,textarea{font-family:inherit;font-size:inherit}
button{cursor:pointer;border:0;background:transparent;color:inherit}
a{color:inherit;text-decoration:none}
.ic{width:16px;height:16px;display:inline-block;flex-shrink:0;stroke:currentColor;fill:none;stroke-width:2;stroke-linecap:round;stroke-linejoin:round;vertical-align:middle}
.hdr{position:relative;background:linear-gradient(110deg,#92400e 0%,#d97706 55%,#fbbf24 100%);color:#fff;padding:46px 22px 30px;overflow:hidden;border-bottom:2px solid rgba(254,243,199,.2);min-height:130px}
.hdr::before{content:'ГЛАВА 1';position:absolute;right:-12px;top:50%;transform:translateY(-50%);font-family:'Unbounded',sans-serif;font-size:clamp(5rem,15vw,11rem);font-weight:900;letter-spacing:-.04em;color:transparent;-webkit-text-stroke:1.5px rgba(254,243,199,.12);line-height:1;pointer-events:none;user-select:none;z-index:0}
.hdr-row{position:relative;z-index:1;display:flex;align-items:center;gap:14px;flex-wrap:wrap}
.hdr h1{font-family:'Unbounded',sans-serif;font-size:1.5rem;font-weight:900;letter-spacing:-.01em;line-height:1.3;padding-top:4px}
.hdr-sub{font-size:.85rem;opacity:.88;margin-top:6px;font-weight:500;line-height:1.4}
.hdr-side{margin-left:auto;display:flex;gap:8px;align-items:center;flex-wrap:wrap}
.hdr-btn{padding:7px 12px;border-radius:9px;background:rgba(255,255,255,.14);color:#fff;font-weight:600;font-size:.82rem;display:inline-flex;align-items:center;gap:6px;transition:background .15s;text-decoration:none}
.hdr-btn:hover{background:rgba(255,255,255,.24)}
.main{max-width:1240px;margin:0 auto;padding:22px;width:100%;display:grid;grid-template-columns:1fr 280px;gap:24px}
@media(max-width:980px){.main{grid-template-columns:1fr;padding:14px}}
.col-main{min-width:0}
.hero{background:linear-gradient(135deg,var(--pri-soft) 0%,var(--acc-soft) 50%,var(--pri-soft) 100%);background-size:200% 200%;animation:heroShift 12s ease-in-out infinite;border:1px solid var(--border);border-radius:18px;padding:24px 22px;margin-bottom:24px;position:relative;overflow:hidden}
@keyframes heroShift{0%,100%{background-position:0% 50%}50%{background-position:100% 50%}}
.hero::before{content:'sin';position:absolute;right:0;top:-30px;font-size:clamp(2rem,12vw,8rem);font-weight:900;color:var(--pri);opacity:.10;line-height:1;pointer-events:none;font-family:'Unbounded',sans-serif}
.hero h2{font-family:'Unbounded',sans-serif;font-size:1.55rem;font-weight:800;color:var(--pri2);margin-bottom:10px;letter-spacing:-.01em}
.hero p{font-size:.95rem;color:var(--text);opacity:.88;margin-bottom:14px;max-width:640px}
.hero-row{display:flex;gap:14px;flex-wrap:wrap;align-items:center}
.btn-primary{padding:11px 22px;background:linear-gradient(135deg,var(--pri),var(--pri2));color:#fff;border-radius:11px;font-weight:700;font-size:.92rem;display:inline-flex;align-items:center;gap:8px;box-shadow:var(--sh2);transition:transform .15s,box-shadow .15s}
.btn-primary:hover{transform:translateY(-1px);box-shadow:0 8px 28px rgba(0,0,0,.18)}
.hero-progress{flex:1;min-width:200px;max-width:280px}
.hp-label{font-size:.74rem;font-weight:700;color:var(--muted);text-transform:uppercase;letter-spacing:.06em;display:block;margin-bottom:5px}
.hp-bar{height:8px;background:rgba(0,0,0,.12);border-radius:5px;overflow:hidden}
.hp-fill{height:100%;background:linear-gradient(90deg,var(--pri),var(--acc));border-radius:5px;width:0%;transition:width .6s cubic-bezier(.16,1,.3,1)}
.hp-text{font-size:.78rem;color:var(--muted);font-weight:700;margin-top:4px;display:block}
.hero-xp-badge{display:inline-flex;align-items:center;gap:6px;padding:6px 12px;background:linear-gradient(135deg,var(--warn,#f59e0b),var(--pri));color:#fff;border-radius:99px;font-size:.82rem;font-weight:800;letter-spacing:.02em;box-shadow:0 4px 12px rgba(0,0,0,.18);font-family:'Unbounded',sans-serif}
.psel{margin-bottom:24px}
.psel-title{font-size:.72rem;font-weight:800;color:var(--muted);text-transform:uppercase;letter-spacing:.08em;margin-bottom:10px}
.psel-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(180px,1fr));gap:10px}
.psel-card{background:var(--card);border:1.5px solid var(--border);border-radius:13px;padding:14px;cursor:pointer;transition:transform .2s,box-shadow .2s,border-color .2s;text-align:left;position:relative}
.psel-card:hover{transform:translateY(-3px);box-shadow:var(--sh2);border-color:var(--pri)}
.psel-card.active{border-color:var(--pri);background:linear-gradient(135deg,var(--pri-soft),var(--card));box-shadow:var(--sh2)}
.psel-card.active::after{content:'';position:absolute;top:0;left:0;right:0;height:3px;background:linear-gradient(90deg,var(--pri),var(--acc));border-radius:13px 13px 0 0}
.psel-num{font-family:'Unbounded',sans-serif;font-size:.72rem;font-weight:800;color:var(--pri);text-transform:uppercase;letter-spacing:.08em;margin-bottom:5px}
.psel-name{font-size:.86rem;font-weight:700;color:var(--text);line-height:1.3;margin-bottom:8px}
.psel-prog{height:4px;background:rgba(0,0,0,.10);border-radius:3px;overflow:hidden}
.psel-prog-fill{height:100%;background:var(--pri);width:0%;transition:width .4s}
.psel-card.final{background:linear-gradient(135deg,#fff5e1,#fef3c7)}
.psel-card.final .psel-num{color:var(--warn)}
.sec{display:none;position:relative;animation:fadeIn .35s ease}
.sec.active{display:block}
@keyframes fadeIn{from{opacity:0;transform:translateY(8px)}to{opacity:1;transform:none}}
.sec::before{content:attr(data-watermark);position:absolute;right:-20px;top:10%;font-family:'Unbounded',sans-serif;font-size:clamp(6rem,18vw,14rem);font-weight:900;color:transparent;-webkit-text-stroke:1.5px var(--pri-soft);line-height:1;pointer-events:none;user-select:none;z-index:0;opacity:.35}
.sec-header{margin-bottom:22px;padding-bottom:14px;border-bottom:2px solid var(--pri-soft);position:relative;z-index:1}
.sec-num{display:inline-block;padding:4px 10px;background:linear-gradient(135deg,var(--pri),var(--pri2));color:#fff;border-radius:7px;font-family:'Unbounded',sans-serif;font-size:.78rem;font-weight:800;letter-spacing:.04em;margin-bottom:8px}
.sec-h{font-family:'Unbounded',sans-serif;font-size:1.6rem;font-weight:800;color:var(--pri2);letter-spacing:-.01em;line-height:1.25}
.card{background:var(--card);border:1px solid var(--border);border-radius:14px;padding:18px 20px;margin-bottom:16px;box-shadow:0 1px 3px rgba(0,0,0,.04),0 8px 24px rgba(0,0,0,.04);position:relative;z-index:1;transition:transform .25s cubic-bezier(.16,1,.3,1),box-shadow .25s}
.card:hover{transform:translateY(-2px);box-shadow:0 4px 10px rgba(0,0,0,.06),0 16px 36px rgba(0,0,0,.08)}
.card-header{display:flex;align-items:center;gap:10px;margin-bottom:12px;padding-bottom:10px;border-bottom:1px dashed var(--border)}
.card-icon{width:32px;height:32px;border-radius:9px;display:flex;align-items:center;justify-content:center;flex-shrink:0;color:#fff}
.card-icon.repeat{background:#0ea5e9}.card-icon.theory{background:#8b5cf6}.card-icon.algo{background:#f59e0b}.card-icon.rule{background:#ec4899}.card-icon.example{background:#10b981}.card-icon.oral{background:#06b6d4}
.card-icon .ic{width:18px;height:18px}
.card-title{font-family:'Unbounded',sans-serif;font-size:.82rem;font-weight:800;text-transform:uppercase;letter-spacing:.06em;color:var(--muted);flex:1}
.card-num{font-size:.74rem;font-weight:700;color:var(--muted);background:var(--pri-soft);padding:3px 7px;border-radius:5px}
.card-body{font-size:.94rem;line-height:1.65}
.card-body p{margin-bottom:8px}
.card-body p:last-child{margin-bottom:0}
.btn{padding:8px 16px;border-radius:8px;background:var(--card);color:var(--text);border:1.5px solid var(--border);font-weight:600;font-size:.88rem;transition:background .15s,border-color .15s,transform .1s}
.btn:hover{background:var(--pri-soft);border-color:var(--pri)}
.btn:active{transform:scale(.96)}
.btn.primary{background:var(--pri);color:#fff;border-color:var(--pri)}
.btn.primary:hover{background:var(--pri2);border-color:var(--pri2)}
.feedback{padding:10px 14px;border-radius:9px;font-weight:600;font-size:.88rem;margin-top:8px;display:none}
.feedback.ok{display:block;background:var(--ok-bg);color:#065f46;border-left:4px solid var(--ok)}
.feedback.fail{display:block;background:var(--fail-bg);color:#7f1d1d;border-left:4px solid var(--fail)}
.wg{background:linear-gradient(135deg,var(--card),var(--pri-soft));border:1.5px solid var(--pri);border-radius:14px;padding:18px 20px;margin-bottom:18px;box-shadow:var(--sh2);position:relative;z-index:1}
.wg-header{display:flex;align-items:center;gap:8px;margin-bottom:14px}
.wg-badge{padding:4px 9px;background:var(--pri);color:#fff;border-radius:6px;font-family:'Unbounded',sans-serif;font-size:.68rem;font-weight:800;text-transform:uppercase;letter-spacing:.06em}
.wg-title{font-family:'Unbounded',sans-serif;font-size:1.05rem;font-weight:800;color:var(--pri2);flex:1}
.wg-help{font-size:.88rem;color:var(--text);margin-bottom:12px;line-height:1.55;background:linear-gradient(135deg,var(--warn-bg),var(--pri-soft));border-left:4px solid var(--warn);padding:9px 14px;border-radius:9px}
.tinp{padding:8px 12px;border:1.5px solid var(--border);border-radius:8px;background:var(--card);color:var(--text);transition:border-color .15s;font-family:'JetBrains Mono',monospace}
.tinp:focus{outline:0;border-color:var(--pri);box-shadow:0 0 0 3px var(--pri-soft)}
.actions{display:flex;gap:8px;flex-wrap:wrap;margin-top:10px}
.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(--pri2);margin-left:4px}
.sliders label input[type="range"]{display:block;width:100%;margin-top:6px;accent-color:var(--pri)}
.score-display{display:flex;gap:14px;flex-wrap:wrap;align-items:center;padding:10px 14px;background:var(--pri-soft);border-radius:10px;margin-bottom:12px}
.score-display b{color:var(--pri2);font-size:1.15rem}
.spoiler{border:1px solid var(--border);border-radius:10px;background:var(--card);margin:10px 0;overflow:hidden}
.spoiler summary{padding:8px 14px;background:var(--pri-soft);font-weight:700;cursor:pointer;font-size:.88rem;color:var(--pri2);list-style:none;display:flex;align-items:center;gap:8px}
.spoiler summary::-webkit-details-marker{display:none}
.spoiler summary::before{content:'+';font-size:1.2rem;font-weight:900;color:var(--pri);width:18px}
.spoiler[open] summary::before{content:'\2212'}
.spoiler-body{padding:10px 14px;font-size:.92rem;line-height:1.6}
.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(--pri);background:var(--pri-soft);border-style:solid}
.dnd-pool.col{flex-direction:column;align-items:stretch}
.dnd-pool.col .dnd-chip{width:auto}
.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(--pri);box-shadow:var(--sh)}
.dnd-chip:active{cursor:grabbing}
.dnd-chip.armed{border-color:var(--pri);background:var(--pri-soft);box-shadow:0 0 0 3px rgba(217,119,6,.22);transform:translateY(-1px)}
.dnd-chip.dragging{opacity:.28}
.dnd-chip.placed{background:var(--pri-soft);border-color: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);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(--pri);background:var(--pri-soft)}
.drop-box h5{font-family:'Unbounded',sans-serif;font-size:.78rem;color:var(--pri2);margin-bottom:8px;text-transform:uppercase;letter-spacing:.05em}
.drop-box.over{border-color:var(--pri);background: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}
.col-side{position:sticky;top:14px;align-self:start;height:fit-content;max-height:calc(100vh - 28px);overflow-y:auto}
.sidecard{background:var(--card);border:1px solid var(--border);border-radius:14px;padding:16px;margin-bottom:14px;box-shadow:var(--sh)}
.sidecard h4{font-family:'Unbounded',sans-serif;font-size:.74rem;font-weight:800;color:var(--pri2);text-transform:uppercase;letter-spacing:.07em;margin-bottom:10px;padding-bottom:8px;border-bottom:1px solid var(--border)}
.sidecard-row{margin-bottom:8px;font-size:.86rem;line-height:1.6}
.sidecard-row b{color:var(--pri);font-weight:700}
.sidecard-row:last-child{margin-bottom:0}
@media(max-width:980px){.col-side{position:static;max-height:none}}
.xp-card{background:linear-gradient(135deg,var(--acc-soft),var(--pri-soft));border:1.5px solid var(--acc);border-radius:12px;padding:14px;margin-bottom:14px}
.xp-card-title{font-size:.68rem;font-weight:800;color:var(--acc2);text-transform:uppercase;letter-spacing:.07em;margin-bottom:8px;display:flex;align-items:center;justify-content:space-between}
.xp-level{font-size:1.1rem;font-weight:900;color:var(--acc2);font-family:'Unbounded',sans-serif}
.xp-bar{height:9px;background:rgba(0,0,0,.10);border-radius:6px;overflow:hidden;margin:7px 0}
.xp-fill{height:100%;background:linear-gradient(90deg,var(--acc),var(--pri));border-radius:6px;transition:width .5s cubic-bezier(.4,0,.2,1)}
.xp-nums{font-size:.74rem;color:var(--muted);display:flex;justify-content:space-between}
.sec-nav{display:flex;gap:10px;margin-top:24px;padding-top:20px;border-top:1px solid var(--border);justify-content:space-between;flex-wrap:wrap}
.foot{text-align:center;padding:30px 16px;color:var(--muted);font-size:.78rem;border-top:1px solid var(--border);margin-top:30px}
.ach-popup{position:fixed;top:80px;right:18px;background:linear-gradient(135deg,var(--pri),var(--acc));color:#fff;padding:12px 18px;border-radius:11px;font-weight:700;font-size:.9rem;box-shadow:0 8px 28px rgba(0,0,0,.32);z-index:1002;display:none;align-items:center;gap:8px;max-width:340px}
.ach-popup.show{display:flex}
.col-side-backdrop{position:fixed;inset:0;background:rgba(0,0,0,.42);z-index:9990;display:none}
.col-side-backdrop.show{display:block}
@media(max-width:980px){
.col-side{position:fixed;top:0;right:0;height:100vh;width:300px;max-width:88vw;background:var(--bg);box-shadow:-12px 0 24px rgba(0,0,0,.18);padding:18px 16px;overflow-y:auto;transform:translateX(100%);transition:transform .25s ease;z-index:9991;max-height:none}
.col-side.open{transform:none}
}
.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}
.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:0;width:100%;color:var(--text)}
.search-row:hover,.search-row.active{background: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}
.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>Геометрия 9 · Глава 1</h1>
<div class="hdr-sub">Прямоугольный треугольник · тригонометрия · площадь</div>
</div>
<div class="hdr-side">
<a href="/textbook/geometry-9" class="hdr-btn"><svg class="ic" viewBox="0 0 24 24"><polyline points="15 18 9 12 15 6"/></svg> К геометрии 9</a>
<button id="search-btn" class="hdr-btn"><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"><svg class="ic" viewBox="0 0 24 24"><line x1="4" y1="6" x2="20" y2="6"/><line x1="4" y1="12" x2="20" y2="12"/><line x1="4" y1="18" x2="14" y2="18"/></svg> Шпаргалка</button>
<button id="theme-btn" class="hdr-btn"><svg class="ic" viewBox="0 0 24 24"><path d="M21 12.8A9 9 0 1 1 11.2 3a7 7 0 0 0 9.8 9.8z"/></svg><span id="theme-lab">Тёмная</span></button>
</div>
</div>
</header>
<main class="main">
<div class="col-main">
<section class="hero">
<h2>Соотношения в прямоугольном треугольнике</h2>
<p>Здесь мы изучаем тригонометрические функции <b>sin</b>, <b>cos</b>, <b>tg</b>, <b>ctg</b> острого и тупого угла, решаем прямоугольный треугольник, выводим формулы площади $S = \tfrac{1}{2}ab\sin C$ и находим высоту к гипотенузе как среднее геометрическое $h^2 = a_1 b_1$.</p>
<div class="hero-row">
<button class="btn-primary" onclick="goTo('p1')"><svg class="ic" viewBox="0 0 24 24"><polygon points="6 4 20 12 6 20 6 4" fill="currentColor" stroke="none"/></svg> Начать § 1</button>
<div class="hero-progress">
<span class="hp-label">Прогресс по главе</span>
<div class="hp-bar"><div id="hero-hp-fill" class="hp-fill"></div></div>
<span id="hero-hp-text" class="hp-text">0%</span>
</div>
<div id="hero-xp-badge" class="hero-xp-badge"></div>
</div>
</section>
<section class="psel">
<div class="psel-title">Параграфы главы</div>
<div id="psel-grid" class="psel-grid"></div>
</section>
<section id="sec-p1" class="sec" data-watermark="sin"><div class="sec-header"><span class="sec-num">§ 1</span><h2 class="sec-h">sin, cos, tg, ctg острого угла</h2></div><div id="p1-body"></div></section>
<section id="sec-p2" class="sec" data-watermark="△"><div class="sec-header"><span class="sec-num">§ 2</span><h2 class="sec-h">Решение прямоугольного треугольника</h2></div><div id="p2-body"></div></section>
<section id="sec-p3" class="sec" data-watermark="sin²"><div class="sec-header"><span class="sec-num">§ 3</span><h2 class="sec-h">Тригонометрические формулы</h2></div><div id="p3-body"></div></section>
<section id="sec-p4" class="sec" data-watermark="∠"><div class="sec-header"><span class="sec-num">§ 4</span><h2 class="sec-h">sin, cos, tg, ctg тупого угла</h2></div><div id="p4-body"></div></section>
<section id="sec-p5" class="sec" data-watermark="S"><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="h²"><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-final1" class="sec" data-watermark="★"><div class="sec-header"><span class="sec-num" style="background:linear-gradient(135deg,#d97706,#fbbf24)">Финал главы</span><h2 class="sec-h">Итоги главы 1</h2></div><div id="final1-body"></div></section>
</div>
<aside class="col-side" id="col-side"><div id="sidebar-content"></div></aside>
<div class="col-side-backdrop" id="col-side-backdrop"></div>
</main>
<footer class="foot">Интерактивный учебник «Геометрия 9» · Глава 1 · Прямоугольный треугольник · LearnSpace</footer>
<div id="ach-popup" class="ach-popup"><svg class="ic" viewBox="0 0 24 24" style="width:22px;height:22px"><polygon points="12,2 22,20 2,20"/></svg><span id="ach-text">Достижение!</span></div>
<div id="search-modal" class="search-modal" role="dialog">
<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>↑↓</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,final1:0}, achievements:new Map(), xp:0, level:1 };
const TOTAL_PARAS = 7;
const _TB_SLUG = 'geometry-9-ch1';
function calcLevel(xp){ return Math.floor(Math.sqrt((xp||0)/100))+1; }
function _xpForLevel(lv){ return (lv-1)*(lv-1)*100; }
const ACH_LABELS = {
start:'Начало главы 1!',
p1_done:'sin, cos, tg, ctg острого угла освоены!',
p3_done:'Тригонометрические формулы освоены!',
p5_done:'Формулы площади освоены!',
ch1_done:'Глава 1 пройдена! Прямоугольный треугольник — финал!'
};
function loadProgress(){
try{
const s=localStorage.getItem('geometry9_ch1_progress'); if(s) Object.assign(STATE.progress, JSON.parse(s));
const a=localStorage.getItem('geometry9_ch1_achievements');
if(a){ const p=JSON.parse(a); if(Array.isArray(p)) p.forEach(id=>STATE.achievements.set(id, ACH_LABELS[id]||id)); else if(p&&typeof p==='object'){ for(const[id,t] of Object.entries(p)) STATE.achievements.set(id,(t&&t!==id)?t:(ACH_LABELS[id]||id)); } }
STATE.xp=+(localStorage.getItem('geometry9_xp')||0); STATE.level=calcLevel(STATE.xp);
}catch(e){}
}
function saveProgress(){
try{
localStorage.setItem('geometry9_ch1_progress', JSON.stringify(STATE.progress));
localStorage.setItem('geometry9_ch1_achievements', JSON.stringify(Object.fromEntries(STATE.achievements)));
localStorage.setItem('geometry9_xp', String(STATE.xp));
}catch(e){}
}
function bumpProgress(key, delta){
STATE.progress[key]=Math.max(0,Math.min(100,(STATE.progress[key]||0)+delta));
saveProgress(); refreshProgressUI();
if(STATE.progress[key]>=50) markParaRead(key);
}
const _markedRead=new Set();
let _pendingProgressBody=null, _progressTimer=null;
function _flushProgress(){
const body=_pendingProgressBody; _pendingProgressBody=null; if(!body) return;
const tok=(window.LS&&LS.getToken)?LS.getToken():''; if(!tok) return;
fetch('/api/textbooks/'+_TB_SLUG+'/progress',{method:'POST',headers:{'Content-Type':'application/json','Authorization':'Bearer '+tok},body:JSON.stringify(body),keepalive:true}).catch(()=>{});
}
function _queueProgress(patch){ _pendingProgressBody=Object.assign(_pendingProgressBody||{},patch); if(_progressTimer) clearTimeout(_progressTimer); _progressTimer=setTimeout(_flushProgress, 600); }
function markLastPara(id){ _queueProgress({last_para:id}); }
function markParaRead(id){ if(_markedRead.has(id)) return; _markedRead.add(id); _queueProgress({mark_read:id}); }
window.addEventListener('beforeunload', _flushProgress);
function 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,'geometry9-ch1-'+(src||'misc'));
if(STATE.level>prev){
const pop=document.getElementById('ach-popup');
if(pop){ document.getElementById('ach-text').textContent='Уровень '+STATE.level+'!'; pop.classList.add('show'); setTimeout(()=>pop.classList.remove('show'),2600); }
}
}
function refreshProgressUI(){
const total=Math.round(Object.values(STATE.progress).reduce((a,b)=>a+b,0)/TOTAL_PARAS);
const f=document.getElementById('hero-hp-fill'); if(f) f.style.width=total+'%';
const t=document.getElementById('hero-hp-text'); if(t) t.textContent=total+'% пройдено';
document.querySelectorAll('[data-prog-card]').forEach(el=>{ const k=el.dataset.progCard; const fl=el.querySelector('.psel-prog-fill'); if(fl) fl.style.width=(STATE.progress[k]||0)+'%'; });
const xpBadge=document.getElementById('hero-xp-badge');
if(xpBadge){ xpBadge.innerHTML='<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" style="width:13px;height:13px"><polygon points="12 2 22 20 2 20"/></svg> Ур. '+STATE.level+' \xb7 '+(STATE.xp||0)+' XP'; }
if(STATE.current && document.getElementById('sidebar-content')){ try{ buildSidebar(STATE.current); }catch(e){} }
}
function achievement(id,text){
if(STATE.achievements.has(id)) return;
STATE.achievements.set(id, text||ACH_LABELS[id]||id); saveProgress();
const pop=document.getElementById('ach-popup');
if(pop){ document.getElementById('ach-text').textContent=text||ACH_LABELS[id]||id; pop.classList.add('show'); setTimeout(()=>pop.classList.remove('show'),3300); }
addXp(20,'ach-'+id);
}
const PARAS = [
{ id:'p1', num:'§ 1', name:'sin, cos, tg, ctg острого угла', sub:'$\\sin\\alpha = \\tfrac{a}{c}$' },
{ id:'p2', num:'§ 2', name:'Решение прямоугольного треугольника', sub:'$a, b, c, \\alpha$' },
{ id:'p3', num:'§ 3', name:'Тригонометрические формулы', sub:'$\\sin^2 + \\cos^2 = 1$' },
{ id:'p4', num:'§ 4', name:'sin, cos, tg, ctg тупого угла', sub:'$\\sin(180^\\circ - \\alpha)$' },
{ id:'p5', num:'§ 5', name:'Формулы площади', sub:'$S = \\tfrac{1}{2}ab\\sin C$' },
{ id:'p6', num:'§ 6', name:'Среднее геометрическое', sub:'$h^2 = a_1 b_1$' },
{ id:'final1', num:'★', name:'Финал главы', sub:'Итоги главы 1', final:true }
];
function buildParaSelector(){
const g=document.getElementById('psel-grid'); g.innerHTML='';
PARAS.forEach(p=>{
const card=document.createElement('div');
card.className='psel-card'+(p.final?' final':'');
card.dataset.id=p.id; card.dataset.progCard=p.id;
card.innerHTML='<div class="psel-num">'+p.num+'</div><div class="psel-name">'+p.name+'</div><div class="psel-prog"><div class="psel-prog-fill"></div></div>';
card.addEventListener('click', ()=>goTo(p.id));
g.appendChild(card);
});
}
const BUILT=new Set();
const BUILDERS = { p1:()=>buildP1(), p2:()=>buildP2(), p3:()=>buildP3(), p4:()=>buildP4(), p5:()=>buildP5(), p6:()=>buildP6(), final1:()=>buildFinal1() };
function ensureBuilt(id){ if(BUILT.has(id)) return; const fn=BUILDERS[id]; if(fn){ fn(); BUILT.add(id); } }
function goTo(id){
STATE.current=id; ensureBuilt(id);
document.querySelectorAll('.sec').forEach(s=>s.classList.remove('active'));
const el=document.getElementById('sec-'+id); if(el) el.classList.add('active');
document.querySelectorAll('.psel-card').forEach(c=>c.classList.toggle('active', c.dataset.id===id));
buildSidebar(id);
window.scrollTo({top:0,behavior:'smooth'});
if((STATE.progress[id]||0)<10) bumpProgress(id, 10);
if(window.renderMathInElement) setTimeout(()=>renderMath(el), 0);
markLastPara(id);
}
const SIDEBARS = {
p1:{title:'Шпаргалка \xA71',rows:[['sin α','противолежащий / гипотенуза'],['cos α','прилежащий / гипотенуза'],['tg α','sin α / cos α']]},
p2:{title:'Шпаргалка \xA72',rows:[['Дано 2 элемента','найти остальные'],['Гипотенуза','$c = \\sqrt{a^2 + b^2}$'],['Угол','$\\alpha + \\beta = 90^\\circ$']]},
p3:{title:'Шпаргалка \xA73',rows:[['Основное','$\\sin^2\\alpha + \\cos^2\\alpha = 1$'],['30°','$\\sin = \\tfrac{1}{2}$, $\\cos = \\tfrac{\\sqrt3}{2}$'],['45°','$\\sin = \\cos = \\tfrac{\\sqrt2}{2}$'],['60°','$\\sin = \\tfrac{\\sqrt3}{2}$, $\\cos = \\tfrac{1}{2}$']]},
p4:{title:'Шпаргалка \xA74',rows:[['Тупой угол','$90^\\circ < \\alpha < 180^\\circ$'],['Симметрия','$\\sin(180^\\circ - \\alpha) = \\sin\\alpha$'],['Знак','$\\cos(180^\\circ - \\alpha) = -\\cos\\alpha$']]},
p5:{title:'Шпаргалка \xA75',rows:[['Через сторону и угол','$S = \\tfrac{1}{2}ab\\sin C$'],['Через высоту','$S = \\tfrac{1}{2}ah$']]},
p6:{title:'Шпаргалка \xA76',rows:[['Среднее геом.','$\\sqrt{ab}$'],['Высота','$h = \\sqrt{a_1 \\cdot b_1}$'],['Катет','$a^2 = a_1 \\cdot c$']]},
final1:{title:'Финал главы',rows:[['§§16','теория главы 1'],['Награда','XP за прочтение'],['Дальше','глава 2 — окружности']]}
};
const TIPS=[
{sec:'p1',html:'$\\sin\\alpha = \\dfrac{\\text{противолежащий}}{\\text{гипотенуза}}$, $\\cos\\alpha = \\dfrac{\\text{прилежащий}}{\\text{гипотенуза}}$.'},
{sec:'p2',html:'Решить треугольник — найти все стороны и углы по двум данным элементам.'},
{sec:'p3',html:'$\\sin^2\\alpha + \\cos^2\\alpha = 1$ — следствие теоремы Пифагора.'},
{sec:'p4',html:'Для тупого угла $\\alpha$: $\\sin\\alpha = \\sin(180^\\circ - \\alpha)$, $\\cos\\alpha = -\\cos(180^\\circ - \\alpha)$.'},
{sec:'p5',html:'$S = \\dfrac{1}{2}ab\\sin C$ — площадь по двум сторонам и углу между ними.'},
{sec:'p6',html:'В прямоугольном треугольнике высота к гипотенузе равна $h = \\sqrt{a_1 b_1}$.'},
{sec:'final1',html:'Главные результаты главы 1: тригонометрические функции, формулы площади и среднее геометрическое.'}
];
function buildSidebar(id){
const box=document.getElementById('sidebar-content');
const sb=SIDEBARS[id]||SIDEBARS['p1'];
let html='';
const xpForLv=_xpForLevel(STATE.level), xpNext=_xpForLevel(STATE.level+1);
const xpInLv=STATE.xp-xpForLv, xpRange=xpNext-xpForLv;
const xpPct=xpRange>0?Math.round(xpInLv/xpRange*100):100;
html+='<div class="xp-card"><div class="xp-card-title"><span>XP-прогресс</span><span class="xp-level">Ур. '+STATE.level+'</span></div><div class="xp-bar"><div class="xp-fill" style="width:'+xpPct+'%"></div></div><div class="xp-nums"><span>'+STATE.xp+' XP</span><span>'+xpNext+' XP</span></div></div>';
html+='<div class="sidecard"><h4>'+sb.title+'</h4>';
sb.rows.forEach(([k,v])=>{ html+='<div class="sidecard-row"><b>'+k+'</b>'+(v?' — '+v:'')+'</div>'; });
html+='</div>';
const tip=TIPS.find(t=>t.sec===id)||TIPS[0];
if(tip){
html+='<div class="sidecard" style="background:linear-gradient(135deg,var(--warn-bg,#fef3c7),var(--pri-soft));border-color:var(--warn,#f59e0b)"><h4 style="color:#92400e;display:flex;align-items:center;gap:6px"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" style="width:14px;height:14px"><polygon points="12,2 22,20 2,20"/></svg>Подсказка</h4><div class="sidecard-row" style="margin-bottom:0;font-size:.84rem;line-height:1.55">'+tip.html+'</div></div>';
}
if(STATE.achievements.size>0){
html+='<div class="sidecard"><h4>Достижения <span style="color:var(--warn);float:right">'+STATE.achievements.size+'</span></h4>';
[...STATE.achievements.values()].slice(-4).forEach(text=>{ html+='<div class="sidecard-row" style="font-size:.78rem;color:var(--ok)">&#10003; '+text+'</div>'; });
html+='</div>';
}
box.innerHTML=html;
if(window.renderMathInElement) try{ renderMath(box); }catch(e){}
}
function initTheme(){
const t=localStorage.getItem('geometry9_ch1_theme')||'light';
if(t==='dark') document.documentElement.classList.add('dark');
document.getElementById('theme-lab').textContent=t==='dark'?'Светлая':'Тёмная';
document.getElementById('theme-btn').addEventListener('click', ()=>{
document.documentElement.classList.toggle('dark');
const dark=document.documentElement.classList.contains('dark');
localStorage.setItem('geometry9_ch1_theme', dark?'dark':'light');
document.getElementById('theme-lab').textContent=dark?'Светлая':'Тёмная';
});
}
function renderMath(root){ if(window.renderMathInElement){ try{ renderMathInElement(root, {delimiters:[{left:'$$',right:'$$',display:true},{left:'$',right:'$',display:false},{left:'\\[',right:'\\]',display:true},{left:'\\(',right:'\\)',display:false}],throwOnError:false}); }catch(e){} } }
function feedback(elm, ok, text){ if(!elm) return; elm.className='feedback '+(ok?'ok':'fail'); elm.innerHTML=text||(ok?'&#10003; Верно!':'&#10007; Неверно'); elm.style.display='block'; try{renderMath(elm);}catch(e){} }
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(6)).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>',
};
/* ===== Геометрические SVG-хелперы (используются во всей главе) ===== */
// L-маркер прямого угла (polyline ВНУТРЬ угла).
// V — вершина прямого угла; uIn, wIn — единичные векторы в SVG-координатах вдоль двух катетов.
function rightAngleMark(V, uIn, wIn, s){
s = s || 9;
const p1 = {x: V.x + s*uIn.x, y: V.y + s*uIn.y};
const c = {x: p1.x + s*wIn.x, y: p1.y + s*wIn.y};
const p2 = {x: V.x + s*wIn.x, y: V.y + s*wIn.y};
return p1.x+','+p1.y+' '+c.x+','+c.y+' '+p2.x+','+p2.y;
}
// Дуга угла с автовыбором sweep (через cross product).
// V — вершина, uA/uB — единичные векторы вдоль сторон угла (в SVG-координатах), R — радиус.
function angleArcAuto(V, uA, uB, R){
const sA = {x: V.x + R*uA.x, y: V.y + R*uA.y};
const eB = {x: V.x + R*uB.x, y: V.y + R*uB.y};
const cross = uA.x*uB.y - uA.y*uB.x;
const sweep = cross > 0 ? 1 : 0;
return 'M'+sA.x+','+sA.y+' A'+R+','+R+' 0 0,'+sweep+' '+eB.x+','+eB.y;
}
// Нормализованный вектор от p1 к p2 (в координатах того же пространства).
function unitVec(p1, p2){
const dx = p2.x - p1.x, dy = p2.y - p1.y;
const len = Math.sqrt(dx*dx + dy*dy) || 1;
return {x: dx/len, y: dy/len};
}
function deg2rad(d){ return d * Math.PI / 180; }
function gcd(a,b){ a=Math.abs(a|0); b=Math.abs(b|0); while(b){ const t=b; b=a%b; a=t; } return a||1; }
/* ===== DnD сортер ===== */
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;
ev.preventDefault(); 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(); }};
}
function makeCard(kind, title, num, body){
const labels = {repeat:'Повторение',theory:'Теория',algo:'Алгоритм',rule:'Правило',example:'Пример',oral:'Устно'};
return '<div class="card"><div class="card-header"><div class="card-icon '+kind+'">'+ICONS[kind]+'</div><div class="card-title">'+(labels[kind]||'')+(title&&title!==labels[kind]?' \xb7 '+title:'')+'</div>'+(num?'<div class="card-num">'+num+'</div>':'')+'</div><div class="card-body">'+body+'</div></div>';
}
function secNav(prev, next){
const NAMES={p1:'\xA71',p2:'\xA72',p3:'\xA73',p4:'\xA74',p5:'\xA75',p6:'\xA76',final1:'Финал'};
let h='<div class="sec-nav">';
h+=prev?'<button class="btn" onclick="goTo(\''+prev+'\')"><svg class="ic" viewBox="0 0 24 24"><polyline points="15 18 9 12 15 6"/></svg> '+NAMES[prev]+'</button>':'<span></span>';
h+=next?'<button class="btn primary" onclick="goTo(\''+next+'\')">'+NAMES[next]+' <svg class="ic" viewBox="0 0 24 24"><polyline points="9 18 15 12 9 6"/></svg></button>':'<span></span>';
h+='</div>'; return h;
}
function readButton(paraId){
return '<div style="margin-top:18px;display:flex;justify-content:center">'
+'<button class="btn primary" id="'+paraId+'-read-btn">'
+'<svg class="ic" viewBox="0 0 24 24"><path d="M4 19.5A2.5 2.5 0 0 1 6.5 17H20"/><path d="M6.5 2H20v20H6.5A2.5 2.5 0 0 1 4 19.5v-15A2.5 2.5 0 0 1 6.5 2z"/></svg>'
+' Я прочитал — '+(paraId.startsWith('final')?'финал':'\xA7'+paraId.replace('p',''))+' (+10 XP)'
+'</button></div>';
}
function wireReadBtn(paraId){
const btn = document.getElementById(paraId+'-read-btn'); if(!btn) return;
btn.addEventListener('click', ()=>{
addXp(10, paraId+'-read'); bumpProgress(paraId, 100);
btn.textContent='Прочитано! +10 XP'; btn.disabled=true; btn.style.opacity=.6;
if(paraId==='final1') achievement('ch1_done');
});
}
/* ===== STUB BUILDERS — наполнение в Phase 7+ ===== */
function _stubBuilder(paraId, num, name, prev, next){
const body = document.getElementById(paraId+'-body');
let html = '';
html += makeCard('theory', 'В разработке', num, `
<p>Содержание параграфа <b>«${name}»</b> будет добавлено в следующих обновлениях.</p>
<p style="color:var(--muted);font-size:.9rem">Раздел Phase 7.</p>`);
html += readButton(paraId);
html += secNav(prev, next);
body.innerHTML = html;
wireReadBtn(paraId);
if(window.renderMathInElement) renderMath(body);
}
/* ===== §1 sin, cos, tg, ctg острого угла ===== */
function buildP1(){
const box = document.getElementById('p1-body');
let html = '';
html += makeCard('theory', 'Определения', '1.1', `
<p>Рассмотрим прямоугольный треугольник с острым углом $\\alpha$. Относительно $\\alpha$ катеты делятся на <b>противолежащий</b> (лежит напротив $\\alpha$) и <b>прилежащий</b> (образует $\\alpha$ вместе с гипотенузой).</p>
<p>Тригонометрические функции острого угла:</p>
<ul style="padding-left:22px;line-height:1.95">
<li>$\\sin \\alpha = \\dfrac{\\text{противолежащий катет}}{\\text{гипотенуза}}$</li>
<li>$\\cos \\alpha = \\dfrac{\\text{прилежащий катет}}{\\text{гипотенуза}}$</li>
<li>$\\tan \\alpha = \\dfrac{\\sin \\alpha}{\\cos \\alpha} = \\dfrac{\\text{противолежащий}}{\\text{прилежащий}}$</li>
<li>$\\cot \\alpha = \\dfrac{\\cos \\alpha}{\\sin \\alpha} = \\dfrac{1}{\\tan \\alpha} = \\dfrac{\\text{прилежащий}}{\\text{противолежащий}}$</li>
</ul>
<details class="spoiler"><summary>Почему отношения, а не «длины»?</summary><div class="spoiler-body">
Все прямоугольные треугольники с одним и тем же острым углом $\\alpha$ <b>подобны</b>. У подобных треугольников <b>отношения сторон</b> совпадают. Поэтому $\\sin \\alpha$, $\\cos \\alpha$ и $\\tan \\alpha$ зависят только от угла, а не от размера треугольника.
</div></details>`);
html += makeCard('rule', 'Свойства тригонометрических функций', '1.2', `
<p>Для острого угла $\\alpha$ ($0^\\circ < \\alpha < 90^\\circ$):</p>
<ul style="padding-left:22px;line-height:1.95">
<li><b>Ограничены единицей:</b> $0 < \\sin \\alpha < 1$ и $0 < \\cos \\alpha < 1$ (катет меньше гипотенузы).</li>
<li><b>Косинус дополнительного угла:</b> $\\sin \\alpha = \\cos(90^\\circ - \\alpha)$ и $\\cos \\alpha = \\sin(90^\\circ - \\alpha)$.</li>
<li><b>Произведение:</b> $\\tan \\alpha \\cdot \\cot \\alpha = 1$.</li>
<li><b>Основное тригонометрическое тождество:</b> $\\sin^2 \\alpha + \\cos^2 \\alpha = 1$.</li>
</ul>
<details class="spoiler"><summary>Откуда тождество $\\sin^2 + \\cos^2 = 1$?</summary><div class="spoiler-body">
В прямоугольном треугольнике катеты $a, b$ и гипотенуза $c$. По Пифагору $a^2 + b^2 = c^2$. Поделим на $c^2$: $\\left(\\dfrac{a}{c}\\right)^2 + \\left(\\dfrac{b}{c}\\right)^2 = 1$, то есть $\\sin^2 \\alpha + \\cos^2 \\alpha = 1$.
</div></details>`);
html += makeCard('example', 'Эталонные значения 30°, 45°, 60°', '1.3', `
<p>Запомни эту таблицу — она встречается во всех задачах:</p>
<div style="overflow-x:auto;margin:10px 0">
<table style="width:100%;border-collapse:collapse;font-size:.95rem;background:var(--card);border:1.5px solid var(--border);border-radius:9px;overflow:hidden">
<thead><tr style="background:var(--pri-soft)">
<th style="padding:8px;border-bottom:1px solid var(--border);text-align:left">Угол</th>
<th style="padding:8px;border-bottom:1px solid var(--border)">$\\sin$</th>
<th style="padding:8px;border-bottom:1px solid var(--border)">$\\cos$</th>
<th style="padding:8px;border-bottom:1px solid var(--border)">$\\tan$</th>
<th style="padding:8px;border-bottom:1px solid var(--border)">$\\cot$</th>
</tr></thead>
<tbody>
<tr><td style="padding:8px;border-bottom:1px solid var(--border)"><b>30°</b></td><td style="padding:8px;border-bottom:1px solid var(--border);text-align:center">$\\tfrac{1}{2}$</td><td style="padding:8px;border-bottom:1px solid var(--border);text-align:center">$\\tfrac{\\sqrt{3}}{2}$</td><td style="padding:8px;border-bottom:1px solid var(--border);text-align:center">$\\tfrac{1}{\\sqrt{3}}$</td><td style="padding:8px;border-bottom:1px solid var(--border);text-align:center">$\\sqrt{3}$</td></tr>
<tr><td style="padding:8px;border-bottom:1px solid var(--border)"><b>45°</b></td><td style="padding:8px;border-bottom:1px solid var(--border);text-align:center">$\\tfrac{\\sqrt{2}}{2}$</td><td style="padding:8px;border-bottom:1px solid var(--border);text-align:center">$\\tfrac{\\sqrt{2}}{2}$</td><td style="padding:8px;border-bottom:1px solid var(--border);text-align:center">$1$</td><td style="padding:8px;border-bottom:1px solid var(--border);text-align:center">$1$</td></tr>
<tr><td style="padding:8px"><b>60°</b></td><td style="padding:8px;text-align:center">$\\tfrac{\\sqrt{3}}{2}$</td><td style="padding:8px;text-align:center">$\\tfrac{1}{2}$</td><td style="padding:8px;text-align:center">$\\sqrt{3}$</td><td style="padding:8px;text-align:center">$\\tfrac{1}{\\sqrt{3}}$</td></tr>
</tbody>
</table>
</div>
<p><b>Заметь:</b> $\\sin 30^\\circ = \\cos 60^\\circ$, $\\sin 45^\\circ = \\cos 45^\\circ$ — это и есть «косинус дополнительного угла» из §1.2.</p>`);
/* IV1 — Конструктор прямоугольного треугольника */
html += `<div class="wg" id="p1-iv1">
<div class="wg-header"><span class="wg-badge">ИНТЕРАКТИВ 1</span><div class="wg-title">Конструктор прямоугольного треугольника</div></div>
<div class="wg-help">Меняй угол $\\alpha$ ползунком — катеты и гипотенуза перестроятся, а внизу появятся значения $\\sin \\alpha$, $\\cos \\alpha$, $\\tan \\alpha$, $\\cot \\alpha$.</div>
<div class="sliders">
<label>Угол $\\alpha$, °<b id="p1-iv1-aval">40</b><input type="range" id="p1-iv1-a" min="10" max="80" step="1" value="40"></label>
</div>
<div style="background:var(--card);border-radius:10px;padding:10px;overflow-x:auto">
<svg id="p1-iv1-svg" viewBox="0 0 400 320" style="width:100%;min-width:320px;height:auto;display:block"></svg>
</div>
<div id="p1-iv1-out" style="margin-top:10px;padding:10px 14px;background:var(--pri-soft);border-radius:9px;font-size:.95rem;text-align:center;line-height:1.9"></div>
</div>`;
/* IV2 — Калькулятор сторон */
html += `<div class="wg" id="p1-iv2">
<div class="wg-header"><span class="wg-badge">ИНТЕРАКТИВ 2</span><div class="wg-title">Калькулятор сторон</div></div>
<div class="wg-help">Введи угол $\\alpha$ и гипотенузу $c$ — посчитаем оба катета: $a = c \\sin \\alpha$ (противолежащий) и $b = c \\cos \\alpha$ (прилежащий).</div>
<div style="display:flex;gap:10px;align-items:center;flex-wrap:wrap;justify-content:center;margin-bottom:10px">
<span style="font-family:'JetBrains Mono',monospace">$\\alpha$ =</span>
<input type="number" id="p1-iv2-a" class="tinp" style="width:90px;text-align:center" value="30" min="10" max="80">
<span style="font-family:'JetBrains Mono',monospace">°,&nbsp; $c$ =</span>
<input type="number" id="p1-iv2-c" class="tinp" style="width:90px;text-align:center" value="10" min="0.1" step="0.1">
<button class="btn primary" id="p1-iv2-go">Найти катеты</button>
</div>
<div id="p1-iv2-out" style="padding:12px 14px;background:var(--card);border-radius:9px;text-align:center;font-size:1rem;min-height:50px"></div>
<div class="feedback" id="p1-iv2-fb"></div>
</div>`;
/* IV3 — Quickfire «Какое отношение?» */
html += `<div class="wg" id="p1-iv3">
<div class="wg-header"><span class="wg-badge">ИНТЕРАКТИВ 3</span><div class="wg-title">Какое отношение?</div></div>
<div class="wg-help">Прямоугольный треугольник с гипотенузой $c$, катетами $a$ (противолежащий $\\alpha$) и $b$ (прилежащий $\\alpha$). Какое из четырёх отношений равно указанному выражению?</div>
<div class="score-display"><span>Задача <b id="p1-iv3-i">1</b> / 8</span><span>Очки: <b id="p1-iv3-s">0</b> / 8</span></div>
<div id="p1-iv3-q" style="padding:14px;background:var(--pri-soft);border-radius:10px;font-size:1.15rem;text-align:center;margin-bottom:10px"></div>
<div style="display:grid;grid-template-columns:repeat(auto-fit,minmax(110px,1fr));gap:8px">
<button class="btn primary" data-ans="ac" id="p1-iv3-ac">$a / c$</button>
<button class="btn primary" data-ans="bc" id="p1-iv3-bc">$b / c$</button>
<button class="btn primary" data-ans="ab" id="p1-iv3-ab">$a / b$</button>
<button class="btn primary" data-ans="ba" id="p1-iv3-ba">$b / a$</button>
</div>
<div class="feedback" id="p1-iv3-fb"></div>
</div>`;
/* IV4 — Тренажёр */
html += `<div class="wg" id="p1-iv4">
<div class="wg-header"><span class="wg-badge">ИНТЕРАКТИВ 4</span><div class="wg-title">Тренажёр</div></div>
<div class="wg-help">Реши задачу и введи число (округли до 2 знаков, если получается дробное).</div>
<div class="score-display"><span>Задача <b id="p1-iv4-i">1</b> / 6</span><span>Очки: <b id="p1-iv4-s">0</b> / 6</span></div>
<div id="p1-iv4-q" style="padding:14px;background:var(--pri-soft);border-radius:10px;font-size:1.05rem;margin-bottom:10px;text-align:center"></div>
<div style="display:flex;gap:10px;align-items:center;flex-wrap:wrap;justify-content:center">
<span style="font-family:'JetBrains Mono',monospace">ответ =</span>
<input type="number" id="p1-iv4-ans" class="tinp" style="width:110px;text-align:center" step="0.01">
<button class="btn primary" id="p1-iv4-go">Проверить</button>
<button class="btn" id="p1-iv4-start">Заново</button>
</div>
<div class="feedback" id="p1-iv4-fb"></div>
</div>`;
html += secNav(null, 'p2');
html += readButton('p1');
box.innerHTML = html;
renderMath(box);
/* IV1 — слайдер + SVG */
(function(){
const sl = document.getElementById('p1-iv1-a');
const lab = document.getElementById('p1-iv1-aval');
const svg = document.getElementById('p1-iv1-svg');
const out = document.getElementById('p1-iv1-out');
const seen = new Set();
function draw(){
const aDeg = +sl.value;
lab.textContent = aDeg;
const aRad = deg2rad(aDeg);
const c = 220; // гипотенуза в пикселях
// Геометрические вершины: A (внизу слева), B (верх — прямой угол), C (внизу справа).
// Прямой угол при B. Угол α при C.
// BC = c·cos α (горизонтальный катет, прилежащий к α)
// AB = c·sin α (вертикальный катет, противолежащий α)
const BCpx = c * Math.cos(aRad);
const ABpx = c * Math.sin(aRad);
const cx = 60, cyBase = 270; // позиция A
const A = {x: cx, y: cyBase};
const C = {x: cx + BCpx, y: cyBase};
const B = {x: cx, y: cyBase - ABpx};
// Внутренние векторы в B (прямой угол): по BA — вниз, по BC — вправо-вниз
const uBA = unitVec(B, A);
const uBC = unitVec(B, C);
const uCA = unitVec(C, A); // вдоль гипотенузы из C
const uCB = unitVec(C, B); // вдоль катета из C
let s = '';
// фон
s += '<rect x="0" y="0" width="400" height="320" fill="none"/>';
// треугольник
s += '<polygon points="'+A.x+','+A.y+' '+B.x+','+B.y+' '+C.x+','+C.y+'" fill="rgba(217,119,6,.08)" stroke="#b45309" stroke-width="2.2" stroke-linejoin="round"/>';
// маркер прямого угла в B (внутренние векторы — uBA и uBC)
s += '<polyline points="'+rightAngleMark(B, uBA, uBC, 12)+'" fill="none" stroke="#0f172a" stroke-width="1.8"/>';
// дуга угла α при C (от CA к CB)
s += '<path d="'+angleArcAuto(C, uCA, uCB, 30)+'" fill="none" stroke="#dc2626" stroke-width="2"/>';
// подпись α
const aMid = {x: C.x + 44*Math.cos(Math.atan2((uCA.y+uCB.y)/2,(uCA.x+uCB.x)/2)), y: C.y + 44*Math.sin(Math.atan2((uCA.y+uCB.y)/2,(uCA.x+uCB.x)/2))};
s += '<text x="'+aMid.x+'" y="'+aMid.y+'" text-anchor="middle" dominant-baseline="middle" font-family="Inter,sans-serif" font-size="15" font-weight="700" fill="#dc2626">α</text>';
// вершины
s += '<circle cx="'+A.x+'" cy="'+A.y+'" r="4" fill="#0f172a"/>';
s += '<circle cx="'+B.x+'" cy="'+B.y+'" r="4" fill="#0f172a"/>';
s += '<circle cx="'+C.x+'" cy="'+C.y+'" r="4" fill="#0f172a"/>';
s += '<text x="'+(A.x-12)+'" y="'+(A.y+18)+'" text-anchor="end" font-family="Inter,sans-serif" font-size="16" font-weight="700" fill="#0f172a">A</text>';
s += '<text x="'+(B.x-12)+'" y="'+(B.y-4)+'" text-anchor="end" font-family="Inter,sans-serif" font-size="16" font-weight="700" fill="#0f172a">B</text>';
s += '<text x="'+(C.x+12)+'" y="'+(C.y+18)+'" text-anchor="start" font-family="Inter,sans-serif" font-size="16" font-weight="700" fill="#0f172a">C</text>';
// подписи сторон
const labBC = 'BC='+(BCpx/22).toFixed(2);
const labAB = 'AB='+(ABpx/22).toFixed(2);
const labAC = 'AC='+(c/22).toFixed(2);
s += '<text x="'+((B.x+C.x)/2)+'" y="'+(cyBase+34)+'" text-anchor="middle" font-family="JetBrains Mono,monospace" font-size="12" fill="#b45309">'+labBC+'</text>';
s += '<text x="'+(B.x-32)+'" y="'+((A.y+B.y)/2)+'" text-anchor="middle" font-family="JetBrains Mono,monospace" font-size="12" fill="#b45309">'+labAB+'</text>';
// подпись гипотенузы — поднимем над линией AC
const midAC = {x:(A.x+C.x)/2, y:(A.y+C.y)/2};
const nAC = {x:-(C.y-A.y), y:(C.x-A.x)};
const nL = Math.sqrt(nAC.x*nAC.x+nAC.y*nAC.y)||1;
const labP = {x: midAC.x + 16*nAC.x/nL, y: midAC.y + 16*nAC.y/nL};
s += '<text x="'+labP.x+'" y="'+labP.y+'" text-anchor="middle" font-family="JetBrains Mono,monospace" font-size="12" fill="#b45309">'+labAC+'</text>';
svg.innerHTML = s;
// числовые значения
const sn = Math.sin(aRad), cs = Math.cos(aRad), tn = Math.tan(aRad), ct = 1/Math.tan(aRad);
out.innerHTML = '$\\sin '+aDeg+'^\\circ \\approx '+sn.toFixed(3)+'$ &nbsp;·&nbsp; $\\cos '+aDeg+'^\\circ \\approx '+cs.toFixed(3)+'$<br>'
+ '$\\tan '+aDeg+'^\\circ \\approx '+tn.toFixed(3)+'$ &nbsp;·&nbsp; $\\cot '+aDeg+'^\\circ \\approx '+ct.toFixed(3)+'$';
renderMath(out);
seen.add(aDeg);
if(seen.size >= 6 && !seen.has('done')){ addXp(10,'p1-iv1'); bumpProgress('p1', 15); seen.add('done'); }
}
sl.addEventListener('input', draw);
draw();
})();
/* IV2 — калькулятор сторон */
(function(){
const aI = document.getElementById('p1-iv2-a');
const cI = document.getElementById('p1-iv2-c');
const go = document.getElementById('p1-iv2-go');
const out= document.getElementById('p1-iv2-out');
const fb = document.getElementById('p1-iv2-fb');
let solved = 0;
function calc(){
const aDeg = parseFloat(aI.value), c = parseFloat(cI.value);
if(isNaN(aDeg) || isNaN(c)){ feedback(fb, false, '&#10007; Введи число для $\\alpha$ и $c$.'); return; }
if(aDeg<=0 || aDeg>=90){ feedback(fb, false, '&#10007; Угол должен быть в диапазоне (0°; 90°). Лучше 10..80.'); return; }
if(c<=0){ feedback(fb, false, '&#10007; Гипотенуза должна быть положительной.'); return; }
const r = deg2rad(aDeg);
const a = c * Math.sin(r);
const b = c * Math.cos(r);
out.innerHTML = '<b>$a = c \\sin \\alpha = '+c+' \\cdot \\sin '+aDeg+'^\\circ \\approx '+a.toFixed(2)+'$</b> (противолежащий)<br>'
+ '<b>$b = c \\cos \\alpha = '+c+' \\cdot \\cos '+aDeg+'^\\circ \\approx '+b.toFixed(2)+'$</b> (прилежащий)';
renderMath(out);
feedback(fb, true, '&#10003; Катеты найдены.');
solved++;
if(solved === 1){ addXp(10,'p1-iv2'); bumpProgress('p1', 15); }
}
go.addEventListener('click', calc);
calc();
})();
/* IV3 — Какое отношение? */
(function(){
const Q = [
{ expr:'$\\sin \\alpha$', ans:'ac', why:'противолежащий $a$ к гипотенузе $c$' },
{ expr:'$\\cos \\alpha$', ans:'bc', why:'прилежащий $b$ к гипотенузе $c$' },
{ expr:'$\\tan \\alpha$', ans:'ab', why:'противолежащий $a$ к прилежащему $b$' },
{ expr:'$\\cot \\alpha$', ans:'ba', why:'прилежащий $b$ к противолежащему $a$' },
{ expr:'$\\cos(90^\\circ - \\alpha)$', ans:'ac', why:'$= \\sin \\alpha = a/c$' },
{ expr:'$\\sin(90^\\circ - \\alpha)$', ans:'bc', why:'$= \\cos \\alpha = b/c$' },
{ expr:'$\\dfrac{1}{\\cot \\alpha}$', ans:'ab', why:'$= \\tan \\alpha = a/b$' },
{ expr:'$\\dfrac{\\sin \\alpha \\cdot \\cos \\alpha}{\\sin \\alpha}$', ans:'bc', why:'$= \\cos \\alpha = b/c$' },
];
let i = 0, score = 0;
function show(){
if(i >= Q.length){
document.getElementById('p1-iv3-q').innerHTML = '<b>Готово!</b> Результат: ' + score + ' / ' + Q.length;
if(score === Q.length){ addXp(15,'p1-iv3'); bumpProgress('p1', 25); }
else if(score >= Q.length - 2){ addXp(8,'p1-iv3'); bumpProgress('p1', 15); }
return;
}
document.getElementById('p1-iv3-i').textContent = (i+1);
document.getElementById('p1-iv3-s').textContent = score;
document.getElementById('p1-iv3-q').innerHTML = Q[i].expr;
renderMath(document.getElementById('p1-iv3-q'));
document.getElementById('p1-iv3-fb').style.display = 'none';
}
function answer(a){
if(i >= Q.length) return;
const fb = document.getElementById('p1-iv3-fb');
if(a === Q[i].ans){ score++; feedback(fb, true, '&#10003; Верно! '+Q[i].why+'. Дальше ▶'); }
else feedback(fb, false, '&#10007; Нет. '+Q[i].why+'. Дальше ▶');
document.getElementById('p1-iv3-s').textContent = score;
i++;
setTimeout(show, 1100);
}
['ac','bc','ab','ba'].forEach(k=>{
const b = document.getElementById('p1-iv3-'+k); if(b) b.addEventListener('click', ()=>answer(k));
});
show();
})();
/* IV4 — Тренажёр */
(function(){
const Q = [
{ q:'$\\sin 30^\\circ = ?$', ans:0.5, tol:0.02, hint:'эталон: $\\sin 30^\\circ = 1/2$' },
{ q:'$\\cos 60^\\circ = ?$', ans:0.5, tol:0.02, hint:'эталон: $\\cos 60^\\circ = 1/2$' },
{ q:'В прямоуг. треугольнике катет $a = 3$, гипотенуза $c = 5$. Найди $\\sin \\alpha$ ($\\alpha$ — угол, противолежащий $a$).', ans:0.6, tol:0.02, hint:'$\\sin \\alpha = a/c = 3/5 = 0{,}6$' },
{ q:'В прямоуг. треугольнике катеты 3 и 4. Чему равна гипотенуза?', ans:5, tol:0.05, hint:'$c = \\sqrt{9+16} = \\sqrt{25} = 5$' },
{ q:'$\\tan 45^\\circ = ?$', ans:1, tol:0.02, hint:'эталон: $\\tan 45^\\circ = 1$' },
{ q:'$c = 10$, $\\alpha = 30^\\circ$. Найди катет, противолежащий $\\alpha$.', ans:5, tol:0.05, hint:'$a = c \\sin \\alpha = 10 \\cdot 0{,}5 = 5$' },
];
let i = 0, score = 0;
function show(){
if(i >= Q.length){
document.getElementById('p1-iv4-q').innerHTML = '<b>Готово!</b> Результат: ' + score + ' / ' + Q.length;
if(score === Q.length){ addXp(15,'p1-iv4'); bumpProgress('p1', 25); }
else if(score >= 4){ addXp(8,'p1-iv4'); bumpProgress('p1', 15); }
return;
}
document.getElementById('p1-iv4-i').textContent = (i+1);
document.getElementById('p1-iv4-s').textContent = score;
document.getElementById('p1-iv4-q').innerHTML = Q[i].q;
document.getElementById('p1-iv4-ans').value = '';
renderMath(document.getElementById('p1-iv4-q'));
document.getElementById('p1-iv4-fb').style.display = 'none';
}
function go(){
if(i >= Q.length) return;
const fb = document.getElementById('p1-iv4-fb');
const ans = parseFloat(document.getElementById('p1-iv4-ans').value);
if(isNaN(ans)){ feedback(fb, false, '&#10007; Введи число.'); return; }
if(Math.abs(ans - Q[i].ans) <= Q[i].tol){ score++; feedback(fb, true, '&#10003; Верно! '+Q[i].hint+'. Дальше ▶'); }
else feedback(fb, false, '&#10007; Неверно. Ответ: '+Q[i].ans+' ('+Q[i].hint+'). Дальше ▶');
document.getElementById('p1-iv4-s').textContent = score;
i++;
setTimeout(show, 1300);
}
document.getElementById('p1-iv4-go').addEventListener('click', go);
document.getElementById('p1-iv4-ans').addEventListener('keydown', e=>{ if(e.key==='Enter') go(); });
document.getElementById('p1-iv4-start').addEventListener('click', ()=>{ i=0; score=0; show(); });
show();
})();
wireReadBtn('p1');
}
/* ===== §2 Решение прямоугольного треугольника ===== */
function buildP2(){
const box = document.getElementById('p2-body');
let html = '';
html += makeCard('theory', 'Что значит «решить треугольник»', '2.1', `
<p><b>Решить треугольник</b> — найти все его неизвестные элементы (стороны и углы) по заданным.</p>
<p>В прямоугольном треугольнике один угол всегда равен $90^\\circ$. Поэтому, чтобы найти все остальные элементы, достаточно знать <b>любые два из них</b> (кроме случая «два угла» — там стороны определяются с точностью до подобия):</p>
<ul style="padding-left:22px;line-height:1.95">
<li>две стороны (два катета; катет и гипотенузу), <b>или</b></li>
<li>одну сторону и один из острых углов.</li>
</ul>
<p>Обозначения: пусть $A, B, C$ — вершины (угол $C = 90^\\circ$), $a = BC$ — катет, лежащий напротив $A$, $b = AC$ — катет, лежащий напротив $B$, $c = AB$ — гипотенуза. Тогда $A + B = 90^\\circ$.</p>`);
html += makeCard('rule', 'Четыре случая решения', '2.2', `
<p>В каждом случае схема одна: применяем теорему Пифагора или тригонометрическую функцию.</p>
<p><b>Случай 1.</b> Даны два катета $a, b$.<br>
$c = \\sqrt{a^2 + b^2}$; &nbsp; $\\tan A = \\dfrac{a}{b} \\Rightarrow A$; &nbsp; $B = 90^\\circ - A$.</p>
<p><b>Случай 2.</b> Даны катет $a$ и гипотенуза $c$.<br>
$b = \\sqrt{c^2 - a^2}$; &nbsp; $\\sin A = \\dfrac{a}{c} \\Rightarrow A$; &nbsp; $B = 90^\\circ - A$.</p>
<p><b>Случай 3.</b> Даны катет $a$ и противолежащий угол $A$.<br>
$c = \\dfrac{a}{\\sin A}$; &nbsp; $b = \\dfrac{a}{\\tan A}$; &nbsp; $B = 90^\\circ - A$.</p>
<p><b>Случай 4.</b> Даны гипотенуза $c$ и угол $A$.<br>
$a = c \\sin A$; &nbsp; $b = c \\cos A$; &nbsp; $B = 90^\\circ - A$.</p>
<details class="spoiler"><summary>Совет по выбору формулы</summary><div class="spoiler-body">
<ul style="padding-left:18px;line-height:1.85;margin:0">
<li>Есть <b>обе</b> стороны без угла — теорема Пифагора + $\\tan$.</li>
<li>Есть <b>гипотенуза и катет</b> — Пифагор для второго катета, $\\sin$ для угла.</li>
<li>Есть <b>сторона и угол</b> — выбирай функцию так, чтобы данный отрезок встал в числитель или знаменатель: $\\sin$ для пары «противолежащий–гипотенуза», $\\cos$ — «прилежащий–гипотенуза», $\\tan$ — два катета.</li>
</ul>
</div></details>`);
html += makeCard('example', 'Пример: даны два катета', '2.3', `
<p><b>Дано:</b> $a = 6$, $b = 8$. <b>Найти:</b> $c$, $A$, $B$.</p>
<p><b>1.</b> $c = \\sqrt{a^2 + b^2} = \\sqrt{36 + 64} = \\sqrt{100} = 10$.</p>
<p><b>2.</b> $\\tan A = \\dfrac{a}{b} = \\dfrac{6}{8} = 0{,}75 \\Rightarrow A \\approx 36{,}87^\\circ$.</p>
<p><b>3.</b> $B = 90^\\circ - A \\approx 53{,}13^\\circ$.</p>
<p><b>Ответ:</b> $a = 6$, $b = 8$, $c = 10$, $A \\approx 36{,}87^\\circ$, $B \\approx 53{,}13^\\circ$.</p>
<details class="spoiler"><summary>Проверка через $\\sin$</summary><div class="spoiler-body">
$\\sin A = a / c = 6/10 = 0{,}6 \\Rightarrow A = \\arcsin 0{,}6 \\approx 36{,}87^\\circ$ — совпадает.
</div></details>`);
/* IV1 — Универсальный решатель */
html += `<div class="wg" id="p2-iv1">
<div class="wg-header"><span class="wg-badge">ИНТЕРАКТИВ 1</span><div class="wg-title">Универсальный решатель</div></div>
<div class="wg-help">Выбери тип задачи и введи данные — найдём все остальные элементы с подстановкой формул.</div>
<div style="display:flex;gap:6px;flex-wrap:wrap;margin-bottom:12px" id="p2-iv1-cases">
<button class="btn primary" data-c="1">Случай 1: $a, b$</button>
<button class="btn" data-c="2">Случай 2: $a, c$</button>
<button class="btn" data-c="3">Случай 3: $a, A$</button>
<button class="btn" data-c="4">Случай 4: $c, A$</button>
</div>
<div id="p2-iv1-inputs" style="display:flex;gap:10px;align-items:center;flex-wrap:wrap;justify-content:center;margin-bottom:10px"></div>
<div style="text-align:center;margin-bottom:10px"><button class="btn primary" id="p2-iv1-go">Решить</button></div>
<div id="p2-iv1-out" style="padding:12px 14px;background:var(--card);border-radius:9px;font-size:.96rem;line-height:1.75;min-height:50px"></div>
<div style="background:var(--card);border-radius:10px;padding:10px;overflow-x:auto;margin-top:10px">
<svg id="p2-iv1-svg" viewBox="0 0 400 300" style="width:100%;min-width:320px;height:auto;display:block"></svg>
</div>
<div class="feedback" id="p2-iv1-fb"></div>
</div>`;
/* IV2 — DnD сортер «Подбор формулы» */
html += `<div class="wg" id="p2-iv2">
<div class="wg-header"><span class="wg-badge">ИНТЕРАКТИВ 2</span><div class="wg-title">Подбор формулы по случаю</div></div>
<div class="wg-help">Распредели 6 формул по четырём случаям решения. Тапни карточку, потом — нужный ящик (или перетащи).</div>
<div class="dnd-hint"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M12 2v20"/><path d="M5 12h14"/></svg> 6 формул — 4 ящика</div>
<div id="p2-iv2-pool"></div>
<div style="display:grid;grid-template-columns:repeat(auto-fit,minmax(170px,1fr));gap:10px;margin-top:8px">
<div class="drop-box"><h5 data-cat="c1">Случай 1 ($a, b$)</h5><div class="drop-items" data-cat="c1"></div></div>
<div class="drop-box"><h5 data-cat="c2">Случай 2 ($a, c$)</h5><div class="drop-items" data-cat="c2"></div></div>
<div class="drop-box"><h5 data-cat="c3">Случай 3 ($a, A$)</h5><div class="drop-items" data-cat="c3"></div></div>
<div class="drop-box"><h5 data-cat="c4">Случай 4 ($c, A$)</h5><div class="drop-items" data-cat="c4"></div></div>
</div>
<div class="actions"><button class="btn primary" id="p2-iv2-check">Проверить</button><button class="btn" id="p2-iv2-reset">Сначала</button></div>
<div class="feedback" id="p2-iv2-fb"></div>
</div>`;
/* IV3 — Какой угол найдём? */
html += `<div class="wg" id="p2-iv3">
<div class="wg-header"><span class="wg-badge">ИНТЕРАКТИВ 3</span><div class="wg-title">Какой функцией найдём угол?</div></div>
<div class="wg-help">Дано — нужно выбрать тригонометрическую функцию, через которую быстрее всего найти угол $A$.</div>
<div class="score-display"><span>Задача <b id="p2-iv3-i">1</b> / 6</span><span>Очки: <b id="p2-iv3-s">0</b> / 6</span></div>
<div id="p2-iv3-q" style="padding:14px;background:var(--pri-soft);border-radius:10px;font-size:1.05rem;text-align:center;margin-bottom:10px"></div>
<div style="display:grid;grid-template-columns:repeat(auto-fit,minmax(130px,1fr));gap:8px">
<button class="btn primary" data-ans="sin" id="p2-iv3-sin">через $\\sin$</button>
<button class="btn primary" data-ans="cos" id="p2-iv3-cos">через $\\cos$</button>
<button class="btn primary" data-ans="tan" id="p2-iv3-tan">через $\\tan$</button>
</div>
<div class="feedback" id="p2-iv3-fb"></div>
</div>`;
/* IV4 — Тренажёр решения */
html += `<div class="wg" id="p2-iv4">
<div class="wg-header"><span class="wg-badge">ИНТЕРАКТИВ 4</span><div class="wg-title">Тренажёр решения треугольника</div></div>
<div class="wg-help">Реши задачу и введи число (округли до 1 знака, если получается дробное).</div>
<div class="score-display"><span>Задача <b id="p2-iv4-i">1</b> / 5</span><span>Очки: <b id="p2-iv4-s">0</b> / 5</span></div>
<div id="p2-iv4-q" style="padding:14px;background:var(--pri-soft);border-radius:10px;font-size:1.05rem;margin-bottom:10px;text-align:center"></div>
<div style="display:flex;gap:10px;align-items:center;flex-wrap:wrap;justify-content:center">
<span style="font-family:'JetBrains Mono',monospace">ответ =</span>
<input type="number" id="p2-iv4-ans" class="tinp" style="width:110px;text-align:center" step="0.1">
<button class="btn primary" id="p2-iv4-go">Проверить</button>
<button class="btn" id="p2-iv4-start">Заново</button>
</div>
<div class="feedback" id="p2-iv4-fb"></div>
</div>`;
html += secNav('p1', 'p3');
html += readButton('p2');
box.innerHTML = html;
renderMath(box);
/* IV1 — универсальный решатель */
(function(){
let curCase = 1;
const inputsBox = document.getElementById('p2-iv1-inputs');
const out = document.getElementById('p2-iv1-out');
const fb = document.getElementById('p2-iv1-fb');
const svg = document.getElementById('p2-iv1-svg');
const caseBtns = document.querySelectorAll('#p2-iv1-cases button');
let solved = 0;
function setInputs(){
let h = '';
if(curCase === 1){ h = '$a$ = <input type="number" id="p2-iv1-i1" class="tinp" style="width:80px;text-align:center" value="6">&nbsp; $b$ = <input type="number" id="p2-iv1-i2" class="tinp" style="width:80px;text-align:center" value="8">'; }
else if(curCase === 2){ h = '$a$ = <input type="number" id="p2-iv1-i1" class="tinp" style="width:80px;text-align:center" value="6">&nbsp; $c$ = <input type="number" id="p2-iv1-i2" class="tinp" style="width:80px;text-align:center" value="10">'; }
else if(curCase === 3){ h = '$a$ = <input type="number" id="p2-iv1-i1" class="tinp" style="width:80px;text-align:center" value="5">&nbsp; $A$ = <input type="number" id="p2-iv1-i2" class="tinp" style="width:80px;text-align:center" value="30">°'; }
else { h = '$c$ = <input type="number" id="p2-iv1-i1" class="tinp" style="width:80px;text-align:center" value="10">&nbsp; $A$ = <input type="number" id="p2-iv1-i2" class="tinp" style="width:80px;text-align:center" value="30">°'; }
inputsBox.innerHTML = h;
renderMath(inputsBox);
}
function drawTri(a, b, c, ADeg){
const aR = deg2rad(ADeg);
// нарисуем по c и углу A: BC=a (вертикаль), AC=b (горизонталь), угол A внизу слева
const scale = Math.min(200/Math.max(b,1), 160/Math.max(a,1), 22);
const Bx = 80, By = 250; // вершина B (прямой угол) внизу слева
const Ax = Bx, Ay = By; // ...A совпадёт ниже
// ставим: B (низ-лев, прямой угол), C (низ-прав, угол A?). Свяжем по стандарту: C=90°.
// На этот раз: C = (low-right) — прямой угол; A = (low-left); B = (top-right).
// a = BC (противолежащий A) — вертикальный; b = AC — горизонтальный.
const Cx = Bx + b*scale, Cy = By;
const A2 = {x: Bx, y: By};
const C2 = {x: Cx, y: Cy};
const B2 = {x: Cx, y: Cy - a*scale};
const uCA = unitVec(C2, A2);
const uCB = unitVec(C2, B2);
const uAB = unitVec(A2, B2);
const uAC = unitVec(A2, C2);
let s = '';
s += '<polygon points="'+A2.x+','+A2.y+' '+B2.x+','+B2.y+' '+C2.x+','+C2.y+'" fill="rgba(217,119,6,.08)" stroke="#b45309" stroke-width="2.2" stroke-linejoin="round"/>';
// прямой угол в C
s += '<polyline points="'+rightAngleMark(C2, uCA, uCB, 12)+'" fill="none" stroke="#0f172a" stroke-width="1.8"/>';
// угол A (от AC к AB)
s += '<path d="'+angleArcAuto(A2, uAC, uAB, 28)+'" fill="none" stroke="#dc2626" stroke-width="2"/>';
// вершины
s += '<circle cx="'+A2.x+'" cy="'+A2.y+'" r="4" fill="#0f172a"/>';
s += '<circle cx="'+B2.x+'" cy="'+B2.y+'" r="4" fill="#0f172a"/>';
s += '<circle cx="'+C2.x+'" cy="'+C2.y+'" r="4" fill="#0f172a"/>';
s += '<text x="'+(A2.x-12)+'" y="'+(A2.y+18)+'" text-anchor="end" font-family="Inter,sans-serif" font-size="16" font-weight="700">A</text>';
s += '<text x="'+(B2.x+12)+'" y="'+(B2.y-4)+'" text-anchor="start" font-family="Inter,sans-serif" font-size="16" font-weight="700">B</text>';
s += '<text x="'+(C2.x+12)+'" y="'+(C2.y+18)+'" text-anchor="start" font-family="Inter,sans-serif" font-size="16" font-weight="700">C</text>';
// подписи сторон
s += '<text x="'+((A2.x+C2.x)/2)+'" y="'+(C2.y+22)+'" text-anchor="middle" font-family="JetBrains Mono,monospace" font-size="12" fill="#b45309">b='+b.toFixed(2)+'</text>';
s += '<text x="'+(C2.x+22)+'" y="'+((B2.y+C2.y)/2)+'" text-anchor="start" font-family="JetBrains Mono,monospace" font-size="12" fill="#b45309">a='+a.toFixed(2)+'</text>';
const midAB = {x:(A2.x+B2.x)/2, y:(A2.y+B2.y)/2};
const nAB = {x:-(B2.y-A2.y), y:(B2.x-A2.x)};
const nL = Math.sqrt(nAB.x*nAB.x+nAB.y*nAB.y)||1;
const labP = {x:midAB.x - 18*nAB.x/nL, y:midAB.y - 18*nAB.y/nL};
s += '<text x="'+labP.x+'" y="'+labP.y+'" text-anchor="middle" font-family="JetBrains Mono,monospace" font-size="12" fill="#b45309">c='+c.toFixed(2)+'</text>';
s += '<text x="'+(A2.x+24)+'" y="'+(A2.y-10)+'" font-family="Inter,sans-serif" font-size="13" font-weight="700" fill="#dc2626">A='+ADeg.toFixed(1)+'°</text>';
svg.innerHTML = s;
}
function solve(){
const v1 = parseFloat(document.getElementById('p2-iv1-i1').value);
const v2 = parseFloat(document.getElementById('p2-iv1-i2').value);
if(isNaN(v1) || isNaN(v2)){ feedback(fb, false, '&#10007; Введи оба числа.'); return; }
let a, b, c, ADeg, BDeg, html;
if(curCase === 1){
a = v1; b = v2;
if(a<=0||b<=0){ feedback(fb,false,'&#10007; Стороны должны быть положительны.'); return; }
c = Math.sqrt(a*a+b*b);
ADeg = Math.atan2(a,b) * 180 / Math.PI;
BDeg = 90 - ADeg;
html = '<b>$c = \\sqrt{a^2+b^2} = \\sqrt{'+(a*a)+'+'+(b*b)+'} = \\sqrt{'+(a*a+b*b)+'} \\approx '+c.toFixed(2)+'$</b><br>'
+ '$\\tan A = a/b = '+a+'/'+b+' \\approx '+(a/b).toFixed(3)+' \\Rightarrow A \\approx '+ADeg.toFixed(2)+'^\\circ$<br>'
+ '$B = 90^\\circ - A \\approx '+BDeg.toFixed(2)+'^\\circ$';
} else if(curCase === 2){
a = v1; c = v2;
if(a<=0||c<=0||a>=c){ feedback(fb,false,'&#10007; Нужно $0 < a < c$.'); return; }
b = Math.sqrt(c*c-a*a);
ADeg = Math.asin(a/c) * 180 / Math.PI;
BDeg = 90 - ADeg;
html = '<b>$b = \\sqrt{c^2-a^2} = \\sqrt{'+(c*c)+'-'+(a*a)+'} = \\sqrt{'+(c*c-a*a)+'} \\approx '+b.toFixed(2)+'$</b><br>'
+ '$\\sin A = a/c = '+a+'/'+c+' \\approx '+(a/c).toFixed(3)+' \\Rightarrow A \\approx '+ADeg.toFixed(2)+'^\\circ$<br>'
+ '$B = 90^\\circ - A \\approx '+BDeg.toFixed(2)+'^\\circ$';
} else if(curCase === 3){
a = v1; ADeg = v2;
if(a<=0||ADeg<=0||ADeg>=90){ feedback(fb,false,'&#10007; Нужно $a > 0$ и $0 < A < 90°$.'); return; }
const r = deg2rad(ADeg);
c = a / Math.sin(r);
b = a / Math.tan(r);
BDeg = 90 - ADeg;
html = '<b>$c = a/\\sin A = '+a+'/\\sin '+ADeg+'^\\circ \\approx '+c.toFixed(2)+'$</b><br>'
+ '<b>$b = a/\\tan A = '+a+'/\\tan '+ADeg+'^\\circ \\approx '+b.toFixed(2)+'$</b><br>'
+ '$B = 90^\\circ - A = '+BDeg.toFixed(2)+'^\\circ$';
} else {
c = v1; ADeg = v2;
if(c<=0||ADeg<=0||ADeg>=90){ feedback(fb,false,'&#10007; Нужно $c > 0$ и $0 < A < 90°$.'); return; }
const r = deg2rad(ADeg);
a = c * Math.sin(r);
b = c * Math.cos(r);
BDeg = 90 - ADeg;
html = '<b>$a = c \\sin A = '+c+' \\cdot \\sin '+ADeg+'^\\circ \\approx '+a.toFixed(2)+'$</b><br>'
+ '<b>$b = c \\cos A = '+c+' \\cdot \\cos '+ADeg+'^\\circ \\approx '+b.toFixed(2)+'$</b><br>'
+ '$B = 90^\\circ - A = '+BDeg.toFixed(2)+'^\\circ$';
}
out.innerHTML = html;
renderMath(out);
drawTri(a, b, c, ADeg);
feedback(fb, true, '&#10003; Треугольник решён.');
solved++;
if(solved === 1){ addXp(10,'p2-iv1'); bumpProgress('p2', 15); }
}
caseBtns.forEach(b=>{
b.addEventListener('click', ()=>{
curCase = +b.dataset.c;
caseBtns.forEach(x=>{ x.classList.remove('primary'); x.classList.add('btn'); });
b.classList.remove('btn'); b.classList.add('btn','primary');
setInputs();
out.innerHTML = ''; svg.innerHTML='';
fb.style.display = 'none';
});
});
document.getElementById('p2-iv1-go').addEventListener('click', solve);
setInputs();
solve();
})();
/* IV2 — DnD сортер «Подбор формулы» */
(function(){
const items = [
{ id:'f1', cat:'c1', html:'$c = \\sqrt{a^2 + b^2}$' },
{ id:'f2', cat:'c1', html:'$\\tan A = a / b$' },
{ id:'f3', cat:'c2', html:'$b = \\sqrt{c^2 - a^2}$' },
{ id:'f4', cat:'c3', html:'$c = a / \\sin A$' },
{ id:'f5', cat:'c4', html:'$a = c \\sin A$' },
{ id:'f6', cat:'c4', html:'$b = c \\cos A$' },
];
const sorter = setupSorter({
poolId:'p2-iv2-pool',
scopeSelector:'#p2-iv2',
items: items,
cats:['c1','c2','c3','c4'],
columnLayout:false,
});
document.getElementById('p2-iv2-check').addEventListener('click', ()=>{
const fb = document.getElementById('p2-iv2-fb');
const placedCount = items.filter(it => sorter.placed[it.id]).length;
const correct = items.filter(it => sorter.placed[it.id] === it.cat).length;
if(placedCount < items.length){ feedback(fb, false, '&#10007; Размести все 6 формул.'); return; }
if(correct === items.length){ feedback(fb, true, '&#10003; Все 6 на месте! +10 XP'); addXp(10,'p2-iv2'); bumpProgress('p2', 15); }
else feedback(fb, false, '&#10007; Правильно ' + correct + ' из 6. Попробуй ещё.');
});
document.getElementById('p2-iv2-reset').addEventListener('click', ()=>{ sorter.reset(); document.getElementById('p2-iv2-fb').style.display = 'none'; });
})();
/* IV3 — Какой функцией найдём угол */
(function(){
const Q = [
{ expr:'Даны $a$ (противолежащий $A$) и гипотенуза $c$.', ans:'sin', why:'$\\sin A = a/c$' },
{ expr:'Даны $b$ (прилежащий $A$) и гипотенуза $c$.', ans:'cos', why:'$\\cos A = b/c$' },
{ expr:'Даны оба катета $a$ и $b$.', ans:'tan', why:'$\\tan A = a/b$' },
{ expr:'Известно произведение $c \\sin A$ и значение $c$. Найти $A$.', ans:'sin', why:'$\\sin A = (c \\sin A)/c$' },
{ expr:'Даны два катета (противолежащий и прилежащий).', ans:'tan', why:'$\\tan A$ — отношение катетов' },
{ expr:'Даны противолежащий катет и гипотенуза.', ans:'sin', why:'$\\sin A = \\text{противолежащий}/\\text{гипотенуза}$' },
];
let i = 0, score = 0;
function show(){
if(i >= Q.length){
document.getElementById('p2-iv3-q').innerHTML = '<b>Готово!</b> Результат: ' + score + ' / ' + Q.length;
if(score === Q.length){ addXp(15,'p2-iv3'); bumpProgress('p2', 25); }
else if(score >= Q.length - 1){ addXp(8,'p2-iv3'); bumpProgress('p2', 15); }
return;
}
document.getElementById('p2-iv3-i').textContent = (i+1);
document.getElementById('p2-iv3-s').textContent = score;
document.getElementById('p2-iv3-q').innerHTML = Q[i].expr;
renderMath(document.getElementById('p2-iv3-q'));
document.getElementById('p2-iv3-fb').style.display = 'none';
}
function answer(a){
if(i >= Q.length) return;
const fb = document.getElementById('p2-iv3-fb');
if(a === Q[i].ans){ score++; feedback(fb, true, '&#10003; Верно! '+Q[i].why+'. Дальше ▶'); }
else feedback(fb, false, '&#10007; Нет. Нужно: '+Q[i].why+'. Дальше ▶');
document.getElementById('p2-iv3-s').textContent = score;
i++;
setTimeout(show, 1100);
}
['sin','cos','tan'].forEach(k=>{
const b = document.getElementById('p2-iv3-'+k); if(b) b.addEventListener('click', ()=>answer(k));
});
show();
})();
/* IV4 — Тренажёр решения */
(function(){
const Q = [
{ q:'Катеты 3 и 4. Чему равна гипотенуза?', ans:5, tol:0.1, hint:'$c = \\sqrt{9+16} = 5$' },
{ q:'Катет $5$, гипотенуза $13$. Найди второй катет.', ans:12, tol:0.1, hint:'$b = \\sqrt{169-25} = \\sqrt{144} = 12$' },
{ q:'В прямоуг. треуг. $c = 20$, $A = 30^\\circ$. Найди катет $a$ (противолежащий $A$).', ans:10, tol:0.1, hint:'$a = c \\sin A = 20 \\cdot 0{,}5 = 10$' },
{ q:'$c = 10$, $a = 6$. Найди угол $A$ в градусах (округли до целых).', ans:37, tol:1, hint:'$\\sin A = 0{,}6 \\Rightarrow A \\approx 36{,}87^\\circ \\approx 37^\\circ$' },
{ q:'Катет $a = 3$, угол $A = 45^\\circ$. Чему равна гипотенуза?', ans:4.2, tol:0.2, hint:'$c = a/\\sin 45^\\circ = 3\\sqrt{2} \\approx 4{,}24$' },
];
let i = 0, score = 0;
function show(){
if(i >= Q.length){
document.getElementById('p2-iv4-q').innerHTML = '<b>Готово!</b> Результат: ' + score + ' / ' + Q.length;
if(score === Q.length){ addXp(15,'p2-iv4'); bumpProgress('p2', 25); }
else if(score >= 3){ addXp(8,'p2-iv4'); bumpProgress('p2', 15); }
return;
}
document.getElementById('p2-iv4-i').textContent = (i+1);
document.getElementById('p2-iv4-s').textContent = score;
document.getElementById('p2-iv4-q').innerHTML = Q[i].q;
document.getElementById('p2-iv4-ans').value = '';
renderMath(document.getElementById('p2-iv4-q'));
document.getElementById('p2-iv4-fb').style.display = 'none';
}
function go(){
if(i >= Q.length) return;
const fb = document.getElementById('p2-iv4-fb');
const ans = parseFloat(document.getElementById('p2-iv4-ans').value);
if(isNaN(ans)){ feedback(fb, false, '&#10007; Введи число.'); return; }
if(Math.abs(ans - Q[i].ans) <= Q[i].tol){ score++; feedback(fb, true, '&#10003; Верно! '+Q[i].hint+'. Дальше ▶'); }
else feedback(fb, false, '&#10007; Неверно. Ответ: '+Q[i].ans+' ('+Q[i].hint+'). Дальше ▶');
document.getElementById('p2-iv4-s').textContent = score;
i++;
setTimeout(show, 1300);
}
document.getElementById('p2-iv4-go').addEventListener('click', go);
document.getElementById('p2-iv4-ans').addEventListener('keydown', e=>{ if(e.key==='Enter') go(); });
document.getElementById('p2-iv4-start').addEventListener('click', ()=>{ i=0; score=0; show(); });
show();
})();
wireReadBtn('p2');
}
function buildP3(){ _stubBuilder('p3', '§3', 'Тригонометрические формулы', 'p2', 'p4'); }
function buildP4(){ _stubBuilder('p4', '§4', 'sin, cos, tg, ctg тупого угла', 'p3', 'p5'); }
function buildP5(){ _stubBuilder('p5', '§5', 'Формулы площади', 'p4', 'p6'); }
function buildP6(){ _stubBuilder('p6', '§6', 'Среднее геометрическое', 'p5', 'final1'); }
function buildFinal1(){
const body = document.getElementById('final1-body');
let html = '';
html += makeCard('theory', 'Финал главы 1', '★', `
<p>Итоговый раздел главы <b>«Соотношения в прямоугольном треугольнике»</b> будет добавлен в следующих обновлениях.</p>
<p style="color:var(--muted);font-size:.9rem">Раздел Phase 7.</p>`);
html += readButton('final1');
html += secNav('p6', null);
body.innerHTML = html;
wireReadBtn('final1');
if(window.renderMathInElement) renderMath(body);
}
/* ===== Search ===== */
const SEARCH_INDEX = (function(){
const arr=[];
PARAS.forEach(p=>arr.push({kind:'Параграф',title:p.num+' '+p.name,desc:p.sub||'',sec:p.id}));
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); }
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(); initSearch();
buildParaSelector(); refreshProgressUI(); loadServerReadState(); goTo('p1');
setTimeout(()=>achievement('start'), 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);
</script>
</body>
</html>