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

1992 lines
132 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 · Глава 3 · Дробно-рациональные уравнения и неравенства</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:#7c3aed; --pri2:#6d28d9; --pri-soft:#ede9fe;
--acc:#a78bfa; --acc2:#7c3aed; --acc-soft:#f5f3ff;
--ok:#10b981; --ok-bg:#d1fae5; --warn:#f59e0b; --warn-bg:#fef3c7;
--bad:#ef4444; --fail:#dc2626; --fail-bg:#fee2e2;
}
.dark{--bg:#0d0418; --card:#1a0d2a; --card-soft:#1f1130; --text:#f3e8ff; --ink:#f3e8ff; --muted:#a08fb5; --border:#3a1f54}
*{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,#3b0764 0%,#7c3aed 55%,#a78bfa 100%);color:#fff;padding:46px 22px 30px;overflow:hidden;border-bottom:2px solid rgba(196,181,253,.2);min-height:130px}
.hdr::before{content:'ГЛАВА 3';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(237,233,254,.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:'≠0';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[id="sec-p10"]{ --sec-acc:#7c3aed; --sec-acc-d:#6d28d9; --sec-acc-soft:#ede9fe; }
.sec[id="sec-p11"]{ --sec-acc:#0891b2; --sec-acc-d:#0e7490; --sec-acc-soft:#cffafe; }
.sec[id="sec-p12"]{ --sec-acc:#db2777; --sec-acc-d:#9d174d; --sec-acc-soft:#fce7f3; }
.sec[id="sec-p13"]{ --sec-acc:#059669; --sec-acc-d:#047857; --sec-acc-soft:#d1fae5; }
.sec[id="sec-final3"]{ --sec-acc:#7c3aed; --sec-acc-d:#6d28d9; --sec-acc-soft:#ede9fe; }
.sec{display:none;position:relative;animation:fadeIn .35s ease}
.sec.active{display:block}
@keyframes fadeIn{from{opacity:0;transform:translateY(8px)}to{opacity:1;transform:none}}
.sec::before{content:attr(data-watermark);position:absolute;right:-20px;top:10%;font-family:'Unbounded',sans-serif;font-size:clamp(6rem,18vw,14rem);font-weight:900;color:transparent;-webkit-text-stroke:1.5px var(--sec-acc-soft,var(--pri-soft));line-height:1;pointer-events:none;user-select:none;z-index:0;opacity:.35}
.sec-header{margin-bottom:22px;padding-bottom:14px;border-bottom:2px solid var(--sec-acc-soft,var(--pri-soft));position:relative;z-index:1}
.sec-num{display:inline-block;padding:4px 10px;background:linear-gradient(135deg,var(--sec-acc,var(--pri)),var(--sec-acc-d,var(--pri2)));color:#fff;border-radius:7px;font-family:'Unbounded',sans-serif;font-size:.78rem;font-weight:800;letter-spacing:.04em;margin-bottom:8px}
.sec-h{font-family:'Unbounded',sans-serif;font-size:1.6rem;font-weight:800;color:var(--sec-acc-d,var(--pri2));letter-spacing:-.01em;line-height:1.25}
.card{background:var(--card);border:1px solid var(--border);border-radius:14px;padding:18px 20px;margin-bottom:16px;box-shadow:0 1px 3px rgba(0,0,0,.04),0 8px 24px rgba(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(--sec-acc-soft,var(--pri-soft));padding:3px 7px;border-radius:5px}
.card-body{font-size:.94rem;line-height:1.65}
.card-body p{margin-bottom:8px}
.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(--sec-acc-soft,var(--pri-soft));border-color:var(--sec-acc,var(--pri))}
.btn:active{transform:scale(.96)}
.btn.primary{background:var(--sec-acc,var(--pri));color:#fff;border-color:var(--sec-acc,var(--pri))}
.btn.primary:hover{background:var(--sec-acc-d,var(--pri2));border-color:var(--sec-acc-d,var(--pri2))}
.feedback{padding:10px 14px;border-radius:9px;font-weight:600;font-size:.88rem;margin-top:8px;display:none}
.feedback.ok{display:block;background:var(--ok-bg);color:#065f46;border-left:4px solid var(--ok)}
.feedback.fail{display:block;background:var(--fail-bg);color:#7f1d1d;border-left:4px solid var(--fail)}
.col-side{position:sticky;top:14px;align-self:start;height:fit-content;max-height:calc(100vh - 28px);overflow-y:auto}
.sidecard{background:var(--card);border:1px solid var(--border);border-radius:14px;padding:16px;margin-bottom:14px;box-shadow:var(--sh)}
.sidecard h4{font-family:'Unbounded',sans-serif;font-size:.74rem;font-weight:800;color:var(--pri2);text-transform:uppercase;letter-spacing:.07em;margin-bottom:10px;padding-bottom:8px;border-bottom:1px solid var(--border)}
.sidecard-row{margin-bottom:8px;font-size:.86rem;line-height:1.6}
.sidecard-row b{color:var(--pri);font-weight:700}
.sidecard-row:last-child{margin-bottom:0}
@media(max-width:980px){.col-side{position:static;max-height:none}}
.xp-card{background:linear-gradient(135deg,var(--acc-soft),var(--pri-soft));border:1.5px solid var(--acc);border-radius:12px;padding:14px;margin-bottom:14px}
.xp-card-title{font-size:.68rem;font-weight:800;color:var(--acc2);text-transform:uppercase;letter-spacing:.07em;margin-bottom:8px;display:flex;align-items:center;justify-content:space-between}
.xp-level{font-size:1.1rem;font-weight:900;color:var(--acc2);font-family:'Unbounded',sans-serif}
.xp-bar{height:9px;background:rgba(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}
}
.gloss-term{border-bottom:1.5px dotted var(--sec-acc,var(--pri));cursor:help;color:var(--sec-acc-d,var(--pri2));font-weight:600;padding:0 1px}
.gloss-term:hover{background:var(--sec-acc-soft,var(--pri-soft));border-radius:3px}
.gloss-tip{position:fixed;max-width:320px;padding:11px 14px;background:var(--card);border:1.5px solid var(--sec-acc,var(--pri));border-radius:11px;font-size:.84rem;line-height:1.55;box-shadow:0 12px 32px rgba(0,0,0,.18);z-index:9994;display:none;pointer-events:none;color:var(--text)}
.gloss-tip.show{display:block}
.gloss-tip b{color:var(--sec-acc-d,var(--pri2));font-size:.92rem}
.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(--sec-acc-soft,var(--pri-soft))}
.search-row .sr-kind{font-size:.7rem;font-weight:800;color:var(--muted);text-transform:uppercase;letter-spacing:.06em;margin-bottom:2px}
.search-row .sr-title{font-weight:700;font-size:.92rem;color:var(--text)}
.search-row .sr-desc{font-size:.8rem;color:var(--muted);margin-top:2px}
.search-empty{padding:20px;text-align:center;color:var(--muted);font-size:.88rem}
.search-foot{padding:8px 14px;border-top:1px solid var(--border);font-size:.74rem;color:var(--muted);display:flex;gap:14px}
.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}
.wg{background:linear-gradient(135deg,var(--card),var(--sec-acc-soft,var(--pri-soft)));border:1.5px solid var(--sec-acc,var(--pri));border-radius:14px;padding:18px 20px;margin-bottom:18px;box-shadow:var(--sh2);position:relative;z-index:1}
.wg-header{display:flex;align-items:center;gap:8px;margin-bottom:14px}
.wg-badge{padding:4px 9px;background:var(--sec-acc,var(--pri));color:#fff;border-radius:6px;font-family:'Unbounded',sans-serif;font-size:.68rem;font-weight:800;text-transform:uppercase;letter-spacing:.06em}
.wg-title{font-family:'Unbounded',sans-serif;font-size:1.05rem;font-weight:800;color:var(--sec-acc-d,var(--pri2));flex:1}
.wg-help{font-size:.88rem;color:var(--text);margin-bottom:12px;line-height:1.55;background:linear-gradient(135deg,var(--warn-bg,#fef3c7),var(--sec-acc-soft,var(--pri-soft)));border-left:4px solid var(--warn,#f59e0b);padding:9px 14px;border-radius:9px}
.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(--sec-acc,var(--pri));box-shadow:0 0 0 3px var(--sec-acc-soft,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(--sec-acc-d,var(--pri2));margin-left:4px}
.sliders label input[type="range"]{display:block;width:100%;margin-top:6px;accent-color:var(--sec-acc,var(--pri))}
.score-display{display:flex;gap:14px;flex-wrap:wrap;align-items:center;padding:10px 14px;background:var(--sec-acc-soft,var(--pri-soft));border-radius:10px;margin-bottom:12px}
.score-display b{color:var(--sec-acc-d,var(--pri2));font-size:1.15rem}
.spoiler{border:1px solid var(--border);border-radius:10px;background:var(--card);margin:10px 0;overflow:hidden}
.spoiler summary{padding:8px 14px;background:var(--sec-acc-soft,var(--pri-soft));font-weight:700;cursor:pointer;font-size:.88rem;color:var(--sec-acc-d,var(--pri2));list-style:none;display:flex;align-items:center;gap:8px}
.spoiler summary::-webkit-details-marker{display:none}
.spoiler summary::before{content:'+';font-size:1.2rem;font-weight:900;color:var(--sec-acc,var(--pri));width:18px}
.spoiler[open] summary::before{content:'\2212'}
.spoiler-body{padding:10px 14px;font-size:.92rem;line-height:1.6}
.step-box{background:var(--card);border:1.5px solid var(--border);border-radius:10px;padding:12px 14px;margin-top:10px;font-size:.94rem;line-height:1.55}
.step-box.show{border-color:var(--sec-acc,var(--pri));background:var(--sec-acc-soft,var(--pri-soft))}
.dnd-pool{display:flex;flex-wrap:wrap;gap:8px;margin-bottom:14px;padding:10px;border:1.5px dashed var(--border);border-radius:10px;min-height:54px;transition:border-color .18s,background .18s}
.dnd-pool.over{border-color:var(--sec-acc,var(--pri));background:var(--sec-acc-soft,var(--pri-soft));border-style:solid}
.dnd-pool.col{flex-direction:column;align-items:stretch}
.dnd-pool.col .dnd-chip{width:auto}
.dnd-chip{display:inline-flex;align-items:center;gap:6px;padding:6px 12px;background:var(--card);border:1.5px solid var(--border);border-radius:10px;cursor:grab;user-select:none;font-size:.92rem;line-height:1.4;transition:transform .12s,box-shadow .12s,border-color .12s;touch-action:none;max-width:100%}
.dnd-chip:hover{transform:translateY(-1px);border-color:var(--sec-acc,var(--pri));box-shadow:var(--sh)}
.dnd-chip:active{cursor:grabbing}
.dnd-chip.armed{border-color:var(--sec-acc,var(--pri));background:var(--sec-acc-soft,var(--pri-soft));box-shadow:0 0 0 3px rgba(217,119,6,.22);transform:translateY(-1px)}
.dnd-chip.dragging{opacity:.28}
.dnd-chip.placed{background:var(--sec-acc-soft,var(--pri-soft));border-color:var(--sec-acc,var(--pri))}
.dnd-chip .dnd-x{padding:0 5px;color:var(--muted);font-weight:700;font-size:1.05rem;border-radius:4px;cursor:pointer}
.dnd-chip .dnd-x:hover{color:var(--bad,var(--fail));background:var(--fail-bg)}
.drop-box{background:var(--card);border:1.5px dashed var(--border);border-radius:10px;padding:10px;min-height:90px;transition:border-color .15s,background .15s}
.drop-box:hover{border-color:var(--sec-acc,var(--pri));background:var(--sec-acc-soft,var(--pri-soft))}
.drop-box h5{font-family:'Unbounded',sans-serif;font-size:.78rem;color:var(--sec-acc-d,var(--pri2));margin-bottom:8px;text-transform:uppercase;letter-spacing:.05em}
.drop-box.over{border-color:var(--sec-acc,var(--pri));background:var(--sec-acc-soft,var(--pri-soft));border-style:solid;transform:scale(1.015)}
.drop-items{display:flex;flex-wrap:wrap;gap:6px;min-height:32px}
</style>
</head>
<body>
<header class="hdr">
<div class="hdr-row">
<div>
<h1>Алгебра 9 · Глава 3</h1>
<div class="hdr-sub">Уравнения · системы · окружность · метод интервалов</div>
</div>
<div class="hdr-side">
<a href="/textbook/algebra-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> (включая графический способ), <b>длину отрезка</b> и <b>уравнение окружности</b> $(x-a)^2 + (y-b)^2 = r^2$, а также <b>метод интервалов</b> для дробно-рациональных неравенств.</p>
<div class="hero-row">
<button class="btn-primary" onclick="goTo('p10')"><svg class="ic" viewBox="0 0 24 24"><polygon points="6 4 20 12 6 20 6 4" fill="currentColor" stroke="none"/></svg> Начать § 10</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-p10" class="sec" data-watermark="=0"><div class="sec-header"><span class="sec-num">§ 10</span><h2 class="sec-h">Дробно-рациональные уравнения</h2></div><div id="p10-body"></div></section>
<section id="sec-p11" class="sec" data-watermark="{"><div class="sec-header"><span class="sec-num">§ 11</span><h2 class="sec-h">Системы нелинейных уравнений</h2></div><div id="p11-body"></div></section>
<section id="sec-p12" class="sec" data-watermark="○"><div class="sec-header"><span class="sec-num">§ 12</span><h2 class="sec-h">Уравнение окружности</h2></div><div id="p12-body"></div></section>
<section id="sec-p13" class="sec" data-watermark=">0"><div class="sec-header"><span class="sec-num">§ 13</span><h2 class="sec-h">Метод интервалов</h2></div><div id="p13-body"></div></section>
<section id="sec-final3" class="sec" data-watermark="★"><div class="sec-header"><span class="sec-num" style="background:linear-gradient(135deg,#7c3aed,#a78bfa)">Финал главы</span><h2 class="sec-h">Итоги. 4 боссов главы 3</h2></div><div id="final3-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» · Глава 3 · Дробно-рациональные уравнения и неравенства · 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="gloss-tip" class="gloss-tip"></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:'p10', progress:{p10:0,p11:0,p12:0,p13:0,final3:0}, achievements:new Map(), xp:0, level:1 };
const TOTAL_PARAS = 5;
const _TB_SLUG = 'algebra-9-ch3';
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:'Начало главы 3!',
p11_done:'Системы нелинейных уравнений освоены!',
p12_done:'Уравнение окружности освоено!',
p13_done:'Метод интервалов освоен!',
ch3_done:'Глава 3 пройдена!'
};
function loadProgress(){
try{
const s=localStorage.getItem('algebra9_ch3_progress'); if(s) Object.assign(STATE.progress, JSON.parse(s));
const a=localStorage.getItem('algebra9_ch3_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('algebra9_xp')||0); STATE.level=calcLevel(STATE.xp);
}catch(e){}
}
function saveProgress(){
try{
localStorage.setItem('algebra9_ch3_progress', JSON.stringify(STATE.progress));
localStorage.setItem('algebra9_ch3_achievements', JSON.stringify(Object.fromEntries(STATE.achievements)));
localStorage.setItem('algebra9_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,'algebra9-ch3-'+(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:'p10', num:'§ 10', name:'Дробно-рациональные уравнения', sub:'$\\dfrac{P}{Q} = 0$' },
{ id:'p11', num:'§ 11', name:'Системы нелинейных уравнений', sub:'подстановка · графика' },
{ id:'p12', num:'§ 12', name:'Уравнение окружности', sub:'$(x-a)^2+(y-b)^2=r^2$' },
{ id:'p13', num:'§ 13', name:'Метод интервалов', sub:'неравенства' },
{ id:'final3', num:'★', name:'Финал главы', sub:'Итоги · 4 боссов', 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 = { p10:()=>buildP10(), p11:()=>buildP11(), p12:()=>buildP12(), p13:()=>buildP13(), final3:()=>buildFinal3() };
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 = {
p10:{title:'Шпаргалка \xA710',rows:[['Дробно-рац. уравн.','$\\dfrac{P(x)}{Q(x)} = 0$'],['Условие','$P(x) = 0$ и $Q(x) \\ne 0$'],['Алгоритм','найди корни $P$ → проверь ОДЗ']]},
p11:{title:'Шпаргалка \xA711',rows:[['Система','несколько уравнений с общими $x, y$'],['Подстановка','выразил → подставил'],['Графически','точки пересечения графиков']]},
p12:{title:'Шпаргалка \xA712',rows:[['Длина','$d = \\sqrt{(x_2-x_1)^2 + (y_2-y_1)^2}$'],['Окружность','$(x-a)^2 + (y-b)^2 = r^2$'],['Центр','$(a; b)$'],['Радиус','$r$']]},
p13:{title:'Шпаргалка \xA713',rows:[['Шаг 1','перенеси всё влево, приведи к виду $\\dfrac{P}{Q}$'],['Шаг 2','найди нули $P$ и $Q$'],['Шаг 3','отметь на оси'],['Шаг 4','определи знаки']]},
final3:{title:'Финал главы',rows:[['§§1013','теория главы 3'],['Боссов','4'],['Награда','+100 XP']]}
};
const TIPS=[
{sec:'p10',html:'Дробно-рациональное уравнение $\\dfrac{P(x)}{Q(x)} = 0$ равносильно системе: $P(x) = 0$ и $Q(x) \\ne 0$.'},
{sec:'p11',html:'В системах нелинейных уравнений часто помогает <b>метод подстановки</b> или сложение.'},
{sec:'p12',html:'Длина отрезка: $d = \\sqrt{(x_2 - x_1)^2 + (y_2 - y_1)^2}$. Окружность: $(x - a)^2 + (y - b)^2 = r^2$.'},
{sec:'p13',html:'<b>Метод интервалов</b>: нули → точки на оси → знаки на промежутках.'},
{sec:'final3',html:'4 босса главы 3.'}
];
function buildSidebar(id){
const box=document.getElementById('sidebar-content');
const sb=SIDEBARS[id]||SIDEBARS['p10'];
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('algebra9_ch3_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('algebra9_ch3_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 secNav(prev, next){
const NAMES={p10:'\xA710',p11:'\xA711',p12:'\xA712',p13:'\xA713',final3:'Финал'};
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==='final3') achievement('ch3_done');
});
}
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; }
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>';
}
/* DnD-сортер для распределения карточек по категориям */
function setupSorter(cfg){
const placed = {}; const pool = document.getElementById(cfg.poolId); const scope = document.querySelector(cfg.scopeSelector);
if(!pool||!scope) return {placed,render:()=>{},reset:()=>{}};
pool.classList.add('dnd-pool'); if(cfg.columnLayout) pool.classList.add('col');
let armed = null;
function buildChip(it,isPlaced){ const e=document.createElement('div'); e.className='dnd-chip'+(isPlaced?' placed':''); e.dataset.id=it.id; e.innerHTML='<span class="dnd-txt">'+it.html+'</span><span class="dnd-x" title="Убрать">\xd7</span>'; attach(e,it.id); return e; }
function attach(elm,itId){ let ghost=null,dragging=false,sx=0,sy=0; elm.addEventListener('pointerdown',ev=>{ if(ev.button!==undefined&&ev.button!==0) return;
ev.preventDefault(); if(ev.target.classList&&ev.target.classList.contains('dnd-x')){ ev.stopPropagation(); if(placed[itId]){delete placed[itId];render();}else if(armed===itId){armed=null;render();} return; } sx=ev.clientX;sy=ev.clientY; const r=elm.getBoundingClientRect(); const ox=ev.clientX-r.left,oy=ev.clientY-r.top; try{elm.setPointerCapture(ev.pointerId);}catch(e){} function onMove(e){ const dx=e.clientX-sx,dy=e.clientY-sy; if(!dragging&&Math.hypot(dx,dy)>8){ dragging=true; ghost=elm.cloneNode(true); ghost.classList.remove('armed'); ghost.style.cssText='position:fixed;z-index:9999;pointer-events:none;opacity:.9;transform:rotate(-2.5deg);box-shadow:0 14px 36px rgba(0,0,0,.32);width:'+r.width+'px;left:'+(e.clientX-ox)+'px;top:'+(e.clientY-oy)+'px'; document.body.appendChild(ghost); elm.classList.add('dragging'); } if(dragging&&ghost){ ghost.style.left=(e.clientX-ox)+'px';ghost.style.top=(e.clientY-oy)+'px'; const under=document.elementsFromPoint(e.clientX,e.clientY); scope.querySelectorAll('.drop-box.over,.dnd-pool.over').forEach(n=>n.classList.remove('over')); const tgt=under.find(n=>n.classList&&(n.classList.contains('drop-box')||n.classList.contains('dnd-pool'))); if(tgt)tgt.classList.add('over'); } } function onUp(e){ elm.removeEventListener('pointermove',onMove);elm.removeEventListener('pointerup',onUp);elm.removeEventListener('pointercancel',onUp);elm.classList.remove('dragging'); if(ghost){ghost.remove();ghost=null;} scope.querySelectorAll('.drop-box.over,.dnd-pool.over').forEach(n=>n.classList.remove('over')); if(dragging){ const under=document.elementsFromPoint(e.clientX,e.clientY); const box=under.find(n=>n.classList&&n.classList.contains('drop-box')); const pl=under.find(n=>n.classList&&n.classList.contains('dnd-pool')); if(box){const di=box.querySelector('[data-cat]');if(di){placed[itId]=di.dataset.cat;armed=null;render();return;}}else if(pl){delete placed[itId];armed=null;render();return;} }else{ if(placed[itId]){delete placed[itId];armed=null;render();}else{armed=(armed===itId)?null:itId;render();} } dragging=false; } elm.addEventListener('pointermove',onMove);elm.addEventListener('pointerup',onUp);elm.addEventListener('pointercancel',onUp); }); }
function attachBoxTaps(){ scope.querySelectorAll('.drop-box').forEach(box=>{ box.addEventListener('click',ev=>{ if(!armed)return; if(ev.target.closest('.dnd-chip'))return; const di=box.querySelector('[data-cat]'); if(di){placed[armed]=di.dataset.cat;armed=null;render();} }); }); }
function render(){ pool.innerHTML=''; cfg.items.forEach(it=>{if(placed[it.id])return;const c=buildChip(it,false);if(armed===it.id)c.classList.add('armed');pool.appendChild(c);}); cfg.cats.forEach(cat=>{const box=scope.querySelector('.drop-items[data-cat="'+cat+'"]');if(!box)return;box.innerHTML='';cfg.items.forEach(it=>{if(placed[it.id]!==cat)return;box.appendChild(buildChip(it,true));});}); if(window.renderMathInElement)try{renderMath(scope);}catch(_){} }
attachBoxTaps(); render();
return {placed,render,reset(){ for(const k in placed)delete placed[k];armed=null;render(); }};
}
/* Координатная плоскость в SVG */
function axes2D(W, H, pad, xmin, xmax, ymin, ymax){
const ux = (W - 2*pad) / (xmax - xmin);
const uy = (H - 2*pad) / (ymax - ymin);
const toX = v => pad + (v - xmin) * ux;
const toY = v => H - pad - (v - ymin) * uy;
let g = '';
g += '<g stroke="#e5e7eb" stroke-width="1">';
for (let x = Math.ceil(xmin); x <= xmax; x++){
g += '<line x1="'+toX(x)+'" y1="'+pad+'" x2="'+toX(x)+'" y2="'+(H-pad)+'"/>';
}
for (let y = Math.ceil(ymin); y <= ymax; y++){
g += '<line x1="'+pad+'" y1="'+toY(y)+'" x2="'+(W-pad)+'" y2="'+toY(y)+'"/>';
}
g += '</g>';
const y0 = toY(0), x0 = toX(0);
g += '<line x1="'+pad+'" y1="'+y0+'" x2="'+(W-pad)+'" y2="'+y0+'" stroke="#0f172a" stroke-width="1.5"/>';
g += '<line x1="'+x0+'" y1="'+pad+'" x2="'+x0+'" y2="'+(H-pad)+'" stroke="#0f172a" stroke-width="1.5"/>';
g += '<text x="'+(W-pad+2)+'" y="'+(y0-4)+'" font-size="11" fill="#0f172a">x</text>';
g += '<text x="'+(x0+4)+'" y="'+(pad-2)+'" font-size="11" fill="#0f172a">y</text>';
g += '<g font-size="10" fill="#64748b">';
for (let x = Math.ceil(xmin); x <= xmax; x++){
if (x !== 0) g += '<text x="'+(toX(x)-3)+'" y="'+(y0+12)+'">'+x+'</text>';
}
for (let y = Math.ceil(ymin); y <= ymax; y++){
if (y !== 0) g += '<text x="'+(x0+4)+'" y="'+(toY(y)+3)+'">'+y+'</text>';
}
g += '<text x="'+(x0+4)+'" y="'+(y0+12)+'">0</text>';
g += '</g>';
return { content: g, toX, toY, ux, uy };
}
/* График функции y=f(x) — возвращает строку <path ...> */
function plotFunc(f, xmin, xmax, toX, toY, color, N){
N = N || 200;
let d = '';
let prevValid = false;
for (let i = 0; i <= N; i++){
const x = xmin + (xmax - xmin) * i / N;
let y;
try { y = f(x); } catch(e){ y = NaN; }
if (!isFinite(y) || isNaN(y) || y < -1e4 || y > 1e4){ prevValid = false; continue; }
d += (prevValid ? ' L' : ' M') + toX(x).toFixed(2) + ',' + toY(y).toFixed(2);
prevValid = true;
}
return '<path d="'+d+'" stroke="'+color+'" stroke-width="2" fill="none" stroke-linecap="round" stroke-linejoin="round"/>';
}
/* ===== STUB BUILDERS — наполнение в Phase 1+ ===== */
function buildP10(){
const box = document.getElementById('p10-body');
let html = '';
html += makeCard('theory', 'Дробно-рациональное уравнение', '10.1', `
<p>Уравнение вида $\\dfrac{P(x)}{Q(x)} = 0$, где $P(x)$ и $Q(x)$ — многочлены, называется <b>дробно-рациональным</b>.</p>
<p>Оно равносильно системе:
<span style="display:inline-block;background:var(--card);border:1px solid var(--border);border-radius:8px;padding:5px 12px;margin:4px 0">$P(x) = 0$ <b>и</b> $Q(x) \\ne 0$</span>
</p>
<p>Корни уравнения $P(x) = 0$, при которых $Q(x) = 0$, называют <b>посторонними</b> и исключают из ответа.</p>
<details class="spoiler"><summary>Почему именно так?</summary><div class="spoiler-body">
Дробь равна нулю только тогда, когда её числитель равен нулю, а знаменатель — нет. Если бы знаменатель тоже был нулём — дробь не определена.
</div></details>`);
html += makeCard('rule', 'Общий вид уравнения', '10.2', `
<p>Уравнение вида $\\dfrac{P_1(x)}{Q_1(x)} = \\dfrac{P_2(x)}{Q_2(x)}$ решается по схеме:</p>
<ol style="padding-left:22px;line-height:1.9">
<li>Найти <b>ОДЗ</b>: $Q_1(x) \\ne 0$ и $Q_2(x) \\ne 0$.</li>
<li>Перенести всё в одну сторону и привести к общему знаменателю $\\Rightarrow$ форма $\\dfrac{N(x)}{D(x)} = 0$.</li>
<li>Решить $N(x) = 0$.</li>
<li>Проверить найденные корни по ОДЗ — те, что нарушают $D(x) \\ne 0$, отбросить.</li>
</ol>`);
html += makeCard('example', 'Пошаговое решение', '10.3', `
<p><b>Пример 1.</b> Решить $\\dfrac{2}{x - 3} = \\dfrac{1}{x + 1}$.</p>
<p>ОДЗ: $x \\ne 3$ и $x \\ne -1$. Перекрёстное умножение:
$2(x + 1) = x - 3 \\Rightarrow 2x + 2 = x - 3 \\Rightarrow x = -5$.</p>
<p>Проверка: $-5 \\ne 3,\\ -5 \\ne -1$ — подходит. <b>Ответ:</b> $x = -5$.</p>
<p style="margin-top:10px"><b>Пример 2.</b> Решить $\\dfrac{x + 1}{x - 2} = 2$.</p>
<p>ОДЗ: $x \\ne 2$. Умножим обе части на $x - 2$:
$x + 1 = 2(x - 2) \\Rightarrow x + 1 = 2x - 4 \\Rightarrow x = 5$.</p>
<p>Проверка: $5 \\ne 2$ — корень подходит. <b>Ответ:</b> $x = 5$.</p>`);
/* INTERACTIVE 1 — пошаговый решатель */
html += `<div class="wg" id="p10-iv1">
<div class="wg-header"><span class="wg-badge">ИНТЕРАКТИВ 1</span><div class="wg-title">Пошаговый решатель</div></div>
<div class="wg-help">Выбери задачу ползунком и раскрывай шаги решения по одному.</div>
<div class="sliders">
<label>Задача №<b id="p10-iv1-idx">1</b> / 5<input type="range" id="p10-iv1-slider" min="1" max="5" step="1" value="1"></label>
</div>
<div id="p10-iv1-eq" style="text-align:center;font-size:1.1rem;padding:12px;background:var(--card);border-radius:9px;margin-bottom:10px"></div>
<div id="p10-iv1-steps"></div>
<div class="actions" style="justify-content:center">
<button class="btn primary" id="p10-iv1-next">Следующий шаг <svg class="ic" viewBox="0 0 24 24"><polyline points="9 18 15 12 9 6"/></svg></button>
<button class="btn" id="p10-iv1-reset">Сбросить</button>
</div>
</div>`;
/* INTERACTIVE 2 — калькулятор */
html += `<div class="wg" id="p10-iv2">
<div class="wg-header"><span class="wg-badge">ИНТЕРАКТИВ 2</span><div class="wg-title">Калькулятор $\\dfrac{a}{x - b} = c$</div></div>
<div class="wg-help">Введи целые $a$, $b$, $c$ — калькулятор найдёт ОДЗ, решит уравнение и проверит корень.</div>
<div style="display:flex;gap:10px;align-items:center;flex-wrap:wrap;justify-content:center;margin-bottom:10px">
<span>$a$ =</span><input type="number" id="p10-iv2-a" class="tinp" style="width:80px;text-align:center" value="6">
<span>$b$ =</span><input type="number" id="p10-iv2-b" class="tinp" style="width:80px;text-align:center" value="2">
<span>$c$ =</span><input type="number" id="p10-iv2-c" class="tinp" style="width:80px;text-align:center" value="3">
<button class="btn primary" id="p10-iv2-go">Решить</button>
</div>
<div id="p10-iv2-out" style="padding:12px 14px;background:var(--card);border-radius:9px;text-align:center;font-size:1rem;min-height:48px"></div>
</div>`;
/* INTERACTIVE 3 — посторонний корень? */
html += `<div class="wg" id="p10-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">Задача: <b id="p10-iv3-idx">1</b> / 6 &middot; Очки: <b id="p10-iv3-sc">0</b></div>
<div id="p10-iv3-q" style="text-align:center;font-size:1.05rem;padding:14px;background:var(--card);border-radius:9px;margin-bottom:12px;min-height:60px"></div>
<div class="actions" style="justify-content:center">
<button class="btn primary" id="p10-iv3-ok">Подходит</button>
<button class="btn" id="p10-iv3-bad">Посторонний</button>
</div>
<div class="feedback" id="p10-iv3-fb"></div>
</div>`;
/* INTERACTIVE 4 — тренажёр корней */
html += `<div class="wg" id="p10-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">Задача: <b id="p10-iv4-idx">1</b> / 6 &middot; Очки: <b id="p10-iv4-sc">0</b></div>
<div id="p10-iv4-q" style="text-align:center;font-size:1.1rem;padding:14px;background:var(--card);border-radius:9px;margin-bottom:10px;min-height:60px"></div>
<div style="display:flex;gap:10px;align-items:center;justify-content:center;flex-wrap:wrap">
<span>$x$ =</span>
<input type="number" id="p10-iv4-inp" class="tinp" style="width:100px;text-align:center" step="0.5">
<button class="btn primary" id="p10-iv4-go">Проверить</button>
<button class="btn" id="p10-iv4-skip">Пропустить</button>
</div>
<div class="feedback" id="p10-iv4-fb"></div>
</div>`;
box.innerHTML = html + secNav(null, 'p11') + readButton('p10');
renderMath(box);
/* ===== IV1 wiring ===== */
(function(){
const tasks = [
{
eq: '$\\dfrac{1}{x} = 2$',
steps: [
'ОДЗ: $x \\ne 0$.',
'Умножим обе части на $x$: $1 = 2x$.',
'Отсюда $x = \\dfrac{1}{2}$.',
'Проверка: $\\dfrac{1}{2} \\ne 0$ — подходит. <b>Ответ: $x = 0{,}5$.</b>'
]
},
{
eq: '$\\dfrac{2}{x - 1} = \\dfrac{3}{x + 1}$',
steps: [
'ОДЗ: $x \\ne 1$ и $x \\ne -1$.',
'Перекрёстное умножение: $2(x + 1) = 3(x - 1)$.',
'Раскроем: $2x + 2 = 3x - 3 \\Rightarrow x = 5$.',
'Проверка: $5 \\ne 1,\\ 5 \\ne -1$ — подходит. <b>Ответ: $x = 5$.</b>'
]
},
{
eq: '$\\dfrac{x}{x - 3} = \\dfrac{2}{x - 3} + 1$',
steps: [
'ОДЗ: $x \\ne 3$.',
'Перенесём дроби: $\\dfrac{x - 2}{x - 3} = 1$.',
'Умножим на $(x - 3)$: $x - 2 = x - 3 \\Rightarrow -2 = -3$.',
'Противоречие! <b>Ответ: корней нет.</b>'
]
},
{
eq: '$\\dfrac{3}{x + 2} = \\dfrac{6}{2x + 4}$',
steps: [
'ОДЗ: $x \\ne -2$.',
'Заметим: $2x + 4 = 2(x + 2)$, значит правая дробь $= \\dfrac{6}{2(x + 2)} = \\dfrac{3}{x + 2}$.',
'Получили $\\dfrac{3}{x + 2} = \\dfrac{3}{x + 2}$ — верно при любом $x \\ne -2$.',
'<b>Ответ:</b> $x$ — любое число, кроме $-2$.'
]
},
{
eq: '$\\dfrac{x + 1}{x - 2} = 2$',
steps: [
'ОДЗ: $x \\ne 2$.',
'Умножим на $(x - 2)$: $x + 1 = 2(x - 2)$.',
'Раскроем: $x + 1 = 2x - 4 \\Rightarrow x = 5$.',
'Проверка: $5 \\ne 2$ — подходит. <b>Ответ: $x = 5$.</b>'
]
}
];
const sl = document.getElementById('p10-iv1-slider');
const idxEl = document.getElementById('p10-iv1-idx');
const eqEl = document.getElementById('p10-iv1-eq');
const stepsEl = document.getElementById('p10-iv1-steps');
const nextBtn = document.getElementById('p10-iv1-next');
const resetBtn = document.getElementById('p10-iv1-reset');
let shown = 0;
let bumped = false;
function render(){
const idx = (+sl.value) - 1;
const t = tasks[idx];
idxEl.textContent = idx + 1;
eqEl.innerHTML = t.eq;
let h = '';
for (let i = 0; i < shown; i++){
h += '<div class="step-box show"><b>Шаг ' + (i + 1) + '.</b> ' + t.steps[i] + '</div>';
}
stepsEl.innerHTML = h;
nextBtn.disabled = (shown >= t.steps.length);
nextBtn.style.opacity = nextBtn.disabled ? .5 : 1;
renderMath(document.getElementById('p10-iv1'));
}
sl.addEventListener('input', ()=>{ shown = 0; render(); });
nextBtn.addEventListener('click', ()=>{
const t = tasks[(+sl.value) - 1];
if (shown < t.steps.length){ shown++; render(); }
if (!bumped){ bumped = true; bumpProgress('p10', 15); addXp(10, 'p10-iv1'); }
});
resetBtn.addEventListener('click', ()=>{ shown = 0; render(); });
render();
})();
/* ===== IV2 wiring — калькулятор ===== */
(function(){
const aIn = document.getElementById('p10-iv2-a');
const bIn = document.getElementById('p10-iv2-b');
const cIn = document.getElementById('p10-iv2-c');
const out = document.getElementById('p10-iv2-out');
let bumped = false;
document.getElementById('p10-iv2-go').addEventListener('click', ()=>{
const a = +aIn.value, b = +bIn.value, c = +cIn.value;
let html = '<div><b>Уравнение:</b> $\\dfrac{' + a + '}{x - (' + b + ')} = ' + c + '$</div>';
html += '<div style="margin-top:6px"><b>ОДЗ:</b> $x \\ne ' + b + '$.</div>';
if (c === 0){
if (a === 0) html += '<div style="margin-top:6px;color:var(--ok)"><b>Решение:</b> любое $x \\ne ' + b + '$ (тождество $0 = 0$).</div>';
else html += '<div style="margin-top:6px;color:var(--fail)"><b>Решение:</b> корней нет (дробь $\\dfrac{' + a + '}{x - ' + b + '}$ не равна нулю).</div>';
} else {
const x = b + a / c;
html += '<div style="margin-top:6px">Умножим на $(x - ' + b + ')$: $' + a + ' = ' + c + '(x - ' + b + ')$.</div>';
html += '<div>$x = ' + b + ' + \\dfrac{' + a + '}{' + c + '} = ' + fmt(x) + '$.</div>';
if (Math.abs(x - b) < 1e-9){
html += '<div style="margin-top:6px;color:var(--fail)"><b>Посторонний корень</b> (нарушает ОДЗ). Корней нет.</div>';
} else {
html += '<div style="margin-top:6px;color:var(--ok)"><b>Ответ:</b> $x = ' + fmt(x) + '$.</div>';
}
}
out.innerHTML = html;
renderMath(out);
if (!bumped){ bumped = true; bumpProgress('p10', 15); addXp(10, 'p10-iv2'); }
});
})();
/* ===== IV3 wiring — посторонний? ===== */
(function(){
const items = [
{ q: '$\\dfrac{x^2}{x - 1} = \\dfrac{1}{x - 1}$, найден $x = 1$.', ans: false, hint: 'ОДЗ: $x \\ne 1$. Корень $x = 1$ нарушает ОДЗ → посторонний.' },
{ q: '$\\dfrac{x}{x + 2} = \\dfrac{3}{x + 2}$, найден $x = 3$.', ans: true, hint: 'ОДЗ: $x \\ne -2$. Корень $x = 3$ её удовлетворяет.' },
{ q: '$\\dfrac{1}{x} = \\dfrac{1}{x}$, найден $x = 0$.', ans: false, hint: 'ОДЗ: $x \\ne 0$. Корень $x = 0$ нарушает ОДЗ → посторонний.' },
{ q: '$\\dfrac{2}{x - 5} = \\dfrac{4}{x}$, найден $x = 10$.', ans: true, hint: 'ОДЗ: $x \\ne 5,\\ x \\ne 0$. $10 \\ne 5,\\ 10 \\ne 0$. Проверка: $\\dfrac{2}{5} = \\dfrac{4}{10}$ — верно.' },
{ q: '$\\dfrac{x^2 - 4}{x - 2} = 4$, найден $x = 2$.', ans: false, hint: 'ОДЗ: $x \\ne 2$. Корень $x = 2$ — посторонний.' },
{ q: '$\\dfrac{x - 1}{x + 1} = 0$, найден $x = 1$.', ans: true, hint: 'ОДЗ: $x \\ne -1$. $1 \\ne -1$ — корень подходит.' }
];
let i = 0, sc = 0, bumped = false;
const idxEl = document.getElementById('p10-iv3-idx');
const scEl = document.getElementById('p10-iv3-sc');
const qEl = document.getElementById('p10-iv3-q');
const fb = document.getElementById('p10-iv3-fb');
const okBtn = document.getElementById('p10-iv3-ok');
const badBtn = document.getElementById('p10-iv3-bad');
function render(){
idxEl.textContent = Math.min(i + 1, items.length);
scEl.textContent = sc;
if (i >= items.length){
qEl.innerHTML = '<b>Готово!</b> Результат: ' + sc + ' / ' + items.length;
okBtn.disabled = true; badBtn.disabled = true;
okBtn.style.opacity = .5; badBtn.style.opacity = .5;
if (!bumped){ bumped = true; bumpProgress('p10', 25); addXp(15, 'p10-iv3'); }
return;
}
qEl.innerHTML = items[i].q;
fb.style.display = 'none';
renderMath(qEl);
}
function answer(v){
if (i >= items.length) return;
const it = items[i];
const ok = (v === it.ans);
if (ok) sc++;
feedback(fb, ok, (ok ? '&#10003; Верно. ' : '&#10007; Неверно. ') + it.hint);
i++;
setTimeout(render, 1100);
}
okBtn.addEventListener('click', ()=>answer(true));
badBtn.addEventListener('click', ()=>answer(false));
render();
})();
/* ===== IV4 wiring — тренажёр корней ===== */
(function(){
const items = [
{ q: '$\\dfrac{x}{2} = 5$', ans: 10, hint: '$x = 5 \\cdot 2 = 10$.' },
{ q: '$\\dfrac{6}{x} = 3$', ans: 2, hint: '$6 = 3x \\Rightarrow x = 2$.' },
{ q: '$\\dfrac{1}{x - 1} = \\dfrac{1}{3}$', ans: 4, hint: '$x - 1 = 3 \\Rightarrow x = 4$.' },
{ q: '$\\dfrac{x + 2}{x - 1} = 2$', ans: 4, hint: '$x + 2 = 2(x - 1) \\Rightarrow x = 4$.' },
{ q: '$\\dfrac{2}{x - 3} = -1$', ans: 1, hint: '$2 = -(x - 3) \\Rightarrow x = 1$.' },
{ q: '$\\dfrac{x - 5}{x + 5} = 0$', ans: 5, hint: 'Числитель $= 0$: $x - 5 = 0 \\Rightarrow x = 5$.' }
];
let i = 0, sc = 0, bumped = false;
const idxEl = document.getElementById('p10-iv4-idx');
const scEl = document.getElementById('p10-iv4-sc');
const qEl = document.getElementById('p10-iv4-q');
const inp = document.getElementById('p10-iv4-inp');
const fb = document.getElementById('p10-iv4-fb');
const goBtn = document.getElementById('p10-iv4-go');
const skipBtn = document.getElementById('p10-iv4-skip');
function render(){
idxEl.textContent = Math.min(i + 1, items.length);
scEl.textContent = sc;
if (i >= items.length){
qEl.innerHTML = '<b>Готово!</b> Результат: ' + sc + ' / ' + items.length;
inp.disabled = true; goBtn.disabled = true; skipBtn.disabled = true;
inp.style.opacity = .5; goBtn.style.opacity = .5; skipBtn.style.opacity = .5;
if (!bumped){ bumped = true; bumpProgress('p10', 25); addXp(15, 'p10-iv4'); }
return;
}
qEl.innerHTML = items[i].q;
inp.value = '';
fb.style.display = 'none';
renderMath(qEl);
}
function check(){
if (i >= items.length) return;
const v = parseFloat(inp.value);
const it = items[i];
if (isNaN(v)){ feedback(fb, false, 'Введи число.'); return; }
const ok = Math.abs(v - it.ans) < 1e-6;
if (ok) sc++;
feedback(fb, ok, (ok ? '&#10003; Верно! ' : '&#10007; Неверно. ' + 'Правильно: $x = ' + it.ans + '$. ') + it.hint);
i++;
setTimeout(render, 1200);
}
function skip(){
if (i >= items.length) return;
const it = items[i];
feedback(fb, false, 'Пропущено. Правильно: $x = ' + it.ans + '$. ' + it.hint);
i++;
setTimeout(render, 1200);
}
goBtn.addEventListener('click', check);
skipBtn.addEventListener('click', skip);
inp.addEventListener('keydown', e=>{ if (e.key === 'Enter') check(); });
render();
})();
wireReadBtn('p10');
}
function buildP11(){
const box = document.getElementById('p11-body');
let html = '';
html += makeCard('theory', 'Способ подстановки', '11.1', `
<p>В системе двух уравнений с двумя переменными $x, y$ выражаем одну переменную через другую из одного уравнения и подставляем в другое.</p>
<p><b>Пример.</b> $\\begin{cases} y = x + 1 \\\\ x^2 + y^2 = 5 \\end{cases}$</p>
<p>Подставим $y = x + 1$ во второе уравнение:
$x^2 + (x + 1)^2 = 5 \\Rightarrow 2x^2 + 2x - 4 = 0 \\Rightarrow x^2 + x - 2 = 0$.</p>
<p>Корни: $x_1 = 1,\\ x_2 = -2$. Тогда $y_1 = 2,\\ y_2 = -1$.</p>
<p><b>Ответ:</b> $(1;\\ 2)$ и $(-2;\\ -1)$.</p>`);
html += makeCard('rule', 'Способ сложения', '11.2', `
<p>Систему приводят к виду, в котором при сложении (или вычитании) уравнений одна из переменных исчезает. Удобно для <b>симметричных</b> систем.</p>
<p><b>Пример.</b> $\\begin{cases} x + y = 5 \\\\ x^2 + y^2 = 13 \\end{cases}$</p>
<p>Из первого: $y = 5 - x$. Подставим: $x^2 + (5 - x)^2 = 13 \\Rightarrow 2x^2 - 10x + 12 = 0 \\Rightarrow x^2 - 5x + 6 = 0$.</p>
<p>Корни: $x = 2$ или $x = 3$. <b>Ответ:</b> $(2;\\ 3)$ и $(3;\\ 2)$.</p>`);
html += makeCard('example', 'Графическая интерпретация', '11.3', `
<p>Решение системы двух уравнений — это <b>координаты точек пересечения</b> графиков уравнений.</p>
<p><b>Пример.</b> $\\begin{cases} y = x^2 \\\\ y = x + 2 \\end{cases}$ — это парабола и прямая.</p>
<p>Найдём пересечения: $x^2 = x + 2 \\Rightarrow x^2 - x - 2 = 0 \\Rightarrow x = 2$ или $x = -1$. Тогда $y = 4$ или $y = 1$.</p>
<p><b>Ответ:</b> $(2;\\ 4)$ и $(-1;\\ 1)$. На графике это две точки пересечения параболы $y = x^2$ и прямой $y = x + 2$.</p>`);
/* INTERACTIVE 1 — графический решатель */
html += `<div class="wg" id="p11-iv1">
<div class="wg-header"><span class="wg-badge">ИНТЕРАКТИВ 1</span><div class="wg-title">Графический решатель</div></div>
<div class="wg-help">Выбери систему ползунком — на графике увидишь оба уравнения и красные точки пересечения с координатами.</div>
<div class="sliders">
<label>Система №<b id="p11-iv1-idx">1</b> / 5<input type="range" id="p11-iv1-slider" min="1" max="5" step="1" value="1"></label>
</div>
<div id="p11-iv1-sys" style="text-align:center;font-size:1.05rem;padding:10px;background:var(--card);border-radius:9px;margin-bottom:10px"></div>
<div style="background:var(--card);border-radius:10px;padding:10px;overflow-x:auto">
<svg id="p11-iv1-svg" viewBox="0 0 400 320" style="width:100%;max-width:520px;height:auto;display:block;margin:0 auto"></svg>
</div>
<div id="p11-iv1-out" style="margin-top:10px;padding:10px 14px;background:var(--sec-acc-soft);border-radius:9px;font-size:.95rem;text-align:center"></div>
</div>`;
/* INTERACTIVE 2 — калькулятор подстановки */
html += `<div class="wg" id="p11-iv2">
<div class="wg-header"><span class="wg-badge">ИНТЕРАКТИВ 2</span><div class="wg-title">Калькулятор подстановки</div></div>
<div class="wg-help">Для системы $\\begin{cases} y = ax + b \\\\ y = x^2 \\end{cases}$ калькулятор найдёт точки пересечения через дискриминант.</div>
<div style="display:flex;gap:10px;align-items:center;flex-wrap:wrap;justify-content:center;margin-bottom:10px">
<span>$a$ =</span><input type="number" id="p11-iv2-a" class="tinp" style="width:80px;text-align:center" value="1">
<span>$b$ =</span><input type="number" id="p11-iv2-b" class="tinp" style="width:80px;text-align:center" value="2">
<button class="btn primary" id="p11-iv2-go">Решить</button>
</div>
<div id="p11-iv2-out" style="padding:12px 14px;background:var(--card);border-radius:9px;text-align:center;font-size:1rem;min-height:48px"></div>
</div>`;
/* INTERACTIVE 3 — сколько решений? */
html += `<div class="wg" id="p11-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">Задача: <b id="p11-iv3-idx">1</b> / 6 &middot; Очки: <b id="p11-iv3-sc">0</b></div>
<div id="p11-iv3-q" style="text-align:center;font-size:1.05rem;padding:14px;background:var(--card);border-radius:9px;margin-bottom:12px;min-height:60px"></div>
<div class="actions" style="justify-content:center">
<button class="btn" data-ans="0" id="p11-iv3-b0">0</button>
<button class="btn primary" data-ans="1" id="p11-iv3-b1">1</button>
<button class="btn" data-ans="2" id="p11-iv3-b2">2</button>
</div>
<div class="feedback" id="p11-iv3-fb"></div>
</div>`;
/* INTERACTIVE 4 — тренажёр решений */
html += `<div class="wg" id="p11-iv4">
<div class="wg-header"><span class="wg-badge">ИНТЕРАКТИВ 4</span><div class="wg-title">Сумма координат всех решений</div></div>
<div class="wg-help">Найди все решения системы и введи <b>сумму всех координат</b> (сложи $x$-и и $y$-и всех точек).</div>
<div class="score-display">Задача: <b id="p11-iv4-idx">1</b> / 5 &middot; Очки: <b id="p11-iv4-sc">0</b></div>
<div id="p11-iv4-q" style="text-align:center;font-size:1.05rem;padding:14px;background:var(--card);border-radius:9px;margin-bottom:10px;min-height:60px"></div>
<div style="display:flex;gap:10px;align-items:center;justify-content:center;flex-wrap:wrap">
<span>Сумма =</span>
<input type="number" id="p11-iv4-inp" class="tinp" style="width:100px;text-align:center" step="1">
<button class="btn primary" id="p11-iv4-go">Проверить</button>
<button class="btn" id="p11-iv4-skip">Пропустить</button>
</div>
<div class="feedback" id="p11-iv4-fb"></div>
</div>`;
box.innerHTML = html + secNav('p10', 'p12') + readButton('p11');
renderMath(box);
/* ===== IV1 wiring — графический решатель ===== */
(function(){
const systems = [
{
sys: '$\\begin{cases} y = x \\\\ y = x^2 \\end{cases}$',
f1: x => x, label1: 'y = x',
f2: x => x*x, label2: 'y = x²',
roots: [[0, 0], [1, 1]]
},
{
sys: '$\\begin{cases} y = x + 1 \\\\ y = x^2 - 1 \\end{cases}$',
f1: x => x + 1, label1: 'y = x + 1',
f2: x => x*x - 1, label2: 'y = x² - 1',
roots: [[-1, 0], [2, 3]]
},
{
sys: '$\\begin{cases} y = 2 \\\\ y = x^2 - 2 \\end{cases}$',
f1: x => 2, label1: 'y = 2',
f2: x => x*x - 2, label2: 'y = x² - 2',
roots: [[-2, 2], [2, 2]]
},
{
sys: '$\\begin{cases} y = -x \\\\ y = x^2 - 2 \\end{cases}$',
f1: x => -x, label1: 'y = -x',
f2: x => x*x - 2, label2: 'y = x² - 2',
roots: [[1, -1], [-2, 2]]
},
{
sys: '$\\begin{cases} y = 4 - x^2 \\\\ y = 0 \\end{cases}$',
f1: x => 4 - x*x, label1: 'y = 4 - x²',
f2: x => 0, label2: 'y = 0',
roots: [[-2, 0], [2, 0]]
}
];
const sl = document.getElementById('p11-iv1-slider');
const idxEl = document.getElementById('p11-iv1-idx');
const sysEl = document.getElementById('p11-iv1-sys');
const svg = document.getElementById('p11-iv1-svg');
const out = document.getElementById('p11-iv1-out');
let bumped = false;
function redraw(){
const idx = (+sl.value) - 1;
const s = systems[idx];
idxEl.textContent = idx + 1;
sysEl.innerHTML = s.sys;
const ax = axes2D(400, 320, 30, -5, 5, -5, 5);
let g = ax.content;
g += plotFunc(s.f1, -5, 5, ax.toX, ax.toY, '#2563eb', 260);
g += plotFunc(s.f2, -5, 5, ax.toX, ax.toY, '#059669', 260);
s.roots.forEach(([rx, ry])=>{
if (rx >= -5 && rx <= 5 && ry >= -5 && ry <= 5){
const px = ax.toX(rx), py = ax.toY(ry);
g += '<circle cx="'+px+'" cy="'+py+'" r="5" fill="#ef4444" stroke="#fff" stroke-width="2"/>';
g += '<text x="'+(px + 8)+'" y="'+(py - 8)+'" font-size="11" fill="#7f1d1d" font-weight="700">('+fmt(rx)+'; '+fmt(ry)+')</text>';
}
});
g += '<g font-family="Inter" font-size="10">';
g += '<rect x="290" y="14" width="100" height="38" rx="6" fill="#fff" stroke="#e5e7eb"/>';
g += '<line x1="298" y1="24" x2="316" y2="24" stroke="#2563eb" stroke-width="2"/>';
g += '<text x="320" y="27" fill="#1e3a8a">'+s.label1+'</text>';
g += '<line x1="298" y1="40" x2="316" y2="40" stroke="#059669" stroke-width="2"/>';
g += '<text x="320" y="43" fill="#065f46">'+s.label2+'</text>';
g += '</g>';
svg.innerHTML = g;
let txt = '<b>Решения системы:</b> ';
txt += s.roots.map(([rx, ry])=>'$(' + fmt(rx) + ';\\ ' + fmt(ry) + ')$').join(', ');
out.innerHTML = txt;
renderMath(sysEl); renderMath(out);
if (!bumped){ bumped = true; bumpProgress('p11', 15); addXp(10, 'p11-iv1'); }
}
sl.addEventListener('input', redraw);
redraw();
})();
/* ===== IV2 wiring — калькулятор подстановки ===== */
(function(){
const aIn = document.getElementById('p11-iv2-a');
const bIn = document.getElementById('p11-iv2-b');
const out = document.getElementById('p11-iv2-out');
let bumped = false;
document.getElementById('p11-iv2-go').addEventListener('click', ()=>{
const a = +aIn.value, b = +bIn.value;
let html = '<div><b>Система:</b> $\\begin{cases} y = ' + a + 'x + (' + b + ') \\\\ y = x^2 \\end{cases}$</div>';
html += '<div style="margin-top:6px">Подстановка: $x^2 = ' + a + 'x + ' + b + ' \\Rightarrow x^2 - ' + a + 'x - (' + b + ') = 0$.</div>';
const D = a*a + 4*b;
html += '<div>Дискриминант: $D = (' + a + ')^2 + 4 \\cdot (' + b + ') = ' + D + '$.</div>';
if (D < 0){
html += '<div style="margin-top:6px;color:var(--fail)"><b>$D < 0$ → решений нет.</b></div>';
} else if (Math.abs(D) < 1e-9){
const x = a / 2;
html += '<div style="margin-top:6px;color:var(--warn)"><b>$D = 0$ → одно решение:</b> $x = ' + fmt(x) + ',\\ y = ' + fmt(x*x) + '$.</div>';
} else {
const sq = Math.sqrt(D);
const x1 = (a + sq) / 2, x2 = (a - sq) / 2;
const y1 = x1*x1, y2 = x2*x2;
html += '<div style="margin-top:6px;color:var(--ok)"><b>$D > 0$ → два решения:</b></div>';
html += '<div>$(' + fmt(+x1.toFixed(4)) + ';\\ ' + fmt(+y1.toFixed(4)) + ')$ и $(' + fmt(+x2.toFixed(4)) + ';\\ ' + fmt(+y2.toFixed(4)) + ')$.</div>';
}
out.innerHTML = html;
renderMath(out);
if (!bumped){ bumped = true; bumpProgress('p11', 15); addXp(10, 'p11-iv2'); }
});
})();
/* ===== IV3 wiring — сколько решений? ===== */
(function(){
const items = [
{ q: '$\\begin{cases} y = x \\\\ y = x^2 \\end{cases}$', ans: 2, hint: '$x = x^2 \\Rightarrow x(x - 1) = 0$. Решения $(0;0)$ и $(1;1)$.' },
{ q: '$\\begin{cases} y = -2 \\\\ y = x^2 \\end{cases}$', ans: 0, hint: '$x^2 = -2$ — нет действительных корней.' },
{ q: '$\\begin{cases} y = 0 \\\\ y = x^2 \\end{cases}$', ans: 1, hint: '$x^2 = 0 \\Rightarrow x = 0$. Одно решение $(0;0)$ — касание.' },
{ q: '$\\begin{cases} y = x + 2 \\\\ y = -x^2 \\end{cases}$', ans: 0, hint: '$-x^2 = x + 2 \\Rightarrow x^2 + x + 2 = 0$. $D = 1 - 8 = -7 < 0$.' },
{ q: '$\\begin{cases} x + y = 4 \\\\ xy = 3 \\end{cases}$', ans: 2, hint: '$x$ и $y$ — корни $t^2 - 4t + 3 = 0$: $t = 1$ или $t = 3$. Точки $(1;3)$ и $(3;1)$.' },
{ q: '$\\begin{cases} y = 3 \\\\ x^2 + y^2 = 25 \\end{cases}$', ans: 2, hint: '$x^2 = 16 \\Rightarrow x = \\pm 4$. Точки $(4;3)$ и $(-4;3)$.' }
];
let i = 0, sc = 0, bumped = false;
const idxEl = document.getElementById('p11-iv3-idx');
const scEl = document.getElementById('p11-iv3-sc');
const qEl = document.getElementById('p11-iv3-q');
const fb = document.getElementById('p11-iv3-fb');
const btns = [document.getElementById('p11-iv3-b0'), document.getElementById('p11-iv3-b1'), document.getElementById('p11-iv3-b2')];
function render(){
idxEl.textContent = Math.min(i + 1, items.length);
scEl.textContent = sc;
if (i >= items.length){
qEl.innerHTML = '<b>Готово!</b> Результат: ' + sc + ' / ' + items.length;
btns.forEach(b=>{ b.disabled = true; b.style.opacity = .5; });
if (!bumped){ bumped = true; bumpProgress('p11', 25); addXp(15, 'p11-iv3'); }
return;
}
qEl.innerHTML = items[i].q;
fb.style.display = 'none';
renderMath(qEl);
}
function answer(v){
if (i >= items.length) return;
const it = items[i];
const ok = (v === it.ans);
if (ok) sc++;
feedback(fb, ok, (ok ? '&#10003; Верно. ' : '&#10007; Неверно (' + it.ans + '). ') + it.hint);
i++;
setTimeout(render, 1200);
}
btns.forEach(b=>b.addEventListener('click', ()=>answer(+b.dataset.ans)));
render();
})();
/* ===== IV4 wiring — сумма координат ===== */
(function(){
const items = [
{ q: '$\\begin{cases} y = x \\\\ y = 2x - 1 \\end{cases}$', ans: 2, hint: 'Решение $(1;\\ 1)$. Сумма $= 1 + 1 = 2$.' },
{ q: '$\\begin{cases} y = x^2 \\\\ y = 4 \\end{cases}$', ans: 8, hint: 'Решения $(2;\\ 4)$ и $(-2;\\ 4)$. Сумма $= 2 + 4 + (-2) + 4 = 8$.' },
{ q: '$\\begin{cases} y = x + 1 \\\\ y = x^2 - 1 \\end{cases}$', ans: 4, hint: '$x^2 - x - 2 = 0 \\Rightarrow x = -1, 2$. Точки $(-1;0),\\ (2;3)$. Сумма $= -1 + 0 + 2 + 3 = 4$.' },
{ q: '$\\begin{cases} x + y = 5 \\\\ x - y = 1 \\end{cases}$', ans: 5, hint: 'Сложение: $2x = 6 \\Rightarrow x = 3,\\ y = 2$. Сумма $= 3 + 2 = 5$.' },
{ q: '$\\begin{cases} y = x^2 \\\\ y = -x + 6 \\end{cases}$', ans: 12, hint: '$x^2 + x - 6 = 0 \\Rightarrow x = 2, -3$. Точки $(2;4),\\ (-3;9)$. Сумма $= 2 + 4 + (-3) + 9 = 12$.' }
];
let i = 0, sc = 0, bumped = false;
const idxEl = document.getElementById('p11-iv4-idx');
const scEl = document.getElementById('p11-iv4-sc');
const qEl = document.getElementById('p11-iv4-q');
const inp = document.getElementById('p11-iv4-inp');
const fb = document.getElementById('p11-iv4-fb');
const goBtn = document.getElementById('p11-iv4-go');
const skipBtn = document.getElementById('p11-iv4-skip');
function render(){
idxEl.textContent = Math.min(i + 1, items.length);
scEl.textContent = sc;
if (i >= items.length){
qEl.innerHTML = '<b>Готово!</b> Результат: ' + sc + ' / ' + items.length;
inp.disabled = true; goBtn.disabled = true; skipBtn.disabled = true;
inp.style.opacity = .5; goBtn.style.opacity = .5; skipBtn.style.opacity = .5;
if (!bumped){ bumped = true; bumpProgress('p11', 25); addXp(15, 'p11-iv4'); }
return;
}
qEl.innerHTML = items[i].q;
inp.value = '';
fb.style.display = 'none';
renderMath(qEl);
}
function check(){
if (i >= items.length) return;
const v = parseFloat(inp.value);
const it = items[i];
if (isNaN(v)){ feedback(fb, false, 'Введи число.'); return; }
const ok = Math.abs(v - it.ans) < 1e-6;
if (ok) sc++;
feedback(fb, ok, (ok ? '&#10003; Верно! ' : '&#10007; Неверно. Сумма $= ' + it.ans + '$. ') + it.hint);
i++;
setTimeout(render, 1300);
}
function skip(){
if (i >= items.length) return;
const it = items[i];
feedback(fb, false, 'Пропущено. Сумма $= ' + it.ans + '$. ' + it.hint);
i++;
setTimeout(render, 1300);
}
goBtn.addEventListener('click', check);
skipBtn.addEventListener('click', skip);
inp.addEventListener('keydown', e=>{ if (e.key === 'Enter') check(); });
render();
})();
wireReadBtn('p11');
}
function buildP12(){
const box = document.getElementById('p12-body');
let html = '';
html += makeCard('theory', 'Длина отрезка (расстояние между точками)', '12.1', `
<p>Для двух точек $A(x_1;\\ y_1)$ и $B(x_2;\\ y_2)$ длина отрезка $AB$ (= расстояние между точками) равна:</p>
<p style="text-align:center;font-size:1.05rem">$|AB| = \\sqrt{(x_2 - x_1)^2 + (y_2 - y_1)^2}$</p>
<p>Это прямое следствие <b>теоремы Пифагора</b>: разности координат — катеты прямоугольного треугольника, а отрезок $AB$ — его гипотенуза.</p>
<p><b>Пример.</b> $A(1;\\ 2),\\ B(4;\\ 6)$:
$|AB| = \\sqrt{(4-1)^2 + (6-2)^2} = \\sqrt{9 + 16} = \\sqrt{25} = 5$.</p>
<details class="spoiler"><summary>Почему именно квадраты?</summary><div class="spoiler-body">
Если построить из $A$ горизонтальный, а из $B$ вертикальный отрезок до их пересечения, получится прямоугольный треугольник с катетами $|x_2 - x_1|$ и $|y_2 - y_1|$. По Пифагору квадрат гипотенузы $|AB|^2 = (x_2-x_1)^2 + (y_2-y_1)^2$, откуда и формула.
</div></details>`);
html += makeCard('rule', 'Уравнение окружности', '12.2', `
<p>Окружность с центром $C(a;\\ b)$ и радиусом $R$ задаётся уравнением:</p>
<p style="text-align:center;font-size:1.1rem">$(x - a)^2 + (y - b)^2 = R^2$</p>
<p>Геометрический смысл: <b>все точки $(x;\\ y)$ плоскости, удовлетворяющие этому равенству, лежат на окружности</b>, и наоборот. Это запись свойства «расстояние от $(x;y)$ до центра $C$ равно $R$»:
$\\sqrt{(x - a)^2 + (y - b)^2} = R$, после возведения в квадрат получаем уравнение.</p>
<p>Числа $a$ и $b$ — координаты центра, $R > 0$ — радиус.</p>`);
html += makeCard('example', 'Особые случаи и примеры', '12.3', `
<p><b>1. Центр в начале координат.</b> При $a = 0,\\ b = 0$ уравнение упрощается:
$x^2 + y^2 = R^2$. Например, $x^2 + y^2 = 9$ — окружность с центром $(0;0)$ и радиусом $R = 3$.</p>
<p><b>2. Через центр и точку на окружности.</b> Радиус — расстояние от центра до этой точки.
<i>Пример.</i> Центр $C(1;\\ 2)$, точка $M(4;\\ 6)$ лежит на окружности. Тогда
$R = \\sqrt{(4-1)^2 + (6-2)^2} = 5$, и уравнение: $(x - 1)^2 + (y - 2)^2 = 25$.</p>
<p><b>3. По заданным центру и радиусу.</b> Уравнение окружности с центром $(2;\\ -1)$ и радиусом $R = 4$:
$(x - 2)^2 + (y + 1)^2 = 16$.</p>`);
/* INTERACTIVE 1 — Окружность-конструктор */
html += `<div class="wg" id="p12-iv1">
<div class="wg-header"><span class="wg-badge">ИНТЕРАКТИВ 1</span><div class="wg-title">Окружность-конструктор</div></div>
<div class="wg-help">Двигай ползунки центра $a, b$ и радиуса $R$ — окружность перерисуется, а уравнение под графиком подстроится.</div>
<div class="sliders">
<label>центр $a$ =<b id="p12-iv1-aL">0</b><input type="range" id="p12-iv1-a" min="-5" max="5" step="1" value="0"></label>
<label>центр $b$ =<b id="p12-iv1-bL">0</b><input type="range" id="p12-iv1-b" min="-5" max="5" step="1" value="0"></label>
<label>радиус $R$ =<b id="p12-iv1-rL">3</b><input type="range" id="p12-iv1-r" min="1" max="6" step="1" value="3"></label>
</div>
<div style="background:var(--card);border-radius:10px;padding:10px;overflow-x:auto">
<svg id="p12-iv1-svg" viewBox="0 0 400 400" style="width:100%;max-width:480px;height:auto;display:block;margin:0 auto"></svg>
</div>
<div id="p12-iv1-eq" style="margin-top:10px;padding:10px 14px;background:var(--sec-acc-soft);border-radius:9px;font-size:1rem;text-align:center"></div>
</div>`;
/* INTERACTIVE 2 — Калькулятор расстояния */
html += `<div class="wg" id="p12-iv2">
<div class="wg-header"><span class="wg-badge">ИНТЕРАКТИВ 2</span><div class="wg-title">Калькулятор расстояния $|AB|$</div></div>
<div class="wg-help">Введи целые координаты двух точек $A(x_1;y_1)$ и $B(x_2;y_2)$ — калькулятор покажет подробное вычисление и нарисует отрезок.</div>
<div style="display:flex;gap:10px;align-items:center;flex-wrap:wrap;justify-content:center;margin-bottom:10px">
<span>$A(\\ $</span>
<input type="number" id="p12-iv2-x1" class="tinp" style="width:70px;text-align:center" value="1">
<span>;</span>
<input type="number" id="p12-iv2-y1" class="tinp" style="width:70px;text-align:center" value="2">
<span>$\\ )$</span>
<span style="margin:0 6px">$B(\\ $</span>
<input type="number" id="p12-iv2-x2" class="tinp" style="width:70px;text-align:center" value="4">
<span>;</span>
<input type="number" id="p12-iv2-y2" class="tinp" style="width:70px;text-align:center" value="6">
<span>$\\ )$</span>
<button class="btn primary" id="p12-iv2-go">Вычислить</button>
</div>
<div id="p12-iv2-out" style="padding:12px 14px;background:var(--card);border-radius:9px;text-align:center;font-size:1rem;min-height:48px"></div>
<div style="background:var(--card);border-radius:10px;padding:8px;margin-top:10px;overflow-x:auto">
<svg id="p12-iv2-svg" viewBox="0 0 360 280" style="width:100%;max-width:420px;height:auto;display:block;margin:0 auto"></svg>
</div>
</div>`;
/* INTERACTIVE 3 — Принадлежит ли точка окружности? */
html += `<div class="wg" id="p12-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">Задача: <b id="p12-iv3-idx">1</b> / 6 &middot; Очки: <b id="p12-iv3-sc">0</b></div>
<div id="p12-iv3-q" style="text-align:center;font-size:1.05rem;padding:14px;background:var(--card);border-radius:9px;margin-bottom:12px;min-height:60px"></div>
<div class="actions" style="justify-content:center">
<button class="btn primary" id="p12-iv3-ok">Лежит на окружности</button>
<button class="btn" id="p12-iv3-bad">Не лежит</button>
</div>
<div class="feedback" id="p12-iv3-fb"></div>
</div>`;
/* INTERACTIVE 4 — Найди радиус или координату */
html += `<div class="wg" id="p12-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">Задача: <b id="p12-iv4-idx">1</b> / 6 &middot; Очки: <b id="p12-iv4-sc">0</b></div>
<div id="p12-iv4-q" style="text-align:center;font-size:1.05rem;padding:14px;background:var(--card);border-radius:9px;margin-bottom:10px;min-height:60px"></div>
<div style="display:flex;gap:10px;align-items:center;justify-content:center;flex-wrap:wrap">
<span>Ответ =</span>
<input type="number" id="p12-iv4-inp" class="tinp" style="width:100px;text-align:center" step="1">
<button class="btn primary" id="p12-iv4-go">Проверить</button>
<button class="btn" id="p12-iv4-skip">Пропустить</button>
</div>
<div class="feedback" id="p12-iv4-fb"></div>
</div>`;
box.innerHTML = html + secNav('p11', 'p13') + readButton('p12');
renderMath(box);
/* ===== IV1 wiring — Окружность-конструктор ===== */
(function(){
const aIn = document.getElementById('p12-iv1-a');
const bIn = document.getElementById('p12-iv1-b');
const rIn = document.getElementById('p12-iv1-r');
const aL = document.getElementById('p12-iv1-aL');
const bL = document.getElementById('p12-iv1-bL');
const rL = document.getElementById('p12-iv1-rL');
const svg = document.getElementById('p12-iv1-svg');
const eqEl = document.getElementById('p12-iv1-eq');
let bumped = false;
function redraw(){
const a = +aIn.value, b = +bIn.value, R = +rIn.value;
aL.textContent = a; bL.textContent = b; rL.textContent = R;
const ax = axes2D(400, 400, 30, -7, 7, -7, 7);
let g = ax.content;
const cx = ax.toX(a), cy = ax.toY(b), rPx = R * ax.ux;
g += '<circle cx="'+cx+'" cy="'+cy+'" r="'+rPx+'" fill="rgba(37,99,235,.10)" stroke="#2563eb" stroke-width="2.2"/>';
const ex = ax.toX(a + R), ey = cy;
g += '<line x1="'+cx+'" y1="'+cy+'" x2="'+ex+'" y2="'+ey+'" stroke="#db2777" stroke-width="2" stroke-dasharray="4 3"/>';
g += '<text x="'+((cx + ex)/2)+'" y="'+(cy - 6)+'" font-size="11" fill="#9d174d" font-weight="700" text-anchor="middle">R = '+R+'</text>';
g += '<circle cx="'+cx+'" cy="'+cy+'" r="4.5" fill="#ef4444" stroke="#fff" stroke-width="2"/>';
g += '<text x="'+(cx + 8)+'" y="'+(cy - 8)+'" font-size="11" fill="#7f1d1d" font-weight="700">C('+a+'; '+b+')</text>';
svg.innerHTML = g;
const aS = a >= 0 ? '- ' + a : '+ ' + (-a);
const bS = b >= 0 ? '- ' + b : '+ ' + (-b);
eqEl.innerHTML = '$(x \\, ' + aS + ')^2 + (y \\, ' + bS + ')^2 = ' + (R*R) + '$';
renderMath(eqEl);
if (!bumped){ bumped = true; bumpProgress('p12', 15); addXp(10, 'p12-iv1'); }
}
[aIn, bIn, rIn].forEach(el => el.addEventListener('input', redraw));
redraw();
})();
/* ===== IV2 wiring — Калькулятор расстояния ===== */
(function(){
const x1In = document.getElementById('p12-iv2-x1');
const y1In = document.getElementById('p12-iv2-y1');
const x2In = document.getElementById('p12-iv2-x2');
const y2In = document.getElementById('p12-iv2-y2');
const out = document.getElementById('p12-iv2-out');
const svg = document.getElementById('p12-iv2-svg');
let bumped = false;
function compute(){
const x1 = +x1In.value, y1 = +y1In.value, x2 = +x2In.value, y2 = +y2In.value;
const dx = x2 - x1, dy = y2 - y1;
const sq = dx*dx + dy*dy;
const d = Math.sqrt(sq);
const dStr = Number.isInteger(d) ? String(d) : d.toFixed(2);
let h = '<div>$|AB| = \\sqrt{(' + x2 + ' - ' + x1 + ')^2 + (' + y2 + ' - ' + y1 + ')^2}$</div>';
h += '<div style="margin-top:6px">$= \\sqrt{(' + dx + ')^2 + (' + dy + ')^2} = \\sqrt{' + (dx*dx) + ' + ' + (dy*dy) + '} = \\sqrt{' + sq + '}$</div>';
h += '<div style="margin-top:6px;color:var(--ok)"><b>$|AB| = ' + dStr + '$</b></div>';
out.innerHTML = h;
renderMath(out);
const lo = Math.min(x1, y1, x2, y2, 0) - 1;
const hi = Math.max(x1, y1, x2, y2, 0) + 1;
const ax = axes2D(360, 280, 26, Math.floor(lo), Math.ceil(hi), Math.floor(lo), Math.ceil(hi));
let g = ax.content;
const ax1 = ax.toX(x1), ay1 = ax.toY(y1), ax2 = ax.toX(x2), ay2 = ax.toY(y2);
g += '<line x1="'+ax1+'" y1="'+ay1+'" x2="'+ax2+'" y2="'+ay2+'" stroke="#7c3aed" stroke-width="2.4"/>';
g += '<circle cx="'+ax1+'" cy="'+ay1+'" r="4" fill="#ef4444" stroke="#fff" stroke-width="2"/>';
g += '<text x="'+(ax1 + 7)+'" y="'+(ay1 - 7)+'" font-size="11" fill="#7f1d1d" font-weight="700">A('+x1+';'+y1+')</text>';
g += '<circle cx="'+ax2+'" cy="'+ay2+'" r="4" fill="#10b981" stroke="#fff" stroke-width="2"/>';
g += '<text x="'+(ax2 + 7)+'" y="'+(ay2 - 7)+'" font-size="11" fill="#065f46" font-weight="700">B('+x2+';'+y2+')</text>';
svg.innerHTML = g;
if (!bumped){ bumped = true; bumpProgress('p12', 15); addXp(10, 'p12-iv2'); }
}
document.getElementById('p12-iv2-go').addEventListener('click', compute);
compute();
})();
/* ===== IV3 wiring — Точка на окружности? ===== */
(function(){
const items = [
{ q: 'Окружность $x^2 + y^2 = 25$, точка $(3;\\ 4)$.', ans: true, hint: '$3^2 + 4^2 = 9 + 16 = 25$ — равно $R^2$. Точка лежит.' },
{ q: 'Окружность $x^2 + y^2 = 25$, точка $(0;\\ 5)$.', ans: true, hint: '$0 + 25 = 25$. Точка лежит.' },
{ q: 'Окружность $x^2 + y^2 = 25$, точка $(2;\\ 3)$.', ans: false, hint: '$4 + 9 = 13 \\ne 25$. Точка не лежит.' },
{ q: 'Окружность $(x - 1)^2 + y^2 = 4$, точка $(3;\\ 0)$.', ans: true, hint: '$(3-1)^2 + 0 = 4$. Точка лежит.' },
{ q: 'Окружность $(x - 2)^2 + (y + 1)^2 = 9$, точка $(2;\\ 2)$.', ans: true, hint: '$0 + (2+1)^2 = 9$. Точка лежит.' },
{ q: 'Окружность $x^2 + (y - 3)^2 = 16$, точка $(4;\\ 3)$.', ans: true, hint: '$16 + 0 = 16$. Точка лежит.' }
];
let i = 0, sc = 0, bumped = false;
const idxEl = document.getElementById('p12-iv3-idx');
const scEl = document.getElementById('p12-iv3-sc');
const qEl = document.getElementById('p12-iv3-q');
const fb = document.getElementById('p12-iv3-fb');
const okBtn = document.getElementById('p12-iv3-ok');
const badBtn = document.getElementById('p12-iv3-bad');
function render(){
idxEl.textContent = Math.min(i + 1, items.length);
scEl.textContent = sc;
if (i >= items.length){
qEl.innerHTML = '<b>Готово!</b> Результат: ' + sc + ' / ' + items.length;
okBtn.disabled = true; badBtn.disabled = true;
okBtn.style.opacity = .5; badBtn.style.opacity = .5;
if (!bumped){ bumped = true; bumpProgress('p12', 25); addXp(15, 'p12-iv3'); }
return;
}
qEl.innerHTML = items[i].q;
fb.style.display = 'none';
renderMath(qEl);
}
function answer(v){
if (i >= items.length) return;
const it = items[i];
const ok = (v === it.ans);
if (ok) sc++;
feedback(fb, ok, (ok ? '&#10003; Верно. ' : '&#10007; Неверно. ') + it.hint);
i++;
setTimeout(render, 1100);
}
okBtn.addEventListener('click', ()=>answer(true));
badBtn.addEventListener('click', ()=>answer(false));
render();
})();
/* ===== IV4 wiring — Тренажёр: радиус / центр / длина ===== */
(function(){
const items = [
{ q: 'Уравнение окружности: $(x - 3)^2 + (y - 5)^2 = 49$. Найди <b>радиус</b> $R$.', ans: 7, hint: '$R^2 = 49 \\Rightarrow R = 7$.' },
{ q: 'Уравнение окружности: $(x + 2)^2 + (y - 4)^2 = 16$. Найди координату центра <b>$a$</b>.', ans: -2, hint: 'Сравни с $(x - a)^2$: $x + 2 = x - (-2) \\Rightarrow a = -2$.' },
{ q: 'Уравнение окружности: $x^2 + (y + 1)^2 = 100$. Найди <b>радиус</b> $R$.', ans: 10, hint: '$R^2 = 100 \\Rightarrow R = 10$.' },
{ q: 'Найди длину отрезка $AB$, где $A(0;\\ 0),\\ B(6;\\ 8)$.', ans: 10, hint: '$|AB| = \\sqrt{36 + 64} = \\sqrt{100} = 10$.' },
{ q: 'Найди длину отрезка $AB$, где $A(-2;\\ 1),\\ B(4;\\ 1)$.', ans: 6, hint: 'Точки на одной горизонтали: $|AB| = |4 - (-2)| = 6$.' },
{ q: 'Окружность $(x - 3)^2 + y^2 = 25$ пересекает ось $Ox$ в двух точках. Найди <b>меньшую</b> координату $x$ точки пересечения.', ans: -2, hint: 'При $y = 0$: $(x-3)^2 = 25 \\Rightarrow x - 3 = \\pm 5 \\Rightarrow x = 8$ или $x = -2$. Меньшая = $-2$.' }
];
let i = 0, sc = 0, bumped = false;
const idxEl = document.getElementById('p12-iv4-idx');
const scEl = document.getElementById('p12-iv4-sc');
const qEl = document.getElementById('p12-iv4-q');
const inp = document.getElementById('p12-iv4-inp');
const fb = document.getElementById('p12-iv4-fb');
const goBtn = document.getElementById('p12-iv4-go');
const skipBtn = document.getElementById('p12-iv4-skip');
function render(){
idxEl.textContent = Math.min(i + 1, items.length);
scEl.textContent = sc;
if (i >= items.length){
qEl.innerHTML = '<b>Готово!</b> Результат: ' + sc + ' / ' + items.length;
inp.disabled = true; goBtn.disabled = true; skipBtn.disabled = true;
inp.style.opacity = .5; goBtn.style.opacity = .5; skipBtn.style.opacity = .5;
if (!bumped){ bumped = true; bumpProgress('p12', 25); addXp(15, 'p12-iv4'); achievement('p12_done'); }
return;
}
qEl.innerHTML = items[i].q;
inp.value = '';
fb.style.display = 'none';
renderMath(qEl);
}
function check(){
if (i >= items.length) return;
const v = parseFloat(inp.value);
const it = items[i];
if (isNaN(v)){ feedback(fb, false, 'Введи число.'); return; }
const ok = Math.abs(v - it.ans) < 1e-6;
if (ok) sc++;
feedback(fb, ok, (ok ? '&#10003; Верно! ' : '&#10007; Неверно. Ответ: $' + it.ans + '$. ') + it.hint);
i++;
setTimeout(render, 1200);
}
function skip(){
if (i >= items.length) return;
const it = items[i];
feedback(fb, false, 'Пропущено. Ответ: $' + it.ans + '$. ' + it.hint);
i++;
setTimeout(render, 1200);
}
goBtn.addEventListener('click', check);
skipBtn.addEventListener('click', skip);
inp.addEventListener('keydown', e => { if (e.key === 'Enter') check(); });
render();
})();
wireReadBtn('p12');
}
function buildP13(){
const box = document.getElementById('p13-body');
let html = '';
html += makeCard('theory', 'Метод интервалов', '13.1', `
<p>Для неравенств вида $\\dfrac{P(x)}{Q(x)} \\, [><] \\, 0$ применяют <b>метод интервалов</b>:</p>
<ol style="padding-left:22px;line-height:1.85">
<li>Найти все корни уравнений $P(x) = 0$ <i>(нули числителя)</i> и $Q(x) = 0$ <i>(нули знаменателя)</i>.</li>
<li>Отметить корни на числовой прямой. Корни знаменателя — <b>всегда выколоты</b> (в них дробь не определена). Корни числителя — закрашены при нестрогом неравенстве ($\\le,\\ \\ge$) и выколоты при строгом ($<,\\ >$).</li>
<li>На каждом интервале определить <b>знак</b> выражения (например, подставив пробное число).</li>
<li>Записать ответ — объединение интервалов с нужным знаком, учитывая закрашенные/выколотые точки.</li>
</ol>`);
html += makeCard('rule', 'Правило знаков (чередование)', '13.2', `
<p>Если все корни <b>простые</b> (степени 1), знаки на интервалах <b>чередуются</b>: $\\ldots\\,+\\,-\\,+\\,-\\,\\ldots$ Достаточно определить знак на одном интервале (обычно крайнем правом) — остальные восстановятся.</p>
<p>Если корень с <b>чётной кратностью</b> (например, $(x - 2)^2$) — при переходе через него знак <b>не меняется</b>.</p>
<p>На крайнем правом интервале знак удобно проверить, подставив очень большое $x$ — обычно знак совпадает со знаком старшего коэффициента в числителе $\\div$ знаменателя.</p>`);
html += makeCard('example', 'Пример пошагово', '13.3', `
<p><b>Решить:</b> $\\dfrac{x - 1}{x + 2} \\le 0$.</p>
<p><b>1.</b> Корни: $x = 1$ (числитель, закрашен — неравенство нестрогое) и $x = -2$ (знаменатель, всегда выколота).</p>
<p><b>2.</b> На числовой прямой: $\\ldots \\;[-]\\; \\circ_{-2} \\;[+]\\; \\bullet_{1} \\;[-]\\; \\ldots$</p>
<p><b>3.</b> Проверка знака при $x = 5$: $\\dfrac{4}{7} > 0$ — крайний правый интервал «$+$». Значит, чередование такое, как показано.</p>
<p><b>4.</b> Нужен $\\le 0$ — берём «$-$»-интервалы. Слева от $-2$ — но $x = -2$ выколота. Справа от $-2$ — «$+$», пропускаем. Между $-2$ и $1$ — это «$+$»-интервал? Перепроверим: при $x = 0$: $\\dfrac{-1}{2} < 0$ — да, «$-$». Берём $(-2;\\ 1]$.</p>
<p style="color:var(--ok);font-weight:700">Ответ: $x \\in (-2;\\ 1]$.</p>`);
/* INTERACTIVE 1 — Числовая прямая знаков */
html += `<div class="wg" id="p13-iv1">
<div class="wg-header"><span class="wg-badge">ИНТЕРАКТИВ 1</span><div class="wg-title">Числовая прямая знаков</div></div>
<div class="wg-help">Выбери неравенство ползунком — на числовой прямой увидишь корни (закрашенные/выколотые) и знаки на интервалах. Внизу — ответ.</div>
<div class="sliders">
<label>Задача №<b id="p13-iv1-idx">1</b> / 5<input type="range" id="p13-iv1-slider" min="1" max="5" step="1" value="1"></label>
</div>
<div id="p13-iv1-ineq" style="text-align:center;font-size:1.1rem;padding:10px;background:var(--card);border-radius:9px;margin-bottom:10px"></div>
<div style="background:var(--card);border-radius:10px;padding:10px;overflow-x:auto">
<svg id="p13-iv1-svg" viewBox="0 0 600 120" style="width:100%;max-width:680px;height:auto;display:block;margin:0 auto"></svg>
</div>
<div id="p13-iv1-out" style="margin-top:10px;padding:10px 14px;background:var(--sec-acc-soft);border-radius:9px;font-size:.98rem;text-align:center"></div>
</div>`;
/* INTERACTIVE 2 — Закрашена или выколота? (DnD) */
html += `<div class="wg" id="p13-iv2">
<div class="wg-header"><span class="wg-badge">ИНТЕРАКТИВ 2</span><div class="wg-title">Закрашена или выколота?</div></div>
<div class="wg-help">Перетащи каждую карточку «корень в неравенстве» в нужную колонку. Корни числителя при нестрогом неравенстве — закрашены ($\\bullet$); корни знаменателя и любые корни при строгом неравенстве — выколоты ($\\circ$).</div>
<div id="p13-iv2-pool"></div>
<div style="display:grid;grid-template-columns:1fr 1fr;gap:12px">
<div class="drop-box"><h5>Закрашена &#9679;</h5><div class="drop-items" data-cat="full"></div></div>
<div class="drop-box"><h5>Выколота &#9675;</h5><div class="drop-items" data-cat="empty"></div></div>
</div>
<div class="actions" style="justify-content:center;margin-top:12px">
<button class="btn primary" id="p13-iv2-check">Проверить</button>
<button class="btn" id="p13-iv2-reset">Сбросить</button>
</div>
<div class="feedback" id="p13-iv2-fb"></div>
</div>`;
/* INTERACTIVE 3 — Сколько целых решений? */
html += `<div class="wg" id="p13-iv3">
<div class="wg-header"><span class="wg-badge">ИНТЕРАКТИВ 3</span><div class="wg-title">Сколько целых $x \\in [-5;5]$ — решения?</div></div>
<div class="wg-help">Для каждого неравенства подсчитай, сколько целых $x$ из отрезка $[-5;\\ 5]$ ему удовлетворяют.</div>
<div class="score-display">Задача: <b id="p13-iv3-idx">1</b> / 6 &middot; Очки: <b id="p13-iv3-sc">0</b></div>
<div id="p13-iv3-q" style="text-align:center;font-size:1.05rem;padding:14px;background:var(--card);border-radius:9px;margin-bottom:10px;min-height:60px"></div>
<div style="display:flex;gap:10px;align-items:center;justify-content:center;flex-wrap:wrap">
<span>Количество =</span>
<input type="number" id="p13-iv3-inp" class="tinp" style="width:90px;text-align:center" step="1">
<button class="btn primary" id="p13-iv3-go">Проверить</button>
<button class="btn" id="p13-iv3-skip">Пропустить</button>
</div>
<div class="feedback" id="p13-iv3-fb"></div>
</div>`;
/* INTERACTIVE 4 — Тренажёр интервалов (сумма концов) */
html += `<div class="wg" id="p13-iv4">
<div class="wg-header"><span class="wg-badge">ИНТЕРАКТИВ 4</span><div class="wg-title">Сумма концов интервалов</div></div>
<div class="wg-help">Реши неравенство, запиши ответ в виде объединения интервалов, и введи <b>сумму всех конечных концов</b> (бесконечности не учитываются).</div>
<div class="score-display">Задача: <b id="p13-iv4-idx">1</b> / 6 &middot; Очки: <b id="p13-iv4-sc">0</b></div>
<div id="p13-iv4-q" style="text-align:center;font-size:1.05rem;padding:14px;background:var(--card);border-radius:9px;margin-bottom:10px;min-height:60px"></div>
<div style="display:flex;gap:10px;align-items:center;justify-content:center;flex-wrap:wrap">
<span>Сумма =</span>
<input type="number" id="p13-iv4-inp" class="tinp" style="width:100px;text-align:center" step="1">
<button class="btn primary" id="p13-iv4-go">Проверить</button>
<button class="btn" id="p13-iv4-skip">Пропустить</button>
</div>
<div class="feedback" id="p13-iv4-fb"></div>
</div>`;
box.innerHTML = html + secNav('p12', 'final3') + readButton('p13');
renderMath(box);
/* ===== IV1 wiring — Числовая прямая ===== */
(function(){
const tasks = [
{
ineq: '$\\dfrac{x}{x - 3} > 0$',
points: [{x:0, full:false}, {x:3, full:false}],
signs: [{from:-8, to:0, s:'+'}, {from:0, to:3, s:'-'}, {from:3, to:8, s:'+'}],
answer: '$(-\\infty;\\ 0) \\cup (3;\\ +\\infty)$'
},
{
ineq: '$\\dfrac{x - 2}{x + 1} \\le 0$',
points: [{x:-1, full:false}, {x:2, full:true}],
signs: [{from:-8, to:-1, s:'+'}, {from:-1, to:2, s:'-'}, {from:2, to:8, s:'+'}],
answer: '$(-1;\\ 2]$'
},
{
ineq: '$(x - 1)(x - 3) > 0$',
points: [{x:1, full:false}, {x:3, full:false}],
signs: [{from:-8, to:1, s:'+'}, {from:1, to:3, s:'-'}, {from:3, to:8, s:'+'}],
answer: '$(-\\infty;\\ 1) \\cup (3;\\ +\\infty)$'
},
{
ineq: '$\\dfrac{1}{x^2 - 4} < 0$',
points: [{x:-2, full:false}, {x:2, full:false}],
signs: [{from:-8, to:-2, s:'+'}, {from:-2, to:2, s:'-'}, {from:2, to:8, s:'+'}],
answer: '$(-2;\\ 2)$'
},
{
ineq: '$\\dfrac{x + 2}{(x - 1)(x + 1)} \\ge 0$',
points: [{x:-2, full:true}, {x:-1, full:false}, {x:1, full:false}],
signs: [{from:-8, to:-2, s:'-'}, {from:-2, to:-1, s:'+'}, {from:-1, to:1, s:'-'}, {from:1, to:8, s:'+'}],
answer: '$[-2;\\ -1) \\cup (1;\\ +\\infty)$'
}
];
const sl = document.getElementById('p13-iv1-slider');
const idxEl = document.getElementById('p13-iv1-idx');
const ineqEl = document.getElementById('p13-iv1-ineq');
const svg = document.getElementById('p13-iv1-svg');
const out = document.getElementById('p13-iv1-out');
let bumped = false;
function draw(){
const idx = (+sl.value) - 1;
const t = tasks[idx];
idxEl.textContent = idx + 1;
ineqEl.innerHTML = t.ineq;
const W = 600, H = 120, padX = 30, y0 = 70;
const xmin = -8, xmax = 8;
const toX = v => padX + (v - xmin) * (W - 2*padX) / (xmax - xmin);
let g = '';
g += '<line x1="'+padX+'" y1="'+y0+'" x2="'+(W - padX)+'" y2="'+y0+'" stroke="#0f172a" stroke-width="2"/>';
g += '<polygon points="'+(W-padX-10)+','+(y0-5)+' '+(W-padX)+','+y0+' '+(W-padX-10)+','+(y0+5)+'" fill="#0f172a"/>';
for (let x = xmin + 1; x <= xmax - 1; x++){
g += '<line x1="'+toX(x)+'" y1="'+(y0-4)+'" x2="'+toX(x)+'" y2="'+(y0+4)+'" stroke="#94a3b8" stroke-width="1"/>';
g += '<text x="'+toX(x)+'" y="'+(y0+18)+'" font-size="10" fill="#64748b" text-anchor="middle">'+x+'</text>';
}
t.signs.forEach(s => {
const cx = (toX(s.from) + toX(s.to)) / 2;
const col = s.s === '+' ? '#10b981' : '#ef4444';
g += '<text x="'+cx+'" y="'+(y0-14)+'" font-size="20" font-weight="800" fill="'+col+'" text-anchor="middle">'+s.s+'</text>';
});
t.points.forEach(p => {
const px = toX(p.x);
if (p.full){
g += '<circle cx="'+px+'" cy="'+y0+'" r="6" fill="#0f172a" stroke="#0f172a" stroke-width="2"/>';
} else {
g += '<circle cx="'+px+'" cy="'+y0+'" r="6" fill="#fff" stroke="#0f172a" stroke-width="2"/>';
}
g += '<text x="'+px+'" y="'+(y0+34)+'" font-size="11" fill="#0f172a" font-weight="700" text-anchor="middle">'+p.x+'</text>';
});
svg.innerHTML = g;
out.innerHTML = '<b>Ответ:</b> $x \\in $ ' + t.answer;
renderMath(ineqEl); renderMath(out);
if (!bumped){ bumped = true; bumpProgress('p13', 15); addXp(10, 'p13-iv1'); }
}
sl.addEventListener('input', draw);
draw();
})();
/* ===== IV2 wiring — DnD: закрашена/выколота ===== */
(function(){
const items = [
{ id:'a', html:'$x = 0$ в $\\dfrac{x}{x - 3} \\ge 0$', cat:'full' },
{ id:'b', html:'$x = 3$ в $\\dfrac{x}{x - 3} \\ge 0$', cat:'empty' },
{ id:'c', html:'$x = 1$ в $\\dfrac{x - 1}{x + 2} > 0$', cat:'empty' },
{ id:'d', html:'$x = -2$ в $\\dfrac{x - 1}{x + 2} > 0$', cat:'empty' },
{ id:'e', html:'$x = 5$ в $(x - 5)(x + 1) \\ge 0$', cat:'full' },
{ id:'f', html:'$x = -3$ в $(x + 3)^2 > 0$', cat:'empty' }
];
const sorter = setupSorter({
poolId: 'p13-iv2-pool',
scopeSelector: '#p13-iv2',
cats: ['full', 'empty'],
items: items
});
let bumped = false;
document.getElementById('p13-iv2-check').addEventListener('click', () => {
const fb = document.getElementById('p13-iv2-fb');
const total = items.length;
let correct = 0, placed = 0;
items.forEach(it => { if (sorter.placed[it.id]){ placed++; if (sorter.placed[it.id] === it.cat) correct++; } });
if (placed < total){
feedback(fb, false, 'Размещены не все: ' + placed + ' / ' + total + '.');
return;
}
const ok = (correct === total);
feedback(fb, ok, ok ? '&#10003; Все верно! ' + correct + ' / ' + total : '&#10007; Правильно: ' + correct + ' / ' + total);
if (ok && !bumped){ bumped = true; bumpProgress('p13', 15); addXp(10, 'p13-iv2'); }
});
document.getElementById('p13-iv2-reset').addEventListener('click', () => {
sorter.reset();
const fb = document.getElementById('p13-iv2-fb'); fb.style.display = 'none';
});
})();
/* ===== IV3 wiring — Сколько целых решений? ===== */
(function(){
const items = [
{ q: '$\\dfrac{x}{x - 2} > 0$', ans: 8, hint: 'Решение $(-\\infty;0) \\cup (2;+\\infty)$. Целые из $[-5;5]$: $\\{-5,-4,-3,-2,-1,3,4,5\\}$ — 8 шт.' },
{ q: '$\\dfrac{1}{x + 1} < 0$', ans: 4, hint: 'Решение $x < -1$. Целые из $[-5;5]$: $\\{-5,-4,-3,-2\\}$ — 4 шт.' },
{ q: '$\\dfrac{x - 2}{x + 3} \\le 0$', ans: 5, hint: 'Решение $(-3;\\ 2]$. Целые: $\\{-2,-1,0,1,2\\}$ — 5 шт.' },
{ q: '$(x - 1)(x + 1) > 0$', ans: 8, hint: 'Решение $|x| > 1$. Целые из $[-5;5]$: $\\{-5,-4,-3,-2,2,3,4,5\\}$ — 8 шт.' },
{ q: '$\\dfrac{x}{x^2 + 1} \\ge 0$', ans: 6, hint: 'Знаменатель $> 0$, значит $x \\ge 0$. Целые: $\\{0,1,2,3,4,5\\}$ — 6 шт.' },
{ q: '$\\dfrac{1}{x^2 - 4} > 0$', ans: 6, hint: 'Решение $|x| > 2$. Целые из $[-5;5]$: $\\{-5,-4,-3,3,4,5\\}$ — 6 шт.' }
];
let i = 0, sc = 0, bumped = false;
const idxEl = document.getElementById('p13-iv3-idx');
const scEl = document.getElementById('p13-iv3-sc');
const qEl = document.getElementById('p13-iv3-q');
const inp = document.getElementById('p13-iv3-inp');
const fb = document.getElementById('p13-iv3-fb');
const goBtn = document.getElementById('p13-iv3-go');
const skipBtn = document.getElementById('p13-iv3-skip');
function render(){
idxEl.textContent = Math.min(i + 1, items.length);
scEl.textContent = sc;
if (i >= items.length){
qEl.innerHTML = '<b>Готово!</b> Результат: ' + sc + ' / ' + items.length;
inp.disabled = true; goBtn.disabled = true; skipBtn.disabled = true;
inp.style.opacity = .5; goBtn.style.opacity = .5; skipBtn.style.opacity = .5;
if (!bumped){ bumped = true; bumpProgress('p13', 25); addXp(15, 'p13-iv3'); }
return;
}
qEl.innerHTML = items[i].q;
inp.value = '';
fb.style.display = 'none';
renderMath(qEl);
}
function check(){
if (i >= items.length) return;
const v = parseFloat(inp.value);
const it = items[i];
if (isNaN(v)){ feedback(fb, false, 'Введи число.'); return; }
const ok = Math.abs(v - it.ans) < 1e-6;
if (ok) sc++;
feedback(fb, ok, (ok ? '&#10003; Верно! ' : '&#10007; Неверно. Ответ: ' + it.ans + '. ') + it.hint);
i++;
setTimeout(render, 1300);
}
function skip(){
if (i >= items.length) return;
const it = items[i];
feedback(fb, false, 'Пропущено. Ответ: ' + it.ans + '. ' + it.hint);
i++;
setTimeout(render, 1300);
}
goBtn.addEventListener('click', check);
skipBtn.addEventListener('click', skip);
inp.addEventListener('keydown', e => { if (e.key === 'Enter') check(); });
render();
})();
/* ===== IV4 wiring — Сумма концов интервалов ===== */
(function(){
const items = [
{ q: '$\\dfrac{x}{x - 3} > 0$', ans: 3, hint: 'Ответ $(-\\infty;0) \\cup (3;+\\infty)$. Конечные концы: $0,\\ 3$. Сумма $= 3$.' },
{ q: '$\\dfrac{x - 2}{x + 1} \\le 0$', ans: 1, hint: 'Ответ $(-1;\\ 2]$. Концы $-1,\\ 2$. Сумма $= 1$.' },
{ q: '$(x - 1)(x - 3) > 0$', ans: 4, hint: 'Ответ $(-\\infty;1) \\cup (3;+\\infty)$. Концы $1,\\ 3$. Сумма $= 4$.' },
{ q: '$\\dfrac{x + 2}{x - 4} < 0$', ans: 2, hint: 'Ответ $(-2;\\ 4)$. Концы $-2,\\ 4$. Сумма $= 2$.' },
{ q: '$(x - 5)(x + 5) \\le 0$', ans: 0, hint: 'Ответ $[-5;\\ 5]$. Концы $-5,\\ 5$. Сумма $= 0$.' },
{ q: '$\\dfrac{x}{(x - 1)(x + 1)} \\ge 0$', ans: 0, hint: 'Корни $0$ (закрашен), $\\pm 1$ (выколоты). Знаки $-,+,-,+$. Ответ $(-1;\\ 0] \\cup (1;+\\infty)$. Концы $-1,\\ 0,\\ 1$. Сумма $= 0$.' }
];
let i = 0, sc = 0, bumped = false;
const idxEl = document.getElementById('p13-iv4-idx');
const scEl = document.getElementById('p13-iv4-sc');
const qEl = document.getElementById('p13-iv4-q');
const inp = document.getElementById('p13-iv4-inp');
const fb = document.getElementById('p13-iv4-fb');
const goBtn = document.getElementById('p13-iv4-go');
const skipBtn = document.getElementById('p13-iv4-skip');
function render(){
idxEl.textContent = Math.min(i + 1, items.length);
scEl.textContent = sc;
if (i >= items.length){
qEl.innerHTML = '<b>Готово!</b> Результат: ' + sc + ' / ' + items.length;
inp.disabled = true; goBtn.disabled = true; skipBtn.disabled = true;
inp.style.opacity = .5; goBtn.style.opacity = .5; skipBtn.style.opacity = .5;
if (!bumped){ bumped = true; bumpProgress('p13', 25); addXp(15, 'p13-iv4'); achievement('p13_done'); }
return;
}
qEl.innerHTML = items[i].q;
inp.value = '';
fb.style.display = 'none';
renderMath(qEl);
}
function check(){
if (i >= items.length) return;
const v = parseFloat(inp.value);
const it = items[i];
if (isNaN(v)){ feedback(fb, false, 'Введи число.'); return; }
const ok = Math.abs(v - it.ans) < 1e-6;
if (ok) sc++;
feedback(fb, ok, (ok ? '&#10003; Верно! ' : '&#10007; Неверно. Сумма $= ' + it.ans + '$. ') + it.hint);
i++;
setTimeout(render, 1300);
}
function skip(){
if (i >= items.length) return;
const it = items[i];
feedback(fb, false, 'Пропущено. Сумма $= ' + it.ans + '$. ' + it.hint);
i++;
setTimeout(render, 1300);
}
goBtn.addEventListener('click', check);
skipBtn.addEventListener('click', skip);
inp.addEventListener('keydown', e => { if (e.key === 'Enter') check(); });
render();
})();
wireReadBtn('p13');
}
function buildFinal3(){
const box = document.getElementById('final3-body');
let html = '';
/* Часть А — Шпаргалка главы (4 mini-карточки) */
html += `<div class="card">
<div class="card-header">
<span class="card-icon theory">${ICONS.theory}</span>
<span class="card-title">Шпаргалка главы 3</span>
<span class="card-num">Итог</span>
</div>
<div class="card-body">
<p>Все ключевые правила главы — в одном месте. Просмотри перед боссами!</p>
<div style="display:grid;grid-template-columns:repeat(auto-fit,minmax(220px,1fr));gap:12px;margin-top:10px">
<div style="padding:12px 14px;background:var(--sec-acc-soft);border-radius:11px;border-left:3px solid var(--pri)">
<div style="font-family:'Unbounded',sans-serif;font-weight:700;color:var(--pri2);margin-bottom:6px;font-size:.92rem">§ 10 · Дробно-рац. уравнения</div>
<div style="font-size:.95rem">$\\dfrac{P(x)}{Q(x)} = 0 \\Leftrightarrow P(x) = 0,\\ Q(x) \\ne 0$. Корни, при которых знаменатель $=0$ — <b>посторонние</b>!</div>
</div>
<div style="padding:12px 14px;background:var(--sec-acc-soft);border-radius:11px;border-left:3px solid var(--pri)">
<div style="font-family:'Unbounded',sans-serif;font-weight:700;color:var(--pri2);margin-bottom:6px;font-size:.92rem">§ 11 · Системы нелинейных</div>
<div style="font-size:.95rem">Подстановка (выразить переменную) или сложение. Решения — <b>точки пересечения</b> графиков.</div>
</div>
<div style="padding:12px 14px;background:var(--sec-acc-soft);border-radius:11px;border-left:3px solid var(--pri)">
<div style="font-family:'Unbounded',sans-serif;font-weight:700;color:var(--pri2);margin-bottom:6px;font-size:.92rem">§ 12 · Окружность</div>
<div style="font-size:.95rem">$|AB| = \\sqrt{(x_2-x_1)^2 + (y_2-y_1)^2}$. Окр.: $(x-a)^2 + (y-b)^2 = R^2$, центр $(a;b)$, радиус $R$.</div>
</div>
<div style="padding:12px 14px;background:var(--sec-acc-soft);border-radius:11px;border-left:3px solid var(--pri)">
<div style="font-family:'Unbounded',sans-serif;font-weight:700;color:var(--pri2);margin-bottom:6px;font-size:.92rem">§ 13 · Метод интервалов</div>
<div style="font-size:.95rem">Корни на прямой $\\to$ знаки чередуются $\\to$ выбрать подходящие интервалы. Корни знаменателя — <b>выколоты</b>!</div>
</div>
</div>
</div>
</div>`;
/* Часть Б — 5 боссов */
html += `<div class="card">
<div class="card-header">
<span class="card-icon rule">${ICONS.rule}</span>
<span class="card-title">Боссы главы 3</span>
<span class="card-num">5</span>
</div>
<div class="card-body">
<p>5 интегрированных задач. Каждая комбинирует несколько тем главы 3. За каждого побеждённого босса — <b>+10 XP</b>. Победишь всех — <b>+50 XP бонус</b> и ачивка «Мастер дробно-рациональных»!</p>
</div>
</div>`;
html += '<div id="ch3-bosses-container"></div>';
html += `<div style="margin-top:18px;padding:18px 20px;background:linear-gradient(135deg,var(--pri-soft),var(--sec-acc-soft));border-radius:14px;border:1.5px solid var(--pri);text-align:center" id="ch3-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="ch3-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="ch3-boss-overall-fill" style="height:100%;width:0%;background:linear-gradient(90deg,#7c3aed,#a78bfa);transition:width .35s"></div>
</div>
<div id="ch3-final-reward" style="margin-top:14px;display:none;padding:14px;background:var(--card);border-radius:11px;border:2px solid #f59e0b">
<div style="font-family:'Unbounded',sans-serif;font-weight:800;color:#92400e;font-size:1.05rem;margin-bottom:6px">Мастер дробно-рациональных</div>
<div style="font-size:.92rem;margin-bottom:10px">Глава 3 пройдена! Все 5 боссов повержены. +50 XP бонус.</div>
<a class="btn primary" href="/textbook/algebra-9-ch4" style="text-decoration:none">Дальше: Глава 4 <svg class="ic" viewBox="0 0 24 24"><polyline points="9 18 15 12 9 6"/></svg></a>
</div>
</div>`;
html += secNav('p13', null);
box.innerHTML = html;
renderMath(box);
/* Боссы */
const BOSSES = [
{
n:1, color:'#10b981',
title:'Циклоп Уравнений',
tag:'§ 10 + § 13',
q:'Реши уравнение $\\dfrac{x^2 - 9}{x - 3} = 0$. Сколько корней у уравнения?',
ans:1,
hint:'$x^2 - 9 = 0 \\Rightarrow x = \\pm 3$. ОДЗ: $x \\ne 3$. Значит $x = 3$ — посторонний. Остаётся $x = -3$. Итого: <b>1 корень</b>.'
},
{
n:2, color:'#0891b2',
title:'Гарпия Систем',
tag:'§ 11 + § 12',
q:'Сколько решений у системы $\\begin{cases} x^2 + y^2 = 25 \\\\ y = x + 1 \\end{cases}$?',
ans:2,
hint:'Подставим: $x^2 + (x+1)^2 = 25 \\Rightarrow 2x^2 + 2x - 24 = 0 \\Rightarrow x^2 + x - 12 = 0 \\Rightarrow x = 3$ или $x = -4$. <b>2 решения</b>.'
},
{
n:3, color:'#7c3aed',
title:'Сирена Окружности',
tag:'§ 12 + основы',
q:'Найди радиус $R$ окружности $x^2 + y^2 - 6x + 4y - 12 = 0$.',
ans:5,
hint:'Выделим полные квадраты: $(x-3)^2 + (y+2)^2 = 12 + 9 + 4 = 25 \\Rightarrow R^2 = 25 \\Rightarrow R = 5$.'
},
{
n:4, color:'#dc2626',
title:'Минотавр Интервалов',
tag:'§ 13 + § 10',
q:'Реши $\\dfrac{x(x-4)}{x+2} \\ge 0$. Сколько <b>целых</b> значений $x$ из отрезка $[-5;\\ 5]$ удовлетворяют неравенству?',
ans:4,
hint:'Корни числителя $0, 4$ (закрашены), корень знаменателя $-2$ (выколота). Знак справа $(x=10)$: $+$. Чередование: $-, +, -, +$. Решение: $(-2;\\ 0] \\cup [4;\\ +\\infty)$. Целые в $[-5;5]$: $\\{-1, 0, 4, 5\\}$ — <b>4 значения</b>.'
},
{
n:5, color:'#f59e0b',
title:'Мастер Глубин',
tag:'§§ 1013 — синтез',
q:'Сколько решений у системы $\\begin{cases} (x-1)^2 + (y-2)^2 = 25 \\\\ y = x + 1 \\end{cases}$?',
ans:2,
hint:'Подставим $y = x + 1$: $(x-1)^2 + (x-1)^2 = 25 \\Rightarrow 2(x-1)^2 = 25 \\Rightarrow (x-1)^2 = 12{,}5$. Корней <b>2</b> (квадратное с $D > 0$).'
},
];
const cont = document.getElementById('ch3-bosses-container');
const STATE_KEY = 'algebra9_ch3_bosses';
const BOSS_STATE = (function(){
try{ const s = localStorage.getItem(STATE_KEY); if(s) return JSON.parse(s); }catch(e){}
return BOSSES.map(()=>({defeated:false}));
})();
function saveBosses(){ try{ localStorage.setItem(STATE_KEY, JSON.stringify(BOSS_STATE)); }catch(e){} }
cont.innerHTML = BOSSES.map((b, idx)=>{
return '<div class="boss-card" id="boss3-'+b.n+'-card" style="padding:16px;background:var(--card);border-radius:12px;border:2px solid '+b.color+';margin-bottom:14px">'
+'<div style="display:flex;align-items:center;gap:10px;margin-bottom:10px;flex-wrap:wrap">'
+'<svg viewBox="0 0 24 24" fill="none" stroke="'+b.color+'" stroke-width="2.2" style="width:28px;height:28px;flex-shrink:0"><polygon points="12,2 22,20 2,20"/></svg>'
+'<div style="font-family:\'Unbounded\',sans-serif;font-weight:800;color:'+b.color+';font-size:1.05rem">Босс '+b.n+': '+b.title+'</div>'
+'<div style="margin-left:auto;font-size:.78rem;color:var(--muted);padding:3px 8px;background:var(--sec-acc-soft);border-radius:6px">'+b.tag+'</div>'
+'</div>'
+'<div id="boss3-'+b.n+'-q" style="padding:12px 14px;background:var(--sec-acc-soft);border-radius:9px;font-size:1rem;line-height:1.5;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" id="boss3-'+b.n+'-ans" class="tinp" style="width:120px;text-align:center" placeholder="число">'
+'<button class="btn primary" id="boss3-'+b.n+'-go" style="background:'+b.color+';border-color:'+b.color+'">Атаковать</button>'
+'<button class="btn" id="boss3-'+b.n+'-hint">Подсказка</button>'
+'</div>'
+'<div class="feedback" id="boss3-'+b.n+'-fb"></div>'
+'</div>';
}).join('');
renderMath(cont);
function refreshOverall(){
const won = BOSS_STATE.filter(s => s.defeated).length;
const txt = document.getElementById('ch3-boss-overall');
const fill = document.getElementById('ch3-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('ch3-final-reward');
if(reward && reward.style.display === 'none'){
reward.style.display = 'block';
if(!STATE.achievements.has('ch3_done')){
achievement('ch3_done','Мастер дробно-рациональных');
addXp(50, 'ch3-bonus');
bumpProgress('final3', 30);
if(window.confetti){ try{ confetti(); }catch(e){} }
}
}
}
}
BOSSES.forEach((b, idx)=>{
const card = document.getElementById('boss3-'+b.n+'-card');
const goBtn = document.getElementById('boss3-'+b.n+'-go');
const hintBtn = document.getElementById('boss3-'+b.n+'-hint');
const ansInp = document.getElementById('boss3-'+b.n+'-ans');
if(BOSS_STATE[idx].defeated){
card.style.background = 'linear-gradient(135deg,var(--sec-acc-soft),var(--pri-soft))';
goBtn.disabled = true; goBtn.style.opacity = .55; goBtn.textContent = '✓ Повержен';
ansInp.disabled = true;
}
goBtn.addEventListener('click', ()=>{
if(BOSS_STATE[idx].defeated) return;
const fb = document.getElementById('boss3-'+b.n+'-fb');
const val = parseInt(ansInp.value, 10);
if(isNaN(val)){ feedback(fb, false, '&#10007; Введи целое число.'); return; }
if(val === b.ans){
BOSS_STATE[idx].defeated = true; saveBosses();
feedback(fb, true, '&#10003; Босс '+b.n+' повержен! +10 XP. '+b.hint);
addXp(10, 'boss-ch3-'+b.n);
bumpProgress('final3', 18);
goBtn.disabled = true; goBtn.style.opacity = .55; goBtn.textContent = '✓ Повержен';
ansInp.disabled = true;
card.style.background = 'linear-gradient(135deg,var(--sec-acc-soft),var(--pri-soft))';
refreshOverall();
} else {
feedback(fb, false, '&#10007; Промах. Попробуй ещё. Подсказка доступна.');
}
});
hintBtn.addEventListener('click', ()=>{
const fb = document.getElementById('boss3-'+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('p10');
setTimeout(()=>achievement('start'), 600);
if(window.LS&&window.LS.xp){
window.LS.xp.load().then(function(s){ if(s&&s.xp>STATE.xp){ STATE.xp=s.xp; STATE.level=calcLevel(STATE.xp); saveProgress(); refreshProgressUI(); if(STATE.current) buildSidebar(STATE.current); } });
}
}
document.addEventListener('DOMContentLoaded', init);
</script>
</body>
</html>