Files
Maxim Dolgolyov 9d5a2959e1 fix(textbooks): кнопка «Шпаргалка» не открывала контент на desktop
На десктопе (>980px) .col-side уже видна как sticky-колонка справа в grid 1fr 280px.
Клик по кнопке #sidebar-btn добавлял .col-side-backdrop.show — backdrop с
z-index:9990 затемнял всю страницу, перекрывая sticky-aside. Со стороны
выглядело как «ничего не открылось» — на самом деле появлялась чёрная вуаль.

Фикс: @media(min-width:981px) скрывает #sidebar-btn и подавляет показ backdrop.
На мобайле (≤980px) кнопка и overlay работают как раньше.

Применено в 51 файле: physics 8/9/10 chN, algebra 7/9/10/11 chN + 8 ch2-3,
geometry 7/8/9/11 chN, geometry_10 r1-4.
2026-05-30 09:51:04 +03:00

1774 lines
115 KiB
HTML
Raw Permalink 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 · Глава 2 · Окружности</title>
<meta name="viewport" content="width=device-width,initial-scale=1">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/katex@0.16.9/dist/katex.min.css">
<script defer src="https://cdn.jsdelivr.net/npm/katex@0.16.9/dist/katex.min.js"></script>
<script defer src="https://cdn.jsdelivr.net/npm/katex@0.16.9/dist/contrib/auto-render.min.js"
onload="renderMathInElement(document.body,{delimiters:[{left:'$$',right:'$$',display:true},{left:'$',right:'$',display:false},{left:'\\[',right:'\\]',display:true},{left:'\\(',right:'\\)',display:false}],throwOnError:false})"></script>
<script src="/js/api.js" defer></script>
<script src="/js/xp.js" defer></script>
<link 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:#059669; --pri2:#047857; --pri-soft:#d1fae5;
--acc:#34d399; --acc2:#059669; --acc-soft:#ecfdf5;
--ok:#10b981; --ok-bg:#d1fae5; --warn:#f59e0b; --warn-bg:#fef3c7;
--bad:#ef4444; --fail:#dc2626; --fail-bg:#fee2e2;
}
.dark{--bg:#08201b; --card:#0a2b22; --card-soft:#0d3329; --text:#d1fae5; --ink:#d1fae5; --muted:#7aa89a; --border:#1c463a}
*{margin:0;padding:0;box-sizing:border-box;-webkit-tap-highlight-color:transparent}
html,body{font-family:'Inter',system-ui,sans-serif;background:var(--bg);color:var(--text);line-height:1.55;font-size:15px}
button,input,select,textarea{font-family:inherit;font-size:inherit}
button{cursor:pointer;border:0;background:transparent;color:inherit}
a{color:inherit;text-decoration:none}
.ic{width:16px;height:16px;display:inline-block;flex-shrink:0;stroke:currentColor;fill:none;stroke-width:2;stroke-linecap:round;stroke-linejoin:round;vertical-align:middle}
.hdr{position:relative;background:linear-gradient(110deg,#064e3b 0%,#059669 55%,#34d399 100%);color:#fff;padding:46px 22px 30px;overflow:hidden;border-bottom:2px solid rgba(209,250,229,.2);min-height:130px}
.hdr::before{content:'ГЛАВА 2';position:absolute;right:-12px;top:50%;transform:translateY(-50%);font-family:'Unbounded',sans-serif;font-size:clamp(5rem,15vw,11rem);font-weight:900;letter-spacing:-.04em;color:transparent;-webkit-text-stroke:1.5px rgba(209,250,229,.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:'○';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}
.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(min-width:981px){#sidebar-btn{display:none}.col-side-backdrop.show{display:none}}
@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}
/* === GEOM9 POLISH (visual + micro-interactions) === */
@keyframes wgFadeIn{from{opacity:0;transform:translateY(8px)}to{opacity:1;transform:none}}
.sec.active .wg{animation:wgFadeIn .35s cubic-bezier(.16,1,.3,1) backwards}
.sec.active .wg:nth-of-type(1){animation-delay:.02s}
.sec.active .wg:nth-of-type(2){animation-delay:.08s}
.sec.active .wg:nth-of-type(3){animation-delay:.14s}
.sec.active .wg:nth-of-type(4){animation-delay:.20s}
.sec.active .wg:nth-of-type(5){animation-delay:.26s}
.sec.active .wg:nth-of-type(6){animation-delay:.32s}
.wg svg{transition:filter .25s ease}
.wg:hover svg{filter:drop-shadow(0 4px 16px rgba(0,0,0,.10))}
input[type=range]:active{box-shadow:0 0 0 4px var(--pri-soft);border-radius:8px}
.wg input[type=range]{cursor:ew-resize}
.score-display b{transition:transform .22s cubic-bezier(.16,1,.3,1),color .22s;display:inline-block;transform-origin:center}
.score-display b.bump{transform:scale(1.28);color:var(--pri)}
.katex{transition:color .2s}
.wg-help .katex:hover,.card-body .katex:hover{color:var(--pri2,var(--pri));cursor:help}
.hp-fill,.psel-prog-fill,.xp-fill,[id$="-overall-fill"]{transition:width .6s cubic-bezier(.16,1,.3,1)!important}
.boss-card,.btn.primary,.btn-primary{position:relative;overflow:hidden}
.btn.primary::after,.btn-primary::after{content:'';position:absolute;inset:0;background:radial-gradient(circle at center,rgba(255,255,255,.42) 0%,transparent 60%);opacity:0;transition:opacity .25s;pointer-events:none}
.btn.primary:hover::after,.btn-primary:hover::after{opacity:1}
.psel-card{position:relative}
.psel-card .psel-done{position:absolute;top:6px;right:6px;width:18px;height:18px;border-radius:50%;background:#10b981;display:none;align-items:center;justify-content:center;box-shadow:0 2px 6px rgba(16,185,129,.45);z-index:2}
.psel-card .psel-done svg{width:11px;height:11px;stroke:#fff;fill:none;stroke-width:3;stroke-linecap:round;stroke-linejoin:round}
.psel-card.done .psel-done{display:flex}
.boss-card{transition:border-color .35s,box-shadow .6s,background .3s,transform .2s}
.boss-card.glow{box-shadow:0 0 24px rgba(16,185,129,.6),0 0 0 2px rgba(16,185,129,.45)!important}
@keyframes bossPulse{0%{box-shadow:0 0 0 0 rgba(16,185,129,.55)}70%{box-shadow:0 0 0 14px rgba(16,185,129,0)}100%{box-shadow:0 0 0 0 rgba(16,185,129,0)}}
.boss-card.pulse{animation:bossPulse .8s ease-out}
.psel-card{transition:transform .2s,box-shadow .2s,border-color .2s,background .25s}
.sec{transition:opacity .25s}
</style>
</head>
<body>
<header class="hdr">
<div class="hdr-row">
<div>
<h1>Геометрия 9 · Глава 2</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>описанную</b> и <b>вписанную</b> окружности треугольника, специальные формулы для прямоугольного треугольника $R = c/2$ и $r = (a+b-c)/2$, а также критерии вписанных и описанных четырёхугольников: $\alpha + \gamma = 180^\circ$ и $a+c = b+d$.</p>
<div class="hero-row">
<button class="btn-primary" onclick="goTo('p7')"><svg class="ic" viewBox="0 0 24 24"><polygon points="6 4 20 12 6 20 6 4" fill="currentColor" stroke="none"/></svg> Начать § 7</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" data-gamified></div>
</div>
</section>
<section class="psel">
<div class="psel-title">Параграфы главы</div>
<div id="psel-grid" class="psel-grid"></div>
</section>
<section id="sec-p7" class="sec" data-watermark="○"><div class="sec-header"><span class="sec-num">§ 7</span><h2 class="sec-h">Описанная и вписанная окружности треугольника</h2></div><div id="p7-body"></div></section>
<section id="sec-p8" class="sec" data-watermark="⊥"><div class="sec-header"><span class="sec-num">§ 8</span><h2 class="sec-h">Окружности прямоугольного треугольника</h2></div><div id="p8-body"></div></section>
<section id="sec-p9" class="sec" data-watermark="◇"><div class="sec-header"><span class="sec-num">§ 9</span><h2 class="sec-h">Вписанные и описанные четырёхугольники</h2></div><div id="p9-body"></div></section>
<section id="sec-final2" class="sec" data-watermark="★"><div class="sec-header"><span class="sec-num" style="background:linear-gradient(135deg,#059669,#34d399)"></span><h2 class="sec-h">Итоги главы 2</h2></div><div id="final2-body"></div></section>
</div>
<aside class="col-side" id="col-side"><div id="sidebar-content"></div></aside>
<div class="col-side-backdrop" id="col-side-backdrop"></div>
</main>
<footer class="foot">Интерактивный учебник «Геометрия 9» · Глава 2 · Окружности · 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:'p7', progress:{p7:0,p8:0,p9:0,final2:0}, achievements:new Map(), xp:0, level:1 };
const TOTAL_PARAS = 4;
const _TB_SLUG = 'geometry-9-ch2';
function calcLevel(xp){ return Math.floor(Math.sqrt((xp||0)/100))+1; }
function _xpForLevel(lv){ return (lv-1)*(lv-1)*100; }
const ACH_LABELS = {
start:'Начало главы 2!',
p7_done:'Описанная и вписанная окружности освоены!',
p8_done:'Окружности прямоугольного треугольника освоены!',
p9_done:'Вписанные и описанные четырёхугольники освоены!',
ch2_done:'Глава 2 пройдена! Окружности — финал!'
};
function loadProgress(){
try{
const s=localStorage.getItem('geometry9_ch2_progress'); if(s) Object.assign(STATE.progress, JSON.parse(s));
const a=localStorage.getItem('geometry9_ch2_achievements');
if(a){ const p=JSON.parse(a); if(Array.isArray(p)) p.forEach(id=>STATE.achievements.set(id, ACH_LABELS[id]||id)); else if(p&&typeof p==='object'){ for(const[id,t] of Object.entries(p)) STATE.achievements.set(id,(t&&t!==id)?t:(ACH_LABELS[id]||id)); } }
STATE.xp=+(localStorage.getItem('geometry9_xp')||0); STATE.level=calcLevel(STATE.xp);
}catch(e){}
}
function saveProgress(){
try{
localStorage.setItem('geometry9_ch2_progress', JSON.stringify(STATE.progress));
localStorage.setItem('geometry9_ch2_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-ch2-'+(src||'misc'));
if(STATE.level>prev){
const pop=document.getElementById('ach-popup');
if(pop){ document.getElementById('ach-text').textContent='Уровень '+STATE.level+'!'; pop.classList.add('show'); setTimeout(()=>pop.classList.remove('show'),2600); }
}
}
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:'p7', num:'§ 7', name:'Описанная и вписанная окружности треугольника', sub:'центр $O$, радиус $R$, $r$' },
{ id:'p8', num:'§ 8', name:'Окружности прямоугольного треугольника', sub:'$R = c/2$, $r = (a+b-c)/2$' },
{ id:'p9', num:'§ 9', name:'Вписанные и описанные четырёхугольники', sub:'$\\alpha + \\gamma = 180^\\circ$' },
{ id:'final2', num:'★', name:'Финал главы', sub:'Итоги главы 2', 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 = { p7:()=>buildP7(), p8:()=>buildP8(), p9:()=>buildP9(), final2:()=>buildFinal2() };
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 = {
p7:{title:'Шпаргалка \xA77',rows:[['Описанная','через все вершины'],['Центр','пересечение серединных перпендикуляров'],['Вписанная','касается всех сторон'],['Центр','пересечение биссектрис']]},
p8:{title:'Шпаргалка \xA78',rows:[['Описанная','$R = \\tfrac{c}{2}$ — половина гипотенузы'],['Центр','середина гипотенузы'],['Вписанная','$r = \\tfrac{a+b-c}{2}$']]},
p9:{title:'Шпаргалка \xA79',rows:[['Вписанный','$\\alpha + \\gamma = 180^\\circ$'],['Описанный','$a+c = b+d$']]},
final2:{title:'Финал главы',rows:[['§§79','теория главы 2'],['Дальше','глава 3 — теоремы синусов и косинусов']]}
};
const TIPS=[
{sec:'p7',html:'Центр описанной окружности — точка пересечения серединных перпендикуляров. Центр вписанной — точка пересечения биссектрис.'},
{sec:'p8',html:'В прямоугольном треугольнике гипотенуза — диаметр описанной окружности, отсюда $R = \\tfrac{c}{2}$.'},
{sec:'p9',html:'Четырёхугольник вписан в окружность ⟺ суммы противоположных углов равны $180^\\circ$. Четырёхугольник описан ⟺ суммы противоположных сторон равны.'},
{sec:'final2',html:'Главные результаты главы 2: формулы радиусов окружностей треугольника и критерии вписанных и описанных четырёхугольников.'}
];
function buildSidebar(id){
const box=document.getElementById('sidebar-content');
const sb=SIDEBARS[id]||SIDEBARS['p7'];
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" data-gamified><div class="xp-card-title" data-gamified><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_ch2_theme')||'light';
if(t==='dark') document.documentElement.classList.add('dark');
document.getElementById('theme-lab').textContent=t==='dark'?'Светлая':'Тёмная';
document.getElementById('theme-btn').addEventListener('click', ()=>{
document.documentElement.classList.toggle('dark');
const dark=document.documentElement.classList.contains('dark');
localStorage.setItem('geometry9_ch2_theme', dark?'dark':'light');
document.getElementById('theme-lab').textContent=dark?'Светлая':'Тёмная';
});
}
function renderMath(root){ if(window.renderMathInElement){ try{ renderMathInElement(root, {delimiters:[{left:'$$',right:'$$',display:true},{left:'$',right:'$',display:false},{left:'\\[',right:'\\]',display:true},{left:'\\(',right:'\\)',display:false}],throwOnError:false}); }catch(e){} } }
function feedback(elm, ok, text){ 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>'
};
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={p7:'\xA77',p8:'\xA78',p9:'\xA79',final2:'Финал'};
let h='<div class="sec-nav">';
h+=prev?'<button class="btn" onclick="goTo(\''+prev+'\')"><svg class="ic" viewBox="0 0 24 24"><polyline points="15 18 9 12 15 6"/></svg> '+NAMES[prev]+'</button>':'<span></span>';
h+=next?'<button class="btn primary" onclick="goTo(\''+next+'\')">'+NAMES[next]+' <svg class="ic" viewBox="0 0 24 24"><polyline points="9 18 15 12 9 6"/></svg></button>':'<span></span>';
h+='</div>'; return h;
}
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==='final2') achievement('ch2_done');
});
}
/* ===== SVG helpers ===== */
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;
}
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;
}
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; }
/* ===== 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);
}
/* ===== §7 Описанная и вписанная окружности треугольника ===== */
function buildP7(){
const box = document.getElementById('p7-body');
let html = '';
html += makeCard('theory', 'Описанная окружность', '7.1', `
<p><b>Описанная</b> около треугольника окружность проходит через все три вершины. Её центр — точка пересечения <b>серединных перпендикуляров</b> к сторонам, обозначается $O$. Радиус $R$.</p>
<p>Для любого треугольника <b>существует и единственна</b> описанная окружность.</p>
<details class="spoiler"><summary>Почему серединные перпендикуляры?</summary><div class="spoiler-body">
Серединный перпендикуляр к отрезку — множество точек, равноудалённых от его концов. Точка $O$ равноудалена от $A$ и $B$ (на серединном перпендикуляре к $AB$) и от $B$ и $C$ (на серединном перпендикуляре к $BC$), значит $OA = OB = OC = R$.
</div></details>`);
html += makeCard('rule', 'Вписанная окружность', '7.2', `
<p><b>Вписанная</b> в треугольник окружность касается всех трёх сторон изнутри. Её центр — точка пересечения <b>биссектрис</b> углов, обозначается $I$. Радиус $r$.</p>
<p>Также существует и единственна. Формула связи площади и вписанного радиуса:</p>
$$S_\\triangle = p \\cdot r$$
<p>где $p = \\dfrac{a+b+c}{2}$ — полупериметр.</p>
<p>Радиус описанной выражается через стороны и площадь:</p>
$$R = \\dfrac{abc}{4S}, \\qquad r = \\dfrac{S}{p}$$`);
html += makeCard('example', 'Замечательные свойства', '7.3', `
<ul style="padding-left:22px;line-height:1.95">
<li>Центр $O$ описанной окружности <b>внутри</b> треугольника, если все углы острые; <b>на гипотенузе</b>, если есть прямой угол; <b>снаружи</b>, если есть тупой угол.</li>
<li>Центр $I$ вписанной окружности <b>всегда внутри</b> треугольника.</li>
<li>Расстояние от центра $I$ до любой стороны равно $r$.</li>
</ul>
<p><b>Пример.</b> В треугольнике 3-4-5 (прямоугольном): $S = 6$, $p = 6$, $r = S/p = 1$, $R = abc/(4S) = 60/24 = 2{,}5$ — половина гипотенузы.</p>`);
/* IV1 — Описанная и вписанная окружности */
html += `<div class="wg" id="p7-iv1">
<div class="wg-header"><span class="wg-badge">ИНТЕРАКТИВ 1</span><div class="wg-title">Описанная и вписанная окружности</div></div>
<div class="wg-help">Выбери тип треугольника — увидишь его описанную ($O$, синяя) и вписанную ($I$, оранжевая) окружности. Заметь, где располагается центр $O$ в каждом случае.</div>
<div class="sliders">
<label>Тип треугольника<b id="p7-iv1-tval">1</b><input type="range" id="p7-iv1-t" min="1" max="5" step="1" value="1"></label>
</div>
<div style="background:var(--card);border-radius:10px;padding:10px;overflow-x:auto">
<svg id="p7-iv1-svg" viewBox="0 0 420 380" style="width:100%;min-width:340px;height:auto;display:block"></svg>
</div>
<div id="p7-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 — Калькулятор R и r */
html += `<div class="wg" id="p7-iv2">
<div class="wg-header"><span class="wg-badge">ИНТЕРАКТИВ 2</span><div class="wg-title">Калькулятор $R$ и $r$</div></div>
<div class="wg-help">Введи три стороны треугольника $a$, $b$, $c$ — программа проверит неравенство треугольника и найдёт радиусы по формулам Герона: $S = \\sqrt{p(p-a)(p-b)(p-c)}$, $R = \\tfrac{abc}{4S}$, $r = \\tfrac{S}{p}$.</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="p7-iv2-a" class="tinp" style="width:80px;text-align:center" value="3" min="0.1" step="0.1">
<span style="font-family:'JetBrains Mono',monospace">$b$ =</span>
<input type="number" id="p7-iv2-b" class="tinp" style="width:80px;text-align:center" value="4" min="0.1" step="0.1">
<span style="font-family:'JetBrains Mono',monospace">$c$ =</span>
<input type="number" id="p7-iv2-c" class="tinp" style="width:80px;text-align:center" value="5" min="0.1" step="0.1">
<button class="btn primary" id="p7-iv2-go">Вычислить</button>
</div>
<div id="p7-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="p7-iv2-fb"></div>
</div>`;
/* IV3 — Quickfire «Внутри или снаружи?» */
html += `<div class="wg" id="p7-iv3">
<div class="wg-header"><span class="wg-badge">ИНТЕРАКТИВ 3</span><div class="wg-title">Внутри или снаружи?</div></div>
<div class="wg-help">Дан треугольник — определи, где находится центр $O$ описанной окружности.</div>
<div class="score-display"><span>Задача <b id="p7-iv3-i">1</b> / 6</span><span>Очки: <b id="p7-iv3-s">0</b> / 6</span></div>
<div id="p7-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="in" id="p7-iv3-in">Внутри</button>
<button class="btn primary" data-ans="hyp" id="p7-iv3-hyp">На гипотенузе</button>
<button class="btn primary" data-ans="out" id="p7-iv3-out">Снаружи</button>
</div>
<div class="feedback" id="p7-iv3-fb"></div>
</div>`;
/* IV4 — Тренажёр */
html += `<div class="wg" id="p7-iv4">
<div class="wg-header"><span class="wg-badge">ИНТЕРАКТИВ 4</span><div class="wg-title">Тренажёр $R$ и $r$</div></div>
<div class="wg-help">Реши задачу и введи число (округли до 2 знаков, если получается дробное).</div>
<div class="score-display"><span>Задача <b id="p7-iv4-i">1</b> / 6</span><span>Очки: <b id="p7-iv4-s">0</b> / 6</span></div>
<div id="p7-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="p7-iv4-ans" class="tinp" style="width:110px;text-align:center" step="0.01">
<button class="btn primary" id="p7-iv4-go">Проверить</button>
<button class="btn" id="p7-iv4-start">Заново</button>
</div>
<div class="feedback" id="p7-iv4-fb"></div>
</div>`;
html += secNav(null, 'p8');
html += readButton('p7');
box.innerHTML = html;
renderMath(box);
/* IV1 — визуализатор окружностей */
(function(){
const sl = document.getElementById('p7-iv1-t');
const lab = document.getElementById('p7-iv1-tval');
const svg = document.getElementById('p7-iv1-svg');
const out = document.getElementById('p7-iv1-out');
const seen = new Set();
// 5 пресетов: углы A, B, C
const PRESETS = [
{name:'Равносторонний', angles:[60,60,60], kind:'in'},
{name:'Равнобедренный 70-70-40', angles:[70,70,40], kind:'in'},
{name:'Прямоугольный 30-60-90', angles:[30,60,90], kind:'hyp'},
{name:'Произвольный 40-60-80', angles:[40,60,80], kind:'in'},
{name:'Тупоугольный 30-30-120', angles:[30,30,120], kind:'out'}
];
function draw(){
const idx = (+sl.value) - 1;
lab.textContent = (idx+1);
const P = PRESETS[idx];
const [Ad, Bd, Cd] = P.angles;
const aR = deg2rad(Ad), bR = deg2rad(Bd);
// Сторона BC = a, CA = b, AB = c (закон синусов: a/sinA = 2R)
// Зададим масштаб через 2R = 180 (картинка ~ 360 широкая после)
const twoR = 170;
const a = twoR * Math.sin(aR);
const b = twoR * Math.sin(bR);
const c = twoR * Math.sin(deg2rad(Cd));
// Положим B в (xB, yB) и C в (xB+a, yB) — горизонтально.
// Тогда A находится в позиции с углом B при вершине B и c — сторона BA.
const cx0 = 210, cy0 = 230; // центр SVG
const Bx = -a/2, By = 0;
const Cx = a/2, Cy = 0;
// A: длина BA = c, угол при B = B (между BA и BC), в SVG y вниз — поднимем A выше
const Ax = Bx + c * Math.cos(bR);
const Ay = By - c * Math.sin(bR);
// Сдвиг так, чтобы центрировать
const cxAll = (Ax+Bx+Cx)/3, cyAll = (Ay+By+Cy)/3;
const A = {x: cx0 + (Ax - cxAll), y: cy0 + (Ay - cyAll)};
const B = {x: cx0 + (Bx - cxAll), y: cy0 + (By - cyAll)};
const C = {x: cx0 + (Cx - cxAll), y: cy0 + (Cy - cyAll)};
// Длины сторон (в SVG пикселях)
function dist(P,Q){ return Math.hypot(P.x-Q.x, P.y-Q.y); }
const sa = dist(B,C), sb = dist(C,A), sc = dist(A,B);
const p = (sa+sb+sc)/2;
const S = Math.sqrt(Math.max(0, p*(p-sa)*(p-sb)*(p-sc)));
const R = (sa*sb*sc)/(4*S);
const r = S / p;
// Центр описанной (через формулы): O = пересечение серединных перпендикуляров.
const D = 2 * (A.x*(B.y-C.y) + B.x*(C.y-A.y) + C.x*(A.y-B.y));
const Ox = ((A.x*A.x+A.y*A.y)*(B.y-C.y) + (B.x*B.x+B.y*B.y)*(C.y-A.y) + (C.x*C.x+C.y*C.y)*(A.y-B.y)) / D;
const Oy = ((A.x*A.x+A.y*A.y)*(C.x-B.x) + (B.x*B.x+B.y*B.y)*(A.x-C.x) + (C.x*C.x+C.y*C.y)*(B.x-A.x)) / D;
// Инцентр: I = (sa·A + sb·B + sc·C) / (sa+sb+sc)
const Ix = (sa*A.x + sb*B.x + sc*C.x) / (sa+sb+sc);
const Iy = (sa*A.y + sb*B.y + sc*C.y) / (sa+sb+sc);
let s = '';
s += '<rect x="0" y="0" width="420" height="380" fill="none"/>';
// Описанная окружность
s += '<circle cx="'+Ox.toFixed(2)+'" cy="'+Oy.toFixed(2)+'" r="'+R.toFixed(2)+'" fill="none" stroke="#0ea5e9" stroke-width="2" stroke-dasharray="6 4"/>';
// Вписанная окружность
s += '<circle cx="'+Ix.toFixed(2)+'" cy="'+Iy.toFixed(2)+'" r="'+r.toFixed(2)+'" fill="rgba(245,158,11,.10)" stroke="#f59e0b" stroke-width="2"/>';
// Треугольник
s += '<polygon points="'+A.x.toFixed(2)+','+A.y.toFixed(2)+' '+B.x.toFixed(2)+','+B.y.toFixed(2)+' '+C.x.toFixed(2)+','+C.y.toFixed(2)+'" fill="rgba(5,150,105,.10)" stroke="#059669" stroke-width="2.2" stroke-linejoin="round"/>';
// Центры
s += '<circle cx="'+Ox.toFixed(2)+'" cy="'+Oy.toFixed(2)+'" r="4" fill="#0ea5e9"/>';
s += '<text x="'+(Ox+8)+'" y="'+(Oy-8)+'" font-family="Inter,sans-serif" font-size="14" font-weight="700" fill="#0ea5e9">O</text>';
s += '<circle cx="'+Ix.toFixed(2)+'" cy="'+Iy.toFixed(2)+'" r="4" fill="#f59e0b"/>';
s += '<text x="'+(Ix+8)+'" y="'+(Iy+14)+'" font-family="Inter,sans-serif" font-size="14" font-weight="700" fill="#b45309">I</text>';
// Вершины
s += '<circle cx="'+A.x.toFixed(2)+'" cy="'+A.y.toFixed(2)+'" r="4" fill="#0f172a"/>';
s += '<circle cx="'+B.x.toFixed(2)+'" cy="'+B.y.toFixed(2)+'" r="4" fill="#0f172a"/>';
s += '<circle cx="'+C.x.toFixed(2)+'" cy="'+C.y.toFixed(2)+'" r="4" fill="#0f172a"/>';
s += '<text x="'+(A.x)+'" y="'+(A.y-10)+'" text-anchor="middle" font-family="Inter,sans-serif" font-size="15" font-weight="700" fill="#0f172a">A</text>';
s += '<text x="'+(B.x-12)+'" y="'+(B.y+16)+'" text-anchor="end" font-family="Inter,sans-serif" font-size="15" font-weight="700" fill="#0f172a">B</text>';
s += '<text x="'+(C.x+12)+'" y="'+(C.y+16)+'" text-anchor="start" font-family="Inter,sans-serif" font-size="15" font-weight="700" fill="#0f172a">C</text>';
// Подпись
s += '<text x="210" y="22" text-anchor="middle" font-family="Unbounded,sans-serif" font-size="13" font-weight="800" fill="#047857">'+P.name+'</text>';
svg.innerHTML = s;
const place = P.kind==='in' ? 'внутри треугольника' : (P.kind==='hyp' ? 'на гипотенузе' : 'снаружи треугольника');
// twoR = 170 px соответствует 2R = 10 ед. → коэффициент px / ед. = 17
const pxPerUnit = 17;
const Ru = R / pxPerUnit;
const ru = r / pxPerUnit;
out.innerHTML = 'Углы: '+Ad+'°, '+Bd+'°, '+Cd+'° &nbsp;·&nbsp; $R \\approx '+Ru.toFixed(2)+'$ &nbsp;·&nbsp; $r \\approx '+ru.toFixed(2)+'$<br>Центр $O$ находится <b>'+place+'</b>.';
renderMath(out);
seen.add(idx);
if(seen.size>=4 && !seen.has('done')){ addXp(10,'p7-iv1'); bumpProgress('p7',15); seen.add('done'); }
}
sl.addEventListener('input', draw);
draw();
})();
/* IV2 — Калькулятор */
(function(){
const aI=document.getElementById('p7-iv2-a');
const bI=document.getElementById('p7-iv2-b');
const cI=document.getElementById('p7-iv2-c');
const go=document.getElementById('p7-iv2-go');
const out=document.getElementById('p7-iv2-out');
const fb=document.getElementById('p7-iv2-fb');
let solved=0;
function calc(){
const a=parseFloat(aI.value), b=parseFloat(bI.value), c=parseFloat(cI.value);
if(!isFinite(a)||!isFinite(b)||!isFinite(c)){ feedback(fb,false,'&#10007; Введи три числа.'); return; }
if(a<=0||b<=0||c<=0){ feedback(fb,false,'&#10007; Стороны должны быть положительными.'); return; }
if(a+b<=c||a+c<=b||b+c<=a){ feedback(fb,false,'&#10007; Неравенство треугольника не выполняется: сумма любых двух сторон должна быть больше третьей.'); out.innerHTML=''; return; }
const p=(a+b+c)/2;
const S=Math.sqrt(p*(p-a)*(p-b)*(p-c));
const R=(a*b*c)/(4*S);
const r=S/p;
out.innerHTML='$p = \\tfrac{'+a+'+'+b+'+'+c+'}{2} = '+fmt(p)+'$<br>'
+ '$S = \\sqrt{'+fmt(p)+' \\cdot '+fmt(p-a)+' \\cdot '+fmt(p-b)+' \\cdot '+fmt(p-c)+'} \\approx '+S.toFixed(3)+'$<br>'
+ '$R = \\dfrac{abc}{4S} = \\dfrac{'+(a*b*c)+'}{'+fmt(4*S)+'} \\approx '+R.toFixed(3)+'$<br>'
+ '$r = \\dfrac{S}{p} \\approx '+r.toFixed(3)+'$';
renderMath(out);
feedback(fb,true,'&#10003; Готово!');
solved++;
if(solved===1){ addXp(10,'p7-iv2'); bumpProgress('p7',15); }
}
go.addEventListener('click', calc);
})();
/* IV3 — Quickfire */
(function(){
const Q=[
{t:'Остроугольный треугольник (все углы меньше 90°). Где центр $O$?', a:'in'},
{t:'Прямоугольный треугольник. Где центр $O$?', a:'hyp'},
{t:'Тупоугольный треугольник (один угол больше 90°). Где центр $O$?', a:'out'},
{t:'Равносторонний треугольник. Где центр $O$?', a:'in'},
{t:'Треугольник с углами $90°, 45°, 45°$. Где центр $O$?', a:'hyp'},
{t:'Треугольник с углами $30°, 30°, 120°$. Где центр $O$?', a:'out'}
];
const qBox=document.getElementById('p7-iv3-q');
const iEl=document.getElementById('p7-iv3-i');
const sEl=document.getElementById('p7-iv3-s');
const fb=document.getElementById('p7-iv3-fb');
const btns=document.querySelectorAll('#p7-iv3 button[data-ans]');
let i=0, score=0, done=false;
function show(){
if(i>=Q.length){
qBox.innerHTML='Завершено! Очки: <b>'+score+'</b> / '+Q.length;
if(!done){ done=true; addXp(15,'p7-iv3'); bumpProgress('p7',25); if(score===Q.length) achievement('p7_done'); }
btns.forEach(b=>b.disabled=true);
return;
}
qBox.innerHTML=Q[i].t;
renderMath(qBox);
iEl.textContent=(i+1); sEl.textContent=score;
fb.style.display='none';
}
btns.forEach(b=>b.addEventListener('click', ()=>{
const ok = b.dataset.ans===Q[i].a;
if(ok) score++;
feedback(fb, ok, ok?'&#10003; Верно!':'&#10007; Правильный ответ: '+ ({in:'Внутри',hyp:'На гипотенузе',out:'Снаружи'}[Q[i].a]));
i++;
setTimeout(show, 700);
}));
show();
})();
/* IV4 — Тренажёр */
(function(){
const Q=[
{t:'В треугольнике стороны $3,4,5$. Найди $R$.', a:2.5},
{t:'В треугольнике стороны $3,4,5$. Найди $r$.', a:1},
{t:'В равностороннем треугольнике со стороной $6$ найди $R$ (округли до 2 знаков).', a:3.46},
{t:'В равностороннем треугольнике со стороной $6$ найди $r$ (округли до 2 знаков).', a:1.73},
{t:'В треугольнике стороны $5,12,13$. Найди $R$.', a:6.5},
{t:'В треугольнике стороны $6,8,10$. Найди $r$.', a:2}
];
const qBox=document.getElementById('p7-iv4-q');
const ans=document.getElementById('p7-iv4-ans');
const go=document.getElementById('p7-iv4-go');
const reset=document.getElementById('p7-iv4-start');
const iEl=document.getElementById('p7-iv4-i');
const sEl=document.getElementById('p7-iv4-s');
const fb=document.getElementById('p7-iv4-fb');
let i=0, score=0, done=false;
function show(){
if(i>=Q.length){
qBox.innerHTML='Финиш! Очки: <b>'+score+'</b> / '+Q.length;
if(!done){ done=true; addXp(15,'p7-iv4'); bumpProgress('p7',25); }
go.disabled=true; ans.disabled=true;
return;
}
qBox.innerHTML=Q[i].t; renderMath(qBox);
iEl.textContent=(i+1); sEl.textContent=score;
ans.value=''; fb.style.display='none'; go.disabled=false; ans.disabled=false;
}
go.addEventListener('click', ()=>{
const v=parseFloat(ans.value);
if(!isFinite(v)){ feedback(fb,false,'&#10007; Введи число.'); return; }
const ok=Math.abs(v-Q[i].a)<0.05;
if(ok) score++;
feedback(fb, ok, ok?'&#10003; Верно!':'&#10007; Правильный ответ: $'+Q[i].a+'$');
i++;
setTimeout(show, 900);
});
reset.addEventListener('click', ()=>{ i=0; score=0; done=false; show(); });
show();
})();
wireReadBtn('p7');
}
/* ===== §8 Окружности прямоугольного треугольника ===== */
function buildP8(){
const box = document.getElementById('p8-body');
let html = '';
html += makeCard('theory', 'Описанная окружность', '8.1', `
<p>В прямоугольном треугольнике центр описанной окружности — <b>середина гипотенузы</b>. Радиус:</p>
$$R = \\dfrac{c}{2}$$
<p>где $c$ — гипотенуза. Это следствие теоремы о вписанном угле: вписанный угол, опирающийся на диаметр, равен $90°$.</p>
<p><b>Гипотенуза — диаметр описанной окружности!</b></p>
<details class="spoiler"><summary>Почему именно середина гипотенузы?</summary><div class="spoiler-body">
В прямоугольном треугольнике медиана к гипотенузе равна её половине. Точка $O$ — середина гипотенузы — равноудалена от трёх вершин на $c/2$, значит это центр описанной окружности.
</div></details>`);
html += makeCard('rule', 'Вписанная окружность', '8.2', `
<p>В прямоугольном треугольнике с катетами $a, b$ и гипотенузой $c$ радиус вписанной окружности:</p>
$$r = \\dfrac{a + b - c}{2}$$
<p>Также верно: $r = \\dfrac{a \\cdot b}{a + b + c}$ (через площадь $S = \\tfrac{ab}{2}$ и полупериметр $p = \\tfrac{a+b+c}{2}$, $r = S/p$).</p>`);
html += makeCard('example', 'Примеры применения', '8.3', `
<ul style="padding-left:22px;line-height:1.95">
<li>В треугольнике 3-4-5: $R = 5/2 = 2{,}5$, $r = (3+4-5)/2 = 1$. Проверка: $S = 6, p = 6, r = S/p = 1$. <b>Сошлось.</b></li>
<li>В треугольнике 5-12-13: $R = 13/2 = 6{,}5$, $r = (5+12-13)/2 = 2$.</li>
<li>В треугольнике 8-15-17: $R = 17/2 = 8{,}5$, $r = (8+15-17)/2 = 3$.</li>
</ul>`);
/* IV1 — SVG */
html += `<div class="wg" id="p8-iv1">
<div class="wg-header"><span class="wg-badge">ИНТЕРАКТИВ 1</span><div class="wg-title">Окружности прямоугольного треугольника</div></div>
<div class="wg-help">Меняй катеты ползунками — гипотенуза, $R$ и $r$ пересчитаются. Заметь: центр описанной — посередине гипотенузы.</div>
<div class="sliders">
<label>Катет $a$<b id="p8-iv1-aval">6</b><input type="range" id="p8-iv1-a" min="2" max="8" step="0.1" value="6"></label>
<label>Катет $b$<b id="p8-iv1-bval">4.5</b><input type="range" id="p8-iv1-b" min="2" max="8" step="0.1" value="4.5"></label>
</div>
<div style="background:var(--card);border-radius:10px;padding:10px;overflow-x:auto">
<svg id="p8-iv1-svg" viewBox="0 0 400 340" style="width:100%;min-width:320px;height:auto;display:block"></svg>
</div>
<div id="p8-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="p8-iv2">
<div class="wg-header"><span class="wg-badge">ИНТЕРАКТИВ 2</span><div class="wg-title">Калькулятор $R$ и $r$</div></div>
<div class="wg-help">Введи катеты $a$ и $b$ — программа найдёт гипотенузу, радиусы окружностей и проверит $r = S/p$.</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="p8-iv2-a" class="tinp" style="width:90px;text-align:center" value="3" min="0.1" step="0.1">
<span style="font-family:'JetBrains Mono',monospace">$b$ =</span>
<input type="number" id="p8-iv2-b" class="tinp" style="width:90px;text-align:center" value="4" min="0.1" step="0.1">
<button class="btn primary" id="p8-iv2-go">Вычислить</button>
</div>
<div id="p8-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="p8-iv2-fb"></div>
</div>`;
/* IV3 — «Сравни формулы» */
html += `<div class="wg" id="p8-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="p8-iv3-i">1</b> / 6</span><span>Очки: <b id="p8-iv3-s">0</b> / 6</span></div>
<div id="p8-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(2,1fr);gap:8px">
<button class="btn primary" data-ans="1" id="p8-iv3-ok">Верно</button>
<button class="btn primary" data-ans="0" id="p8-iv3-bad">Ошибка</button>
</div>
<div class="feedback" id="p8-iv3-fb"></div>
</div>`;
/* IV4 — Тренажёр */
html += `<div class="wg" id="p8-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="p8-iv4-i">1</b> / 6</span><span>Очки: <b id="p8-iv4-s">0</b> / 6</span></div>
<div id="p8-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="p8-iv4-ans" class="tinp" style="width:110px;text-align:center" step="0.01">
<button class="btn primary" id="p8-iv4-go">Проверить</button>
<button class="btn" id="p8-iv4-start">Заново</button>
</div>
<div class="feedback" id="p8-iv4-fb"></div>
</div>`;
html += secNav('p7', 'p9');
html += readButton('p8');
box.innerHTML = html;
renderMath(box);
/* IV1 */
(function(){
const aS=document.getElementById('p8-iv1-a');
const bS=document.getElementById('p8-iv1-b');
const aL=document.getElementById('p8-iv1-aval');
const bL=document.getElementById('p8-iv1-bval');
const svg=document.getElementById('p8-iv1-svg');
const out=document.getElementById('p8-iv1-out');
const seen=new Set();
function draw(){
// a, b — катеты в единицах (диапазон 2..8). Для отрисовки умножаем на K.
const a=+aS.value, b=+bS.value;
aL.textContent=a.toFixed(1); bL.textContent=b.toFixed(1);
const K=30; // px на единицу
const aPx=a*K, bPx=b*K;
// Вершины (в SVG-координатах):
// C — прямой угол, нижний-левый.
// B — нижний-правый (горизонтальный катет от C, длиной a).
// A — верхний-левый (вертикальный катет от C, длиной b).
const margin=40;
const C={x:margin, y:margin+bPx};
const B={x:margin+aPx, y:margin+bPx};
const A={x:margin, y:margin};
const c=Math.hypot(a,b); // в единицах
const cPx=Math.hypot(aPx,bPx); // в пикселях
const R=c/2; // в единицах
const r=(a+b-c)/2; // в единицах
const RPx=cPx/2;
const rPx=(aPx+bPx-cPx)/2;
// Центр описанной — середина гипотенузы AB
const O={x:(A.x+B.x)/2, y:(A.y+B.y)/2};
// Центр вписанной — на (r, r) от прямого угла C внутрь треугольника
const I={x:C.x+rPx, y:C.y-rPx};
const uCA=unitVec(C,A);
const uCB=unitVec(C,B);
let s='';
s += '<rect x="0" y="0" width="400" height="340" fill="none"/>';
// Описанная окружность
s += '<circle cx="'+O.x.toFixed(2)+'" cy="'+O.y.toFixed(2)+'" r="'+RPx.toFixed(2)+'" fill="none" stroke="#0ea5e9" stroke-width="2" stroke-dasharray="6 4"/>';
// Вписанная
s += '<circle cx="'+I.x.toFixed(2)+'" cy="'+I.y.toFixed(2)+'" r="'+rPx.toFixed(2)+'" fill="rgba(245,158,11,.12)" stroke="#f59e0b" stroke-width="2"/>';
// Треугольник
s += '<polygon points="'+A.x+','+A.y+' '+B.x+','+B.y+' '+C.x+','+C.y+'" fill="rgba(5,150,105,.10)" stroke="#059669" stroke-width="2.2" stroke-linejoin="round"/>';
// Прямой угол в C
s += '<polyline points="'+rightAngleMark(C, uCA, uCB, 12)+'" fill="none" stroke="#0f172a" stroke-width="1.8"/>';
// Центры
s += '<circle cx="'+O.x.toFixed(2)+'" cy="'+O.y.toFixed(2)+'" r="4" fill="#0ea5e9"/>';
s += '<text x="'+(O.x+9)+'" y="'+(O.y-7)+'" font-family="Inter,sans-serif" font-size="14" font-weight="700" fill="#0369a1">O</text>';
s += '<circle cx="'+I.x.toFixed(2)+'" cy="'+I.y.toFixed(2)+'" r="4" fill="#f59e0b"/>';
s += '<text x="'+(I.x+9)+'" y="'+(I.y+14)+'" font-family="Inter,sans-serif" font-size="14" font-weight="700" fill="#b45309">I</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-4)+'" text-anchor="end" font-family="Inter,sans-serif" font-size="15" font-weight="700" fill="#0f172a">A</text>';
s += '<text x="'+(B.x+10)+'" y="'+(B.y+16)+'" font-family="Inter,sans-serif" font-size="15" font-weight="700" fill="#0f172a">B</text>';
s += '<text x="'+(C.x-12)+'" y="'+(C.y+16)+'" text-anchor="end" font-family="Inter,sans-serif" font-size="15" font-weight="700" fill="#0f172a">C</text>';
// Подписи сторон — в единицах
s += '<text x="'+((B.x+C.x)/2)+'" y="'+(C.y+24)+'" text-anchor="middle" font-family="JetBrains Mono,monospace" font-size="12" font-weight="700" fill="#047857">a='+a.toFixed(1)+'</text>';
s += '<text x="'+(C.x-22)+'" y="'+((A.y+C.y)/2)+'" text-anchor="middle" font-family="JetBrains Mono,monospace" font-size="12" font-weight="700" fill="#047857">b='+b.toFixed(1)+'</text>';
const midAB={x:(A.x+B.x)/2, y:(A.y+B.y)/2};
s += '<text x="'+(midAB.x+12)+'" y="'+(midAB.y-6)+'" font-family="JetBrains Mono,monospace" font-size="12" font-weight="700" fill="#047857">c='+c.toFixed(2)+'</text>';
svg.innerHTML = s;
out.innerHTML = '$c = \\sqrt{'+a.toFixed(1)+'^2+'+b.toFixed(1)+'^2} \\approx '+c.toFixed(2)+'$ &nbsp;·&nbsp; $R = c/2 \\approx '+R.toFixed(2)+'$ &nbsp;·&nbsp; $r = (a+b-c)/2 \\approx '+r.toFixed(2)+'$<br><b>Диаметр</b> описанной $= c$ (гипотенуза).';
renderMath(out);
seen.add(a+'-'+b);
if(seen.size>=4 && !seen.has('done')){ addXp(10,'p8-iv1'); bumpProgress('p8',15); seen.add('done'); }
}
aS.addEventListener('input', draw);
bS.addEventListener('input', draw);
draw();
})();
/* IV2 */
(function(){
const aI=document.getElementById('p8-iv2-a');
const bI=document.getElementById('p8-iv2-b');
const go=document.getElementById('p8-iv2-go');
const out=document.getElementById('p8-iv2-out');
const fb=document.getElementById('p8-iv2-fb');
let solved=0;
function calc(){
const a=parseFloat(aI.value), b=parseFloat(bI.value);
if(!isFinite(a)||!isFinite(b)){ feedback(fb,false,'&#10007; Введи два числа.'); return; }
if(a<=0||b<=0){ feedback(fb,false,'&#10007; Катеты должны быть положительными.'); return; }
const c=Math.sqrt(a*a+b*b);
const R=c/2;
const r=(a+b-c)/2;
const S=a*b/2;
const p=(a+b+c)/2;
const rCheck=S/p;
out.innerHTML='$c = \\sqrt{'+a+'^2+'+b+'^2} = \\sqrt{'+(a*a+b*b)+'} \\approx '+c.toFixed(3)+'$<br>'
+'$R = \\dfrac{c}{2} \\approx '+R.toFixed(3)+'$<br>'
+'$r = \\dfrac{a+b-c}{2} \\approx '+r.toFixed(3)+'$<br>'
+'Проверка: $S = \\tfrac{ab}{2} = '+fmt(S)+'$, $p = '+fmt(p)+'$, $r = S/p \\approx '+rCheck.toFixed(3)+'$';
renderMath(out);
feedback(fb,true,'&#10003; Готово!');
solved++;
if(solved===1){ addXp(10,'p8-iv2'); bumpProgress('p8',15); }
}
go.addEventListener('click', calc);
})();
/* IV3 — Quickfire верно/ошибка */
(function(){
const Q=[
{t:'$R = \\dfrac{c}{2}$, где $c$ — гипотенуза.', a:'1'},
{t:'$r = a + b - c$ (без деления на 2).', a:'0'},
{t:'Гипотенуза — диаметр описанной окружности.', a:'1'},
{t:'Центр описанной окружности лежит вне прямоугольного треугольника.', a:'0'},
{t:'$r = \\dfrac{a+b+c}{2}$.', a:'0'},
{t:'В треугольнике 6-8-10 радиус описанной окружности равен $5$.', a:'1'}
];
const fix=['&#10007; $r = \\dfrac{a+b-c}{2}$ — обязательно делить на 2.',
'',
'',
'&#10007; Центр $O$ лежит на середине гипотенузы (на самой гипотенузе).',
'&#10007; Это полупериметр $p$, а не радиус. $r = \\dfrac{a+b-c}{2}$.',
''];
const qBox=document.getElementById('p8-iv3-q');
const iEl=document.getElementById('p8-iv3-i');
const sEl=document.getElementById('p8-iv3-s');
const fb=document.getElementById('p8-iv3-fb');
const btns=document.querySelectorAll('#p8-iv3 button[data-ans]');
let i=0, score=0, done=false;
function show(){
if(i>=Q.length){
qBox.innerHTML='Завершено! Очки: <b>'+score+'</b> / '+Q.length;
if(!done){ done=true; addXp(15,'p8-iv3'); bumpProgress('p8',25); if(score===Q.length) achievement('p8_done'); }
btns.forEach(b=>b.disabled=true);
return;
}
qBox.innerHTML=Q[i].t; renderMath(qBox);
iEl.textContent=(i+1); sEl.textContent=score;
fb.style.display='none';
}
btns.forEach(b=>b.addEventListener('click', ()=>{
const ok = b.dataset.ans===Q[i].a;
if(ok) score++;
const correctMsg = Q[i].a==='1' ? '&#10003; Верно — утверждение истинно.' : (fix[i] || '&#10003; Верно — утверждение ложно.');
feedback(fb, ok, ok?correctMsg:('&#10007; '+(Q[i].a==='1'?'На самом деле — верно.':(fix[i]||'На самом деле — ошибка.'))));
i++;
setTimeout(show, 900);
}));
show();
})();
/* IV4 */
(function(){
const Q=[
{t:'В треугольнике $3,4,5$ найди $R$.', a:2.5},
{t:'В треугольнике $5,12,13$ найди $r$.', a:2},
{t:'В треугольнике $8,15,17$ найди $R$.', a:8.5},
{t:'В треугольнике $8,15,17$ найди $r$.', a:3},
{t:'В треугольнике $6,8,10$ найди $r$.', a:2},
{t:'В треугольнике $9,40,41$ найди $R$.', a:20.5}
];
const qBox=document.getElementById('p8-iv4-q');
const ans=document.getElementById('p8-iv4-ans');
const go=document.getElementById('p8-iv4-go');
const reset=document.getElementById('p8-iv4-start');
const iEl=document.getElementById('p8-iv4-i');
const sEl=document.getElementById('p8-iv4-s');
const fb=document.getElementById('p8-iv4-fb');
let i=0, score=0, done=false;
function show(){
if(i>=Q.length){
qBox.innerHTML='Финиш! Очки: <b>'+score+'</b> / '+Q.length;
if(!done){ done=true; addXp(15,'p8-iv4'); bumpProgress('p8',25); }
go.disabled=true; ans.disabled=true;
return;
}
qBox.innerHTML=Q[i].t; renderMath(qBox);
iEl.textContent=(i+1); sEl.textContent=score;
ans.value=''; fb.style.display='none'; go.disabled=false; ans.disabled=false;
}
go.addEventListener('click', ()=>{
const v=parseFloat(ans.value);
if(!isFinite(v)){ feedback(fb,false,'&#10007; Введи число.'); return; }
const ok=Math.abs(v-Q[i].a)<0.05;
if(ok) score++;
feedback(fb, ok, ok?'&#10003; Верно!':'&#10007; Правильный ответ: $'+Q[i].a+'$');
i++;
setTimeout(show, 900);
});
reset.addEventListener('click', ()=>{ i=0; score=0; done=false; show(); });
show();
})();
wireReadBtn('p8');
}
/* ===== §9 Вписанные и описанные четырёхугольники ===== */
function buildP9(){
const box = document.getElementById('p9-body');
let html = '';
html += makeCard('theory', 'Вписанный четырёхугольник', '9.1', `
<p>Четырёхугольник <b>вписан</b> в окружность, если все его четыре вершины лежат на этой окружности.</p>
<p><b>Критерий вписанности:</b> четырёхугольник вписан в окружность тогда и только тогда, когда сумма его противоположных углов равна $180°$:</p>
$$\\alpha + \\gamma = 180°, \\qquad \\beta + \\delta = 180°$$
<p>где $\\alpha, \\beta, \\gamma, \\delta$ — углы при последовательных вершинах $A, B, C, D$.</p>
<details class="spoiler"><summary>Какие фигуры всегда вписаны?</summary><div class="spoiler-body">
<b>Прямоугольник</b> всегда можно вписать в окружность: его диагональ — диаметр (углы по 90°, $90+90=180$). <b>Произвольный параллелограмм</b> — нельзя: его противоположные углы равны, и сумма $180°$ возможна только если все углы прямые — то есть только прямоугольник.
</div></details>`);
html += makeCard('rule', 'Описанный четырёхугольник', '9.2', `
<p>Четырёхугольник <b>описан</b> около окружности, если все его четыре стороны касаются окружности.</p>
<p><b>Критерий описанности:</b> четырёхугольник описан около окружности тогда и только тогда, когда суммы его противоположных сторон равны:</p>
$$a + c = b + d$$
<p>где $a, b, c, d$ — последовательные стороны $AB, BC, CD, DA$.</p>
<p><b>Квадрат и ромб</b> всегда описаны около окружности (центр — точка пересечения диагоналей). Произвольный прямоугольник — нет (только квадрат).</p>`);
html += makeCard('example', 'Свойства и примеры', '9.3', `
<ul style="padding-left:22px;line-height:1.95">
<li><b>Квадрат:</b> и вписан, и описан (правильный четырёхугольник).</li>
<li><b>Прямоугольник:</b> вписан (диагональ = диаметр), описан только если квадрат.</li>
<li><b>Ромб:</b> описан (диагонали — биссектрисы), вписан только если квадрат.</li>
<li>В произвольном вписанном четырёхугольнике: если $\\alpha = 80°$, то $\\gamma = 180° - 80° = 100°$.</li>
<li>В описанном со сторонами $3, 5, 7, d$: $3 + 7 = 5 + d \\Rightarrow d = 5$.</li>
</ul>`);
/* IV1 — Вписанный четырёхугольник (SVG) */
html += `<div class="wg" id="p9-iv1">
<div class="wg-header"><span class="wg-badge">ИНТЕРАКТИВ 1</span><div class="wg-title">Вписанный четырёхугольник</div></div>
<div class="wg-help">Перемещай вершины $A, B, C, D$ по окружности — они всегда останутся на ней. Проверь, что $\\alpha + \\gamma = 180°$ и $\\beta + \\delta = 180°$.</div>
<div class="sliders">
<label>$A$ (°)<b id="p9-iv1-aval">20</b><input type="range" id="p9-iv1-a" min="0" max="89" step="1" value="20"></label>
<label>$B$ (°)<b id="p9-iv1-bval">100</b><input type="range" id="p9-iv1-b" min="91" max="179" step="1" value="100"></label>
<label>$C$ (°)<b id="p9-iv1-cval">200</b><input type="range" id="p9-iv1-c" min="181" max="269" step="1" value="200"></label>
<label>$D$ (°)<b id="p9-iv1-dval">290</b><input type="range" id="p9-iv1-d" min="271" max="359" step="1" value="290"></label>
</div>
<div style="background:var(--card);border-radius:10px;padding:10px;overflow-x:auto">
<svg id="p9-iv1-svg" viewBox="0 0 380 380" style="width:100%;min-width:320px;height:auto;display:block"></svg>
</div>
<div id="p9-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="p9-iv2">
<div class="wg-header"><span class="wg-badge">ИНТЕРАКТИВ 2</span><div class="wg-title">Калькулятор 4-й стороны</div></div>
<div class="wg-help">Введи три последовательные стороны описанного четырёхугольника $a, b, c$ — программа найдёт $d$ из условия $a + c = b + d$.</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="p9-iv2-a" class="tinp" style="width:80px;text-align:center" value="3" min="0.1" step="0.1">
<span style="font-family:'JetBrains Mono',monospace">$b$ =</span>
<input type="number" id="p9-iv2-b" class="tinp" style="width:80px;text-align:center" value="5" min="0.1" step="0.1">
<span style="font-family:'JetBrains Mono',monospace">$c$ =</span>
<input type="number" id="p9-iv2-c" class="tinp" style="width:80px;text-align:center" value="7" min="0.1" step="0.1">
<button class="btn primary" id="p9-iv2-go">Найти $d$</button>
</div>
<div id="p9-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="p9-iv2-fb"></div>
</div>`;
/* IV3 — «Вписан или описан?» */
html += `<div class="wg" id="p9-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="p9-iv3-i">1</b> / 6</span><span>Очки: <b id="p9-iv3-s">0</b> / 6</span></div>
<div id="p9-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="in" id="p9-iv3-in">Вписан</button>
<button class="btn primary" data-ans="out" id="p9-iv3-out">Описан</button>
<button class="btn primary" data-ans="both" id="p9-iv3-both">И то, и то</button>
</div>
<div class="feedback" id="p9-iv3-fb"></div>
</div>`;
/* IV4 — Тренажёр */
html += `<div class="wg" id="p9-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="p9-iv4-i">1</b> / 6</span><span>Очки: <b id="p9-iv4-s">0</b> / 6</span></div>
<div id="p9-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="p9-iv4-ans" class="tinp" style="width:110px;text-align:center" step="0.01">
<button class="btn primary" id="p9-iv4-go">Проверить</button>
<button class="btn" id="p9-iv4-start">Заново</button>
</div>
<div class="feedback" id="p9-iv4-fb"></div>
</div>`;
html += secNav('p8', 'final2');
html += readButton('p9');
box.innerHTML = html;
renderMath(box);
/* IV1 — Вписанный четырёхугольник */
(function(){
const sA=document.getElementById('p9-iv1-a');
const sB=document.getElementById('p9-iv1-b');
const sC=document.getElementById('p9-iv1-c');
const sD=document.getElementById('p9-iv1-d');
const lA=document.getElementById('p9-iv1-aval');
const lB=document.getElementById('p9-iv1-bval');
const lC=document.getElementById('p9-iv1-cval');
const lD=document.getElementById('p9-iv1-dval');
const svg=document.getElementById('p9-iv1-svg');
const out=document.getElementById('p9-iv1-out');
const seen=new Set();
const cx=190, cy=190, R=140;
function pt(angDeg){
const a = deg2rad(angDeg);
return { x: cx + R*Math.cos(a), y: cy - R*Math.sin(a) };
}
function interiorAngle(prev, cur, next){
// Угол при cur между сторонами cur->prev и cur->next
const v1 = unitVec(cur, prev);
const v2 = unitVec(cur, next);
let cos = v1.x*v2.x + v1.y*v2.y;
if(cos>1) cos=1; if(cos<-1) cos=-1;
return Math.acos(cos) * 180 / Math.PI;
}
function draw(){
const aA=+sA.value, aB=+sB.value, aC=+sC.value, aD=+sD.value;
lA.textContent=aA; lB.textContent=aB; lC.textContent=aC; lD.textContent=aD;
const A=pt(aA), B=pt(aB), C=pt(aC), D=pt(aD);
const sides={
AB:Math.hypot(A.x-B.x,A.y-B.y),
BC:Math.hypot(B.x-C.x,B.y-C.y),
CD:Math.hypot(C.x-D.x,C.y-D.y),
DA:Math.hypot(D.x-A.x,D.y-A.y)
};
const angA=interiorAngle(D,A,B);
const angB=interiorAngle(A,B,C);
const angC=interiorAngle(B,C,D);
const angD=interiorAngle(C,D,A);
let s='';
s += '<rect x="0" y="0" width="380" height="380" fill="none"/>';
// Окружность
s += '<circle cx="'+cx+'" cy="'+cy+'" r="'+R+'" fill="rgba(14,165,233,.06)" stroke="#0ea5e9" stroke-width="2" stroke-dasharray="6 4"/>';
// Центр
s += '<circle cx="'+cx+'" cy="'+cy+'" r="3" fill="#0ea5e9"/>';
s += '<text x="'+(cx+8)+'" y="'+(cy-6)+'" font-family="Inter,sans-serif" font-size="13" font-weight="700" fill="#0369a1">O</text>';
// Четырёхугольник
s += '<polygon points="'+A.x.toFixed(2)+','+A.y.toFixed(2)+' '+B.x.toFixed(2)+','+B.y.toFixed(2)+' '+C.x.toFixed(2)+','+C.y.toFixed(2)+' '+D.x.toFixed(2)+','+D.y.toFixed(2)+'" fill="rgba(5,150,105,.10)" stroke="#059669" stroke-width="2.2" stroke-linejoin="round"/>';
// Вершины
const labs=[{p:A,n:'A',ang:aA},{p:B,n:'B',ang:aB},{p:C,n:'C',ang:aC},{p:D,n:'D',ang:aD}];
labs.forEach(L=>{
s += '<circle cx="'+L.p.x.toFixed(2)+'" cy="'+L.p.y.toFixed(2)+'" r="4" fill="#0f172a"/>';
// подпись наружу
const ra=deg2rad(L.ang);
const lx=cx + (R+18)*Math.cos(ra);
const ly=cy - (R+18)*Math.sin(ra);
s += '<text x="'+lx.toFixed(2)+'" y="'+(ly+4).toFixed(2)+'" text-anchor="middle" font-family="Inter,sans-serif" font-size="15" font-weight="700" fill="#0f172a">'+L.n+'</text>';
});
svg.innerHTML=s;
const sumAC=angA+angC, sumBD=angB+angD;
const okAC=Math.abs(sumAC-180)<0.5;
const okBD=Math.abs(sumBD-180)<0.5;
let info = 'Углы: $\\alpha = '+angA.toFixed(1)+'°$, $\\beta = '+angB.toFixed(1)+'°$, $\\gamma = '+angC.toFixed(1)+'°$, $\\delta = '+angD.toFixed(1)+'°$<br>'
+ '$\\alpha + \\gamma = '+sumAC.toFixed(1)+'°$ &nbsp;·&nbsp; $\\beta + \\delta = '+sumBD.toFixed(1)+'°$';
if(okAC && okBD) info += '<br><b style="color:#059669">&#10003; Это вписанный четырёхугольник!</b>';
out.innerHTML=info;
renderMath(out);
seen.add(aA+'|'+aB+'|'+aC+'|'+aD);
if(seen.size>=4 && !seen.has('done')){ addXp(10,'p9-iv1'); bumpProgress('p9',15); seen.add('done'); }
}
[sA,sB,sC,sD].forEach(s=>s.addEventListener('input', draw));
draw();
})();
/* IV2 — Калькулятор 4-й стороны */
(function(){
const aI=document.getElementById('p9-iv2-a');
const bI=document.getElementById('p9-iv2-b');
const cI=document.getElementById('p9-iv2-c');
const go=document.getElementById('p9-iv2-go');
const out=document.getElementById('p9-iv2-out');
const fb=document.getElementById('p9-iv2-fb');
let solved=0;
function calc(){
const a=parseFloat(aI.value), b=parseFloat(bI.value), c=parseFloat(cI.value);
if(!isFinite(a)||!isFinite(b)||!isFinite(c)){ feedback(fb,false,'&#10007; Введи три числа.'); return; }
if(a<=0||b<=0||c<=0){ feedback(fb,false,'&#10007; Стороны должны быть положительными.'); return; }
const d = a + c - b;
if(d<=0){
out.innerHTML = '$a + c = b + d \\Rightarrow d = a + c - b = '+a+' + '+c+' - '+b+' = '+fmt(d)+'$';
renderMath(out);
feedback(fb,false,'&#10007; Не существует описанного четырёхугольника с такими сторонами: $d \\le 0$.');
return;
}
out.innerHTML = '$a + c = b + d$<br>'
+ '$d = a + c - b = '+a+' + '+c+' - '+b+' = '+fmt(d)+'$<br>'
+ '<b>4-я сторона: $d = '+fmt(d)+'$</b>';
renderMath(out);
feedback(fb,true,'&#10003; Готово!');
solved++;
if(solved===1){ addXp(10,'p9-iv2'); bumpProgress('p9',15); }
}
go.addEventListener('click', calc);
})();
/* IV3 — Quickfire «Вписан или описан?» */
(function(){
const Q=[
{t:'Квадрат.', a:'both'},
{t:'Прямоугольник (не квадрат).', a:'in'},
{t:'Ромб (не квадрат).', a:'out'},
{t:'Трапеция, у которой суммы противоположных углов равны $180°$.', a:'in'},
{t:'Четырёхугольник со сторонами $3, 5, 7, 5$ (по порядку).', a:'out'},
{t:'Четырёхугольник с углами $90°, 80°, 90°, 100°$ (по порядку).', a:'in'}
];
const explain={
in:'Вписан: $\\alpha + \\gamma = 180°$.',
out:'Описан: $a + c = b + d$.',
both:'И вписан, и описан (правильный четырёхугольник).'
};
const qBox=document.getElementById('p9-iv3-q');
const iEl=document.getElementById('p9-iv3-i');
const sEl=document.getElementById('p9-iv3-s');
const fb=document.getElementById('p9-iv3-fb');
const btns=document.querySelectorAll('#p9-iv3 button[data-ans]');
let i=0, score=0, done=false;
function show(){
if(i>=Q.length){
qBox.innerHTML='Завершено! Очки: <b>'+score+'</b> / '+Q.length;
if(!done){ done=true; addXp(15,'p9-iv3'); bumpProgress('p9',25); if(score===Q.length) achievement('p9_done'); }
btns.forEach(b=>b.disabled=true);
return;
}
qBox.innerHTML=Q[i].t; renderMath(qBox);
iEl.textContent=(i+1); sEl.textContent=score;
fb.style.display='none';
}
btns.forEach(b=>b.addEventListener('click', ()=>{
const ok = b.dataset.ans===Q[i].a;
if(ok) score++;
feedback(fb, ok, ok?('&#10003; Верно! '+explain[Q[i].a]):('&#10007; Правильный ответ: '+({in:'Вписан',out:'Описан',both:'И то, и то'}[Q[i].a])+'. '+explain[Q[i].a]));
i++;
setTimeout(show, 900);
}));
show();
})();
/* IV4 — Тренажёр */
(function(){
const Q=[
{t:'Во вписанном 4-уг. углы $\\angle A = 75°$, $\\angle B = 110°$. Найди $\\angle C$.', a:105},
{t:'Во вписанном 4-уг. углы $\\angle A = 90°$, $\\angle B = 95°$. Найди $\\angle D$.', a:85},
{t:'В описанном 4-уг. стороны $a=4$, $b=6$, $c=8$. Найди $d$.', a:6},
{t:'В описанном 4-уг. стороны $a=5$, $b=7$, $c=9$. Найди $d$.', a:7},
{t:'Сумма противоположных углов вписанного четырёхугольника равна …', a:180},
{t:'Параллелограмм вписан в окружность. Чему равны его углы (в градусах)?', a:90}
];
const qBox=document.getElementById('p9-iv4-q');
const ans=document.getElementById('p9-iv4-ans');
const go=document.getElementById('p9-iv4-go');
const reset=document.getElementById('p9-iv4-start');
const iEl=document.getElementById('p9-iv4-i');
const sEl=document.getElementById('p9-iv4-s');
const fb=document.getElementById('p9-iv4-fb');
let i=0, score=0, done=false;
function show(){
if(i>=Q.length){
qBox.innerHTML='Финиш! Очки: <b>'+score+'</b> / '+Q.length;
if(!done){ done=true; addXp(15,'p9-iv4'); bumpProgress('p9',25); }
go.disabled=true; ans.disabled=true;
return;
}
qBox.innerHTML=Q[i].t; renderMath(qBox);
iEl.textContent=(i+1); sEl.textContent=score;
ans.value=''; fb.style.display='none'; go.disabled=false; ans.disabled=false;
}
go.addEventListener('click', ()=>{
const v=parseFloat((ans.value||'').replace(',', '.'));
if(!isFinite(v)){ feedback(fb,false,'&#10007; Введи число.'); return; }
const ok=Math.abs(v-Q[i].a)<0.05;
if(ok) score++;
feedback(fb, ok, ok?'&#10003; Верно!':'&#10007; Правильный ответ: $'+Q[i].a+'$');
i++;
setTimeout(show, 900);
});
reset.addEventListener('click', ()=>{ i=0; score=0; done=false; show(); });
show();
})();
wireReadBtn('p9');
}
/* ===== Финал главы 2 ===== */
function buildFinal2(){
const box = document.getElementById('final2-body');
let html = '';
/* Часть А — Шпаргалка главы (3 mini-карточки) */
html += `<div class="card">
<div class="card-header">
<div class="card-icon theory">${ICONS.theory}</div>
<div class="card-title">Шпаргалка главы 2</div>
<div class="card-num">Итог</div>
</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"><circle cx="12" cy="12" r="9"/><polygon points="4 8 12 18 20 8"/></svg>
<span style="font-family:'Unbounded',sans-serif;font-weight:700;color:var(--pri2);font-size:.92rem">§ 7 · Окружности треугольника</span>
</div>
<div style="font-size:.92rem">Описанная — пересечение серединных $\\perp$, $R = \\dfrac{abc}{4S}$. Вписанная — пересечение биссектрис, $r = \\dfrac{S}{p}$.</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 3 3"/><circle cx="12" cy="12" r="9"/></svg>
<span style="font-family:'Unbounded',sans-serif;font-weight:700;color:var(--pri2);font-size:.92rem">§ 8 · Прямоугольный $\\triangle$</span>
</div>
<div style="font-size:.92rem">$R = \\dfrac{c}{2}$ — центр на середине гипотенузы. $r = \\dfrac{a + b - c}{2}$.</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"/><polygon points="6 7 18 7 17 17 7 17"/></svg>
<span style="font-family:'Unbounded',sans-serif;font-weight:700;color:var(--pri2);font-size:.92rem">§ 9 · Четырёхугольники</span>
</div>
<div style="font-size:.92rem">Вписан $\\Leftrightarrow$ $\\alpha + \\gamma = 180°$. Описан $\\Leftrightarrow$ $a + c = b + d$.</div>
</div>
</div>
</div>
</div>`;
/* Часть Б — 5 боссов */
html += `<div class="card">
<div class="card-header">
<div class="card-icon rule">${ICONS.rule}</div>
<div class="card-title">Боссы главы 2</div>
<div class="card-num">5</div>
</div>
<div class="card-body">
<p>5 интегрированных задач — каждая комбинирует темы §§7–9. За каждого побеждённого босса — <b>+10 XP</b> и +18% к прогрессу. Победишь всех — <b>+50 XP бонус</b> и ачивка «Магистр окружностей»!</p>
</div>
</div>`;
html += '<div id="ch2G-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="ch2G-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="ch2G-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="ch2G-boss-overall-fill" style="height:100%;width:0%;background:linear-gradient(90deg,var(--pri),var(--acc));transition:width .35s"></div>
</div>
<div id="ch2G-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">Глава 2 пройдена! Все 5 боссов повержены. +50 XP бонус.</div>
<a class="btn primary" href="/textbook/geometry-9-ch3" style="text-decoration:none">Дальше: Глава 3 <svg class="ic" viewBox="0 0 24 24"><polyline points="9 18 15 12 9 6"/></svg></a>
</div>
</div>`;
html += secNav('p9', null);
box.innerHTML = html;
renderMath(box);
/* Боссы */
const BOSSES = [
{
n:1, color:'#10b981',
title:'Циклоп Окружностей',
tag:'§ 8 + § 7',
q:'В прямоугольном треугольнике катеты $a = 6$ и $b = 8$. Найди радиус <b>описанной</b> окружности.',
ans:5, decimal:false,
hint:'Гипотенуза $c = \\sqrt{6^2 + 8^2} = 10$. $R = \\dfrac{c}{2} = 5$.'
},
{
n:2, color:'#0891b2',
title:'Минотавр Вписанной',
tag:'§ 8 + § 7',
q:'В прямоугольном треугольнике катеты $a = 9$ и $b = 12$. Найди радиус <b>вписанной</b> окружности.',
ans:3, decimal:false,
hint:'Гипотенуза $c = \\sqrt{81+144} = 15$. $r = \\dfrac{a + b - c}{2} = \\dfrac{9 + 12 - 15}{2} = 3$.'
},
{
n:3, color:'#7c3aed',
title:'Гарпия 4-угольников',
tag:'§ 9',
q:'Во вписанном четырёхугольнике углы $\\angle A = 65°$ и $\\angle B = 95°$. Найди сумму $\\angle C + \\angle D$ в градусах.',
ans:200, decimal:false,
hint:'$\\angle C = 180° - \\angle A = 115°$, $\\angle D = 180° - \\angle B = 85°$. Сумма $= 115 + 85 = 200°$.'
},
{
n:4, color:'#dc2626',
title:'Дракон Окружности',
tag:'§ 7 + § 9',
q:'В равностороннем треугольнике со стороной $a = 6$ проведена описанная окружность. Найди её радиус $R$ (округли до 2 знаков).',
ans:3.46, decimal:true,
hint:'Для равностороннего $R = \\dfrac{a}{\\sqrt{3}} = \\dfrac{6}{\\sqrt{3}} = 2\\sqrt{3} \\approx 3{,}46$.'
},
{
n:5, color:'#f59e0b',
title:'Мастер Окружностей',
tag:'§§ 7-9 — синтез',
q:'В описанном четырёхугольнике стороны последовательно равны $5, 7, x, 4$. Найди $x$.',
ans:6, decimal:false,
hint:'$a + c = b + d \\Rightarrow 5 + x = 7 + 4 \\Rightarrow x = 6$.'
}
];
const cont = document.getElementById('ch2G-bosses-container');
const STATE_KEY = 'geometry9_ch2_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)=>{
const stepAttr = b.decimal ? 'step="0.01"' : 'step="1"';
const ph = b.decimal ? 'число (можно с запятой)' : 'целое число';
return '<div class="boss-card" id="bossG2-'+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"><circle cx="12" cy="12" r="9"/><polygon points="12 4 20 18 4 18"/></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="bossG2-'+b.n+'-q" class="boss-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="bossG2-'+b.n+'-ans" class="tinp" style="width:150px;text-align:center" placeholder="'+ph+'">'
+'<button class="btn primary" id="bossG2-'+b.n+'-go" style="background:'+b.color+';border-color:'+b.color+'">Атаковать</button>'
+'<button class="btn" id="bossG2-'+b.n+'-hint">Подсказка</button>'
+'</div>'
+'<div class="feedback" id="bossG2-'+b.n+'-fb"></div>'
+'</div>';
}).join('');
renderMath(cont);
function markDefeatedUI(b){
const card = document.getElementById('bossG2-'+b.n+'-card');
const goBtn = document.getElementById('bossG2-'+b.n+'-go');
const ansInp = document.getElementById('bossG2-'+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('ch2G-boss-overall');
const fill = document.getElementById('ch2G-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('ch2G-final-reward');
if(reward && reward.style.display === 'none'){
reward.style.display = 'block';
if(!STATE.achievements.has('ch2_done')){
achievement('ch2_done','Магистр окружностей');
addXp(50, 'ch2-bonus');
bumpProgress('final2', 10);
if(window.confetti){ try{ window.confetti(); }catch(e){} }
}
}
}
}
BOSSES.forEach((b, idx)=>{
const goBtn = document.getElementById('bossG2-'+b.n+'-go');
const hintBtn = document.getElementById('bossG2-'+b.n+'-hint');
const ansInp = document.getElementById('bossG2-'+b.n+'-ans');
if(BOSS_STATE[idx].defeated) markDefeatedUI(b);
goBtn.addEventListener('click', ()=>{
if(BOSS_STATE[idx].defeated) return;
const fb = document.getElementById('bossG2-'+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-ch2-'+b.n);
bumpProgress('final2', 18);
markDefeatedUI(b);
refreshOverall();
} else {
feedback(fb, false, '&#10007; Промах. Попробуй ещё. Подсказка доступна.');
}
});
hintBtn.addEventListener('click', ()=>{
const fb = document.getElementById('bossG2-'+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('p7');
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);
/* === GEOM9 POLISH JS === */
(function(){
function bumpScore(el){
if(!el) return;
el.classList.remove('bump');
void el.offsetWidth;
el.classList.add('bump');
setTimeout(function(){ try{ el.classList.remove('bump'); }catch(e){} }, 270);
}
window.__geom9BumpScore = bumpScore;
function observeScores(root){
root = root || document;
var nodes = root.querySelectorAll('.score-display b');
nodes.forEach(function(b){
if(b.__scoreObs) return;
b.__scoreObs = true;
var last = b.textContent;
try{
var mo = new MutationObserver(function(){
var nv = b.textContent;
if(nv !== last){ last = nv; bumpScore(b); }
});
mo.observe(b, {childList:true, characterData:true, subtree:true});
}catch(e){}
});
}
function rescanScores(){ try{ observeScores(document); }catch(e){} }
if(document.readyState === 'loading') document.addEventListener('DOMContentLoaded', rescanScores);
else rescanScores();
try{
var rootObs = new MutationObserver(function(muts){
var need = false;
for(var i=0;i<muts.length && !need;i++){
var m = muts[i];
for(var j=0;j<m.addedNodes.length;j++){
var n = m.addedNodes[j];
if(n.nodeType===1){
if(n.classList && n.classList.contains('score-display')) { need = true; break; }
if(n.querySelector && n.querySelector('.score-display b')) { need = true; break; }
}
}
}
if(need) rescanScores();
});
rootObs.observe(document.body, {childList:true, subtree:true});
}catch(e){}
function refreshDoneMarks(){
try{
if(typeof STATE === 'undefined' || !STATE.progress) return;
document.querySelectorAll('.psel-card').forEach(function(c){
var id = c.dataset.id || c.dataset.progCard;
if(!id) return;
var pct = +STATE.progress[id] || 0;
if(!c.querySelector('.psel-done')){
var s = document.createElement('span');
s.className = 'psel-done';
s.setAttribute('title','Прочитано');
s.innerHTML = '<svg viewBox="0 0 24 24"><polyline points="20 6 9 17 4 12"/></svg>';
c.appendChild(s);
}
c.classList.toggle('done', pct >= 50);
});
}catch(e){}
}
try{
if(typeof window.refreshProgressUI === 'function'){
var _origRP = window.refreshProgressUI;
window.refreshProgressUI = function(){ var r = _origRP.apply(this, arguments); setTimeout(refreshDoneMarks, 0); return r; };
}
}catch(e){}
setTimeout(refreshDoneMarks, 600);
setTimeout(refreshDoneMarks, 1800);
window.addEventListener('focus', function(){ setTimeout(refreshDoneMarks, 200); });
document.addEventListener('click', function(e){
var card = e.target.closest && e.target.closest('.psel-card');
if(!card) return;
var id = card.dataset.id;
if(!id) return;
setTimeout(function(){
var sec = document.getElementById('sec-' + id);
if(sec) try{ sec.scrollIntoView({behavior:'smooth', block:'start'}); }catch(e){}
}, 60);
});
function observeBossGlow(){
var cards = document.querySelectorAll('.boss-card');
cards.forEach(function(card){
if(card.__bossObs) return;
card.__bossObs = true;
try{
var mo = new MutationObserver(function(){
if(card.__glowed) return;
var go = card.querySelector('button[id*="-go"]');
var fb = card.querySelector('.feedback.ok, .boss-fb.ok');
var defeatedByBtn = go && go.disabled;
var defeatedByFb = fb && fb.textContent && /Побед|повержен/i.test(fb.textContent);
if(defeatedByBtn || defeatedByFb){
card.__glowed = true;
card.classList.add('glow','pulse');
setTimeout(function(){ try{ card.classList.remove('pulse'); }catch(e){} }, 900);
setTimeout(function(){ try{ card.classList.remove('glow'); }catch(e){} }, 1400);
}
});
mo.observe(card, {childList:true, subtree:true, attributes:true, attributeFilter:['class','style','disabled']});
var goNow = card.querySelector('button[id*="-go"]');
if(goNow && goNow.disabled) card.__glowed = true;
}catch(e){}
});
}
var bossRescan = function(){ try{ observeBossGlow(); }catch(e){} };
setTimeout(bossRescan, 800);
var bossPoll = setInterval(bossRescan, 2500);
setTimeout(function(){ try{ clearInterval(bossPoll); }catch(e){} }, 90000);
})();
</script>
</body>
</html>