Files
Learn_System/frontend/textbooks/geometry_9_ch1.html
T

2683 lines
199 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');
}
/* ===== §3 Тригонометрические формулы ===== */
function buildP3(){
const box = document.getElementById('p3-body');
let html = '';
html += makeCard('theory', 'Основное тригонометрическое тождество', '3.1', `
<p>Возьмём прямоугольный треугольник с острым углом $\\alpha$, противолежащим катетом $a$, прилежащим катетом $b$ и гипотенузой $c$. По определению $\\sin \\alpha = a/c$, $\\cos \\alpha = b/c$.</p>
<p>По теореме Пифагора $a^2 + b^2 = c^2$. Поделим обе части на $c^2$:</p>
<p style="text-align:center">$\\dfrac{a^2}{c^2} + \\dfrac{b^2}{c^2} = 1 \\;\\Rightarrow\\; \\left(\\dfrac{a}{c}\\right)^2 + \\left(\\dfrac{b}{c}\\right)^2 = 1.$</p>
<p style="text-align:center;font-size:1.1rem"><b>$\\sin^2 \\alpha + \\cos^2 \\alpha = 1$</b></p>
<p><b>Следствия</b> (для острого $\\alpha$):</p>
<ul style="padding-left:22px;line-height:1.95">
<li>$\\sin \\alpha = \\sqrt{1 - \\cos^2 \\alpha}$</li>
<li>$\\cos \\alpha = \\sqrt{1 - \\sin^2 \\alpha}$</li>
</ul>
<details class="spoiler"><summary>Зачем это нужно?</summary><div class="spoiler-body">
Если известно одно из значений ($\\sin \\alpha$ или $\\cos \\alpha$), второе мгновенно вычисляется без чертежа треугольника. Это первое настоящее тригонометрическое <b>тождество</b> — равенство, верное для любого допустимого угла.
</div></details>`);
html += makeCard('rule', 'Связь tg и ctg с sin и cos', '3.2', `
<p>Из определений $\\tan \\alpha = \\dfrac{a}{b}$ и $\\sin \\alpha / \\cos \\alpha = \\dfrac{a/c}{b/c} = \\dfrac{a}{b}$ получаем:</p>
<ul style="padding-left:22px;line-height:1.95">
<li>$\\tan \\alpha = \\dfrac{\\sin \\alpha}{\\cos \\alpha}$</li>
<li>$\\cot \\alpha = \\dfrac{\\cos \\alpha}{\\sin \\alpha}$</li>
<li>$\\tan \\alpha \\cdot \\cot \\alpha = 1$ &nbsp;(значит, $\\cot \\alpha = 1/\\tan \\alpha$)</li>
</ul>
<p>Поделим основное тождество $\\sin^2 \\alpha + \\cos^2 \\alpha = 1$ на $\\cos^2 \\alpha$:</p>
<p style="text-align:center">$\\dfrac{\\sin^2 \\alpha}{\\cos^2 \\alpha} + 1 = \\dfrac{1}{\\cos^2 \\alpha} \\;\\Rightarrow\\; \\boxed{\\,1 + \\tan^2 \\alpha = \\dfrac{1}{\\cos^2 \\alpha}\\,}$</p>
<p>Аналогично, поделив на $\\sin^2 \\alpha$:</p>
<p style="text-align:center">$\\boxed{\\,1 + \\cot^2 \\alpha = \\dfrac{1}{\\sin^2 \\alpha}\\,}$</p>`);
html += makeCard('example', 'Откуда берутся 30°, 45°, 60°', '3.3', `
<p><b>1) Треугольник 45°-45°-90°</b> — равнобедренный прямоугольный. Возьмём оба катета равными $1$. Тогда гипотенуза $\\sqrt{1^2 + 1^2} = \\sqrt{2}$. Значит:</p>
<p style="text-align:center">$\\sin 45^\\circ = \\cos 45^\\circ = \\dfrac{1}{\\sqrt{2}} = \\dfrac{\\sqrt{2}}{2},\\quad \\tan 45^\\circ = \\cot 45^\\circ = 1.$</p>
<p><b>2) Треугольник 30°-60°-90°</b> — половина равностороннего. У равностороннего треугольника со стороной $2$ все углы $60^\\circ$. Высота делит его на два прямоугольных треугольника с катетами $1$ (половина основания) и $\\sqrt{2^2 - 1^2} = \\sqrt{3}$, гипотенузой $2$. Углы: $30^\\circ$ (при вершине) и $60^\\circ$ (у основания). Получаем:</p>
<p style="text-align:center">$\\sin 30^\\circ = \\dfrac{1}{2},\\; \\cos 30^\\circ = \\dfrac{\\sqrt{3}}{2},\\; \\tan 30^\\circ = \\dfrac{1}{\\sqrt{3}}.$</p>
<p style="text-align:center">$\\sin 60^\\circ = \\dfrac{\\sqrt{3}}{2},\\; \\cos 60^\\circ = \\dfrac{1}{2},\\; \\tan 60^\\circ = \\sqrt{3}.$</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>`);
/* IV1 — Три эталонных треугольника */
html += `<div class="wg" id="p3-iv1">
<div class="wg-header"><span class="wg-badge">ИНТЕРАКТИВ 1</span><div class="wg-title">Три эталонных треугольника</div></div>
<div class="wg-help">Слева — треугольник $30^\\circ\\text{-}60^\\circ\\text{-}90^\\circ$ (катеты $1$ и $\\sqrt{3}$, гипотенуза $2$). Справа — равнобедренный $45^\\circ\\text{-}45^\\circ\\text{-}90^\\circ$ (катеты $1$, гипотенуза $\\sqrt{2}$).</div>
<div style="background:var(--card);border-radius:10px;padding:10px;overflow-x:auto">
<svg id="p3-iv1-svg" viewBox="0 0 600 220" style="width:100%;min-width:420px;height:auto;display:block"></svg>
</div>
<div style="overflow-x:auto;margin-top:10px">
<table style="width:100%;border-collapse:collapse;font-size:.92rem;background:var(--card);border:1.5px solid var(--border);border-radius:9px;overflow:hidden">
<thead><tr style="background:var(--pri-soft)">
<th style="padding:6px;border-bottom:1px solid var(--border)">Угол</th>
<th style="padding:6px;border-bottom:1px solid var(--border)">$\\sin$</th>
<th style="padding:6px;border-bottom:1px solid var(--border)">$\\cos$</th>
<th style="padding:6px;border-bottom:1px solid var(--border)">$\\tan$</th>
<th style="padding:6px;border-bottom:1px solid var(--border)">$\\cot$</th>
</tr></thead>
<tbody>
<tr><td style="padding:6px;border-bottom:1px solid var(--border);text-align:center"><b>30°</b></td><td style="padding:6px;border-bottom:1px solid var(--border);text-align:center">$\\tfrac{1}{2}$</td><td style="padding:6px;border-bottom:1px solid var(--border);text-align:center">$\\tfrac{\\sqrt{3}}{2}$</td><td style="padding:6px;border-bottom:1px solid var(--border);text-align:center">$\\tfrac{1}{\\sqrt{3}}$</td><td style="padding:6px;border-bottom:1px solid var(--border);text-align:center">$\\sqrt{3}$</td></tr>
<tr><td style="padding:6px;border-bottom:1px solid var(--border);text-align:center"><b>45°</b></td><td style="padding:6px;border-bottom:1px solid var(--border);text-align:center">$\\tfrac{\\sqrt{2}}{2}$</td><td style="padding:6px;border-bottom:1px solid var(--border);text-align:center">$\\tfrac{\\sqrt{2}}{2}$</td><td style="padding:6px;border-bottom:1px solid var(--border);text-align:center">$1$</td><td style="padding:6px;border-bottom:1px solid var(--border);text-align:center">$1$</td></tr>
<tr><td style="padding:6px;text-align:center"><b>60°</b></td><td style="padding:6px;text-align:center">$\\tfrac{\\sqrt{3}}{2}$</td><td style="padding:6px;text-align:center">$\\tfrac{1}{2}$</td><td style="padding:6px;text-align:center">$\\sqrt{3}$</td><td style="padding:6px;text-align:center">$\\tfrac{1}{\\sqrt{3}}$</td></tr>
</tbody>
</table>
</div>
</div>`;
/* IV2 — Калькулятор тождеств */
html += `<div class="wg" id="p3-iv2">
<div class="wg-header"><span class="wg-badge">ИНТЕРАКТИВ 2</span><div class="wg-title">Калькулятор тождеств</div></div>
<div class="wg-help">Введи $\\sin \\alpha$ (значение от $0{,}01$ до $0{,}99$). Получишь $\\cos \\alpha$, $\\tan \\alpha$, $\\cot \\alpha$ и сам угол $\\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">$\\sin \\alpha$ =</span>
<input type="range" id="p3-iv2-s" min="0.01" max="0.99" step="0.01" value="0.6" style="width:200px;accent-color:var(--pri)">
<input type="number" id="p3-iv2-sn" class="tinp" style="width:90px;text-align:center" value="0.6" min="0.01" max="0.99" step="0.01">
<button class="btn primary" id="p3-iv2-go">Вычислить</button>
</div>
<div id="p3-iv2-out" style="padding:12px 14px;background:var(--card);border-radius:9px;text-align:center;font-size:1rem;line-height:1.85;min-height:50px"></div>
<div class="feedback" id="p3-iv2-fb"></div>
</div>`;
/* IV3 — Quickfire «Какое значение?» */
html += `<div class="wg" id="p3-iv3">
<div class="wg-header"><span class="wg-badge">ИНТЕРАКТИВ 3</span><div class="wg-title">Какое значение?</div></div>
<div class="wg-help">Дано тригонометрическое выражение — выбери его точное значение из 4 вариантов.</div>
<div class="score-display"><span>Задача <b id="p3-iv3-i">1</b> / 8</span><span>Очки: <b id="p3-iv3-s">0</b> / 8</span></div>
<div id="p3-iv3-q" style="padding:14px;background:var(--pri-soft);border-radius:10px;font-size:1.2rem;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="half" id="p3-iv3-half">$\\tfrac{1}{2}$</button>
<button class="btn primary" data-ans="s2" id="p3-iv3-s2">$\\tfrac{\\sqrt{2}}{2}$</button>
<button class="btn primary" data-ans="s3" id="p3-iv3-s3">$\\tfrac{\\sqrt{3}}{2}$</button>
<button class="btn primary" data-ans="one" id="p3-iv3-one">$1$</button>
</div>
<div class="feedback" id="p3-iv3-fb"></div>
</div>`;
/* IV4 — Применение тождеств */
html += `<div class="wg" id="p3-iv4">
<div class="wg-header"><span class="wg-badge">ИНТЕРАКТИВ 4</span><div class="wg-title">Применение тождеств</div></div>
<div class="wg-help">Реши задачу и введи число (целое или десятичную дробь до 3 знаков).</div>
<div class="score-display"><span>Задача <b id="p3-iv4-i">1</b> / 6</span><span>Очки: <b id="p3-iv4-s">0</b> / 6</span></div>
<div id="p3-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="p3-iv4-ans" class="tinp" style="width:110px;text-align:center" step="0.001">
<button class="btn primary" id="p3-iv4-go">Проверить</button>
<button class="btn" id="p3-iv4-start">Заново</button>
</div>
<div class="feedback" id="p3-iv4-fb"></div>
</div>`;
html += secNav('p2', 'p4');
html += readButton('p3');
box.innerHTML = html;
renderMath(box);
/* IV1 — рисуем три эталонных треугольника */
(function(){
const svg = document.getElementById('p3-iv1-svg');
if(!svg) return;
let s = '';
// Треугольник 30-60-90: катеты 1 и sqrt(3), гипотенуза 2. Используем масштаб 60 px = 1 ед.
// Левый: BC = sqrt(3) (горизонт., прилежащий 30°), AB = 1 (вертикальный, противолежащий 30°)
// Вершины: A слева внизу, B слева вверху (прямой угол), C справа внизу.
// Угол 30° при C, угол 60° при A.
(function(){
const u = 60;
const Ax = 40, Ay = 180;
const A = {x: Ax, y: Ay};
const B = {x: Ax, y: Ay - u}; // катет AB = 1
const C = {x: Ax + Math.sqrt(3)*u, y: Ay}; // катет BC = sqrt(3)
const uBA = unitVec(B, A);
const uBC = unitVec(B, C);
const uCA = unitVec(C, A);
const uCB = unitVec(C, B);
const uAB = unitVec(A, B);
const uAC = unitVec(A, C);
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" stroke-linejoin="round"/>';
s += '<polyline points="'+rightAngleMark(B, uBA, uBC, 10)+'" fill="none" stroke="#0f172a" stroke-width="1.6"/>';
s += '<path d="'+angleArcAuto(C, uCA, uCB, 26)+'" fill="none" stroke="#dc2626" stroke-width="2"/>';
s += '<path d="'+angleArcAuto(A, uAB, uAC, 22)+'" fill="none" stroke="#0ea5e9" stroke-width="2"/>';
// подписи углов
s += '<text x="'+(C.x-30)+'" y="'+(C.y-8)+'" text-anchor="middle" font-family="Inter,sans-serif" font-size="13" font-weight="700" fill="#dc2626">30°</text>';
s += '<text x="'+(A.x+18)+'" y="'+(A.y-8)+'" text-anchor="middle" font-family="Inter,sans-serif" font-size="13" font-weight="700" fill="#0ea5e9">60°</text>';
// вершины и подписи
['A','B','C'].forEach((nm,i)=>{ const P=[A,B,C][i]; s+='<circle cx="'+P.x+'" cy="'+P.y+'" r="3.5" fill="#0f172a"/>'; });
s += '<text x="'+(A.x-10)+'" y="'+(A.y+15)+'" text-anchor="end" font-family="Inter,sans-serif" font-size="14" font-weight="700">A</text>';
s += '<text x="'+(B.x-10)+'" y="'+(B.y-4)+'" text-anchor="end" font-family="Inter,sans-serif" font-size="14" font-weight="700">B</text>';
s += '<text x="'+(C.x+10)+'" y="'+(C.y+15)+'" text-anchor="start" font-family="Inter,sans-serif" font-size="14" font-weight="700">C</text>';
// длины сторон
s += '<text x="'+(B.x-22)+'" y="'+((A.y+B.y)/2+4)+'" text-anchor="middle" font-family="JetBrains Mono,monospace" font-size="12" fill="#b45309">1</text>';
s += '<text x="'+((B.x+C.x)/2)+'" y="'+(A.y+22)+'" text-anchor="middle" font-family="JetBrains Mono,monospace" font-size="12" fill="#b45309">√3</text>';
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">2</text>';
// заголовок
s += '<text x="'+((A.x+C.x)/2)+'" y="22" text-anchor="middle" font-family="Unbounded,sans-serif" font-size="13" font-weight="800" fill="#92400e">30\xb0-60\xb0-90\xb0</text>';
})();
// Треугольник 45-45-90: оба катета 1, гипотенуза sqrt(2). Масштаб тот же.
(function(){
const u = 90; // покрупнее
const Ax = 360, Ay = 180;
const A = {x: Ax, y: Ay};
const B = {x: Ax, y: Ay - u};
const C = {x: Ax + u, y: Ay};
const uBA = unitVec(B, A);
const uBC = unitVec(B, C);
const uCA = unitVec(C, A);
const uCB = unitVec(C, B);
const uAB = unitVec(A, B);
const uAC = unitVec(A, C);
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" stroke-linejoin="round"/>';
s += '<polyline points="'+rightAngleMark(B, uBA, uBC, 10)+'" fill="none" stroke="#0f172a" stroke-width="1.6"/>';
s += '<path d="'+angleArcAuto(C, uCA, uCB, 28)+'" fill="none" stroke="#dc2626" stroke-width="2"/>';
s += '<path d="'+angleArcAuto(A, uAB, uAC, 26)+'" fill="none" stroke="#0ea5e9" stroke-width="2"/>';
s += '<text x="'+(C.x-30)+'" y="'+(C.y-8)+'" text-anchor="middle" font-family="Inter,sans-serif" font-size="13" font-weight="700" fill="#dc2626">45°</text>';
s += '<text x="'+(A.x+22)+'" y="'+(A.y-8)+'" text-anchor="middle" font-family="Inter,sans-serif" font-size="13" font-weight="700" fill="#0ea5e9">45°</text>';
['A','B','C'].forEach((nm,i)=>{ const P=[A,B,C][i]; s+='<circle cx="'+P.x+'" cy="'+P.y+'" r="3.5" fill="#0f172a"/>'; });
s += '<text x="'+(A.x-10)+'" y="'+(A.y+15)+'" text-anchor="end" font-family="Inter,sans-serif" font-size="14" font-weight="700">A</text>';
s += '<text x="'+(B.x-10)+'" y="'+(B.y-4)+'" text-anchor="end" font-family="Inter,sans-serif" font-size="14" font-weight="700">B</text>';
s += '<text x="'+(C.x+10)+'" y="'+(C.y+15)+'" text-anchor="start" font-family="Inter,sans-serif" font-size="14" font-weight="700">C</text>';
s += '<text x="'+(B.x-22)+'" y="'+((A.y+B.y)/2+4)+'" text-anchor="middle" font-family="JetBrains Mono,monospace" font-size="12" fill="#b45309">1</text>';
s += '<text x="'+((B.x+C.x)/2)+'" y="'+(A.y+22)+'" text-anchor="middle" font-family="JetBrains Mono,monospace" font-size="12" fill="#b45309">1</text>';
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">√2</text>';
s += '<text x="'+((A.x+C.x)/2)+'" y="22" text-anchor="middle" font-family="Unbounded,sans-serif" font-size="13" font-weight="800" fill="#92400e">45\xb0-45\xb0-90\xb0</text>';
})();
svg.innerHTML = s;
addXp(10,'p3-iv1'); bumpProgress('p3', 15);
})();
/* IV2 — Калькулятор тождеств */
(function(){
const sl = document.getElementById('p3-iv2-s');
const inp = document.getElementById('p3-iv2-sn');
const go = document.getElementById('p3-iv2-go');
const out = document.getElementById('p3-iv2-out');
const fb = document.getElementById('p3-iv2-fb');
let solved = 0;
function sync(from){
if(from==='range') inp.value = sl.value;
else { const v = Math.max(0.01, Math.min(0.99, parseFloat(inp.value)||0.5)); sl.value = v; inp.value = v; }
}
function calc(){
const sn = parseFloat(sl.value);
if(isNaN(sn) || sn<=0 || sn>=1){ feedback(fb, false, '&#10007; $\\sin \\alpha$ должен быть в (0; 1).'); return; }
const cs = Math.sqrt(1 - sn*sn);
const tn = sn / cs;
const ct = cs / sn;
const aDeg = Math.asin(sn) * 180 / Math.PI;
out.innerHTML = '$\\sin \\alpha = '+sn.toFixed(3)+'$ &nbsp;·&nbsp; <b>$\\cos \\alpha = \\sqrt{1 - \\sin^2 \\alpha} \\approx '+cs.toFixed(3)+'$</b><br>'
+ '$\\tan \\alpha \\approx '+tn.toFixed(3)+'$ &nbsp;·&nbsp; $\\cot \\alpha \\approx '+ct.toFixed(3)+'$<br>'
+ '<span style="color:var(--pri2);font-weight:700">$\\alpha \\approx '+aDeg.toFixed(2)+'^\\circ$</span>';
renderMath(out);
feedback(fb, true, '&#10003; Все значения вычислены через основное тождество.');
solved++;
if(solved === 1){ addXp(10,'p3-iv2'); bumpProgress('p3', 15); }
}
sl.addEventListener('input', ()=>{ sync('range'); calc(); });
inp.addEventListener('input', ()=>{ sync('input'); });
go.addEventListener('click', calc);
calc();
})();
/* IV3 — Какое значение? */
(function(){
const Q = [
{ expr:'$\\sin 30^\\circ$', ans:'half', why:'эталон: $\\sin 30^\\circ = 1/2$' },
{ expr:'$\\cos 60^\\circ$', ans:'half', why:'эталон: $\\cos 60^\\circ = 1/2$' },
{ expr:'$\\sin 45^\\circ$', ans:'s2', why:'эталон: $\\sin 45^\\circ = \\sqrt{2}/2$' },
{ expr:'$\\cos 45^\\circ$', ans:'s2', why:'эталон: $\\cos 45^\\circ = \\sqrt{2}/2$' },
{ expr:'$\\sin 60^\\circ$', ans:'s3', why:'эталон: $\\sin 60^\\circ = \\sqrt{3}/2$' },
{ expr:'$\\cos 30^\\circ$', ans:'s3', why:'эталон: $\\cos 30^\\circ = \\sqrt{3}/2$' },
{ expr:'$\\sin^2 30^\\circ + \\cos^2 30^\\circ$', ans:'one', why:'основное тождество: $\\sin^2 + \\cos^2 = 1$' },
{ expr:'$\\tan 45^\\circ$', ans:'one', why:'эталон: $\\tan 45^\\circ = 1$' },
];
let i = 0, score = 0;
function show(){
if(i >= Q.length){
document.getElementById('p3-iv3-q').innerHTML = '<b>Готово!</b> Результат: ' + score + ' / ' + Q.length;
if(score === Q.length){ addXp(15,'p3-iv3'); bumpProgress('p3', 25); }
else if(score >= Q.length - 2){ addXp(8,'p3-iv3'); bumpProgress('p3', 15); }
return;
}
document.getElementById('p3-iv3-i').textContent = (i+1);
document.getElementById('p3-iv3-s').textContent = score;
document.getElementById('p3-iv3-q').innerHTML = Q[i].expr;
renderMath(document.getElementById('p3-iv3-q'));
document.getElementById('p3-iv3-fb').style.display = 'none';
}
function answer(a){
if(i >= Q.length) return;
const fb = document.getElementById('p3-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('p3-iv3-s').textContent = score;
i++;
setTimeout(show, 1100);
}
['half','s2','s3','one'].forEach(k=>{
const b = document.getElementById('p3-iv3-'+k); if(b) b.addEventListener('click', ()=>answer(k));
});
show();
})();
/* IV4 — Применение тождеств */
(function(){
const Q = [
{ q:'Если $\\sin \\alpha = 0{,}6$ и $\\alpha$ — острый, найди $\\cos \\alpha$.', ans:0.8, tol:0.02, hint:'$\\cos \\alpha = \\sqrt{1 - 0{,}36} = \\sqrt{0{,}64} = 0{,}8$' },
{ q:'$\\sin^2 70^\\circ + \\cos^2 70^\\circ = ?$', ans:1, tol:0.01, hint:'основное тождество' },
{ q:'Если $\\cos \\alpha = 0{,}5$ и $\\alpha$ — острый, найди $\\sin \\alpha$.', ans:0.866, tol:0.01, hint:'$\\sin \\alpha = \\sqrt{1 - 0{,}25} = \\sqrt{0{,}75} = \\sqrt{3}/2 \\approx 0{,}866$' },
{ q:'$\\tan 60^\\circ \\cdot \\cot 60^\\circ = ?$', ans:1, tol:0.01, hint:'$\\tan \\alpha \\cdot \\cot \\alpha = 1$' },
{ q:'Если $\\tan \\alpha = 1$, то $\\alpha = ?$ (в градусах).', ans:45, tol:0.5, hint:'$\\tan 45^\\circ = 1$' },
{ q:'$\\sin 30^\\circ + \\cos 60^\\circ = ?$', ans:1, tol:0.01, hint:'$\\tfrac{1}{2} + \\tfrac{1}{2} = 1$' },
];
let i = 0, score = 0;
function show(){
if(i >= Q.length){
document.getElementById('p3-iv4-q').innerHTML = '<b>Готово!</b> Результат: ' + score + ' / ' + Q.length;
if(score === Q.length){ addXp(15,'p3-iv4'); bumpProgress('p3', 25); }
else if(score >= 4){ addXp(8,'p3-iv4'); bumpProgress('p3', 15); }
return;
}
document.getElementById('p3-iv4-i').textContent = (i+1);
document.getElementById('p3-iv4-s').textContent = score;
document.getElementById('p3-iv4-q').innerHTML = Q[i].q;
document.getElementById('p3-iv4-ans').value = '';
renderMath(document.getElementById('p3-iv4-q'));
document.getElementById('p3-iv4-fb').style.display = 'none';
}
function go(){
if(i >= Q.length) return;
const fb = document.getElementById('p3-iv4-fb');
const ans = parseFloat(document.getElementById('p3-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('p3-iv4-s').textContent = score;
i++;
setTimeout(show, 1400);
}
document.getElementById('p3-iv4-go').addEventListener('click', go);
document.getElementById('p3-iv4-ans').addEventListener('keydown', e=>{ if(e.key==='Enter') go(); });
document.getElementById('p3-iv4-start').addEventListener('click', ()=>{ i=0; score=0; show(); });
show();
})();
wireReadBtn('p3');
}
/* ===== §4 sin, cos, tg, ctg тупого угла ===== */
function buildP4(){
const box = document.getElementById('p4-body');
let html = '';
html += makeCard('theory', 'Единичная окружность', '4.1', `
<p>Введём координатную плоскость. <b>Единичная окружность</b> — окружность радиуса $1$ с центром в начале координат $O$.</p>
<p>Для острого угла $\\alpha$ отложим его от положительного направления оси $Ox$ <b>против часовой стрелки</b>. Точку пересечения подвижного луча с единичной окружностью обозначим $P$. Тогда:</p>
<ul style="padding-left:22px;line-height:1.95">
<li>$\\cos \\alpha$ — это $x$-координата точки $P$</li>
<li>$\\sin \\alpha$ — это $y$-координата точки $P$</li>
</ul>
<p>Это <b>обобщение</b> старых определений: для острых углов оно совпадает с привычным «отношение катета к гипотенузе» (можно построить прямоугольный треугольник с гипотенузой $OP = 1$). Но теперь определение работает для <b>любого</b> угла от $0^\\circ$ до $180^\\circ$ — даже когда привычного прямоугольного треугольника нет.</p>
<details class="spoiler"><summary>Почему именно так?</summary><div class="spoiler-body">
Если $OP = 1$, то $\\cos \\alpha = \\dfrac{\\text{прилежащий катет}}{\\text{гипотенуза}} = \\dfrac{x_P}{1} = x_P$. Аналогично для синуса. Единичная окружность позволяет «продолжить» функции на углы $> 90^\\circ$.
</div></details>`);
html += makeCard('rule', 'Тупой угол: знаки и формулы приведения', '4.2', `
<p>Для тупого угла $\\alpha$ (где $90^\\circ < \\alpha < 180^\\circ$) точка $P$ оказывается во <b>второй четверти</b>: выше оси $Ox$ и левее оси $Oy$. Значит:</p>
<ul style="padding-left:22px;line-height:1.95">
<li>$\\sin \\alpha = y_P > 0$ — синус <b>положителен</b></li>
<li>$\\cos \\alpha = x_P < 0$ — косинус <b>отрицателен</b></li>
<li>$\\tan \\alpha = \\dfrac{\\sin \\alpha}{\\cos \\alpha} < 0$ — тангенс <b>отрицателен</b></li>
<li>$\\cot \\alpha < 0$ — котангенс <b>отрицателен</b></li>
</ul>
<p><b>Формулы приведения</b> (главные для этого параграфа):</p>
<p style="text-align:center;font-size:1.05rem">$\\boxed{\\,\\sin(180^\\circ - \\alpha) = \\sin \\alpha,\\qquad \\cos(180^\\circ - \\alpha) = -\\cos \\alpha\\,}$</p>
<p>Здесь $\\alpha$ — острый угол, а $180^\\circ - \\alpha$ — соответствующий ему тупой. Идея проста: точки $P(\\alpha)$ и $P(180^\\circ - \\alpha)$ симметричны относительно оси $Oy$ — у них одинаковая $y$-координата (синус), но противоположные $x$-координаты (косинус).</p>`);
html += makeCard('example', 'Эталонные тупые углы', '4.3', `
<p>Считаем через формулы приведения, сводя всё к острым углам $30^\\circ$, $45^\\circ$, $60^\\circ$:</p>
<ul style="padding-left:22px;line-height:1.95">
<li>$\\sin 120^\\circ = \\sin(180^\\circ - 60^\\circ) = \\sin 60^\\circ = \\dfrac{\\sqrt{3}}{2}$</li>
<li>$\\cos 120^\\circ = -\\cos 60^\\circ = -\\dfrac{1}{2}$</li>
<li>$\\sin 135^\\circ = \\sin 45^\\circ = \\dfrac{\\sqrt{2}}{2}$</li>
<li>$\\cos 135^\\circ = -\\cos 45^\\circ = -\\dfrac{\\sqrt{2}}{2}$</li>
<li>$\\sin 150^\\circ = \\sin 30^\\circ = \\dfrac{1}{2}$</li>
<li>$\\cos 150^\\circ = -\\cos 30^\\circ = -\\dfrac{\\sqrt{3}}{2}$</li>
</ul>
<p><b>Граничные случаи:</b></p>
<ul style="padding-left:22px;line-height:1.95">
<li>$\\sin 90^\\circ = 1$, $\\cos 90^\\circ = 0$ (точка $P = (0, 1)$)</li>
<li>$\\sin 180^\\circ = 0$, $\\cos 180^\\circ = -1$ (точка $P = (-1, 0)$)</li>
<li>$\\sin 0^\\circ = 0$, $\\cos 0^\\circ = 1$ (точка $P = (1, 0)$)</li>
</ul>`);
/* IV1 — Единичная окружность */
html += `<div class="wg" id="p4-iv1">
<div class="wg-header"><span class="wg-badge">ИНТЕРАКТИВ 1</span><div class="wg-title">Единичная окружность</div></div>
<div class="wg-help">Двигай ползунок угла $\\alpha$ от $0^\\circ$ до $180^\\circ$. Смотри, как точка $P$ движется по окружности и как меняются $\\sin \\alpha$ (по $y$) и $\\cos \\alpha$ (по $x$). При $\\alpha > 90^\\circ$ — тупой угол, $\\cos \\alpha < 0$.</div>
<div class="sliders">
<label>Угол $\\alpha$, °<b id="p4-iv1-aval">60</b><input type="range" id="p4-iv1-a" min="0" max="180" step="1" value="60"></label>
</div>
<div style="background:var(--card);border-radius:10px;padding:10px;overflow-x:auto">
<svg id="p4-iv1-svg" viewBox="0 0 400 400" style="width:100%;max-width:420px;min-width:300px;height:auto;display:block;margin:0 auto"></svg>
</div>
<div id="p4-iv1-out" style="margin-top:10px;padding:12px 14px;background:var(--pri-soft);border-radius:9px;font-size:.98rem;text-align:center;line-height:1.95"></div>
<div id="p4-iv1-tag" style="margin-top:6px;text-align:center;font-weight:700"></div>
</div>`;
/* IV2 — Формула приведения */
html += `<div class="wg" id="p4-iv2">
<div class="wg-header"><span class="wg-badge">ИНТЕРАКТИВ 2</span><div class="wg-title">Формула приведения</div></div>
<div class="wg-help">Выбери тупой угол $\\alpha$ — увидишь, как формула приведения сводит $\\sin \\alpha$ и $\\cos \\alpha$ к острому углу $180^\\circ - \\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>
<select id="p4-iv2-a" class="tinp" style="width:110px;text-align:center">
<option value="100">100°</option>
<option value="120" selected>120°</option>
<option value="135">135°</option>
<option value="150">150°</option>
<option value="170">170°</option>
</select>
<button class="btn primary" id="p4-iv2-go">Применить формулу</button>
</div>
<div id="p4-iv2-out" style="padding:12px 14px;background:var(--card);border-radius:9px;font-size:1rem;line-height:1.85;min-height:50px"></div>
<div class="feedback" id="p4-iv2-fb"></div>
</div>`;
/* IV3 — Знак значения */
html += `<div class="wg" id="p4-iv3">
<div class="wg-header"><span class="wg-badge">ИНТЕРАКТИВ 3</span><div class="wg-title">Знак значения</div></div>
<div class="wg-help">Каким будет знак данного тригонометрического значения: положительным, отрицательным или равным нулю?</div>
<div class="score-display"><span>Задача <b id="p4-iv3-i">1</b> / 8</span><span>Очки: <b id="p4-iv3-s">0</b> / 8</span></div>
<div id="p4-iv3-q" style="padding:14px;background:var(--pri-soft);border-radius:10px;font-size:1.2rem;text-align:center;margin-bottom:10px"></div>
<div style="display:grid;grid-template-columns:repeat(auto-fit,minmax(140px,1fr));gap:8px">
<button class="btn primary" data-ans="pos" id="p4-iv3-pos">положит. (+)</button>
<button class="btn primary" data-ans="neg" id="p4-iv3-neg">отрицат. ()</button>
<button class="btn primary" data-ans="zero" id="p4-iv3-zero">равно $0$</button>
</div>
<div class="feedback" id="p4-iv3-fb"></div>
</div>`;
/* IV4 — Тренажёр тупого угла */
html += `<div class="wg" id="p4-iv4">
<div class="wg-header"><span class="wg-badge">ИНТЕРАКТИВ 4</span><div class="wg-title">Тренажёр тупого угла</div></div>
<div class="wg-help">Реши задачу и введи число (целое или десятичная дробь до 3 знаков; не забудь знак для отрицательных значений).</div>
<div class="score-display"><span>Задача <b id="p4-iv4-i">1</b> / 6</span><span>Очки: <b id="p4-iv4-s">0</b> / 6</span></div>
<div id="p4-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="p4-iv4-ans" class="tinp" style="width:120px;text-align:center" step="0.001">
<button class="btn primary" id="p4-iv4-go">Проверить</button>
<button class="btn" id="p4-iv4-start">Заново</button>
</div>
<div class="feedback" id="p4-iv4-fb"></div>
</div>`;
html += secNav('p3', 'p5');
html += readButton('p4');
box.innerHTML = html;
renderMath(box);
/* IV1 — Единичная окружность */
(function(){
const sl = document.getElementById('p4-iv1-a');
const lab = document.getElementById('p4-iv1-aval');
const svg = document.getElementById('p4-iv1-svg');
const out = document.getElementById('p4-iv1-out');
const tag = document.getElementById('p4-iv1-tag');
const seen = new Set();
const cx = 200, cy = 200, R = 150;
function draw(){
const aDeg = +sl.value;
lab.textContent = aDeg;
const aRad = deg2rad(aDeg);
const Px = cx + R*Math.cos(aRad);
const Py = cy - R*Math.sin(aRad); // y инвертирован
let s = '';
// Сетка
s += '<rect x="0" y="0" width="400" height="400" fill="none"/>';
// Координатные оси
s += '<line x1="20" y1="'+cy+'" x2="380" y2="'+cy+'" stroke="#94a3b8" stroke-width="1"/>';
s += '<line x1="'+cx+'" y1="20" x2="'+cx+'" y2="380" stroke="#94a3b8" stroke-width="1"/>';
// Стрелки на осях
s += '<polyline points="370,'+(cy-5)+' 380,'+cy+' 370,'+(cy+5)+'" fill="none" stroke="#94a3b8" stroke-width="1"/>';
s += '<polyline points="'+(cx-5)+',30 '+cx+',20 '+(cx+5)+',30" fill="none" stroke="#94a3b8" stroke-width="1"/>';
s += '<text x="384" y="'+(cy+14)+'" font-family="Inter,sans-serif" font-size="13" font-weight="700" fill="#64748b">x</text>';
s += '<text x="'+(cx+8)+'" y="22" font-family="Inter,sans-serif" font-size="13" font-weight="700" fill="#64748b">y</text>';
// Единичная окружность
s += '<circle cx="'+cx+'" cy="'+cy+'" r="'+R+'" fill="none" stroke="#b45309" stroke-width="2"/>';
// Отметки 1, -1
s += '<line x1="'+(cx+R)+'" y1="'+(cy-3)+'" x2="'+(cx+R)+'" y2="'+(cy+3)+'" stroke="#64748b" stroke-width="1.5"/>';
s += '<text x="'+(cx+R)+'" y="'+(cy+16)+'" text-anchor="middle" font-family="JetBrains Mono,monospace" font-size="11" fill="#64748b">1</text>';
s += '<line x1="'+(cx-R)+'" y1="'+(cy-3)+'" x2="'+(cx-R)+'" y2="'+(cy+3)+'" stroke="#64748b" stroke-width="1.5"/>';
s += '<text x="'+(cx-R)+'" y="'+(cy+16)+'" text-anchor="middle" font-family="JetBrains Mono,monospace" font-size="11" fill="#64748b">1</text>';
s += '<line x1="'+(cx-3)+'" y1="'+(cy-R)+'" x2="'+(cx+3)+'" y2="'+(cy-R)+'" stroke="#64748b" stroke-width="1.5"/>';
s += '<text x="'+(cx-10)+'" y="'+(cy-R+4)+'" text-anchor="end" font-family="JetBrains Mono,monospace" font-size="11" fill="#64748b">1</text>';
// Проекции — пунктиром
s += '<line x1="'+Px+'" y1="'+Py+'" x2="'+Px+'" y2="'+cy+'" stroke="#10b981" stroke-width="2" stroke-dasharray="4 3"/>';
s += '<line x1="'+Px+'" y1="'+Py+'" x2="'+cx+'" y2="'+Py+'" stroke="#0ea5e9" stroke-width="2" stroke-dasharray="4 3"/>';
// Радиус OP
s += '<line x1="'+cx+'" y1="'+cy+'" x2="'+Px+'" y2="'+Py+'" stroke="#2563eb" stroke-width="2.5"/>';
// Дуга угла alpha
if(aDeg > 0){
const uOx = {x:1, y:0};
const uOP = {x: Math.cos(aRad), y: -Math.sin(aRad)};
s += '<path d="'+angleArcAuto({x:cx,y:cy}, uOx, uOP, 36)+'" fill="none" stroke="#dc2626" stroke-width="2.2"/>';
// Подпись α
const aHalf = aRad/2;
const tx = cx + 54*Math.cos(aHalf);
const ty = cy - 54*Math.sin(aHalf);
s += '<text x="'+tx+'" y="'+ty+'" text-anchor="middle" dominant-baseline="middle" font-family="Inter,sans-serif" font-size="14" font-weight="700" fill="#dc2626">α</text>';
}
// Точка P
s += '<circle cx="'+Px+'" cy="'+Py+'" r="5.5" fill="#dc2626" stroke="#fff" stroke-width="2"/>';
s += '<text x="'+(Px + (Math.cos(aRad)>=0?10:-10))+'" y="'+(Py - 10)+'" text-anchor="'+(Math.cos(aRad)>=0?'start':'end')+'" font-family="Inter,sans-serif" font-size="14" font-weight="800" fill="#dc2626">P</text>';
// Подписи проекций
s += '<text x="'+Px+'" y="'+(cy + (Math.sin(aRad)>=0?22:-12))+'" text-anchor="middle" font-family="JetBrains Mono,monospace" font-size="11" font-weight="700" fill="#0ea5e9">cos α</text>';
s += '<text x="'+(cx + (Math.cos(aRad)>=0?-14:14))+'" y="'+Py+'" text-anchor="'+(Math.cos(aRad)>=0?'end':'start')+'" dominant-baseline="middle" font-family="JetBrains Mono,monospace" font-size="11" font-weight="700" fill="#10b981">sin α</text>';
// Подпись радиуса
s += '<text x="'+(cx + R*0.55*Math.cos(aRad) + (Math.sin(aRad)>=0?-12:12))+'" y="'+(cy - R*0.55*Math.sin(aRad) - 6)+'" text-anchor="middle" font-family="Inter,sans-serif" font-size="11" font-weight="700" fill="#2563eb">r=1</text>';
svg.innerHTML = s;
const sn = Math.sin(aRad), cs = Math.cos(aRad);
const tn = (Math.abs(cs) < 1e-9) ? null : sn/cs;
out.innerHTML = '$\\sin '+aDeg+'^\\circ \\approx '+sn.toFixed(3)+'$ &nbsp;·&nbsp; $\\cos '+aDeg+'^\\circ \\approx '+cs.toFixed(3)+'$<br>'
+ (tn===null ? '$\\tan '+aDeg+'^\\circ$ — не определён' : '$\\tan '+aDeg+'^\\circ \\approx '+tn.toFixed(3)+'$');
renderMath(out);
if(aDeg > 90 && aDeg < 180){
tag.innerHTML = '<span style="color:var(--pri2);background:var(--pri-soft);padding:5px 12px;border-radius:8px">⚠ Тупой угол! $\\cos \\alpha < 0$.</span>';
renderMath(tag);
} else if(aDeg === 90){
tag.innerHTML = '<span style="color:#065f46;background:var(--ok-bg);padding:5px 12px;border-radius:8px">Прямой угол.</span>';
} else if(aDeg === 180 || aDeg === 0){
tag.innerHTML = '<span style="color:#7f1d1d;background:var(--fail-bg);padding:5px 12px;border-radius:8px">Развёрнутый/нулевой угол.</span>';
} else {
tag.innerHTML = '<span style="color:#065f46;background:var(--ok-bg);padding:5px 12px;border-radius:8px">Острый угол. Обе функции положительны.</span>';
}
seen.add(aDeg);
if(seen.size >= 6 && !seen.has('done')){ addXp(10,'p4-iv1'); bumpProgress('p4', 15); seen.add('done'); }
}
sl.addEventListener('input', draw);
draw();
})();
/* IV2 — Формула приведения */
(function(){
const sel = document.getElementById('p4-iv2-a');
const go = document.getElementById('p4-iv2-go');
const out = document.getElementById('p4-iv2-out');
const fb = document.getElementById('p4-iv2-fb');
let solved = 0;
function apply(){
const aDeg = parseInt(sel.value, 10);
const beta = 180 - aDeg;
const r = deg2rad(aDeg);
const sn = Math.sin(r), cs = Math.cos(r);
out.innerHTML = '<b>Формула приведения для $\\alpha = '+aDeg+'^\\circ$:</b><br>'
+ '$\\sin '+aDeg+'^\\circ = \\sin(180^\\circ - '+aDeg+'^\\circ) = \\sin '+beta+'^\\circ \\approx '+sn.toFixed(3)+'$<br>'
+ '$\\cos '+aDeg+'^\\circ = -\\cos(180^\\circ - '+aDeg+'^\\circ) = -\\cos '+beta+'^\\circ \\approx '+cs.toFixed(3)+'$<br>'
+ '<span style="color:var(--muted);font-size:.9rem">Заметь: синус сохранил знак, косинус — поменял.</span>';
renderMath(out);
feedback(fb, true, '&#10003; Сведено к острому углу '+beta+'°.');
solved++;
if(solved === 1){ addXp(10,'p4-iv2'); bumpProgress('p4', 15); }
}
go.addEventListener('click', apply);
apply();
})();
/* IV3 — Знак значения */
(function(){
const Q = [
{ expr:'$\\sin 120^\\circ$', ans:'pos', why:'тупой угол: $\\sin > 0$' },
{ expr:'$\\cos 120^\\circ$', ans:'neg', why:'тупой угол: $\\cos < 0$' },
{ expr:'$\\sin 90^\\circ$', ans:'pos', why:'$\\sin 90^\\circ = 1 > 0$' },
{ expr:'$\\cos 90^\\circ$', ans:'zero', why:'$\\cos 90^\\circ = 0$' },
{ expr:'$\\sin 180^\\circ$', ans:'zero', why:'$\\sin 180^\\circ = 0$' },
{ expr:'$\\cos 180^\\circ$', ans:'neg', why:'$\\cos 180^\\circ = -1 < 0$' },
{ expr:'$\\sin 60^\\circ$', ans:'pos', why:'острый угол: $\\sin > 0$' },
{ expr:'$\\cos 150^\\circ$', ans:'neg', why:'тупой угол: $\\cos < 0$' },
];
let i = 0, score = 0;
function show(){
if(i >= Q.length){
document.getElementById('p4-iv3-q').innerHTML = '<b>Готово!</b> Результат: ' + score + ' / ' + Q.length;
if(score === Q.length){ addXp(15,'p4-iv3'); bumpProgress('p4', 25); }
else if(score >= Q.length - 2){ addXp(8,'p4-iv3'); bumpProgress('p4', 15); }
return;
}
document.getElementById('p4-iv3-i').textContent = (i+1);
document.getElementById('p4-iv3-s').textContent = score;
document.getElementById('p4-iv3-q').innerHTML = Q[i].expr;
renderMath(document.getElementById('p4-iv3-q'));
document.getElementById('p4-iv3-fb').style.display = 'none';
}
function answer(a){
if(i >= Q.length) return;
const fb = document.getElementById('p4-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('p4-iv3-s').textContent = score;
i++;
setTimeout(show, 1100);
}
['pos','neg','zero'].forEach(k=>{
const b = document.getElementById('p4-iv3-'+k); if(b) b.addEventListener('click', ()=>answer(k));
});
show();
})();
/* IV4 — Тренажёр тупого угла */
(function(){
const Q = [
{ q:'$\\sin 120^\\circ = ?$', ans:0.866, tol:0.01, hint:'$\\sin 120^\\circ = \\sin 60^\\circ = \\sqrt{3}/2 \\approx 0{,}866$' },
{ q:'$\\cos 120^\\circ = ?$', ans:-0.5, tol:0.02, hint:'$\\cos 120^\\circ = -\\cos 60^\\circ = -1/2$' },
{ q:'$\\sin 150^\\circ = ?$', ans:0.5, tol:0.02, hint:'$\\sin 150^\\circ = \\sin 30^\\circ = 1/2$' },
{ q:'$\\cos 150^\\circ = ?$', ans:-0.866, tol:0.01, hint:'$\\cos 150^\\circ = -\\cos 30^\\circ = -\\sqrt{3}/2$' },
{ q:'$\\cos 90^\\circ = ?$', ans:0, tol:0.01, hint:'$\\cos 90^\\circ = 0$' },
{ q:'$\\sin 180^\\circ + \\cos 180^\\circ = ?$', ans:-1, tol:0.02, hint:'$0 + (-1) = -1$' },
];
let i = 0, score = 0;
function show(){
if(i >= Q.length){
document.getElementById('p4-iv4-q').innerHTML = '<b>Готово!</b> Результат: ' + score + ' / ' + Q.length;
if(score === Q.length){ addXp(15,'p4-iv4'); bumpProgress('p4', 25); }
else if(score >= 4){ addXp(8,'p4-iv4'); bumpProgress('p4', 15); }
return;
}
document.getElementById('p4-iv4-i').textContent = (i+1);
document.getElementById('p4-iv4-s').textContent = score;
document.getElementById('p4-iv4-q').innerHTML = Q[i].q;
document.getElementById('p4-iv4-ans').value = '';
renderMath(document.getElementById('p4-iv4-q'));
document.getElementById('p4-iv4-fb').style.display = 'none';
}
function go(){
if(i >= Q.length) return;
const fb = document.getElementById('p4-iv4-fb');
const ans = parseFloat(document.getElementById('p4-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('p4-iv4-s').textContent = score;
i++;
setTimeout(show, 1400);
}
document.getElementById('p4-iv4-go').addEventListener('click', go);
document.getElementById('p4-iv4-ans').addEventListener('keydown', e=>{ if(e.key==='Enter') go(); });
document.getElementById('p4-iv4-start').addEventListener('click', ()=>{ i=0; score=0; show(); });
show();
})();
wireReadBtn('p4');
}
/* ===== §5 Формулы площади ===== */
function buildP5(){
const box = document.getElementById('p5-body');
let html = '';
html += makeCard('theory', 'Площадь треугольника через две стороны и угол', '5.1', `
<p>Площадь треугольника можно вычислить, зная <b>две стороны</b> и <b>угол между ними</b>:</p>
<p style="text-align:center;font-size:1.15rem">$\\boxed{\\,S_\\triangle = \\dfrac{1}{2} a b \\sin C\\,}$</p>
<p>Здесь $a$ и $b$ — две стороны треугольника, а $C$ — угол между ними (то есть угол при общей вершине этих сторон).</p>
<details class="spoiler" open><summary>Откуда берётся $\\sin C$?</summary><div class="spoiler-body">
<p>Опустим высоту $h$ из вершины $A$ на сторону $a = BC$. В прямоугольном треугольнике с гипотенузой $b$ и острым углом $C$ имеем:</p>
<p style="text-align:center">$h = b \\sin C$.</p>
<p>Тогда классическая формула $S = \\dfrac{1}{2} \\cdot a \\cdot h$ превращается в:</p>
<p style="text-align:center">$S = \\dfrac{1}{2} a \\cdot b \\sin C = \\dfrac{1}{2} a b \\sin C$.</p>
<p>Формула работает и для тупого угла $C$: тогда высота попадает на продолжение стороны, но $\\sin C$ всё равно положителен (из §4 знаем: $\\sin(180^\\circ - \\alpha) = \\sin \\alpha$).</p>
</div></details>`);
html += makeCard('rule', 'Площадь параллелограмма', '5.2', `
<p>Аналогичная формула — для параллелограмма:</p>
<p style="text-align:center;font-size:1.15rem">$\\boxed{\\,S_{\\text{пар}} = a b \\sin \\alpha\\,}$</p>
<p>Здесь $a, b$ — <b>смежные стороны</b> параллелограмма, $\\alpha$ — угол между ними.</p>
<p><b>Почему такая формула?</b> Диагональ параллелограмма делит его на <b>два равных треугольника</b>. Площадь каждого по формуле §5.1 равна $\\dfrac{1}{2}ab\\sin\\alpha$, значит площадь всего параллелограмма — вдвое больше:</p>
<p style="text-align:center">$S_{\\text{пар}} = 2 \\cdot \\dfrac{1}{2} ab \\sin \\alpha = ab \\sin \\alpha$.</p>
<p><b>Частные случаи:</b></p>
<ul style="padding-left:22px;line-height:1.85">
<li>$\\alpha = 90^\\circ \\Rightarrow \\sin\\alpha = 1$, получаем <b>прямоугольник</b>: $S = ab$.</li>
<li>Если $a = b$ — ромб со стороной $a$ и углом $\\alpha$: $S = a^2 \\sin \\alpha$.</li>
</ul>`);
html += makeCard('example', 'Площадь четырёхугольника через диагонали', '5.3', `
<p>Для любого выпуклого четырёхугольника есть «универсальная» формула — через диагонали и угол между ними:</p>
<p style="text-align:center;font-size:1.15rem">$\\boxed{\\,S_{4} = \\dfrac{1}{2} d_1 d_2 \\sin \\varphi\\,}$</p>
<p>где $d_1, d_2$ — диагонали, $\\varphi$ — угол между ними.</p>
<p><b>Примеры применения:</b></p>
<ul style="padding-left:22px;line-height:1.85">
<li><b>Ромб</b> с диагоналями $6$ и $8$: диагонали ромба <b>перпендикулярны</b>, поэтому $\\varphi = 90^\\circ$ и $\\sin\\varphi = 1$:<br>
$S = \\dfrac{1}{2} \\cdot 6 \\cdot 8 = 24$.</li>
<li><b>Параллелограмм</b> со сторонами $5$ и $8$, углом $30^\\circ$ между ними:<br>
$S = 5 \\cdot 8 \\cdot \\sin 30^\\circ = 40 \\cdot \\tfrac{1}{2} = 20$.</li>
<li><b>Треугольник</b> со сторонами $4$ и $6$ и углом $90^\\circ$ между ними:<br>
$S = \\dfrac{1}{2} \\cdot 4 \\cdot 6 \\cdot \\sin 90^\\circ = 12$ — то же, что катет × катет / 2.</li>
</ul>
<details class="spoiler"><summary>Подсказка по выбору формулы</summary><div class="spoiler-body">
<ul style="padding-left:18px;line-height:1.85;margin:0">
<li>Даны <b>две стороны + угол между ними</b> треугольника → $\\frac{1}{2}ab\\sin C$.</li>
<li>Даны <b>две стороны + угол</b> параллелограмма → $ab\\sin\\alpha$.</li>
<li>Даны <b>обе диагонали + угол между ними</b> → $\\frac{1}{2}d_1 d_2 \\sin\\varphi$ (для ромба $\\varphi = 90°$).</li>
</ul>
</div></details>`);
/* IV1 — SVG визуализатор «Площадь треугольника по углу» */
html += `<div class="wg" id="p5-iv1">
<div class="wg-header"><span class="wg-badge">ИНТЕРАКТИВ 1</span><div class="wg-title">Площадь треугольника по углу</div></div>
<div class="wg-help">Двигай ползунки сторон $a, b$ и угла $A$ — треугольник перестраивается, а площадь пересчитывается по формуле $S = \\tfrac{1}{2} a b \\sin A$.</div>
<div class="sliders">
<label>Сторона $a$<b id="p5-iv1-aval">6</b><input type="range" id="p5-iv1-a" min="3" max="9" step="1" value="6"></label>
<label>Сторона $b$<b id="p5-iv1-bval">5</b><input type="range" id="p5-iv1-b" min="3" max="9" step="1" value="5"></label>
<label>Угол $A$, °<b id="p5-iv1-Aval">60</b><input type="range" id="p5-iv1-A" min="20" max="160" step="5" value="60"></label>
</div>
<div style="background:var(--card);border-radius:10px;padding:10px;overflow-x:auto">
<svg id="p5-iv1-svg" viewBox="0 0 400 320" style="width:100%;min-width:320px;height:auto;display:block"></svg>
</div>
<div id="p5-iv1-out" style="margin-top:10px;padding:12px 14px;background:var(--pri-soft);border-radius:9px;font-size:1rem;text-align:center;line-height:1.9"></div>
</div>`;
/* IV2 — Калькулятор параллелограмма */
html += `<div class="wg" id="p5-iv2">
<div class="wg-header"><span class="wg-badge">ИНТЕРАКТИВ 2</span><div class="wg-title">Калькулятор площади параллелограмма</div></div>
<div class="wg-help">Введи смежные стороны $a, b$ и угол $\\alpha$ между ними — найдём площадь по формуле $S = a b \\sin \\alpha$. При $\\alpha = 90^\\circ$ это просто прямоугольник.</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">$a$ =</span>
<input type="number" id="p5-iv2-a" class="tinp" style="width:80px;text-align:center" value="6" min="0.1" step="0.1">
<span style="font-family:'JetBrains Mono',monospace">$b$ =</span>
<input type="number" id="p5-iv2-b" class="tinp" style="width:80px;text-align:center" value="10" min="0.1" step="0.1">
<span style="font-family:'JetBrains Mono',monospace">$\\alpha$ =</span>
<input type="number" id="p5-iv2-A" class="tinp" style="width:80px;text-align:center" value="30" min="1" max="179">
<span style="font-family:'JetBrains Mono',monospace">°</span>
<button class="btn primary" id="p5-iv2-go">Вычислить</button>
</div>
<div id="p5-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="p5-iv2-fb"></div>
</div>`;
/* IV3 — DnD сортер «Подбери формулу» */
html += `<div class="wg" id="p5-iv3">
<div class="wg-header"><span class="wg-badge">ИНТЕРАКТИВ 3</span><div class="wg-title">Подбери формулу</div></div>
<div class="wg-help">Распредели 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 фигур — 3 формулы</div>
<div id="p5-iv3-pool"></div>
<div style="display:grid;grid-template-columns:repeat(auto-fit,minmax(200px,1fr));gap:10px;margin-top:8px">
<div class="drop-box"><h5 data-cat="t">$S = \\tfrac{1}{2}ab\\sin C$</h5><div class="drop-items" data-cat="t"></div></div>
<div class="drop-box"><h5 data-cat="p">$S = ab\\sin\\alpha$</h5><div class="drop-items" data-cat="p"></div></div>
<div class="drop-box"><h5 data-cat="d">$S = \\tfrac{1}{2}d_1 d_2 \\sin\\varphi$</h5><div class="drop-items" data-cat="d"></div></div>
</div>
<div class="actions"><button class="btn primary" id="p5-iv3-check">Проверить</button><button class="btn" id="p5-iv3-reset">Сначала</button></div>
<div class="feedback" id="p5-iv3-fb"></div>
</div>`;
/* IV4 — Тренажёр площади */
html += `<div class="wg" id="p5-iv4">
<div class="wg-header"><span class="wg-badge">ИНТЕРАКТИВ 4</span><div class="wg-title">Тренажёр площади</div></div>
<div class="wg-help">Реши задачу и введи число (целое или округлённое до целых).</div>
<div class="score-display"><span>Задача <b id="p5-iv4-i">1</b> / 6</span><span>Очки: <b id="p5-iv4-s">0</b> / 6</span></div>
<div id="p5-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">$S$ =</span>
<input type="number" id="p5-iv4-ans" class="tinp" style="width:110px;text-align:center" step="0.1">
<button class="btn primary" id="p5-iv4-go">Проверить</button>
<button class="btn" id="p5-iv4-start">Заново</button>
</div>
<div class="feedback" id="p5-iv4-fb"></div>
</div>`;
html += secNav('p4', 'p6');
html += readButton('p5');
box.innerHTML = html;
renderMath(box);
/* IV1 — SVG визуализатор */
(function(){
const slA = document.getElementById('p5-iv1-a');
const slB = document.getElementById('p5-iv1-b');
const slAng = document.getElementById('p5-iv1-A');
const labA = document.getElementById('p5-iv1-aval');
const labB = document.getElementById('p5-iv1-bval');
const labAng = document.getElementById('p5-iv1-Aval');
const svg = document.getElementById('p5-iv1-svg');
const out = document.getElementById('p5-iv1-out');
const seen = new Set();
function draw(){
const a = +slA.value, b = +slB.value, Adeg = +slAng.value;
labA.textContent = a; labB.textContent = b; labAng.textContent = Adeg;
const aRad = deg2rad(Adeg);
// В пикселях: множитель 20 на единицу
const k = 20;
const aPx = a * k, bPx = b * k;
// A в (80, 240), B справа по горизонтали, C — поворот на угол A вокруг A.
const A = {x: 80, y: 240};
const B = {x: 80 + aPx, y: 240};
const C = {x: 80 + bPx * Math.cos(aRad), y: 240 - bPx * Math.sin(aRad)};
// векторы в A: вдоль AB и вдоль AC
const uAB = unitVec(A, B);
const uAC = unitVec(A, 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,.1)" stroke="#b45309" stroke-width="2.2" stroke-linejoin="round"/>';
// дуга угла A
s += '<path d="'+angleArcAuto(A, uAB, uAC, 32)+'" fill="none" stroke="#dc2626" stroke-width="2.2"/>';
// подпись угла A
const halfRad = aRad / 2;
const tx = A.x + 52*Math.cos(halfRad);
const ty = A.y - 52*Math.sin(halfRad);
s += '<text x="'+tx+'" y="'+ty+'" text-anchor="middle" dominant-baseline="middle" font-family="Inter,sans-serif" font-size="14" font-weight="700" fill="#dc2626">A='+Adeg+'°</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+18)+'" text-anchor="start" font-family="Inter,sans-serif" font-size="16" font-weight="700" fill="#0f172a">B</text>';
s += '<text x="'+(C.x + (C.x>=A.x?10:-10))+'" y="'+(C.y-8)+'" text-anchor="'+(C.x>=A.x?'start':'end')+'" font-family="Inter,sans-serif" font-size="16" font-weight="700" fill="#0f172a">C</text>';
// подписи сторон
s += '<text x="'+((A.x+B.x)/2)+'" y="'+(A.y+30)+'" text-anchor="middle" font-family="JetBrains Mono,monospace" font-size="12" fill="#b45309">a='+a+'</text>';
const midAC = {x:(A.x+C.x)/2, y:(A.y+C.y)/2};
// нормаль к AC, направленная "наружу" треугольника (от B)
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;
// выбираем сторону, противоположную B
const sign = ((B.x-A.x)*nAC.x + (B.y-A.y)*nAC.y) > 0 ? -1 : 1;
const labP = {x: midAC.x + sign*14*nAC.x/nL, y: midAC.y + sign*14*nAC.y/nL};
s += '<text x="'+labP.x+'" y="'+labP.y+'" text-anchor="middle" font-family="JetBrains Mono,monospace" font-size="12" fill="#b45309">b='+b+'</text>';
svg.innerHTML = s;
// расчёт площади
const S = 0.5 * a * b * Math.sin(aRad);
out.innerHTML = '$S = \\dfrac{1}{2} \\cdot a \\cdot b \\cdot \\sin A = \\dfrac{1}{2} \\cdot '+a+' \\cdot '+b+' \\cdot \\sin '+Adeg+'^\\circ \\approx '+S.toFixed(2)+'$';
renderMath(out);
seen.add(a+'_'+b+'_'+Adeg);
if(seen.size >= 5 && !seen.has('done')){ addXp(10,'p5-iv1'); bumpProgress('p5', 15); seen.add('done'); }
}
[slA, slB, slAng].forEach(sl => sl.addEventListener('input', draw));
draw();
})();
/* IV2 — Калькулятор параллелограмма */
(function(){
const aI = document.getElementById('p5-iv2-a');
const bI = document.getElementById('p5-iv2-b');
const AI = document.getElementById('p5-iv2-A');
const go = document.getElementById('p5-iv2-go');
const out = document.getElementById('p5-iv2-out');
const fb = document.getElementById('p5-iv2-fb');
let solved = 0;
function calc(){
const a = parseFloat(aI.value), b = parseFloat(bI.value), Adeg = parseFloat(AI.value);
if(isNaN(a) || isNaN(b) || isNaN(Adeg)){ feedback(fb, false, '&#10007; Введи все три числа.'); return; }
if(a<=0 || b<=0){ feedback(fb, false, '&#10007; Стороны должны быть положительны.'); return; }
if(Adeg<=0 || Adeg>=180){ feedback(fb, false, '&#10007; Угол должен быть в диапазоне (0°; 180°).'); return; }
const r = deg2rad(Adeg);
const S = a * b * Math.sin(r);
let html = '$S = a \\cdot b \\cdot \\sin \\alpha = '+a+' \\cdot '+b+' \\cdot \\sin '+Adeg+'^\\circ \\approx '+S.toFixed(2)+'$';
if(Math.abs(Adeg - 90) < 1e-9){
html += '<br><span style="color:var(--ok);font-weight:700">Особый случай: $\\alpha = 90^\\circ$ — это прямоугольник, $S = a b = '+(a*b).toFixed(2)+'$.</span>';
}
out.innerHTML = html;
renderMath(out);
feedback(fb, true, '&#10003; Площадь найдена.');
solved++;
if(solved === 1){ addXp(10,'p5-iv2'); bumpProgress('p5', 15); }
}
go.addEventListener('click', calc);
calc();
})();
/* IV3 — DnD сортер */
(function(){
const items = [
{ id:'f1', cat:'t', html:'треугольник со сторонами $a, b$ и углом $C$ между ними' },
{ id:'f2', cat:'p', html:'параллелограмм со сторонами $a, b$ и углом $\\alpha$' },
{ id:'f3', cat:'p', html:'прямоугольник со сторонами $a, b$ ($\\alpha=90^\\circ$)' },
{ id:'f4', cat:'d', html:'ромб с диагоналями $d_1, d_2$' },
{ id:'f5', cat:'d', html:'трапеция с диагоналями $d_1, d_2$ и углом $\\varphi$' },
{ id:'f6', cat:'t', html:'равносторонний треугольник со стороной $a$ ($C = 60^\\circ$)' },
];
const sorter = setupSorter({
poolId:'p5-iv3-pool',
scopeSelector:'#p5-iv3',
items: items,
cats:['t','p','d'],
columnLayout:true,
});
document.getElementById('p5-iv3-check').addEventListener('click', ()=>{
const fb = document.getElementById('p5-iv3-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 на месте! +15 XP'); addXp(15,'p5-iv3'); bumpProgress('p5', 25); }
else feedback(fb, false, '&#10007; Правильно ' + correct + ' из 6. Попробуй ещё.');
});
document.getElementById('p5-iv3-reset').addEventListener('click', ()=>{ sorter.reset(); document.getElementById('p5-iv3-fb').style.display = 'none'; });
})();
/* IV4 — Тренажёр */
(function(){
const Q = [
{ q:'Треугольник: $a = 5$, $b = 8$, $C = 30^\\circ$. Найди $S$.', ans:10, tol:0.2, hint:'$S = \\tfrac{1}{2} \\cdot 5 \\cdot 8 \\cdot \\sin 30^\\circ = 20 \\cdot 0{,}5 = 10$' },
{ q:'Параллелограмм: $a = 6$, $b = 10$, $\\alpha = 90^\\circ$. Найди $S$.', ans:60, tol:0.2, hint:'$\\sin 90^\\circ = 1$, $S = a b = 60$' },
{ q:'Ромб с диагоналями $12$ и $16$. Найди $S$.', ans:96, tol:0.2, hint:'диагонали ромба перпендикулярны: $S = \\tfrac{1}{2} \\cdot 12 \\cdot 16 = 96$' },
{ q:'Треугольник: $a = b = 4$, $C = 60^\\circ$. Найди $S$ (округли до целого).', ans:7, tol:0.5, hint:'$S = \\tfrac{1}{2} \\cdot 16 \\cdot \\sin 60^\\circ = 8 \\cdot \\tfrac{\\sqrt{3}}{2} = 4\\sqrt{3} \\approx 6{,}93 \\approx 7$' },
{ q:'Прямоугольник со сторонами $7$ и $12$. Найди $S$.', ans:84, tol:0.2, hint:'$S = 7 \\cdot 12 = 84$' },
{ q:'Треугольник: $a = 10$, $b = 10$, $C = 90^\\circ$. Найди $S$.', ans:50, tol:0.2, hint:'$S = \\tfrac{1}{2} \\cdot 100 \\cdot \\sin 90^\\circ = 50$' },
];
let i = 0, score = 0;
function show(){
if(i >= Q.length){
document.getElementById('p5-iv4-q').innerHTML = '<b>Готово!</b> Результат: ' + score + ' / ' + Q.length;
if(score === Q.length){ addXp(15,'p5-iv4'); bumpProgress('p5', 25); achievement('p5_done'); }
else if(score >= 4){ addXp(8,'p5-iv4'); bumpProgress('p5', 15); }
return;
}
document.getElementById('p5-iv4-i').textContent = (i+1);
document.getElementById('p5-iv4-s').textContent = score;
document.getElementById('p5-iv4-q').innerHTML = Q[i].q;
document.getElementById('p5-iv4-ans').value = '';
renderMath(document.getElementById('p5-iv4-q'));
document.getElementById('p5-iv4-fb').style.display = 'none';
}
function go(){
if(i >= Q.length) return;
const fb = document.getElementById('p5-iv4-fb');
const ans = parseFloat(document.getElementById('p5-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('p5-iv4-s').textContent = score;
i++;
setTimeout(show, 1400);
}
document.getElementById('p5-iv4-go').addEventListener('click', go);
document.getElementById('p5-iv4-ans').addEventListener('keydown', e=>{ if(e.key==='Enter') go(); });
document.getElementById('p5-iv4-start').addEventListener('click', ()=>{ i=0; score=0; show(); });
show();
})();
wireReadBtn('p5');
}
/* ===== §6 Среднее геометрическое в прямоугольном треугольнике ===== */
function buildP6(){
const box = document.getElementById('p6-body');
let html = '';
html += makeCard('theory', 'Высота к гипотенузе и проекции катетов', '6.1', `
<p>Рассмотрим прямоугольный треугольник $ABC$ с прямым углом $C$. Из вершины $C$ опустим <b>высоту</b> $h = CH$ на гипотенузу $c = AB$. Эта высота делит гипотенузу на два отрезка: $AH = b_1$ и $BH = a_1$, причём $a_1 + b_1 = c$.</p>
<p>Тогда в треугольнике справедливы <b>три замечательных соотношения</b>:</p>
<p style="text-align:center;font-size:1.1rem">$\\boxed{\\,h^2 = a_1 b_1,\\qquad a^2 = c \\cdot a_1,\\qquad b^2 = c \\cdot b_1\\,}$</p>
<p>Здесь $a, b$ — катеты ($a = BC$ напротив $A$, $b = AC$ напротив $B$), $h$ — высота к гипотенузе, $a_1$ — проекция катета $a$ на гипотенузу (отрезок $BH$), $b_1$ — проекция катета $b$ на гипотенузу (отрезок $AH$).</p>
<details class="spoiler"><summary>Откуда берутся эти соотношения?</summary><div class="spoiler-body">
<p>Высота $CH$ разбивает $\\triangle ABC$ на два маленьких прямоугольных треугольника — $\\triangle ACH$ и $\\triangle CBH$. Все три треугольника <b>подобны друг другу</b> (имеют по два равных угла).</p>
<p>Из подобия $\\triangle ACH \\sim \\triangle CBH$ получаем $\\dfrac{AH}{CH} = \\dfrac{CH}{BH}$, то есть $\\dfrac{b_1}{h} = \\dfrac{h}{a_1}$, откуда $h^2 = a_1 b_1$.</p>
<p>Из подобия $\\triangle ABC \\sim \\triangle ACH$ получаем $\\dfrac{AB}{AC} = \\dfrac{AC}{AH}$, то есть $\\dfrac{c}{b} = \\dfrac{b}{b_1}$, откуда $b^2 = c b_1$. Аналогично — третье соотношение.</p>
</div></details>`);
html += makeCard('rule', 'Среднее геометрическое', '6.2', `
<p>Эти три формулы — это <b>среднее геометрическое</b> в действии. Среднее геометрическое чисел $x, y$ — это $\\sqrt{xy}$. Получаем:</p>
<ul style="padding-left:22px;line-height:2">
<li>$h = \\sqrt{a_1 b_1}$ — <b>высота</b> к гипотенузе есть среднее геометрическое проекций катетов.</li>
<li>$a = \\sqrt{c \\cdot a_1}$ — катет $a$ есть среднее геометрическое гипотенузы и проекции этого катета.</li>
<li>$b = \\sqrt{c \\cdot b_1}$ — катет $b$ есть среднее геометрическое гипотенузы и проекции этого катета.</li>
</ul>
<p>Эти три факта называют <b>метрическими соотношениями</b> в прямоугольном треугольнике, и они моментально решают задачи, в которых заданы какие-то «куски» гипотенузы.</p>
<details class="spoiler"><summary>Почему «среднее геометрическое»?</summary><div class="spoiler-body">
<p>Запись $h^2 = a_1 b_1$ означает, что $h$ — такое число, квадрат которого равен произведению $a_1$ и $b_1$. Это и есть классическое определение среднего геометрического двух положительных чисел: $h = \\sqrt{a_1 b_1}$.</p>
</div></details>`);
html += makeCard('example', 'Примеры применения', '6.3', `
<p><b>Пример 1.</b> $c = 10$, проекции $a_1 = 4$, $b_1 = 6$. Найти $h, a, b$.</p>
<ul style="padding-left:22px;line-height:1.85">
<li>$h = \\sqrt{a_1 b_1} = \\sqrt{4 \\cdot 6} = \\sqrt{24} = 2\\sqrt{6} \\approx 4{,}9$</li>
<li>$a = \\sqrt{c \\cdot a_1} = \\sqrt{40} = 2\\sqrt{10}$</li>
<li>$b = \\sqrt{c \\cdot b_1} = \\sqrt{60} = 2\\sqrt{15}$</li>
</ul>
<p><b>Пример 2.</b> Треугольник $6$$8$–$10$ (3–4–5, умноженное на $2$). Найти $h, a_1, b_1$.</p>
<ul style="padding-left:22px;line-height:1.85">
<li>$a = 6$, $c = 10 \\Rightarrow a_1 = \\dfrac{a^2}{c} = \\dfrac{36}{10} = 3{,}6$</li>
<li>$b = 8$, $c = 10 \\Rightarrow b_1 = \\dfrac{b^2}{c} = \\dfrac{64}{10} = 6{,}4$</li>
<li>Проверка: $a_1 + b_1 = 3{,}6 + 6{,}4 = 10 = c$ ✓</li>
<li>$h = \\sqrt{a_1 b_1} = \\sqrt{3{,}6 \\cdot 6{,}4} = \\sqrt{23{,}04} = 4{,}8$</li>
</ul>
<p><b>Сверка площадей:</b> $S = \\tfrac{1}{2} a b = \\tfrac{1}{2} \\cdot 6 \\cdot 8 = 24$ и $S = \\tfrac{1}{2} c h = \\tfrac{1}{2} \\cdot 10 \\cdot 4{,}8 = 24$. Совпало — соотношение работает.</p>`);
/* IV1 — SVG визуализатор */
html += `<div class="wg" id="p6-iv1">
<div class="wg-header"><span class="wg-badge">ИНТЕРАКТИВ 1</span><div class="wg-title">Конструктор соотношений</div></div>
<div class="wg-help">Меняй угол $A$ — точка $H$ перемещается по гипотенузе, проекции $a_1, b_1$ меняются, и три соотношения $h^2 = a_1 b_1$, $a^2 = c a_1$, $b^2 = c b_1$ всё время выполняются.</div>
<div class="sliders">
<label>Угол $A$, °<b id="p6-iv1-aval">40</b><input type="range" id="p6-iv1-a" min="30" max="60" step="1" value="40"></label>
</div>
<div style="background:var(--card);border-radius:10px;padding:10px;overflow-x:auto">
<svg id="p6-iv1-svg" viewBox="0 0 480 320" style="width:100%;min-width:360px;height:auto;display:block"></svg>
</div>
<div id="p6-iv1-out" style="margin-top:10px;padding:12px 14px;background:var(--pri-soft);border-radius:9px;font-size:.98rem;line-height:1.95"></div>
</div>`;
/* IV2 — Калькулятор h */
html += `<div class="wg" id="p6-iv2">
<div class="wg-header"><span class="wg-badge">ИНТЕРАКТИВ 2</span><div class="wg-title">Калькулятор $h$ и $c$</div></div>
<div class="wg-help">Введи проекции катетов $a_1, b_1$ — найдём высоту $h$, гипотенузу $c$, катеты $a, b$ и площадь $S$.</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">$a_1$ =</span>
<input type="number" id="p6-iv2-a1" class="tinp" style="width:90px;text-align:center" value="4" min="0.1" step="0.1">
<span style="font-family:'JetBrains Mono',monospace">$b_1$ =</span>
<input type="number" id="p6-iv2-b1" class="tinp" style="width:90px;text-align:center" value="9" min="0.1" step="0.1">
<button class="btn primary" id="p6-iv2-go">Найти $h$ и $c$</button>
</div>
<div id="p6-iv2-out" style="padding:12px 14px;background:var(--card);border-radius:9px;font-size:1rem;line-height:1.85;min-height:50px"></div>
<div class="feedback" id="p6-iv2-fb"></div>
</div>`;
/* IV3 — Какое соотношение? */
html += `<div class="wg" id="p6-iv3">
<div class="wg-header"><span class="wg-badge">ИНТЕРАКТИВ 3</span><div class="wg-title">Какое соотношение применить?</div></div>
<div class="wg-help">Даны какие-то элементы прямоугольного треугольника — выбери, какое из трёх соотношений поможет найти искомую величину.</div>
<div class="score-display"><span>Задача <b id="p6-iv3-i">1</b> / 6</span><span>Очки: <b id="p6-iv3-s">0</b> / 6</span></div>
<div id="p6-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(150px,1fr));gap:8px">
<button class="btn primary" data-ans="h" id="p6-iv3-h">$h^2 = a_1 b_1$</button>
<button class="btn primary" data-ans="a" id="p6-iv3-a">$a^2 = c a_1$</button>
<button class="btn primary" data-ans="b" id="p6-iv3-b">$b^2 = c b_1$</button>
</div>
<div class="feedback" id="p6-iv3-fb"></div>
</div>`;
/* IV4 — Тренажёр */
html += `<div class="wg" id="p6-iv4">
<div class="wg-header"><span class="wg-badge">ИНТЕРАКТИВ 4</span><div class="wg-title">Тренажёр среднего геометрического</div></div>
<div class="wg-help">Реши задачу и введи число (целое или округлённое до сотых).</div>
<div class="score-display"><span>Задача <b id="p6-iv4-i">1</b> / 6</span><span>Очки: <b id="p6-iv4-s">0</b> / 6</span></div>
<div id="p6-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="p6-iv4-ans" class="tinp" style="width:120px;text-align:center" step="0.01">
<button class="btn primary" id="p6-iv4-go">Проверить</button>
<button class="btn" id="p6-iv4-start">Заново</button>
</div>
<div class="feedback" id="p6-iv4-fb"></div>
</div>`;
html += secNav('p5', 'final1');
html += readButton('p6');
box.innerHTML = html;
renderMath(box);
/* IV1 — SVG */
(function(){
const sl = document.getElementById('p6-iv1-a');
const lab = document.getElementById('p6-iv1-aval');
const svg = document.getElementById('p6-iv1-svg');
const out = document.getElementById('p6-iv1-out');
const seen = new Set();
function draw(){
const Adeg = +sl.value;
lab.textContent = Adeg;
const Arad = deg2rad(Adeg);
// Гипотенуза AB длиной 300px по низу.
const cPx = 300;
const A = {x: 90, y: 260};
const B = {x: 90 + cPx, y: 260};
// Точка C — над гипотенузой, угол при A равен Adeg, угол при B равен 90 - Adeg.
// AC = c * cos A, BC = c * sin A. Из A под углом Adeg вверх.
const ACpx = cPx * Math.cos(Arad);
// Координаты C: от A на расстояние AC под углом Adeg к AB (вверх)
const C = {x: A.x + ACpx * Math.cos(Arad), y: A.y - ACpx * Math.sin(Arad)};
// H — основание высоты из C на AB.
const H = {x: C.x, y: A.y};
// длины
const a1 = Math.abs(H.x - B.x); // BH (проекция BC)
const b1 = Math.abs(H.x - A.x); // AH (проекция AC)
const hPx = Math.abs(C.y - H.y);
const aPx = Math.sqrt((B.x-C.x)*(B.x-C.x) + (B.y-C.y)*(B.y-C.y));
const bPx = Math.sqrt((A.x-C.x)*(A.x-C.x) + (A.y-C.y)*(A.y-C.y));
// в "единичных" единицах (делим px на 30)
const sc = 30;
const a = aPx/sc, b = bPx/sc, c = cPx/sc, h = hPx/sc, _a1 = a1/sc, _b1 = b1/sc;
let s = '';
s += '<rect x="0" y="0" width="480" height="320" fill="none"/>';
// подсветка маленьких треугольников
s += '<polygon points="'+A.x+','+A.y+' '+H.x+','+H.y+' '+C.x+','+C.y+'" fill="rgba(14,165,233,.18)" stroke="none"/>';
s += '<polygon points="'+B.x+','+B.y+' '+H.x+','+H.y+' '+C.x+','+C.y+'" fill="rgba(236,72,153,.18)" stroke="none"/>';
// основной треугольник (контур)
s += '<polygon points="'+A.x+','+A.y+' '+B.x+','+B.y+' '+C.x+','+C.y+'" fill="none" stroke="#b45309" stroke-width="2.4" stroke-linejoin="round"/>';
// высота CH
s += '<line x1="'+C.x+'" y1="'+C.y+'" x2="'+H.x+'" y2="'+H.y+'" stroke="#dc2626" stroke-width="2.2" stroke-dasharray="6 4"/>';
// маркер прямого угла при C (катеты CA и CB)
const uCA = unitVec(C, A);
const uCB = unitVec(C, B);
s += '<polyline points="'+rightAngleMark(C, uCA, uCB, 11)+'" fill="none" stroke="#0f172a" stroke-width="1.8"/>';
// маркер прямого угла при H (CH и HB)
const uHC = unitVec(H, C);
const uHB = unitVec(H, B);
s += '<polyline points="'+rightAngleMark(H, uHC, uHB, 9)+'" fill="none" stroke="#0f172a" stroke-width="1.6"/>';
// вершины
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 += '<circle cx="'+H.x+'" cy="'+H.y+'" r="3.5" fill="#dc2626"/>';
s += '<text x="'+(A.x-12)+'" y="'+(A.y+18)+'" text-anchor="end" font-family="Inter,sans-serif" font-size="15" font-weight="700">A</text>';
s += '<text x="'+(B.x+12)+'" y="'+(B.y+18)+'" text-anchor="start" font-family="Inter,sans-serif" font-size="15" font-weight="700">B</text>';
s += '<text x="'+C.x+'" y="'+(C.y-10)+'" text-anchor="middle" font-family="Inter,sans-serif" font-size="15" font-weight="700">C</text>';
s += '<text x="'+H.x+'" y="'+(H.y+18)+'" text-anchor="middle" font-family="Inter,sans-serif" font-size="13" font-weight="700" fill="#dc2626">H</text>';
// подписи отрезков
s += '<text x="'+((A.x+H.x)/2)+'" y="'+(A.y+32)+'" text-anchor="middle" font-family="JetBrains Mono,monospace" font-size="11" fill="#0ea5e9">b₁='+b1.toFixed(0)+'</text>';
s += '<text x="'+((H.x+B.x)/2)+'" y="'+(A.y+32)+'" text-anchor="middle" font-family="JetBrains Mono,monospace" font-size="11" fill="#ec4899">a₁='+a1.toFixed(0)+'</text>';
s += '<text x="'+(H.x+10)+'" y="'+((C.y+H.y)/2)+'" text-anchor="start" font-family="JetBrains Mono,monospace" font-size="11" fill="#dc2626">h='+hPx.toFixed(0)+'</text>';
s += '<text x="'+((A.x+C.x)/2 - 14)+'" y="'+((A.y+C.y)/2)+'" text-anchor="end" font-family="JetBrains Mono,monospace" font-size="11" fill="#b45309">b='+bPx.toFixed(0)+'</text>';
s += '<text x="'+((B.x+C.x)/2 + 14)+'" y="'+((B.y+C.y)/2)+'" text-anchor="start" font-family="JetBrains Mono,monospace" font-size="11" fill="#b45309">a='+aPx.toFixed(0)+'</text>';
svg.innerHTML = s;
// соотношения (в пикселях — но проверка работает в любых единицах)
out.innerHTML = '<b>Три соотношения (в пикселях):</b><br>'
+ '$h^2 = '+hPx.toFixed(0)+'^2 \\approx '+(hPx*hPx).toFixed(0)+'$, &nbsp; $a_1 \\cdot b_1 = '+a1.toFixed(0)+' \\cdot '+b1.toFixed(0)+' \\approx '+(a1*b1).toFixed(0)+'$ &nbsp;✓<br>'
+ '$a^2 = '+aPx.toFixed(0)+'^2 \\approx '+(aPx*aPx).toFixed(0)+'$, &nbsp; $c \\cdot a_1 = '+cPx+' \\cdot '+a1.toFixed(0)+' \\approx '+(cPx*a1).toFixed(0)+'$ &nbsp;✓<br>'
+ '$b^2 = '+bPx.toFixed(0)+'^2 \\approx '+(bPx*bPx).toFixed(0)+'$, &nbsp; $c \\cdot b_1 = '+cPx+' \\cdot '+b1.toFixed(0)+' \\approx '+(cPx*b1).toFixed(0)+'$ &nbsp;✓';
renderMath(out);
seen.add(Adeg);
if(seen.size >= 5 && !seen.has('done')){ addXp(10,'p6-iv1'); bumpProgress('p6', 15); seen.add('done'); }
}
sl.addEventListener('input', draw);
draw();
})();
/* IV2 — Калькулятор */
(function(){
const aI = document.getElementById('p6-iv2-a1');
const bI = document.getElementById('p6-iv2-b1');
const go = document.getElementById('p6-iv2-go');
const out = document.getElementById('p6-iv2-out');
const fb = document.getElementById('p6-iv2-fb');
let solved = 0;
function calc(){
const a1 = parseFloat(aI.value), b1 = parseFloat(bI.value);
if(isNaN(a1) || isNaN(b1)){ feedback(fb, false, '&#10007; Введи оба числа.'); return; }
if(a1<=0 || b1<=0){ feedback(fb, false, '&#10007; Проекции должны быть положительны.'); return; }
const c = a1 + b1;
const h = Math.sqrt(a1 * b1);
const a = Math.sqrt(c * a1);
const b = Math.sqrt(c * b1);
const S = 0.5 * c * h;
out.innerHTML = '<b>$c = a_1 + b_1 = '+a1+' + '+b1+' = '+c+'$</b><br>'
+ '$h = \\sqrt{a_1 \\cdot b_1} = \\sqrt{'+a1+' \\cdot '+b1+'} = \\sqrt{'+(a1*b1)+'} \\approx '+h.toFixed(2)+'$<br>'
+ '$a = \\sqrt{c \\cdot a_1} = \\sqrt{'+(c*a1)+'} \\approx '+a.toFixed(2)+'$<br>'
+ '$b = \\sqrt{c \\cdot b_1} = \\sqrt{'+(c*b1)+'} \\approx '+b.toFixed(2)+'$<br>'
+ '$S = \\dfrac{1}{2} c h \\approx '+S.toFixed(2)+'$ &nbsp;(сверка: $\\tfrac{1}{2}ab \\approx '+(0.5*a*b).toFixed(2)+'$)';
renderMath(out);
feedback(fb, true, '&#10003; Все элементы найдены.');
solved++;
if(solved === 1){ addXp(10,'p6-iv2'); bumpProgress('p6', 15); }
}
go.addEventListener('click', calc);
calc();
})();
/* IV3 — Какое соотношение? */
(function(){
const Q = [
{ expr:'Известно $c = 10$, $a_1 = 4$. Найти катет $a$.', ans:'a', why:'$a^2 = c \\cdot a_1 = 40 \\Rightarrow a = \\sqrt{40}$' },
{ expr:'Известно $a_1 = 9$, $b_1 = 16$. Найти высоту $h$.', ans:'h', why:'$h^2 = a_1 \\cdot b_1 = 144 \\Rightarrow h = 12$' },
{ expr:'Известно $c = 25$, $b_1 = 9$. Найти катет $b$.', ans:'b', why:'$b^2 = c \\cdot b_1 = 225 \\Rightarrow b = 15$' },
{ expr:'Известно $a = 6$, $c = 9$. Найти $a_1$.', ans:'a', why:'из $a^2 = c \\cdot a_1$: $a_1 = a^2/c = 36/9 = 4$' },
{ expr:'Известно $h = 6$, $a_1 = 4$. Найти $b_1$.', ans:'h', why:'из $h^2 = a_1 b_1$: $b_1 = h^2/a_1 = 36/4 = 9$' },
{ expr:'Известно $b = 8$, $c = 16$. Найти $b_1$.', ans:'b', why:'из $b^2 = c \\cdot b_1$: $b_1 = b^2/c = 64/16 = 4$' },
];
let i = 0, score = 0;
function show(){
if(i >= Q.length){
document.getElementById('p6-iv3-q').innerHTML = '<b>Готово!</b> Результат: ' + score + ' / ' + Q.length;
if(score === Q.length){ addXp(15,'p6-iv3'); bumpProgress('p6', 25); }
else if(score >= Q.length - 1){ addXp(8,'p6-iv3'); bumpProgress('p6', 15); }
return;
}
document.getElementById('p6-iv3-i').textContent = (i+1);
document.getElementById('p6-iv3-s').textContent = score;
document.getElementById('p6-iv3-q').innerHTML = Q[i].expr;
renderMath(document.getElementById('p6-iv3-q'));
document.getElementById('p6-iv3-fb').style.display = 'none';
}
function answer(a){
if(i >= Q.length) return;
const fb = document.getElementById('p6-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('p6-iv3-s').textContent = score;
i++;
setTimeout(show, 1200);
}
['h','a','b'].forEach(k=>{
const btn = document.getElementById('p6-iv3-'+k); if(btn) btn.addEventListener('click', ()=>answer(k));
});
show();
})();
/* IV4 — Тренажёр */
(function(){
const Q = [
{ q:'$a_1 = 4$, $b_1 = 9$. Найти $h$.', ans:6, tol:0.05, hint:'$h = \\sqrt{4 \\cdot 9} = \\sqrt{36} = 6$' },
{ q:'$c = 20$, $b_1 = 5$. Найти $b$.', ans:10, tol:0.05, hint:'$b = \\sqrt{c b_1} = \\sqrt{100} = 10$' },
{ q:'$h = 12$, $a_1 = 9$. Найти $b_1$.', ans:16, tol:0.1, hint:'$b_1 = h^2/a_1 = 144/9 = 16$' },
{ q:'$a = 6$, $c = 9$. Найти $a_1$.', ans:4, tol:0.05, hint:'$a_1 = a^2/c = 36/9 = 4$' },
{ q:'Среднее геометрическое чисел $4$ и $25$.', ans:10, tol:0.05, hint:'$\\sqrt{4 \\cdot 25} = \\sqrt{100} = 10$' },
{ q:'$c = 10$, $a_1 = 2$. Найти $a^2$.', ans:20, tol:0.1, hint:'$a^2 = c \\cdot a_1 = 10 \\cdot 2 = 20$' },
];
let i = 0, score = 0;
function show(){
if(i >= Q.length){
document.getElementById('p6-iv4-q').innerHTML = '<b>Готово!</b> Результат: ' + score + ' / ' + Q.length;
if(score === Q.length){ addXp(15,'p6-iv4'); bumpProgress('p6', 25); }
else if(score >= 4){ addXp(8,'p6-iv4'); bumpProgress('p6', 15); }
return;
}
document.getElementById('p6-iv4-i').textContent = (i+1);
document.getElementById('p6-iv4-s').textContent = score;
document.getElementById('p6-iv4-q').innerHTML = Q[i].q;
document.getElementById('p6-iv4-ans').value = '';
renderMath(document.getElementById('p6-iv4-q'));
document.getElementById('p6-iv4-fb').style.display = 'none';
}
function go(){
if(i >= Q.length) return;
const fb = document.getElementById('p6-iv4-fb');
const ans = parseFloat(document.getElementById('p6-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('p6-iv4-s').textContent = score;
i++;
setTimeout(show, 1400);
}
document.getElementById('p6-iv4-go').addEventListener('click', go);
document.getElementById('p6-iv4-ans').addEventListener('keydown', e=>{ if(e.key==='Enter') go(); });
document.getElementById('p6-iv4-start').addEventListener('click', ()=>{ i=0; score=0; show(); });
show();
})();
wireReadBtn('p6');
}
function buildFinal1(){
const box = document.getElementById('final1-body');
let html = '';
/* Часть А — Шпаргалка главы (6 mini-карточек) */
html += `<div class="card">
<div class="card-header">
<span class="card-icon theory">${ICONS.theory}</span>
<span class="card-title">Шпаргалка главы 1</span>
<span class="card-num">Итог</span>
</div>
<div class="card-body">
<p>Все ключевые формулы главы «Соотношения в прямоугольном треугольнике» — в одном месте. Просмотри перед боссами!</p>
<div style="display:grid;grid-template-columns:repeat(auto-fit,minmax(220px,1fr));gap:12px;margin-top:10px">
<div style="padding:12px 14px;background:var(--acc-soft);border-radius:11px;border-left:3px solid var(--pri)">
<div style="display:flex;align-items:center;gap:8px;margin-bottom:6px">
<svg viewBox="0 0 24 24" fill="none" stroke="var(--pri2)" stroke-width="2" style="width:18px;height:18px"><path d="M3 21h18"/><path d="M3 21L21 3"/><path d="M3 21L3 12"/></svg>
<span style="font-family:'Unbounded',sans-serif;font-weight:700;color:var(--pri2);font-size:.92rem">§ 1 · Тригонометрия</span>
</div>
<div style="font-size:.92rem">$\\sin\\alpha = \\dfrac{a}{c},\\ \\cos\\alpha = \\dfrac{b}{c},\\ \\tan\\alpha = \\dfrac{a}{b},\\ \\cot\\alpha = \\dfrac{1}{\\tan\\alpha}$</div>
</div>
<div style="padding:12px 14px;background:var(--acc-soft);border-radius:11px;border-left:3px solid var(--pri)">
<div style="display:flex;align-items:center;gap:8px;margin-bottom:6px">
<svg viewBox="0 0 24 24" fill="none" stroke="var(--pri2)" stroke-width="2" style="width:18px;height:18px"><polygon points="3 21 21 21 21 3"/></svg>
<span style="font-family:'Unbounded',sans-serif;font-weight:700;color:var(--pri2);font-size:.92rem">§ 2 · Решение прямоуг.</span>
</div>
<div style="font-size:.92rem">Теорема Пифагора: $c^2 = a^2 + b^2$. Достаточно <b>2 стороны</b> или <b>сторона + угол</b>.</div>
</div>
<div style="padding:12px 14px;background:var(--acc-soft);border-radius:11px;border-left:3px solid var(--pri)">
<div style="display:flex;align-items:center;gap:8px;margin-bottom:6px">
<svg viewBox="0 0 24 24" fill="none" stroke="var(--pri2)" stroke-width="2" style="width:18px;height:18px"><path d="M4 17l6-6 4 4 6-8"/></svg>
<span style="font-family:'Unbounded',sans-serif;font-weight:700;color:var(--pri2);font-size:.92rem">§ 3 · Формулы</span>
</div>
<div style="font-size:.92rem">$\\sin^2\\alpha + \\cos^2\\alpha = 1$. Эталоны $30°/45°/60°$ — из специальных треугольников.</div>
</div>
<div style="padding:12px 14px;background:var(--acc-soft);border-radius:11px;border-left:3px solid var(--pri)">
<div style="display:flex;align-items:center;gap:8px;margin-bottom:6px">
<svg viewBox="0 0 24 24" fill="none" stroke="var(--pri2)" stroke-width="2" style="width:18px;height:18px"><circle cx="12" cy="12" r="9"/><line x1="3" y1="12" x2="21" y2="12"/></svg>
<span style="font-family:'Unbounded',sans-serif;font-weight:700;color:var(--pri2);font-size:.92rem">§ 4 · Тупой угол</span>
</div>
<div style="font-size:.92rem">Единичная окружность: $\\sin(180°-\\alpha) = \\sin\\alpha$, $\\cos(180°-\\alpha) = -\\cos\\alpha$.</div>
</div>
<div style="padding:12px 14px;background:var(--acc-soft);border-radius:11px;border-left:3px solid var(--pri)">
<div style="display:flex;align-items:center;gap:8px;margin-bottom:6px">
<svg viewBox="0 0 24 24" fill="none" stroke="var(--pri2)" stroke-width="2" style="width:18px;height:18px"><rect x="3" y="3" width="18" height="18" rx="2"/><path d="M3 3l18 18"/></svg>
<span style="font-family:'Unbounded',sans-serif;font-weight:700;color:var(--pri2);font-size:.92rem">§ 5 · Площадь</span>
</div>
<div style="font-size:.92rem">$S_\\triangle = \\tfrac{1}{2}ab\\sin C$, $S_{пар} = ab\\sin\\alpha$, $S_{4-уг} = \\tfrac{1}{2}d_1 d_2\\sin\\varphi$.</div>
</div>
<div style="padding:12px 14px;background:var(--acc-soft);border-radius:11px;border-left:3px solid var(--pri)">
<div style="display:flex;align-items:center;gap:8px;margin-bottom:6px">
<svg viewBox="0 0 24 24" fill="none" stroke="var(--pri2)" stroke-width="2" style="width:18px;height:18px"><polygon points="3 20 21 20 12 4"/><line x1="12" y1="4" x2="12" y2="20"/></svg>
<span style="font-family:'Unbounded',sans-serif;font-weight:700;color:var(--pri2);font-size:.92rem">§ 6 · Среднее геом.</span>
</div>
<div style="font-size:.92rem">Высота к гипотенузе: $h^2 = a_1 b_1$, $a^2 = c \\cdot a_1$, $b^2 = c \\cdot b_1$.</div>
</div>
</div>
</div>
</div>`;
/* Часть Б — 5 боссов */
html += `<div class="card">
<div class="card-header">
<span class="card-icon rule">${ICONS.rule}</span>
<span class="card-title">Боссы главы 1</span>
<span class="card-num">5</span>
</div>
<div class="card-body">
<p>5 интегрированных задач — каждая комбинирует несколько тем главы. За каждого побеждённого босса — <b>+10 XP</b> и +18% к прогрессу. Победишь всех — <b>+50 XP бонус</b> и ачивка «Мастер прямоугольного треугольника»!</p>
</div>
</div>`;
html += '<div id="ch1G-bosses-container"></div>';
html += `<div style="margin-top:18px;padding:18px 20px;background:linear-gradient(135deg,var(--pri-soft),var(--acc-soft));border-radius:14px;border:1.5px solid var(--pri);text-align:center" id="ch1G-final-summary">
<div style="font-family:'Unbounded',sans-serif;font-weight:800;color:var(--pri2);font-size:1.1rem;margin-bottom:6px">Прогресс по боссам</div>
<div id="ch1G-boss-overall" style="font-size:.95rem;color:var(--text);margin-bottom:10px">0 / 5 боссов побеждено</div>
<div style="height:12px;background:var(--card);border-radius:8px;overflow:hidden;border:1px solid var(--border)">
<div id="ch1G-boss-overall-fill" style="height:100%;width:0%;background:linear-gradient(90deg,var(--pri),var(--acc));transition:width .35s"></div>
</div>
<div id="ch1G-final-reward" style="margin-top:14px;display:none;padding:14px;background:var(--card);border-radius:11px;border:2px solid var(--warn)">
<div style="display:flex;align-items:center;justify-content:center;gap:8px;margin-bottom:6px">
<svg viewBox="0 0 24 24" fill="none" stroke="var(--warn)" stroke-width="2.2" style="width:22px;height:22px"><polygon points="12 2 15 9 22 9 17 14 19 22 12 18 5 22 7 14 2 9 9 9"/></svg>
<div style="font-family:'Unbounded',sans-serif;font-weight:800;color:var(--pri2);font-size:1.05rem">Мастер прямоугольного треугольника</div>
</div>
<div style="font-size:.92rem;margin-bottom:10px">Глава 1 пройдена! Все 5 боссов повержены. +50 XP бонус.</div>
<a class="btn primary" href="/textbook/geometry-9-ch2" style="text-decoration:none">Дальше: Глава 2 <svg class="ic" viewBox="0 0 24 24"><polyline points="9 18 15 12 9 6"/></svg></a>
</div>
</div>`;
html += secNav('p6', null);
box.innerHTML = html;
renderMath(box);
/* Боссы */
const BOSSES = [
{
n:1, color:'#10b981',
title:'Сфинкс Тригонометрии',
tag:'§ 1 + § 3',
q:'В прямоугольном треугольнике катеты $a = 3$ и $b = 4$. Чему равен <b>синус большего острого угла</b>?',
ans:0.8, decimal:true,
hint:'Гипотенуза $c = \\sqrt{3^2 + 4^2} = 5$. Больший острый угол лежит против большего катета (4). $\\sin = \\dfrac{4}{5} = 0{,}8$.'
},
{
n:2, color:'#0891b2',
title:'Минотавр Решения',
tag:'§ 2 + § 1',
q:'В прямоугольном треугольнике $\\angle A = 30°$, гипотенуза $c = 12$. Найди длину катета, <b>противолежащего</b> углу $A$.',
ans:6, decimal:false,
hint:'$a = c \\sin A = 12 \\cdot \\sin 30° = 12 \\cdot \\dfrac{1}{2} = 6$.'
},
{
n:3, color:'#7c3aed',
title:'Гарпия Тупого Угла',
tag:'§ 4 + § 3',
q:'Вычисли: $\\sin 150° + \\cos 60° = \\,?$',
ans:1, decimal:false,
hint:'$\\sin 150° = \\sin(180°-30°) = \\sin 30° = \\dfrac{1}{2}$. $\\cos 60° = \\dfrac{1}{2}$. Сумма $= 1$.'
},
{
n:4, color:'#dc2626',
title:'Дракон Площади',
tag:'§ 5 + § 1',
q:'Треугольник со сторонами $a = 8$, $b = 5$ и углом $C = 30°$ между ними. Найди площадь $S$.',
ans:10, decimal:false,
hint:'$S = \\dfrac{1}{2} ab \\sin C = \\dfrac{1}{2} \\cdot 8 \\cdot 5 \\cdot \\sin 30° = 20 \\cdot \\dfrac{1}{2} = 10$.'
},
{
n:5, color:'#f59e0b',
title:'Мастер Прямоугольного',
tag:'§§ 1-6 — синтез',
q:'В прямоугольном треугольнике катеты $a = 6$, $b = 8$. Из прямого угла опущена <b>высота на гипотенузу</b>. Найди её длину.',
ans:4.8, decimal:true,
hint:'Гипотенуза $c = \\sqrt{36+64} = 10$. Из $S = \\dfrac{1}{2}ab = \\dfrac{1}{2}ch$ следует $h = \\dfrac{ab}{c} = \\dfrac{48}{10} = 4{,}8$.'
},
];
const cont = document.getElementById('ch1G-bosses-container');
const STATE_KEY = 'geometry9_ch1_bosses';
const BOSS_STATE = (function(){
try{ const s = localStorage.getItem(STATE_KEY); if(s){ const p = JSON.parse(s); if(Array.isArray(p) && p.length === BOSSES.length) return p; } }catch(e){}
return BOSSES.map(()=>({defeated:false}));
})();
function saveBosses(){ try{ localStorage.setItem(STATE_KEY, JSON.stringify(BOSS_STATE)); }catch(e){} }
cont.innerHTML = BOSSES.map((b, idx)=>{
const stepAttr = b.decimal ? 'step="0.1"' : 'step="1"';
const ph = b.decimal ? 'число (можно с запятой)' : 'целое число';
return '<div class="boss-card" id="bossG1-'+b.n+'-card" style="padding:16px;background:var(--card);border-radius:12px;border:2px solid '+b.color+';margin-bottom:14px;transition:background .3s,box-shadow .3s">'
+'<div style="display:flex;align-items:center;gap:10px;margin-bottom:10px;flex-wrap:wrap">'
+'<svg viewBox="0 0 24 24" fill="none" stroke="'+b.color+'" stroke-width="2.2" style="width:28px;height:28px;flex-shrink:0"><polygon points="12,2 22,20 2,20"/></svg>'
+'<div style="font-family:\'Unbounded\',sans-serif;font-weight:800;color:'+b.color+';font-size:1.05rem">Босс '+b.n+': '+b.title+'</div>'
+'<div style="margin-left:auto;font-size:.78rem;color:var(--muted);padding:3px 8px;background:var(--acc-soft);border-radius:6px">'+b.tag+'</div>'
+'</div>'
+'<div id="bossG1-'+b.n+'-q" style="padding:12px 14px;background:var(--acc-soft);border-radius:9px;font-size:1rem;line-height:1.55;margin-bottom:10px">'+b.q+'</div>'
+'<div style="display:flex;gap:8px;align-items:center;flex-wrap:wrap">'
+'<span style="font-family:\'JetBrains Mono\',monospace;font-size:.92rem">ответ =</span>'
+'<input type="number" '+stepAttr+' id="bossG1-'+b.n+'-ans" class="tinp" style="width:150px;text-align:center" placeholder="'+ph+'">'
+'<button class="btn primary" id="bossG1-'+b.n+'-go" style="background:'+b.color+';border-color:'+b.color+'">Атаковать</button>'
+'<button class="btn" id="bossG1-'+b.n+'-hint">Подсказка</button>'
+'</div>'
+'<div class="feedback" id="bossG1-'+b.n+'-fb"></div>'
+'</div>';
}).join('');
renderMath(cont);
function markDefeatedUI(b, idx){
const card = document.getElementById('bossG1-'+b.n+'-card');
const goBtn = document.getElementById('bossG1-'+b.n+'-go');
const ansInp = document.getElementById('bossG1-'+b.n+'-ans');
if(!card || !goBtn || !ansInp) return;
card.style.background = 'linear-gradient(135deg,var(--acc-soft),var(--pri-soft))';
card.style.boxShadow = '0 0 0 2px '+b.color+'33, 0 8px 24px rgba(16,185,129,.12)';
goBtn.disabled = true; goBtn.style.opacity = .55;
goBtn.innerHTML = '<svg class="ic" viewBox="0 0 24 24" style="margin-right:4px"><polyline points="20 6 9 17 4 12"/></svg> Повержен';
ansInp.disabled = true;
}
function refreshOverall(){
const won = BOSS_STATE.filter(s => s.defeated).length;
const txt = document.getElementById('ch1G-boss-overall');
const fill = document.getElementById('ch1G-boss-overall-fill');
if(txt) txt.textContent = won + ' / ' + BOSSES.length + ' боссов побеждено';
if(fill) fill.style.width = (won * 100 / BOSSES.length) + '%';
if(won >= BOSSES.length){
const reward = document.getElementById('ch1G-final-reward');
if(reward && reward.style.display === 'none'){
reward.style.display = 'block';
if(!STATE.achievements.has('ch1_done')){
achievement('ch1_done','Мастер прямоугольного треугольника');
addXp(50, 'ch1-bonus');
bumpProgress('final1', 10);
if(window.confetti){ try{ window.confetti(); }catch(e){} }
}
}
}
}
BOSSES.forEach((b, idx)=>{
const goBtn = document.getElementById('bossG1-'+b.n+'-go');
const hintBtn = document.getElementById('bossG1-'+b.n+'-hint');
const ansInp = document.getElementById('bossG1-'+b.n+'-ans');
if(BOSS_STATE[idx].defeated) markDefeatedUI(b, idx);
goBtn.addEventListener('click', ()=>{
if(BOSS_STATE[idx].defeated) return;
const fb = document.getElementById('bossG1-'+b.n+'-fb');
const raw = (ansInp.value||'').replace(',', '.').trim();
const val = parseFloat(raw);
if(isNaN(val) || raw === ''){ feedback(fb, false, '&#10007; Введи число.'); return; }
const ok = Math.abs(val - b.ans) < 0.05;
if(ok){
BOSS_STATE[idx].defeated = true; saveBosses();
feedback(fb, true, '&#10003; Босс '+b.n+' повержен! +10 XP. '+b.hint);
addXp(10, 'boss-ch1-'+b.n);
bumpProgress('final1', 18);
markDefeatedUI(b, idx);
refreshOverall();
} else {
feedback(fb, false, '&#10007; Промах. Попробуй ещё. Подсказка доступна.');
}
});
hintBtn.addEventListener('click', ()=>{
const fb = document.getElementById('bossG1-'+b.n+'-fb');
fb.className = 'feedback ok';
fb.innerHTML = '<b>Подсказка:</b> '+b.hint;
fb.style.display = 'block';
fb.style.background = 'var(--warn-bg)';
fb.style.color = '#92400e';
fb.style.borderLeftColor = 'var(--warn)';
renderMath(fb);
});
ansInp.addEventListener('keydown', e=>{ if(e.key === 'Enter') goBtn.click(); });
});
refreshOverall();
}
/* ===== 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>