Files
Learn_System/frontend/textbooks/algebra_9_ch1.html
T

2072 lines
145 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!doctype html>
<html lang="ru">
<head>
<meta charset="UTF-8">
<meta http-equiv="Cache-Control" content="no-cache, no-store, must-revalidate">
<meta http-equiv="Pragma" content="no-cache">
<meta http-equiv="Expires" content="0">
<title>Алгебра 9 · Глава 1 · Рациональные выражения</title>
<meta name="viewport" content="width=device-width,initial-scale=1">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/katex@0.16.9/dist/katex.min.css">
<script defer src="https://cdn.jsdelivr.net/npm/katex@0.16.9/dist/katex.min.js"></script>
<script defer src="https://cdn.jsdelivr.net/npm/katex@0.16.9/dist/contrib/auto-render.min.js"
onload="renderMathInElement(document.body,{delimiters:[{left:'$$',right:'$$',display:true},{left:'$',right:'$',display:false},{left:'\\[',right:'\\]',display:true},{left:'\\(',right:'\\)',display:false}],throwOnError:false})"></script>
<script src="/js/api.js" defer></script>
<script src="/js/xp.js" defer></script>
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800&family=Manrope:wght@600;700;800;900&family=Unbounded:wght@700;800;900&family=JetBrains+Mono:wght@500;700&display=swap" rel="stylesheet">
<style>
:root{
--bg:#fafafa; --card:#fff; --card-soft:#f8fafc; --text:#0f172a; --ink:#0f172a; --muted:#64748b;
--border:#e2e8f0; --sh:0 1px 3px rgba(0,0,0,.06); --sh2:0 4px 14px rgba(0,0,0,.08);
--pri:#d97706; --pri2:#b45309; --pri-soft:#fef3c7;
--acc:#f59e0b; --acc2:#d97706; --acc-soft:#fef9c3;
--ok:#10b981; --ok-bg:#d1fae5; --warn:#f59e0b; --warn-bg:#fef3c7;
--bad:#ef4444; --fail:#dc2626; --fail-bg:#fee2e2;
}
.dark{--bg:#0a0a0e; --card:#13120a; --card-soft:#18160a; --text:#fef9e7; --ink:#fef9e7; --muted:#a39070; --border:#2a2512}
*{margin:0;padding:0;box-sizing:border-box;-webkit-tap-highlight-color:transparent}
html,body{font-family:'Inter',system-ui,sans-serif;background:var(--bg);color:var(--text);line-height:1.55;font-size:15px}
button,input,select,textarea{font-family:inherit;font-size:inherit}
button{cursor:pointer;border:0;background:transparent;color:inherit}
a{color:inherit;text-decoration:none}
.ic{width:16px;height:16px;display:inline-block;flex-shrink:0;stroke:currentColor;fill:none;stroke-width:2;stroke-linecap:round;stroke-linejoin:round;vertical-align:middle}
.hdr{position:relative;background:linear-gradient(110deg,#92400e 0%,#d97706 55%,#fbbf24 100%);color:#fff;padding:46px 22px 30px;overflow:hidden;border-bottom:2px solid rgba(251,191,36,.2);min-height:130px}
.hdr::before{content:'ГЛАВА 1';position:absolute;right:-12px;top:50%;transform:translateY(-50%);font-family:'Unbounded',sans-serif;font-size:clamp(5rem,15vw,11rem);font-weight:900;letter-spacing:-.04em;color:transparent;-webkit-text-stroke:1.5px rgba(255,235,180,.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:'A/B';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-p1"]{ --sec-acc:#d97706; --sec-acc-d:#b45309; --sec-acc-soft:#fef3c7; }
.sec[id="sec-p2"]{ --sec-acc:#f59e0b; --sec-acc-d:#d97706; --sec-acc-soft:#fef9c3; }
.sec[id="sec-p3"]{ --sec-acc:#059669; --sec-acc-d:#047857; --sec-acc-soft:#d1fae5; }
.sec[id="sec-p4"]{ --sec-acc:#7c3aed; --sec-acc-d:#6d28d9; --sec-acc-soft:#ede9fe; }
.sec[id="sec-p5"]{ --sec-acc:#db2777; --sec-acc-d:#9d174d; --sec-acc-soft:#fce7f3; }
.sec[id="sec-final1"]{ --sec-acc:#d97706; --sec-acc-d:#b45309; --sec-acc-soft:#fef3c7; }
.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)}
.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}
.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}
.dnd-hint{font-size:.78rem;color:var(--muted);margin-bottom:8px;display:flex;align-items:center;gap:6px}
.dnd-hint svg{width:14px;height:14px;flex-shrink:0}
.col-side{position:sticky;top:14px;align-self:start;height:fit-content;max-height:calc(100vh - 28px);overflow-y:auto}
.sidecard{background:var(--card);border:1px solid var(--border);border-radius:14px;padding:16px;margin-bottom:14px;box-shadow:var(--sh)}
.sidecard h4{font-family:'Unbounded',sans-serif;font-size:.74rem;font-weight:800;color:var(--pri2);text-transform:uppercase;letter-spacing:.07em;margin-bottom:10px;padding-bottom:8px;border-bottom:1px solid var(--border)}
.sidecard-row{margin-bottom:8px;font-size:.86rem;line-height:1.6}
.sidecard-row b{color:var(--pri);font-weight:700}
.sidecard-row:last-child{margin-bottom:0}
@media(max-width:980px){.col-side{position:static;max-height:none}}
.xp-card{background:linear-gradient(135deg,var(--acc-soft),var(--pri-soft));border:1.5px solid var(--acc);border-radius:12px;padding:14px;margin-bottom:14px}
.xp-card-title{font-size:.68rem;font-weight:800;color:var(--acc2);text-transform:uppercase;letter-spacing:.07em;margin-bottom:8px;display:flex;align-items:center;justify-content:space-between}
.xp-level{font-size:1.1rem;font-weight:900;color:var(--acc2);font-family:'Unbounded',sans-serif}
.xp-bar{height:9px;background:rgba(0,0,0,.10);border-radius:6px;overflow:hidden;margin:7px 0}
.xp-fill{height:100%;background:linear-gradient(90deg,var(--acc),var(--pri));border-radius:6px;transition:width .5s cubic-bezier(.4,0,.2,1)}
.xp-nums{font-size:.74rem;color:var(--muted);display:flex;justify-content:space-between}
.sec-nav{display:flex;gap:10px;margin-top:24px;padding-top:20px;border-top:1px solid var(--border);justify-content:space-between;flex-wrap:wrap}
.foot{text-align:center;padding:30px 16px;color:var(--muted);font-size:.78rem;border-top:1px solid var(--border);margin-top:30px}
.ach-popup{position:fixed;top:80px;right:18px;background:linear-gradient(135deg,var(--pri),var(--acc));color:#fff;padding:12px 18px;border-radius:11px;font-weight:700;font-size:.9rem;box-shadow:0 8px 28px rgba(0,0,0,.32);z-index:1002;display:none;align-items:center;gap:8px;max-width:340px}
.ach-popup.show{display:flex}
.col-side-backdrop{position:fixed;inset:0;background:rgba(0,0,0,.42);z-index:9990;display:none}
.col-side-backdrop.show{display:block}
@media(max-width:980px){
.col-side{position:fixed;top:0;right:0;height:100vh;width:300px;max-width:88vw;background:var(--bg);box-shadow:-12px 0 24px rgba(0,0,0,.18);padding:18px 16px;overflow-y:auto;transform:translateX(100%);transition:transform .25s ease;z-index:9991;max-height:none}
.col-side.open{transform:none}
}
.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}
</style>
</head>
<body>
<header class="hdr">
<div class="hdr-row">
<div>
<h1>Алгебра 9 · Глава 1</h1>
<div class="hdr-sub">Рациональные дроби · ОДЗ · действия с дробями</div>
</div>
<div class="hdr-side">
<a href="/textbook/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> (выражения вида $\dfrac{P(x)}{Q(x)}$), их <b>область допустимых значений</b>, основное свойство и <b>сокращение</b>, четыре арифметических действия и <b>преобразование</b> сложных рациональных выражений.</p>
<div class="hero-row">
<button class="btn-primary" onclick="goTo('p1')"><svg class="ic" viewBox="0 0 24 24"><polygon points="6 4 20 12 6 20 6 4" fill="currentColor" stroke="none"/></svg> Начать § 1</button>
<div class="hero-progress">
<span class="hp-label">Прогресс по главе</span>
<div class="hp-bar"><div id="hero-hp-fill" class="hp-fill"></div></div>
<span id="hero-hp-text" class="hp-text">0%</span>
</div>
<div id="hero-xp-badge" class="hero-xp-badge"></div>
</div>
</section>
<section class="psel">
<div class="psel-title">Параграфы главы</div>
<div id="psel-grid" class="psel-grid"></div>
</section>
<section id="sec-p1" class="sec" data-watermark="P/Q"><div class="sec-header"><span class="sec-num">§ 1</span><h2 class="sec-h">Рациональная дробь</h2></div><div id="p1-body"></div></section>
<section id="sec-p2" class="sec" data-watermark="k"><div class="sec-header"><span class="sec-num">§ 2</span><h2 class="sec-h">Основное свойство дроби</h2></div><div id="p2-body"></div></section>
<section id="sec-p3" class="sec" data-watermark="+"><div class="sec-header"><span class="sec-num">§ 3</span><h2 class="sec-h">Сложение и вычитание</h2></div><div id="p3-body"></div></section>
<section id="sec-p4" class="sec" data-watermark="×"><div class="sec-header"><span class="sec-num">§ 4</span><h2 class="sec-h">Умножение и деление</h2></div><div id="p4-body"></div></section>
<section id="sec-p5" class="sec" data-watermark="…"><div class="sec-header"><span class="sec-num">§ 5</span><h2 class="sec-h">Преобразование выражений</h2></div><div id="p5-body"></div></section>
<section id="sec-final1" class="sec" data-watermark="★"><div class="sec-header"><span class="sec-num" style="background:linear-gradient(135deg,#d97706,#f59e0b)">Финал главы</span><h2 class="sec-h">Итоги. 5 боссов главы 1</h2></div><div id="final1-body"></div></section>
</div>
<aside class="col-side" id="col-side"><div id="sidebar-content"></div></aside>
<div class="col-side-backdrop" id="col-side-backdrop"></div>
</main>
<footer class="foot">Интерактивный учебник «Алгебра 9» · Глава 1 · Рациональные выражения · LearnSpace</footer>
<div id="ach-popup" class="ach-popup"><svg class="ic" viewBox="0 0 24 24" style="width:22px;height:22px"><polygon points="12,2 22,20 2,20"/></svg><span id="ach-text">Достижение!</span></div>
<div id="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:'p1', progress:{p1:0,p2:0,p3:0,p4:0,p5:0,final1:0}, achievements:new Map(), xp:0, level:1 };
const TOTAL_PARAS = 6;
const _TB_SLUG = 'algebra-9-ch1';
function calcLevel(xp){ return Math.floor(Math.sqrt((xp||0)/100))+1; }
function _xpForLevel(lv){ return (lv-1)*(lv-1)*100; }
const ACH_LABELS = {
start:'Начало главы 1!',
p2_done:'Сокращение дробей освоено!',
p4_done:'Действия с дробями освоены!',
p5_done:'Преобразование выражений освоено!',
ch1_done:'Глава 1 пройдена!'
};
function loadProgress(){
try{
const s=localStorage.getItem('algebra9_ch1_progress'); if(s) Object.assign(STATE.progress, JSON.parse(s));
const a=localStorage.getItem('algebra9_ch1_achievements');
if(a){ const p=JSON.parse(a); if(Array.isArray(p)) p.forEach(id=>STATE.achievements.set(id, ACH_LABELS[id]||id)); else if(p&&typeof p==='object'){ for(const[id,t] of Object.entries(p)) STATE.achievements.set(id,(t&&t!==id)?t:(ACH_LABELS[id]||id)); } }
STATE.xp=+(localStorage.getItem('algebra9_xp')||0); STATE.level=calcLevel(STATE.xp);
}catch(e){}
}
function saveProgress(){
try{
localStorage.setItem('algebra9_ch1_progress', JSON.stringify(STATE.progress));
localStorage.setItem('algebra9_ch1_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-ch1-'+(src||'misc'));
if(STATE.level>prev){
const pop=document.getElementById('ach-popup');
if(pop){ document.getElementById('ach-text').textContent='Уровень '+STATE.level+'!'; pop.classList.add('show'); setTimeout(()=>pop.classList.remove('show'),2600); }
}
}
function refreshProgressUI(){
const total=Math.round(Object.values(STATE.progress).reduce((a,b)=>a+b,0)/TOTAL_PARAS);
const f=document.getElementById('hero-hp-fill'); if(f) f.style.width=total+'%';
const t=document.getElementById('hero-hp-text'); if(t) t.textContent=total+'% пройдено';
document.querySelectorAll('[data-prog-card]').forEach(el=>{ const k=el.dataset.progCard; const fl=el.querySelector('.psel-prog-fill'); if(fl) fl.style.width=(STATE.progress[k]||0)+'%'; });
const xpBadge=document.getElementById('hero-xp-badge');
if(xpBadge){ xpBadge.innerHTML='<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" style="width:13px;height:13px"><polygon points="12 2 22 20 2 20"/></svg> Ур. '+STATE.level+' \xb7 '+(STATE.xp||0)+' XP'; }
if(STATE.current && document.getElementById('sidebar-content')){ try{ buildSidebar(STATE.current); }catch(e){} }
}
function achievement(id,text){
if(STATE.achievements.has(id)) return;
STATE.achievements.set(id, text||ACH_LABELS[id]||id); saveProgress();
const pop=document.getElementById('ach-popup');
if(pop){ document.getElementById('ach-text').textContent=text||ACH_LABELS[id]||id; pop.classList.add('show'); setTimeout(()=>pop.classList.remove('show'),3300); }
addXp(20,'ach-'+id);
}
const PARAS = [
{ id:'p1', num:'§ 1', name:'Рациональная дробь', sub:'ОДЗ выражения' },
{ id:'p2', num:'§ 2', name:'Основное свойство дроби', sub:'Сокращение' },
{ id:'p3', num:'§ 3', name:'Сложение и вычитание', sub:'Общий знаменатель' },
{ id:'p4', num:'§ 4', name:'Умножение и деление', sub:'×, ÷ дробей' },
{ id:'p5', num:'§ 5', name:'Преобразование выражений', sub:'Сложные дроби' },
{ id:'final1', num:'★', name:'Финал главы', sub:'Итоги · 5 боссов', final:true }
];
function buildParaSelector(){
const g=document.getElementById('psel-grid'); g.innerHTML='';
PARAS.forEach(p=>{
const card=document.createElement('div');
card.className='psel-card'+(p.final?' final':'');
card.dataset.id=p.id; card.dataset.progCard=p.id;
card.innerHTML='<div class="psel-num">'+p.num+'</div><div class="psel-name">'+p.name+'</div><div class="psel-prog"><div class="psel-prog-fill"></div></div>';
card.addEventListener('click', ()=>goTo(p.id));
g.appendChild(card);
});
}
const BUILT=new Set();
const BUILDERS = { p1:()=>buildP1(), p2:()=>buildP2(), p3:()=>buildP3(), p4:()=>buildP4(), p5:()=>buildP5(), final1:()=>buildFinal1() };
function ensureBuilt(id){ if(BUILT.has(id)) return; const fn=BUILDERS[id]; if(fn){ fn(); BUILT.add(id); } }
function goTo(id){
STATE.current=id; ensureBuilt(id);
document.querySelectorAll('.sec').forEach(s=>s.classList.remove('active'));
const el=document.getElementById('sec-'+id); if(el) el.classList.add('active');
document.querySelectorAll('.psel-card').forEach(c=>c.classList.toggle('active', c.dataset.id===id));
buildSidebar(id);
window.scrollTo({top:0,behavior:'smooth'});
if((STATE.progress[id]||0)<10) bumpProgress(id, 10);
if(window.renderMathInElement) setTimeout(()=>renderMath(el), 0);
markLastPara(id);
}
const SIDEBARS = {
p1:{title:'Шпаргалка \xA71',rows:[['Дробь','$\dfrac{P(x)}{Q(x)}$, где $P, Q$ — многочлены'],['ОДЗ','$Q(x) \ne 0$'],['Целое','частный случай при $Q = 1$']]},
p2:{title:'Шпаргалка \xA72',rows:[['Свойство','$\dfrac{P \cdot R}{Q \cdot R} = \dfrac{P}{Q}$ при $R \ne 0$'],['Сокращение','делим числитель и знаменатель на общий множитель'],['Знак','$\dfrac{-a}{-b} = \dfrac{a}{b}$, $\dfrac{-a}{b} = -\dfrac{a}{b}$']]},
p3:{title:'Шпаргалка \xA73',rows:[['Одинак.знам.','$\dfrac{a}{c} \pm \dfrac{b}{c} = \dfrac{a \pm b}{c}$'],['Разные знам.','приведи к общему знаменателю'],['НОЗ','наименьший общий знаменатель']]},
p4:{title:'Шпаргалка \xA74',rows:[['Умножение','$\dfrac{a}{b} \cdot \dfrac{c}{d} = \dfrac{ac}{bd}$'],['Деление','$\dfrac{a}{b} : \dfrac{c}{d} = \dfrac{a}{b} \cdot \dfrac{d}{c}$'],['Степень','$\left(\dfrac{a}{b}\right)^n = \dfrac{a^n}{b^n}$']]},
p5:{title:'Шпаргалка \xA75',rows:[['Шаг 1','выпиши ОДЗ'],['Шаг 2','разложи на множители'],['Шаг 3','выполни действия по порядку'],['Шаг 4','сократи результат']]},
final1:{title:'Финал главы',rows:[['§§15','теория главы 1'],['Боссов','5'],['Награда','+100 XP']]}
};
const TIPS=[
{sec:'p1',html:'<b>ОДЗ</b> — это значения, при которых знаменатель $\ne 0$. Всегда выписывай ОДЗ перед работой с дробью.'},
{sec:'p2',html:'Сокращение возможно после <b>разложения на множители</b> числителя и знаменателя.'},
{sec:'p3',html:'Для сложения дробей с разными знаменателями ищи <b>наименьший общий знаменатель</b>.'},
{sec:'p4',html:'$\dfrac{a}{b} \cdot \dfrac{c}{d} = \dfrac{ac}{bd}$, $\dfrac{a}{b} : \dfrac{c}{d} = \dfrac{ad}{bc}$.'},
{sec:'p5',html:'Сложные выражения упрощай по действиям, не забывай об ОДЗ.'},
{sec:'final1',html:'5 боссов главы 1. Удачи!'}
];
function buildSidebar(id){
const box=document.getElementById('sidebar-content');
const sb=SIDEBARS[id]||SIDEBARS['p1'];
let html='';
const xpForLv=_xpForLevel(STATE.level), xpNext=_xpForLevel(STATE.level+1);
const xpInLv=STATE.xp-xpForLv, xpRange=xpNext-xpForLv;
const xpPct=xpRange>0?Math.round(xpInLv/xpRange*100):100;
html+='<div class="xp-card"><div class="xp-card-title"><span>XP-прогресс</span><span class="xp-level">Ур. '+STATE.level+'</span></div><div class="xp-bar"><div class="xp-fill" style="width:'+xpPct+'%"></div></div><div class="xp-nums"><span>'+STATE.xp+' XP</span><span>'+xpNext+' XP</span></div></div>';
html+='<div class="sidecard"><h4>'+sb.title+'</h4>';
sb.rows.forEach(([k,v])=>{ html+='<div class="sidecard-row"><b>'+k+'</b>'+(v?' — '+v:'')+'</div>'; });
html+='</div>';
const tip=TIPS.find(t=>t.sec===id)||TIPS[0];
if(tip){
html+='<div class="sidecard" style="background:linear-gradient(135deg,var(--warn-bg,#fef3c7),var(--pri-soft));border-color:var(--warn,#f59e0b)"><h4 style="color:#92400e;display:flex;align-items:center;gap:6px"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" style="width:14px;height:14px"><polygon points="12,2 22,20 2,20"/></svg>Подсказка</h4><div class="sidecard-row" style="margin-bottom:0;font-size:.84rem;line-height:1.55">'+tip.html+'</div></div>';
}
if(STATE.achievements.size>0){
html+='<div class="sidecard"><h4>Достижения <span style="color:var(--warn);float:right">'+STATE.achievements.size+'</span></h4>';
[...STATE.achievements.values()].slice(-4).forEach(text=>{ html+='<div class="sidecard-row" style="font-size:.78rem;color:var(--ok)">&#10003; '+text+'</div>'; });
html+='</div>';
}
box.innerHTML=html;
if(window.renderMathInElement) try{ renderMath(box); }catch(e){}
}
function initTheme(){
const t=localStorage.getItem('algebra9_ch1_theme')||'light';
if(t==='dark') document.documentElement.classList.add('dark');
document.getElementById('theme-lab').textContent=t==='dark'?'Светлая':'Тёмная';
document.getElementById('theme-btn').addEventListener('click', ()=>{
document.documentElement.classList.toggle('dark');
const dark=document.documentElement.classList.contains('dark');
localStorage.setItem('algebra9_ch1_theme', dark?'dark':'light');
document.getElementById('theme-lab').textContent=dark?'Светлая':'Тёмная';
});
}
function renderMath(root){ if(window.renderMathInElement){ try{ renderMathInElement(root, {delimiters:[{left:'$$',right:'$$',display:true},{left:'$',right:'$',display:false},{left:'\\[',right:'\\]',display:true},{left:'\\(',right:'\\)',display:false}],throwOnError:false}); }catch(e){} } }
function feedback(elm, ok, text){ if(!elm) return; elm.className='feedback '+(ok?'ok':'fail'); elm.innerHTML=text||(ok?'&#10003; Верно!':'&#10007; Неверно'); elm.style.display='block'; try{renderMath(elm);}catch(e){} }
function fmt(n){ if(!isFinite(n)) return '?'; if(Number.isInteger(n)) return String(n); return Math.abs(n-Math.round(n))<1e-9?String(Math.round(n)):(+n.toFixed(6)).toString(); }
function ipow(base, exp){ let r=1; for(let i=0;i<Math.abs(exp);i++) r*=base; return exp<0 ? 1/r : r; }
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>';
}
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(); }};
}
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={p1:'\xA71',p2:'\xA72',p3:'\xA73',p4:'\xA74',p5:'\xA75',final1:'Финал'};
let h='<div class="sec-nav">';
h+=prev?'<button class="btn" onclick="goTo(\''+prev+'\')"><svg class="ic" viewBox="0 0 24 24"><polyline points="15 18 9 12 15 6"/></svg> '+NAMES[prev]+'</button>':'<span></span>';
h+=next?'<button class="btn primary" onclick="goTo(\''+next+'\')">'+NAMES[next]+' <svg class="ic" viewBox="0 0 24 24"><polyline points="9 18 15 12 9 6"/></svg></button>':'<span></span>';
h+='</div>'; return h;
}
function readButton(paraId){
return '<div style="margin-top:18px;display:flex;justify-content:center">'
+'<button class="btn primary" id="'+paraId+'-read-btn">'
+'<svg class="ic" viewBox="0 0 24 24"><path d="M4 19.5A2.5 2.5 0 0 1 6.5 17H20"/><path d="M6.5 2H20v20H6.5A2.5 2.5 0 0 1 4 19.5v-15A2.5 2.5 0 0 1 6.5 2z"/></svg>'
+' Я прочитал — '+(paraId.startsWith('final')?'финал':'\xA7'+paraId.replace('p',''))+' (+10 XP)'
+'</button></div>';
}
function wireReadBtn(paraId){
const btn = document.getElementById(paraId+'-read-btn'); if(!btn) return;
btn.addEventListener('click', ()=>{
addXp(10, paraId+'-read'); bumpProgress(paraId, 30);
btn.textContent='Прочитано! +10 XP'; btn.disabled=true; btn.style.opacity=.6;
if(paraId==='p2') achievement('p2_done');
if(paraId==='p4') achievement('p4_done');
if(paraId==='p5') achievement('p5_done');
if(paraId==='final1') achievement('ch1_done');
});
}
/* ===== STUB BUILDERS — наполнение в Phase 1+ ===== */
function buildP1(){
const box = document.getElementById('p1-body');
let html = '';
html += makeCard('theory', 'Определение рациональной дроби', '1.1', `
<p><b>Рациональной дробью</b> называется выражение вида $\\dfrac{P(x)}{Q(x)}$, где $P(x)$ и $Q(x)$ — многочлены, причём $Q(x) \\ne 0$.</p>
<p>Числитель $P(x)$ может быть любым многочленом (даже нулевым), а знаменатель $Q(x)$ — <b>не равен нулю</b>.</p>
<p>Примеры рациональных дробей:</p>
<ul style="padding-left:22px;line-height:1.9">
<li>$\\dfrac{x+1}{x-2}$ — простейшая дробь с линейными многочленами</li>
<li>$\\dfrac{3}{x^2+1}$ — числитель константа, знаменатель квадратный</li>
<li>$\\dfrac{2a^2-1}{a^2-9}$ — обе части квадратные многочлены</li>
</ul>
<details class="spoiler"><summary>А целые выражения — это дроби?</summary><div class="spoiler-body">
Да! Любой многочлен $P(x)$ — это частный случай рациональной дроби: $P(x) = \\dfrac{P(x)}{1}$. Поэтому рациональные дроби обобщают и целые выражения.
</div></details>`);
html += makeCard('rule', 'Область допустимых значений (ОДЗ)', '1.2', `
<p><b>ОДЗ</b> рациональной дроби $\\dfrac{P(x)}{Q(x)}$ — это все значения переменной, при которых <b>знаменатель не обращается в ноль</b>.</p>
<p>Алгоритм нахождения ОДЗ:</p>
<ol style="padding-left:22px;line-height:2">
<li>Записать уравнение $Q(x) = 0$.</li>
<li>Найти все его корни.</li>
<li>Исключить эти корни из множества действительных чисел.</li>
</ol>
<p>Запись: «$x \\ne$ ...» или «$x \\in \\mathbb{R}, x \\ne $...».</p>
<p>Если уравнение $Q(x) = 0$ <b>не имеет корней</b> (например, $x^2 + 1 = 0$), то ОДЗ — <b>все действительные числа</b>.</p>`);
html += makeCard('example', 'Примеры нахождения ОДЗ', '1.3', `
<p><b>а)</b> $\\dfrac{1}{x-3}$. Знаменатель: $x - 3 = 0 \\Rightarrow x = 3$. <b>ОДЗ: $x \\ne 3$.</b></p>
<p><b>б)</b> $\\dfrac{x+5}{x^2-4}$. Знаменатель: $x^2 - 4 = 0 \\Rightarrow (x-2)(x+2) = 0 \\Rightarrow x = \\pm 2$. <b>ОДЗ: $x \\ne 2, x \\ne -2$.</b></p>
<p><b>в)</b> $\\dfrac{2x}{(x-1)(x+7)}$. Знаменатель: $(x-1)(x+7) = 0 \\Rightarrow x = 1$ или $x = -7$. <b>ОДЗ: $x \\ne 1, x \\ne -7$.</b></p>`);
/* INTERACTIVE 1 — slider + SVG number line */
html += `<div class="wg" id="p1-iv1">
<div class="wg-header"><span class="wg-badge">ИНТЕРАКТИВ 1</span><div class="wg-title">ОДЗ на числовой прямой</div></div>
<div class="wg-help">Выбери дробь ползунком — увидишь её ОДЗ на числовой прямой и в текстовой форме.</div>
<div class="sliders">
<label>Дробь №<b id="p1-iv1-idx">1</b> / 5<input type="range" id="p1-iv1-sl" min="1" max="5" step="1" value="1"></label>
</div>
<div id="p1-iv1-formula" style="text-align:center;font-size:1.15rem;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="p1-iv1-svg" viewBox="0 0 600 100" style="width:100%;min-width:520px;height:auto;display:block"></svg>
</div>
<div id="p1-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 — calc ОДЗ */
html += `<div class="wg" id="p1-iv2">
<div class="wg-header"><span class="wg-badge">ИНТЕРАКТИВ 2</span><div class="wg-title">Калькулятор ОДЗ дроби $\\dfrac{1}{ax+b}$</div></div>
<div class="wg-help">Введи целые коэффициенты $a$ и $b$, нажми «Найти ОДЗ» — посчитаем точку, исключённую из ОДЗ.</div>
<div style="display:flex;gap:10px;align-items:center;flex-wrap:wrap;justify-content:center;margin-bottom:10px">
<span style="font-family:'JetBrains Mono',monospace">$a$ =</span>
<input type="number" id="p1-iv2-a" class="tinp" style="width:80px;text-align:center" value="2">
<span style="font-family:'JetBrains Mono',monospace">$b$ =</span>
<input type="number" id="p1-iv2-b" class="tinp" style="width:80px;text-align:center" value="-6">
<button class="btn primary" id="p1-iv2-go">Найти ОДЗ</button>
</div>
<div id="p1-iv2-out" style="padding:12px 14px;background:var(--card);border-radius:9px;text-align:center;font-size:1rem;min-height:48px"></div>
<div class="feedback" id="p1-iv2-fb"></div>
</div>`;
/* INTERACTIVE 3 — quickfire quiz */
html += `<div class="wg" id="p1-iv3">
<div class="wg-header"><span class="wg-badge">ИНТЕРАКТИВ 3</span><div class="wg-title">Входит ли в ОДЗ?</div></div>
<div class="wg-help">Дробь и значение $x_0$. Решай: входит ли $x_0$ в ОДЗ (т.е. не обращает ли знаменатель в 0)?</div>
<div class="score-display"><span>Задача <b id="p1-iv3-i">1</b> / 8</span><span>Очки: <b id="p1-iv3-s">0</b> / 8</span></div>
<div id="p1-iv3-q" style="padding:14px;background:var(--sec-acc-soft);border-radius:10px;font-size:1.15rem;text-align:center;margin-bottom:10px"></div>
<div style="display:flex;gap:10px;justify-content:center;flex-wrap:wrap">
<button class="btn primary" id="p1-iv3-yes" style="background:#10b981;border-color:#10b981">Да, входит</button>
<button class="btn primary" id="p1-iv3-no" style="background:#dc2626;border-color:#dc2626">Нет, не входит</button>
</div>
<div class="feedback" id="p1-iv3-fb"></div>
</div>`;
/* INTERACTIVE 4 — trainer */
html += `<div class="wg" id="p1-iv4">
<div class="wg-header"><span class="wg-badge">ИНТЕРАКТИВ 4</span><div class="wg-title">Тренажёр: сумма корней знаменателя</div></div>
<div class="wg-help">Для каждой дроби введи <b>сумму всех значений</b>, при которых знаменатель $= 0$ (т.е. сумму точек, исключённых из ОДЗ).</div>
<div class="score-display"><span>Задача <b id="p1-iv4-i">1</b> / 6</span><span>Очки: <b id="p1-iv4-s">0</b> / 6</span></div>
<div id="p1-iv4-q" style="padding:14px;background:var(--sec-acc-soft);border-radius:10px;font-size:1.05rem;margin-bottom:10px;text-align:center"></div>
<div style="display:flex;gap:10px;align-items:center;flex-wrap:wrap;justify-content:center">
<span style="font-family:'JetBrains Mono',monospace;font-size:1rem">сумма корней =</span>
<input type="number" id="p1-iv4-ans" class="tinp" style="width:100px;text-align:center" step="1">
<button class="btn primary" id="p1-iv4-go">Проверить</button>
<button class="btn" id="p1-iv4-start">Заново</button>
</div>
<div class="feedback" id="p1-iv4-fb"></div>
</div>`;
html += secNav(null, 'p2');
html += readButton('p1');
box.innerHTML = html;
renderMath(box);
/* IV1 — slider + SVG */
(function(){
const FRACS = [
{ fr:'\\dfrac{1}{x-2}', roots:[2], text:'$x \\ne 2$' },
{ fr:'\\dfrac{x}{x^2-9}', roots:[-3,3], text:'$x \\ne -3,\\ x \\ne 3$' },
{ fr:'\\dfrac{3}{(x+1)(x-4)}', roots:[-1,4], text:'$x \\ne -1,\\ x \\ne 4$' },
{ fr:'\\dfrac{2x+1}{x^2-25}', roots:[-5,5], text:'$x \\ne -5,\\ x \\ne 5$' },
{ fr:'\\dfrac{1}{x^2+1}', roots:[], text:'$x \\in \\mathbb{R}$ — все действительные' },
];
const sl = document.getElementById('p1-iv1-sl');
const idx = document.getElementById('p1-iv1-idx');
const fEl = document.getElementById('p1-iv1-formula');
const svg = document.getElementById('p1-iv1-svg');
const out = document.getElementById('p1-iv1-out');
const seen = new Set();
function draw(){
const k = +sl.value; idx.textContent = k;
const cur = FRACS[k-1];
fEl.innerHTML = '$' + cur.fr + '$';
let s = '';
// grid
s += '<rect x="0" y="0" width="600" height="100" fill="none"/>';
for(let v=-10; v<=10; v++){
const x = 30 + (v+10) * 27;
s += '<line x1="'+x+'" y1="42" x2="'+x+'" y2="58" stroke="#cbd5e1" stroke-width="1"/>';
if(v%2===0){
s += '<text x="'+x+'" y="76" text-anchor="middle" font-family="JetBrains Mono,monospace" font-size="12" fill="#64748b">'+v+'</text>';
}
}
// axis
s += '<line x1="20" y1="50" x2="580" y2="50" stroke="#0f172a" stroke-width="2"/>';
s += '<polygon points="580,50 570,45 570,55" fill="#0f172a"/>';
s += '<text x="585" y="46" font-family="JetBrains Mono,monospace" font-size="13" fill="#0f172a">x</text>';
// excluded points
cur.roots.forEach(r=>{
const x = 30 + (r+10) * 27;
s += '<circle cx="'+x+'" cy="50" r="6" fill="#fff" stroke="#dc2626" stroke-width="2.5"/>';
s += '<text x="'+x+'" y="32" text-anchor="middle" font-family="Inter,sans-serif" font-size="11" fill="#dc2626" font-weight="700">'+r+'</text>';
});
svg.innerHTML = s;
out.innerHTML = 'ОДЗ: ' + cur.text;
renderMath(fEl); renderMath(out);
seen.add(k);
if(seen.size === FRACS.length){ addXp(10,'p1-iv1'); bumpProgress('p1', 15); seen.clear(); seen.add('done'); }
}
sl.addEventListener('input', draw);
draw();
})();
/* IV2 — ОДЗ калькулятор */
(function(){
const go = document.getElementById('p1-iv2-go');
const aI = document.getElementById('p1-iv2-a');
const bI = document.getElementById('p1-iv2-b');
const out = document.getElementById('p1-iv2-out');
const fb = document.getElementById('p1-iv2-fb');
let solved = 0;
function showFormula(){ out.innerHTML = 'Дробь: $\\dfrac{1}{('+(aI.value||'a')+')x + ('+(bI.value||'b')+')}$'; renderMath(out); }
function calc(){
const a = parseInt(aI.value, 10), b = parseInt(bI.value, 10);
if(isNaN(a) || isNaN(b)){ feedback(fb, false, '&#10007; Введи целые числа $a$ и $b$.'); return; }
if(a === 0){
out.innerHTML = '<b>$a = 0$</b> — выражение не является рациональной дробью с переменной (знаменатель — константа $'+b+'$). Если ещё и $b = 0$, дробь не определена.';
feedback(fb, true, '&#10003; При $a = 0$ переменная в знаменателе исчезает.');
return;
}
// root: ax + b = 0 → x = -b/a
const num = -b, den = a;
const d = gcd(Math.abs(num), Math.abs(den));
let nn = num/d, dd = den/d;
if(dd < 0){ nn = -nn; dd = -dd; }
let xstr;
if(dd === 1) xstr = String(nn);
else xstr = '\\dfrac{'+nn+'}{'+dd+'}';
out.innerHTML = 'Знаменатель: $'+a+'x + ('+b+') = 0 \\Rightarrow x = '+xstr+'$.<br><b>ОДЗ: $x \\ne '+xstr+'$.</b>';
renderMath(out);
feedback(fb, true, '&#10003; ОДЗ найдена! +10 XP');
solved++;
if(solved === 1){ addXp(10,'p1-iv2'); bumpProgress('p1', 15); }
}
aI.addEventListener('input', showFormula); bI.addEventListener('input', showFormula);
go.addEventListener('click', calc);
showFormula();
})();
/* IV3 — quickfire */
(function(){
const Q = [
{ expr:'$\\dfrac{1}{x-3}, \\ x_0 = 5$', yes:true, why:'$5 - 3 = 2 \\ne 0$' },
{ expr:'$\\dfrac{1}{x-3}, \\ x_0 = 3$', yes:false, why:'$3 - 3 = 0$ — знаменатель обнуляется' },
{ expr:'$\\dfrac{x}{x^2-4}, \\ x_0 = 2$', yes:false, why:'$2^2 - 4 = 0$' },
{ expr:'$\\dfrac{x}{x^2-4}, \\ x_0 = 0$', yes:true, why:'$0^2 - 4 = -4 \\ne 0$' },
{ expr:'$\\dfrac{1}{(x+1)(x-5)}, \\ x_0 = -1$', yes:false, why:'$(-1+1)(-1-5) = 0$' },
{ expr:'$\\dfrac{2}{x^2+1}, \\ x_0 = -7$', yes:true, why:'$(-7)^2 + 1 = 50 \\ne 0$ (знаменатель всегда $>0$)' },
{ expr:'$\\dfrac{x+1}{x-x}, \\ x_0 = 2$', yes:false, why:'$x - x = 0$ всегда! Дробь не определена ни при каком $x$' },
{ expr:'$\\dfrac{3}{x^2-9}, \\ x_0 = 3$', yes:false, why:'$3^2 - 9 = 0$' },
];
let i = 0, score = 0;
function show(){
if(i >= Q.length){
document.getElementById('p1-iv3-q').innerHTML = '<b>Готово!</b> Результат: ' + score + ' / ' + Q.length;
if(score === Q.length){ addXp(15,'p1-iv3'); bumpProgress('p1', 25); }
else if(score >= Q.length - 2){ addXp(8,'p1-iv3'); bumpProgress('p1', 15); }
return;
}
document.getElementById('p1-iv3-i').textContent = (i+1);
document.getElementById('p1-iv3-s').textContent = score;
document.getElementById('p1-iv3-q').innerHTML = Q[i].expr;
renderMath(document.getElementById('p1-iv3-q'));
document.getElementById('p1-iv3-fb').style.display = 'none';
}
function answer(isYes){
if(i >= Q.length) return;
const fb = document.getElementById('p1-iv3-fb');
if(isYes === Q[i].yes){ score++; feedback(fb, true, '&#10003; Верно! '+Q[i].why+'. Дальше ▶'); }
else feedback(fb, false, '&#10007; Неверно. '+Q[i].why+'. Дальше ▶');
document.getElementById('p1-iv3-s').textContent = score;
i++;
setTimeout(show, 1000);
}
document.getElementById('p1-iv3-yes').addEventListener('click', ()=>answer(true));
document.getElementById('p1-iv3-no').addEventListener('click', ()=>answer(false));
show();
})();
/* IV4 — trainer */
(function(){
const Q = [
{ q:'$\\dfrac{1}{x-7}$', sum:7, hint:'корень $x = 7$' },
{ q:'$\\dfrac{x}{x^2-16}$', sum:0, hint:'корни $x = \\pm 4$, сумма $= 0$' },
{ q:'$\\dfrac{1}{(x-1)(x+9)}$', sum:-8, hint:'корни $x = 1, -9$, сумма $= -8$' },
{ q:'$\\dfrac{x^2}{x^2-25}$', sum:0, hint:'корни $x = \\pm 5$, сумма $= 0$' },
{ q:'$\\dfrac{2}{(x+3)^2}$', sum:-3, hint:'один корень $x = -3$' },
{ q:'$\\dfrac{1}{x^2-2x-15}$', sum:2, hint:'корни $x = 5, -3$, сумма $= 2$' },
];
let i = 0, score = 0;
function show(){
if(i >= Q.length){
document.getElementById('p1-iv4-q').innerHTML = '<b>Готово!</b> Результат: ' + score + ' / ' + Q.length;
if(score === Q.length){ addXp(15,'p1-iv4'); bumpProgress('p1', 25); }
else if(score >= 4){ addXp(8,'p1-iv4'); bumpProgress('p1', 15); }
return;
}
document.getElementById('p1-iv4-i').textContent = (i+1);
document.getElementById('p1-iv4-s').textContent = score;
document.getElementById('p1-iv4-q').innerHTML = 'Дробь: ' + Q[i].q;
document.getElementById('p1-iv4-ans').value = '';
renderMath(document.getElementById('p1-iv4-q'));
document.getElementById('p1-iv4-fb').style.display = 'none';
}
function go(){
if(i >= Q.length) return;
const fb = document.getElementById('p1-iv4-fb');
const ans = parseInt(document.getElementById('p1-iv4-ans').value, 10);
if(isNaN(ans)){ feedback(fb, false, '&#10007; Введи целое число.'); return; }
if(ans === Q[i].sum){ score++; feedback(fb, true, '&#10003; Верно! '+Q[i].hint+'. Дальше ▶'); }
else feedback(fb, false, '&#10007; Неверно. Сумма $= '+Q[i].sum+'$ ('+Q[i].hint+'). Дальше ▶');
document.getElementById('p1-iv4-s').textContent = score;
i++;
setTimeout(show, 1200);
}
document.getElementById('p1-iv4-go').addEventListener('click', go);
document.getElementById('p1-iv4-ans').addEventListener('keydown', e=>{ if(e.key === 'Enter') go(); });
document.getElementById('p1-iv4-start').addEventListener('click', ()=>{ i=0; score=0; show(); });
show();
})();
wireReadBtn('p1');
}
function buildP2(){
const box = document.getElementById('p2-body');
let html = '';
html += makeCard('theory', 'Основное свойство дроби', '2.1', `
<p><b>Основное свойство рациональной дроби:</b> если числитель и знаменатель дроби умножить или разделить на одно и то же отличное от нуля выражение, то значение дроби не изменится:</p>
\\[\\dfrac{A}{B} = \\dfrac{A \\cdot C}{B \\cdot C}, \\qquad \\dfrac{A \\cdot C}{B \\cdot C} = \\dfrac{A}{B}, \\quad B \\ne 0,\\ C \\ne 0.\\]
<p>Слева направо — это <b>умножение</b> на $C$ (приведение к общему знаменателю), справа налево — это <b>сокращение</b>.</p>
<details class="spoiler"><summary>Правило знаков</summary><div class="spoiler-body">
$\\dfrac{-a}{-b} = \\dfrac{a}{b}$ (минусы сокращаются), $\\dfrac{-a}{b} = -\\dfrac{a}{b} = \\dfrac{a}{-b}$ — минус можно «перекидывать».
</div></details>`);
html += makeCard('rule', 'Как сокращать дробь', '2.2', `
<p><b>Алгоритм сокращения:</b></p>
<ol style="padding-left:22px;line-height:2">
<li><b>Разложить</b> числитель и знаменатель на множители (вынести общий множитель, применить формулы сокращённого умножения).</li>
<li><b>Найти</b> общий множитель числителя и знаменателя.</li>
<li><b>Разделить</b> на него (вычеркнуть).</li>
</ol>
<p>Пример: $\\dfrac{12x^2}{18x} = \\dfrac{6x \\cdot 2x}{6x \\cdot 3} = \\dfrac{2x}{3}$ — общий множитель $6x$.</p>
<p style="background:var(--warn-bg);padding:8px 12px;border-radius:8px;border-left:3px solid var(--warn)"><b>Внимание:</b> сокращать можно только <b>множители</b>, а не слагаемые! $\\dfrac{a+b}{a} \\ne 1 + b$ — это ошибка.</p>`);
html += makeCard('example', 'Примеры сокращения', '2.3', `
<p><b>а)</b> $\\dfrac{a^2-b^2}{a+b} = \\dfrac{(a-b)(a+b)}{a+b} = a - b$ — применили формулу разности квадратов.</p>
<p><b>б)</b> $\\dfrac{x^2-9}{x^2+3x} = \\dfrac{(x-3)(x+3)}{x(x+3)} = \\dfrac{x-3}{x}$ — общий множитель $(x+3)$.</p>
<p><b>в)</b> $\\dfrac{6a^2b}{15ab^2} = \\dfrac{3ab \\cdot 2a}{3ab \\cdot 5b} = \\dfrac{2a}{5b}$ — общий множитель $3ab$.</p>`);
/* INTERACTIVE 1 — slider visualizer */
html += `<div class="wg" id="p2-iv1">
<div class="wg-header"><span class="wg-badge">ИНТЕРАКТИВ 1</span><div class="wg-title">Сократить с подсветкой</div></div>
<div class="wg-help">Выбери задачу — увидишь дробь с подсвеченным общим множителем. Нажми «Показать сокращение», чтобы увидеть результат.</div>
<div class="sliders">
<label>Задача №<b id="p2-iv1-idx">1</b> / 5<input type="range" id="p2-iv1-sl" min="1" max="5" step="1" value="1"></label>
</div>
<div id="p2-iv1-before" style="text-align:center;font-size:1.25rem;padding:14px;background:var(--card);border-radius:9px;margin-bottom:10px;min-height:60px"></div>
<div style="display:flex;gap:10px;justify-content:center;margin-bottom:10px">
<button class="btn primary" id="p2-iv1-go">Показать сокращение</button>
<button class="btn" id="p2-iv1-hide">Скрыть</button>
</div>
<div id="p2-iv1-after" style="text-align:center;font-size:1.25rem;padding:14px;background:var(--sec-acc-soft);border-radius:9px;min-height:50px;display:none"></div>
</div>`;
/* INTERACTIVE 2 — number GCD calc */
html += `<div class="wg" id="p2-iv2">
<div class="wg-header"><span class="wg-badge">ИНТЕРАКТИВ 2</span><div class="wg-title">Калькулятор сокращения числовой дроби</div></div>
<div class="wg-help">Введи целые числитель и знаменатель — посчитаем НОД и покажем сокращённую дробь.</div>
<div style="display:flex;gap:10px;align-items:center;flex-wrap:wrap;justify-content:center;margin-bottom:10px">
<span style="font-family:'JetBrains Mono',monospace">числитель =</span>
<input type="number" id="p2-iv2-num" class="tinp" style="width:90px;text-align:center" value="24">
<span style="font-family:'JetBrains Mono',monospace">знаменатель =</span>
<input type="number" id="p2-iv2-den" class="tinp" style="width:90px;text-align:center" value="36">
<button class="btn primary" id="p2-iv2-go">Сократить</button>
</div>
<div id="p2-iv2-out" style="padding:12px 14px;background:var(--card);border-radius:9px;text-align:center;font-size:1rem;min-height:50px"></div>
<div class="feedback" id="p2-iv2-fb"></div>
</div>`;
/* INTERACTIVE 3 — DnD sorter "Можно сократить / Уже не сокращается" */
html += `<div class="wg" id="p2-iv3">
<div class="wg-header"><span class="wg-badge">ИНТЕРАКТИВ 3</span><div class="wg-title">Сократимо или нет?</div></div>
<div class="wg-help">Перетащи каждую дробь в нужный ящик. Шесть дробей — два варианта.</div>
<div class="dnd-hint"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M9 11V6a3 3 0 0 1 6 0v5"/><path d="M9 11h6v8a4 4 0 0 1-8 0z"/></svg> 6 дробей — 2 ящика</div>
<div id="p2-iv3-pool"></div>
<div style="display:grid;grid-template-columns:repeat(auto-fit,minmax(220px,1fr));gap:10px;margin-top:8px">
<div class="drop-box"><h5 data-cat="yes">Можно сократить</h5><div class="drop-items" data-cat="yes"></div></div>
<div class="drop-box"><h5 data-cat="no">Уже не сокращается</h5><div class="drop-items" data-cat="no"></div></div>
</div>
<div class="actions"><button class="btn primary" id="p2-iv3-check">Проверить</button><button class="btn" id="p2-iv3-reset">Сначала</button></div>
<div class="feedback" id="p2-iv3-fb"></div>
</div>`;
/* INTERACTIVE 4 — trainer */
html += `<div class="wg" id="p2-iv4">
<div class="wg-header"><span class="wg-badge">ИНТЕРАКТИВ 4</span><div class="wg-title">Тренажёр сокращения</div></div>
<div class="wg-help">Сократи дробь и введи <b>числитель</b> результата (число или числовой коэффициент).</div>
<div class="score-display"><span>Задача <b id="p2-iv4-i">1</b> / 6</span><span>Очки: <b id="p2-iv4-s">0</b> / 6</span></div>
<div id="p2-iv4-q" style="padding:14px;background:var(--sec-acc-soft);border-radius:10px;font-size:1.05rem;margin-bottom:10px;text-align:center"></div>
<div style="display:flex;gap:10px;align-items:center;flex-wrap:wrap;justify-content:center">
<span style="font-family:'JetBrains Mono',monospace">числитель =</span>
<input type="number" id="p2-iv4-ans" class="tinp" style="width:90px;text-align:center">
<button class="btn primary" id="p2-iv4-go">Проверить</button>
<button class="btn" id="p2-iv4-start">Заново</button>
</div>
<div class="feedback" id="p2-iv4-fb"></div>
</div>`;
html += secNav('p1', 'p3');
html += readButton('p2');
box.innerHTML = html;
renderMath(box);
/* IV1 */
(function(){
const TASKS = [
{ before:'\\dfrac{6x}{9}', after:'\\dfrac{2x}{3}', common:'общий множитель $3$' },
{ before:'\\dfrac{a^2-1}{a+1}', after:'a - 1', common:'разность квадратов: $a^2 - 1 = (a-1)(a+1)$' },
{ before:'\\dfrac{x^2-4}{x-2}', after:'x + 2', common:'разность квадратов: $x^2 - 4 = (x-2)(x+2)$' },
{ before:'\\dfrac{4ab^2}{6a^2 b}', after:'\\dfrac{2b}{3a}', common:'общий множитель $2ab$' },
{ before:'\\dfrac{x^2+5x}{x^2-25}', after:'\\dfrac{x}{x-5}', common:'$x^2+5x = x(x+5)$, $x^2-25 = (x-5)(x+5)$, сокращаем $(x+5)$' },
];
const sl = document.getElementById('p2-iv1-sl');
const idx = document.getElementById('p2-iv1-idx');
const bEl = document.getElementById('p2-iv1-before');
const aEl = document.getElementById('p2-iv1-after');
const go = document.getElementById('p2-iv1-go');
const hide = document.getElementById('p2-iv1-hide');
const seen = new Set();
function show(){
const k = +sl.value; idx.textContent = k;
const t = TASKS[k-1];
bEl.innerHTML = '$' + t.before + '$';
aEl.innerHTML = '<div style="font-size:.85rem;color:var(--muted);margin-bottom:6px">' + t.common + '</div>$' + t.before + ' \\;=\\; ' + t.after + '$';
aEl.style.display = 'none';
renderMath(bEl);
seen.add(k);
if(seen.size === TASKS.length && !seen.has('done')){ addXp(10,'p2-iv1'); bumpProgress('p2', 15); seen.add('done'); }
}
sl.addEventListener('input', show);
go.addEventListener('click', ()=>{ aEl.style.display = 'block'; renderMath(aEl); });
hide.addEventListener('click', ()=>{ aEl.style.display = 'none'; });
show();
})();
/* IV2 */
(function(){
const go = document.getElementById('p2-iv2-go');
const nI = document.getElementById('p2-iv2-num');
const dI = document.getElementById('p2-iv2-den');
const out = document.getElementById('p2-iv2-out');
const fb = document.getElementById('p2-iv2-fb');
let solved = 0;
function calc(){
const n = parseInt(nI.value, 10), d = parseInt(dI.value, 10);
if(isNaN(n) || isNaN(d)){ feedback(fb, false, '&#10007; Введи целые числа.'); return; }
if(d === 0){ feedback(fb, false, '&#10007; Знаменатель не может быть 0.'); out.innerHTML = ''; return; }
const g = gcd(n, d);
const n2 = n/g, d2 = d/g;
if(g === 1){
out.innerHTML = '$\\dfrac{'+n+'}{'+d+'}$ — НОД $= 1$, дробь уже несократима.';
} else {
out.innerHTML = '$\\dfrac{'+n+'}{'+d+'} = \\dfrac{'+n+':'+g+'}{'+d+':'+g+'} = \\dfrac{'+n2+'}{'+d2+'}$, где НОД $= '+g+'$.';
}
renderMath(out);
feedback(fb, true, '&#10003; Готово! +10 XP');
solved++;
if(solved === 1){ addXp(10,'p2-iv2'); bumpProgress('p2', 15); }
}
go.addEventListener('click', calc);
calc();
})();
/* IV3 — DnD sorter */
(function(){
const items = [
{ id:'i1', cat:'yes', html:'$\\dfrac{12}{18}$' },
{ id:'i2', cat:'no', html:'$\\dfrac{5}{7}$' },
{ id:'i3', cat:'yes', html:'$\\dfrac{a^2-b^2}{a-b}$' },
{ id:'i4', cat:'no', html:'$\\dfrac{x+1}{x-1}$' },
{ id:'i5', cat:'yes', html:'$\\dfrac{4xy}{6xz}$' },
{ id:'i6', cat:'no', html:'$\\dfrac{1}{x^2+1}$' },
];
const sorter = setupSorter({
poolId:'p2-iv3-pool',
scopeSelector:'#p2-iv3',
items: items,
cats:['yes','no'],
columnLayout:true,
});
document.getElementById('p2-iv3-check').addEventListener('click', ()=>{
const fb = document.getElementById('p2-iv3-fb');
const placedCount = items.filter(it => sorter.placed[it.id]).length;
const correct = items.filter(it => sorter.placed[it.id] === it.cat).length;
if(placedCount < items.length){ feedback(fb, false, '&#10007; Размести все 6 дробей.'); return; }
if(correct === items.length){ feedback(fb, true, '&#10003; Все 6 на месте! +15 XP'); addXp(15,'p2-iv3'); bumpProgress('p2', 25); }
else feedback(fb, false, '&#10007; Правильно ' + correct + ' из 6. Попробуй ещё.');
});
document.getElementById('p2-iv3-reset').addEventListener('click', ()=>{ sorter.reset(); document.getElementById('p2-iv3-fb').style.display = 'none'; });
})();
/* IV4 — trainer */
(function(){
const Q = [
{ q:'$\\dfrac{8a}{12}$', n:2, res:'\\dfrac{2a}{3}', hint:'НОД(8,12) $= 4$' },
{ q:'$\\dfrac{15x^2}{25x}$', n:3, res:'\\dfrac{3x}{5}', hint:'НОД(15,25) $= 5$, сокращаем $x$' },
{ q:'$\\dfrac{x^2-25}{x-5}$', n:1, res:'x+5', hint:'$(x-5)(x+5) / (x-5) = x+5$' },
{ q:'$\\dfrac{a^2-9}{a+3}$', n:1, res:'a-3', hint:'$(a-3)(a+3) / (a+3) = a-3$' },
{ q:'$\\dfrac{14m^2 n}{21mn^2}$', n:2, res:'\\dfrac{2m}{3n}', hint:'НОД(14,21) $= 7$, сокращаем $mn$' },
{ q:'$\\dfrac{6(x-1)}{8(x-1)^2}$', n:3, res:'\\dfrac{3}{4(x-1)}', hint:'НОД(6,8) $= 2$, сокращаем $(x-1)$' },
];
let i = 0, score = 0;
function show(){
if(i >= Q.length){
document.getElementById('p2-iv4-q').innerHTML = '<b>Готово!</b> Результат: ' + score + ' / ' + Q.length;
if(score === Q.length){ addXp(15,'p2-iv4'); bumpProgress('p2', 25); }
else if(score >= 4){ addXp(8,'p2-iv4'); bumpProgress('p2', 15); }
return;
}
document.getElementById('p2-iv4-i').textContent = (i+1);
document.getElementById('p2-iv4-s').textContent = score;
document.getElementById('p2-iv4-q').innerHTML = 'Сократи: ' + Q[i].q;
document.getElementById('p2-iv4-ans').value = '';
renderMath(document.getElementById('p2-iv4-q'));
document.getElementById('p2-iv4-fb').style.display = 'none';
}
function go(){
if(i >= Q.length) return;
const fb = document.getElementById('p2-iv4-fb');
const ans = parseInt(document.getElementById('p2-iv4-ans').value, 10);
if(isNaN(ans)){ feedback(fb, false, '&#10007; Введи число.'); return; }
if(ans === Q[i].n){ score++; feedback(fb, true, '&#10003; Верно! $' + Q[i].res + '$ ('+Q[i].hint+'). Дальше ▶'); }
else feedback(fb, false, '&#10007; Неверно. Должно быть $' + Q[i].n + '$, ответ $' + Q[i].res + '$ ('+Q[i].hint+'). Дальше ▶');
document.getElementById('p2-iv4-s').textContent = score;
i++;
setTimeout(show, 1300);
}
document.getElementById('p2-iv4-go').addEventListener('click', go);
document.getElementById('p2-iv4-ans').addEventListener('keydown', e=>{ if(e.key === 'Enter') go(); });
document.getElementById('p2-iv4-start').addEventListener('click', ()=>{ i=0; score=0; show(); });
show();
})();
wireReadBtn('p2');
}
function buildP3(){
const box = document.getElementById('p3-body');
let html = '';
html += makeCard('theory', 'С одинаковыми знаменателями', '3.1', `
<p>Чтобы сложить (или вычесть) рациональные дроби с <b>одинаковыми знаменателями</b>, надо сложить (вычесть) их числители, а знаменатель оставить прежним:</p>
\\[\\dfrac{A}{C} + \\dfrac{B}{C} = \\dfrac{A+B}{C}, \\qquad \\dfrac{A}{C} - \\dfrac{B}{C} = \\dfrac{A-B}{C}, \\quad C \\ne 0.\\]
<p>Примеры:</p>
<ul style="padding-left:22px;line-height:1.9">
<li>$\\dfrac{x}{5} + \\dfrac{3}{5} = \\dfrac{x+3}{5}$</li>
<li>$\\dfrac{2a}{a-1} - \\dfrac{a+5}{a-1} = \\dfrac{2a - (a+5)}{a-1} = \\dfrac{a-5}{a-1}$ — не забывай менять знаки при вычитании!</li>
</ul>
<details class="spoiler"><summary>Часто допускаемая ошибка</summary><div class="spoiler-body">
При вычитании всегда заключай числитель вычитаемой дроби в <b>скобки</b>: $\\dfrac{A}{C} - \\dfrac{B}{C} = \\dfrac{A - (B)}{C}$, иначе можно «потерять» минус и получить $A - B$ вместо $A - B_1 + B_2$.
</div></details>`);
html += makeCard('rule', 'С разными знаменателями · Алгоритм', '3.2', `
<p>Если знаменатели <b>разные</b> — приводим дроби к общему знаменателю.</p>
<ol style="padding-left:22px;line-height:2">
<li><b>Разложить</b> каждый знаменатель на множители.</li>
<li><b>Найти НОЗ</b> — наименьший общий знаменатель: произведение всех различных множителей, взятых в <b>наивысших</b> степенях.</li>
<li><b>Привести</b> каждую дробь к НОЗ: умножить числитель и знаменатель на нужный <b>дополнительный множитель</b>.</li>
<li><b>Сложить/вычесть</b> числители (знаменатель — НОЗ).</li>
<li><b>Сократить</b> результат, если возможно.</li>
</ol>
<p>Пример НОЗ:</p>
<ul style="padding-left:22px;line-height:1.8">
<li>$\\dfrac{1}{2x}$ и $\\dfrac{1}{3x}$ → НОЗ $= 6x$ (НОК(2,3) $= 6$, $x$ один раз).</li>
<li>$\\dfrac{1}{x-2}$ и $\\dfrac{1}{x^2-4}$ → $x^2-4 = (x-2)(x+2)$, НОЗ $= (x-2)(x+2)$.</li>
</ul>`);
html += makeCard('example', 'Примеры пошагово', '3.3', `
<p><b>Пример 1.</b> $\\dfrac{1}{x-2} + \\dfrac{1}{x+2}$. НОЗ $= (x-2)(x+2) = x^2 - 4$.</p>
\\[\\dfrac{1}{x-2} + \\dfrac{1}{x+2} = \\dfrac{(x+2) + (x-2)}{(x-2)(x+2)} = \\dfrac{2x}{x^2-4}.\\]
<p><b>Пример 2.</b> $\\dfrac{2}{a} - \\dfrac{3}{a^2}$. НОЗ $= a^2$. Дополнительный множитель для $\\dfrac{2}{a}$ равен $a$:</p>
\\[\\dfrac{2}{a} - \\dfrac{3}{a^2} = \\dfrac{2a}{a^2} - \\dfrac{3}{a^2} = \\dfrac{2a - 3}{a^2}.\\]
<p><b>Пример 3 (с сокращением).</b> $\\dfrac{1}{x-1} - \\dfrac{1}{x+1} = \\dfrac{(x+1) - (x-1)}{(x-1)(x+1)} = \\dfrac{2}{x^2-1}.$</p>`);
/* INTERACTIVE 1 — Конструктор НОЗ */
html += `<div class="wg" id="p3-iv1">
<div class="wg-header"><span class="wg-badge">ИНТЕРАКТИВ 1</span><div class="wg-title">Конструктор НОЗ</div></div>
<div class="wg-help">Выбери пару знаменателей — увидишь разложение каждого, НОЗ и дополнительные множители.</div>
<div class="sliders">
<label>Пара №<b id="p3-iv1-idx">1</b> / 5<input type="range" id="p3-iv1-sl" min="1" max="5" step="1" value="1"></label>
</div>
<div id="p3-iv1-pair" style="text-align:center;font-size:1.1rem;padding:12px;background:var(--card);border-radius:9px;margin-bottom:10px"></div>
<div style="display:flex;gap:10px;justify-content:center;margin-bottom:10px">
<button class="btn primary" id="p3-iv1-go">Найти НОЗ</button>
<button class="btn" id="p3-iv1-hide">Скрыть</button>
</div>
<div id="p3-iv1-out" style="padding:12px;background:var(--sec-acc-soft);border-radius:9px;font-size:1rem;min-height:50px;display:none"></div>
</div>`;
/* INTERACTIVE 2 — Калькулятор сложения */
html += `<div class="wg" id="p3-iv2">
<div class="wg-header"><span class="wg-badge">ИНТЕРАКТИВ 2</span><div class="wg-title">Калькулятор $\\dfrac{A}{C} \\pm \\dfrac{B}{C}$</div></div>
<div class="wg-help">Введи целые числители $A$ и $B$, выбери знак и знаменатель $C$ — получишь результат.</div>
<div style="display:flex;gap:8px;align-items:center;flex-wrap:wrap;justify-content:center;margin-bottom:10px">
<span>$A =$</span>
<input type="number" id="p3-iv2-a" class="tinp" style="width:80px;text-align:center" value="3">
<select id="p3-iv2-op" class="tinp" style="width:60px;text-align:center;font-size:1.1rem">
<option value="+">+</option>
<option value="-"></option>
</select>
<span>$B =$</span>
<input type="number" id="p3-iv2-b" class="tinp" style="width:80px;text-align:center" value="5">
<span>над $C =$</span>
<select id="p3-iv2-c" class="tinp" style="width:90px;text-align:center">
<option value="x">x</option>
<option value="x+1">x+1</option>
<option value="x-2">x-2</option>
<option value="2">2</option>
<option value="5">5</option>
</select>
<button class="btn primary" id="p3-iv2-go">Вычислить</button>
</div>
<div id="p3-iv2-out" style="padding:12px;background:var(--card);border-radius:9px;text-align:center;font-size:1.05rem;min-height:50px"></div>
<div class="feedback" id="p3-iv2-fb"></div>
</div>`;
/* INTERACTIVE 3 — DnD-сортер: какой НОЗ? */
html += `<div class="wg" id="p3-iv3">
<div class="wg-header"><span class="wg-badge">ИНТЕРАКТИВ 3</span><div class="wg-title">Какой НОЗ?</div></div>
<div class="wg-help">Перетащи каждую пару знаменателей в нужный ящик: тип НОЗ.</div>
<div class="dnd-hint"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M9 11V6a3 3 0 0 1 6 0v5"/><path d="M9 11h6v8a4 4 0 0 1-8 0z"/></svg> 6 пар — 3 типа НОЗ</div>
<div id="p3-iv3-pool"></div>
<div style="display:grid;grid-template-columns:repeat(auto-fit,minmax(200px,1fr));gap:10px;margin-top:8px">
<div class="drop-box"><h5 data-cat="prod">Произведение знаменателей</h5><div class="drop-items" data-cat="prod"></div></div>
<div class="drop-box"><h5 data-cat="one">Один из знаменателей</h5><div class="drop-items" data-cat="one"></div></div>
<div class="drop-box"><h5 data-cat="num">Просто число</h5><div class="drop-items" data-cat="num"></div></div>
</div>
<div class="actions"><button class="btn primary" id="p3-iv3-check">Проверить</button><button class="btn" id="p3-iv3-reset">Сначала</button></div>
<div class="feedback" id="p3-iv3-fb"></div>
</div>`;
/* INTERACTIVE 4 — Тренажёр сложения/вычитания */
html += `<div class="wg" id="p3-iv4">
<div class="wg-header"><span class="wg-badge">ИНТЕРАКТИВ 4</span><div class="wg-title">Тренажёр сложения и вычитания</div></div>
<div class="wg-help">Реши пример и введи число, которое спрашивают в подсказке.</div>
<div class="score-display"><span>Задача <b id="p3-iv4-i">1</b> / 6</span><span>Очки: <b id="p3-iv4-s">0</b> / 6</span></div>
<div id="p3-iv4-q" style="padding:14px;background:var(--sec-acc-soft);border-radius:10px;font-size:1.05rem;margin-bottom:10px;text-align:center"></div>
<div style="display:flex;gap:10px;align-items:center;flex-wrap:wrap;justify-content:center">
<span id="p3-iv4-prompt" style="font-family:'JetBrains Mono',monospace;font-size:.95rem">ответ =</span>
<input type="number" id="p3-iv4-ans" class="tinp" style="width:100px;text-align:center">
<button class="btn primary" id="p3-iv4-go">Проверить</button>
<button class="btn" id="p3-iv4-start">Заново</button>
</div>
<div class="feedback" id="p3-iv4-fb"></div>
</div>`;
html += secNav('p2', 'p4');
html += readButton('p3');
box.innerHTML = html;
renderMath(box);
/* IV1 — Конструктор НОЗ */
(function(){
const PAIRS = [
{ d1:'x', d2:'3x', fact1:'x', fact2:'3 \\cdot x', lcm:'3x', add1:'3', add2:'1' },
{ d1:'x-1', d2:'x+1', fact1:'(x-1)', fact2:'(x+1)', lcm:'(x-1)(x+1) = x^2 - 1', add1:'(x+1)', add2:'(x-1)' },
{ d1:'x-2', d2:'x^2-4', fact1:'(x-2)', fact2:'(x-2)(x+2)', lcm:'(x-2)(x+2)', add1:'(x+2)', add2:'1' },
{ d1:'2x', d2:'3x', fact1:'2 \\cdot x', fact2:'3 \\cdot x', lcm:'6x', add1:'3', add2:'2' },
{ d1:'x', d2:'x^2', fact1:'x', fact2:'x \\cdot x', lcm:'x^2', add1:'x', add2:'1' },
];
const sl = document.getElementById('p3-iv1-sl');
const idx = document.getElementById('p3-iv1-idx');
const pEl = document.getElementById('p3-iv1-pair');
const out = document.getElementById('p3-iv1-out');
const go = document.getElementById('p3-iv1-go');
const hide= document.getElementById('p3-iv1-hide');
const seen = new Set();
function show(){
const k = +sl.value; idx.textContent = k;
const p = PAIRS[k-1];
pEl.innerHTML = 'Знаменатели: $'+p.d1+'$ и $'+p.d2+'$';
out.innerHTML = '<div>Разложение: $'+p.d1+' = '+p.fact1+'$,&nbsp; $'+p.d2+' = '+p.fact2+'$</div>'
+ '<div style="margin-top:6px"><b>НОЗ $= '+p.lcm+'$</b></div>'
+ '<div style="margin-top:6px;font-size:.92rem;color:var(--muted)">Доп. множители: к $\\dfrac{?}{'+p.d1+'}$ → $'+p.add1+'$; к $\\dfrac{?}{'+p.d2+'}$ → $'+p.add2+'$</div>';
out.style.display = 'none';
renderMath(pEl);
seen.add(k);
if(seen.size === PAIRS.length && !seen.has('done')){ addXp(10,'p3-iv1'); bumpProgress('p3', 15); seen.add('done'); }
}
sl.addEventListener('input', show);
go.addEventListener('click', ()=>{ out.style.display = 'block'; renderMath(out); });
hide.addEventListener('click', ()=>{ out.style.display = 'none'; });
show();
})();
/* IV2 — Калькулятор A/C ± B/C */
(function(){
const aI = document.getElementById('p3-iv2-a');
const bI = document.getElementById('p3-iv2-b');
const opI= document.getElementById('p3-iv2-op');
const cI = document.getElementById('p3-iv2-c');
const out= document.getElementById('p3-iv2-out');
const fb = document.getElementById('p3-iv2-fb');
const go = document.getElementById('p3-iv2-go');
let solved = 0;
function calc(){
const a = parseInt(aI.value, 10), b = parseInt(bI.value, 10);
const op= opI.value;
const c = cI.value;
if(isNaN(a) || isNaN(b)){ feedback(fb, false, '&#10007; Введи целые числа $A$ и $B$.'); return; }
const sum = (op === '+') ? a + b : a - b;
const numericC = /^\d+$/.test(c);
let resHtml;
if(sum === 0){
resHtml = '$\\dfrac{'+a+'}{'+c+'} '+op+' \\dfrac{'+b+'}{'+c+'} = \\dfrac{0}{'+c+'} = 0$';
} else if(numericC){
const C = parseInt(c, 10);
const g = gcd(Math.abs(sum), C);
const n2 = sum/g, d2 = C/g;
if(d2 === 1) resHtml = '$\\dfrac{'+a+'}{'+c+'} '+op+' \\dfrac{'+b+'}{'+c+'} = \\dfrac{'+sum+'}{'+c+'} = '+n2+'$';
else if(g === 1) resHtml = '$\\dfrac{'+a+'}{'+c+'} '+op+' \\dfrac{'+b+'}{'+c+'} = \\dfrac{'+sum+'}{'+c+'}$';
else resHtml = '$\\dfrac{'+a+'}{'+c+'} '+op+' \\dfrac{'+b+'}{'+c+'} = \\dfrac{'+sum+'}{'+c+'} = \\dfrac{'+n2+'}{'+d2+'}$';
} else {
resHtml = '$\\dfrac{'+a+'}{'+c+'} '+op+' \\dfrac{'+b+'}{'+c+'} = \\dfrac{'+sum+'}{'+c+'}$';
}
out.innerHTML = resHtml;
renderMath(out);
feedback(fb, true, '&#10003; Готово! +10 XP');
solved++;
if(solved === 1){ addXp(10,'p3-iv2'); bumpProgress('p3', 15); }
}
go.addEventListener('click', calc);
calc();
})();
/* IV3 — DnD сортер НОЗ */
(function(){
const items = [
{ id:'s1', cat:'prod', html:'$x$ и $x+1$' },
{ id:'s2', cat:'one', html:'$x-3$ и $2(x-3)$' },
{ id:'s3', cat:'prod', html:'$x-1$ и $x+1$' },
{ id:'s4', cat:'num', html:'$4$ и $6$' },
{ id:'s5', cat:'one', html:'$x^2$ и $x^3$' },
{ id:'s6', cat:'one', html:'$x$ и $x^2-x$' },
];
const sorter = setupSorter({
poolId:'p3-iv3-pool',
scopeSelector:'#p3-iv3',
items: items,
cats:['prod','one','num'],
columnLayout:true,
});
document.getElementById('p3-iv3-check').addEventListener('click', ()=>{
const fb = document.getElementById('p3-iv3-fb');
const placedCount = items.filter(it => sorter.placed[it.id]).length;
const correct = items.filter(it => sorter.placed[it.id] === it.cat).length;
if(placedCount < items.length){ feedback(fb, false, '&#10007; Размести все 6 пар.'); return; }
if(correct === items.length){ feedback(fb, true, '&#10003; Все 6 на месте! +15 XP'); addXp(15,'p3-iv3'); bumpProgress('p3', 25); }
else feedback(fb, false, '&#10007; Правильно ' + correct + ' из 6. Попробуй ещё.');
});
document.getElementById('p3-iv3-reset').addEventListener('click', ()=>{ sorter.reset(); document.getElementById('p3-iv3-fb').style.display = 'none'; });
})();
/* IV4 — Тренажёр */
(function(){
const Q = [
{ q:'$\\dfrac{3}{x} + \\dfrac{5}{x}$', ans:8, prompt:'числитель =', hint:'сложили $3 + 5 = 8$, знаменатель тот же', res:'\\dfrac{8}{x}' },
{ q:'$\\dfrac{7}{a} - \\dfrac{4}{a}$', ans:3, prompt:'числитель =', hint:'$7 - 4 = 3$', res:'\\dfrac{3}{a}' },
{ q:'$\\dfrac{1}{x-1} + \\dfrac{1}{x-1}$', ans:2, prompt:'числитель =', hint:'$1 + 1 = 2$', res:'\\dfrac{2}{x-1}' },
{ q:'$\\dfrac{x}{2} + \\dfrac{x}{3}$', ans:5, prompt:'коэф. при x =', hint:'НОЗ $= 6$: $\\dfrac{3x + 2x}{6} = \\dfrac{5x}{6}$', res:'\\dfrac{5x}{6}' },
{ q:'$\\dfrac{1}{x} - \\dfrac{1}{x^2}$', ans:2, prompt:'показатель x в знаменателе =', hint:'НОЗ $= x^2$, числитель $= x - 1$', res:'\\dfrac{x-1}{x^2}' },
{ q:'$\\dfrac{2}{x+1} + \\dfrac{3}{x+1}$', ans:5, prompt:'числитель =', hint:'$2 + 3 = 5$', res:'\\dfrac{5}{x+1}' },
];
let i = 0, score = 0;
function show(){
if(i >= Q.length){
document.getElementById('p3-iv4-q').innerHTML = '<b>Готово!</b> Результат: ' + score + ' / ' + Q.length;
document.getElementById('p3-iv4-prompt').textContent = 'ответ =';
if(score === Q.length){ addXp(15,'p3-iv4'); bumpProgress('p3', 25); }
else if(score >= 4){ addXp(8,'p3-iv4'); bumpProgress('p3', 15); }
return;
}
document.getElementById('p3-iv4-i').textContent = (i+1);
document.getElementById('p3-iv4-s').textContent = score;
document.getElementById('p3-iv4-q').innerHTML = 'Найди: ' + Q[i].q;
document.getElementById('p3-iv4-prompt').textContent = Q[i].prompt;
document.getElementById('p3-iv4-ans').value = '';
renderMath(document.getElementById('p3-iv4-q'));
document.getElementById('p3-iv4-fb').style.display = 'none';
}
function go(){
if(i >= Q.length) return;
const fb = document.getElementById('p3-iv4-fb');
const ans = parseInt(document.getElementById('p3-iv4-ans').value, 10);
if(isNaN(ans)){ feedback(fb, false, '&#10007; Введи целое число.'); return; }
if(ans === Q[i].ans){ score++; feedback(fb, true, '&#10003; Верно! Ответ: $' + Q[i].res + '$ ('+Q[i].hint+'). Дальше ▶'); }
else feedback(fb, false, '&#10007; Неверно. Должно быть $' + Q[i].ans + '$. Ответ: $' + Q[i].res + '$ ('+Q[i].hint+'). Дальше ▶');
document.getElementById('p3-iv4-s').textContent = score;
i++;
setTimeout(show, 1400);
}
document.getElementById('p3-iv4-go').addEventListener('click', go);
document.getElementById('p3-iv4-ans').addEventListener('keydown', e=>{ if(e.key === 'Enter') go(); });
document.getElementById('p3-iv4-start').addEventListener('click', ()=>{ i=0; score=0; show(); });
show();
})();
wireReadBtn('p3');
}
function buildP4(){
const box = document.getElementById('p4-body');
let html = '';
html += makeCard('theory', 'Умножение дробей', '4.1', `
<p>Чтобы <b>умножить</b> две рациональные дроби, надо перемножить их числители и перемножить знаменатели:</p>
\\[\\dfrac{A}{B} \\cdot \\dfrac{C}{D} = \\dfrac{A \\cdot C}{B \\cdot D}, \\quad B \\ne 0,\\ D \\ne 0.\\]
<p>Чаще удобно <b>сократить</b> общие множители <i>до</i> умножения — это упрощает вычисления.</p>
<p>Пример: $\\dfrac{2x}{3} \\cdot \\dfrac{5}{x^2} = \\dfrac{2x \\cdot 5}{3 \\cdot x^2} = \\dfrac{10x}{3x^2} = \\dfrac{10}{3x}$ — сократили $x$.</p>
<details class="spoiler"><summary>Можно ли «крест-накрест»?</summary><div class="spoiler-body">
При умножении сокращать можно <b>любой</b> множитель числителя с <b>любым</b> множителем знаменателя (даже «крест-накрест»): $\\dfrac{a}{b} \\cdot \\dfrac{b}{c} = \\dfrac{a}{c}$, $b$ ушло.
</div></details>`);
html += makeCard('rule', 'Деление дробей · Правило', '4.2', `
<p>Чтобы <b>разделить</b> одну рациональную дробь на другую, надо первую дробь умножить на <b>обратную</b> ко второй:</p>
\\[\\dfrac{A}{B} : \\dfrac{C}{D} = \\dfrac{A}{B} \\cdot \\dfrac{D}{C} = \\dfrac{A \\cdot D}{B \\cdot C}, \\quad B \\ne 0,\\ C \\ne 0,\\ D \\ne 0.\\]
<p><b>Обратная дробь</b> к $\\dfrac{C}{D}$ — это $\\dfrac{D}{C}$ (числитель и знаменатель меняются местами).</p>
<p>Условие $C \\ne 0$ возникает потому, что мы делим на дробь $\\dfrac{C}{D}$, а на ноль делить нельзя.</p>
<p style="background:var(--warn-bg);padding:8px 12px;border-radius:8px;border-left:3px solid var(--warn)"><b>Запомни:</b> деление = умножение на <i>перевёрнутую</i> дробь. После этого работаем как с обычным умножением.</p>`);
html += makeCard('example', 'Возведение дроби в степень', '4.3', `
<p>Чтобы возвести дробь в натуральную степень $n$, надо возвести в эту степень и числитель, и знаменатель:</p>
\\[\\left(\\dfrac{A}{B}\\right)^n = \\dfrac{A^n}{B^n}, \\quad B \\ne 0.\\]
<p>Примеры:</p>
<ul style="padding-left:22px;line-height:1.9">
<li>$\\left(\\dfrac{2}{x}\\right)^3 = \\dfrac{2^3}{x^3} = \\dfrac{8}{x^3}$</li>
<li>$\\left(\\dfrac{x-1}{x+1}\\right)^2 = \\dfrac{(x-1)^2}{(x+1)^2}$ — скобки обязательны!</li>
<li>$\\left(\\dfrac{3a}{b^2}\\right)^2 = \\dfrac{9a^2}{b^4}$</li>
</ul>`);
/* INTERACTIVE 1 — Умножение с подсветкой */
html += `<div class="wg" id="p4-iv1">
<div class="wg-header"><span class="wg-badge">ИНТЕРАКТИВ 1</span><div class="wg-title">Умножение с подсветкой</div></div>
<div class="wg-help">Выбери задачу — увидишь произведение дробей. Нажми «Показать шаги», чтобы увидеть сокращение.</div>
<div class="sliders">
<label>Задача №<b id="p4-iv1-idx">1</b> / 5<input type="range" id="p4-iv1-sl" min="1" max="5" step="1" value="1"></label>
</div>
<div id="p4-iv1-before" style="text-align:center;font-size:1.2rem;padding:14px;background:var(--card);border-radius:9px;margin-bottom:10px;min-height:60px"></div>
<div style="display:flex;gap:10px;justify-content:center;margin-bottom:10px">
<button class="btn primary" id="p4-iv1-go">Показать шаги</button>
<button class="btn" id="p4-iv1-hide">Скрыть</button>
</div>
<div id="p4-iv1-after" style="text-align:center;font-size:1.1rem;padding:14px;background:var(--sec-acc-soft);border-radius:9px;min-height:50px;display:none"></div>
</div>`;
/* INTERACTIVE 2 — Калькулятор деления a/b : c/d */
html += `<div class="wg" id="p4-iv2">
<div class="wg-header"><span class="wg-badge">ИНТЕРАКТИВ 2</span><div class="wg-title">Калькулятор деления $\\dfrac{a}{b} : \\dfrac{c}{d}$</div></div>
<div class="wg-help">Введи целые $a, b, c, d$ — увидишь преобразование к умножению и упрощённый результат.</div>
<div style="display:flex;gap:8px;align-items:center;flex-wrap:wrap;justify-content:center;margin-bottom:10px">
<span>$a =$</span><input type="number" id="p4-iv2-a" class="tinp" style="width:70px;text-align:center" value="3">
<span>$b =$</span><input type="number" id="p4-iv2-b" class="tinp" style="width:70px;text-align:center" value="4">
<span>$c =$</span><input type="number" id="p4-iv2-c" class="tinp" style="width:70px;text-align:center" value="9">
<span>$d =$</span><input type="number" id="p4-iv2-d" class="tinp" style="width:70px;text-align:center" value="8">
<button class="btn primary" id="p4-iv2-go">Вычислить</button>
</div>
<div id="p4-iv2-out" style="padding:12px;background:var(--card);border-radius:9px;text-align:center;font-size:1rem;min-height:50px"></div>
<div class="feedback" id="p4-iv2-fb"></div>
</div>`;
/* INTERACTIVE 3 — Найди ошибку */
html += `<div class="wg" id="p4-iv3">
<div class="wg-header"><span class="wg-badge">ИНТЕРАКТИВ 3</span><div class="wg-title">Верно или ошибка?</div></div>
<div class="wg-help">Реши каждое преобразование: верно оно или нет. 6 заданий.</div>
<div class="score-display"><span>Задача <b id="p4-iv3-i">1</b> / 6</span><span>Очки: <b id="p4-iv3-s">0</b> / 6</span></div>
<div id="p4-iv3-q" style="padding:14px;background:var(--sec-acc-soft);border-radius:10px;font-size:1.1rem;text-align:center;margin-bottom:10px"></div>
<div style="display:flex;gap:10px;justify-content:center;flex-wrap:wrap">
<button class="btn primary" id="p4-iv3-ok" style="background:#10b981;border-color:#10b981">Верно</button>
<button class="btn primary" id="p4-iv3-err" style="background:#dc2626;border-color:#dc2626">Ошибка</button>
</div>
<div class="feedback" id="p4-iv3-fb"></div>
</div>`;
/* INTERACTIVE 4 — Тренажёр умножения и деления */
html += `<div class="wg" id="p4-iv4">
<div class="wg-header"><span class="wg-badge">ИНТЕРАКТИВ 4</span><div class="wg-title">Тренажёр умножения и деления</div></div>
<div class="wg-help">Вычисли и введи число, которое спрашивают в подсказке.</div>
<div class="score-display"><span>Задача <b id="p4-iv4-i">1</b> / 6</span><span>Очки: <b id="p4-iv4-s">0</b> / 6</span></div>
<div id="p4-iv4-q" style="padding:14px;background:var(--sec-acc-soft);border-radius:10px;font-size:1.05rem;margin-bottom:10px;text-align:center"></div>
<div style="display:flex;gap:10px;align-items:center;flex-wrap:wrap;justify-content:center">
<span id="p4-iv4-prompt" style="font-family:'JetBrains Mono',monospace;font-size:.95rem">ответ =</span>
<input type="number" id="p4-iv4-ans" class="tinp" style="width:100px;text-align:center">
<button class="btn primary" id="p4-iv4-go">Проверить</button>
<button class="btn" id="p4-iv4-start">Заново</button>
</div>
<div class="feedback" id="p4-iv4-fb"></div>
</div>`;
html += secNav('p3', 'p5');
html += readButton('p4');
box.innerHTML = html;
renderMath(box);
/* IV1 — Умножение со скрытием шагов */
(function(){
const T = [
{ before:'\\dfrac{3}{x} \\cdot \\dfrac{x}{5}', steps:'\\dfrac{3 \\cdot x}{x \\cdot 5} = \\dfrac{3x}{5x}', after:'\\dfrac{3}{5}', note:'сократили $x$' },
{ before:'\\dfrac{2a}{b^2} \\cdot \\dfrac{b}{4}', steps:'\\dfrac{2a \\cdot b}{b^2 \\cdot 4} = \\dfrac{2ab}{4b^2}', after:'\\dfrac{a}{2b}', note:'сократили $2b$' },
{ before:'\\dfrac{x+1}{x-1} \\cdot \\dfrac{x-1}{x+2}', steps:'\\dfrac{(x+1)(x-1)}{(x-1)(x+2)}', after:'\\dfrac{x+1}{x+2}', note:'сократили $(x-1)$' },
{ before:'\\dfrac{6}{x^2} \\cdot \\dfrac{x}{2}', steps:'\\dfrac{6 \\cdot x}{x^2 \\cdot 2} = \\dfrac{6x}{2x^2}', after:'\\dfrac{3}{x}', note:'сократили $2x$' },
{ before:'\\dfrac{x-3}{4} \\cdot \\dfrac{8}{(x-3)^2}', steps:'\\dfrac{(x-3) \\cdot 8}{4 \\cdot (x-3)^2} = \\dfrac{8(x-3)}{4(x-3)^2}', after:'\\dfrac{2}{x-3}', note:'сократили $4(x-3)$' },
];
const sl = document.getElementById('p4-iv1-sl');
const idx = document.getElementById('p4-iv1-idx');
const bEl = document.getElementById('p4-iv1-before');
const aEl = document.getElementById('p4-iv1-after');
const go = document.getElementById('p4-iv1-go');
const hide = document.getElementById('p4-iv1-hide');
const seen = new Set();
function show(){
const k = +sl.value; idx.textContent = k;
const t = T[k-1];
bEl.innerHTML = '$' + t.before + '$';
aEl.innerHTML = '<div style="font-size:.9rem;color:var(--muted);margin-bottom:6px">'+t.note+'</div>$' + t.before + ' \\;=\\; ' + t.steps + ' \\;=\\; ' + t.after + '$';
aEl.style.display = 'none';
renderMath(bEl);
seen.add(k);
if(seen.size === T.length && !seen.has('done')){ addXp(10,'p4-iv1'); bumpProgress('p4', 15); seen.add('done'); }
}
sl.addEventListener('input', show);
go.addEventListener('click', ()=>{ aEl.style.display = 'block'; renderMath(aEl); });
hide.addEventListener('click', ()=>{ aEl.style.display = 'none'; });
show();
})();
/* IV2 — Деление калькулятор */
(function(){
const aI = document.getElementById('p4-iv2-a');
const bI = document.getElementById('p4-iv2-b');
const cI = document.getElementById('p4-iv2-c');
const dI = document.getElementById('p4-iv2-d');
const out= document.getElementById('p4-iv2-out');
const fb = document.getElementById('p4-iv2-fb');
const go = document.getElementById('p4-iv2-go');
let solved = 0;
function calc(){
const a = parseInt(aI.value,10), b = parseInt(bI.value,10),
c = parseInt(cI.value,10), d = parseInt(dI.value,10);
if([a,b,c,d].some(v => isNaN(v))){ feedback(fb, false, '&#10007; Введи 4 целых числа.'); return; }
if(b === 0 || c === 0 || d === 0){ feedback(fb, false, '&#10007; $b$, $c$, $d$ не должны быть равны $0$.'); return; }
const num = a * d;
const den = b * c;
const g = gcd(Math.abs(num), Math.abs(den));
let n2 = num/g, d2 = den/g;
if(d2 < 0){ n2 = -n2; d2 = -d2; }
let resHtml;
if(d2 === 1) resHtml = '$\\dfrac{'+a+'}{'+b+'} : \\dfrac{'+c+'}{'+d+'} = \\dfrac{'+a+'}{'+b+'} \\cdot \\dfrac{'+d+'}{'+c+'} = \\dfrac{'+num+'}{'+den+'} = '+n2+'$';
else if(g === 1) resHtml = '$\\dfrac{'+a+'}{'+b+'} : \\dfrac{'+c+'}{'+d+'} = \\dfrac{'+a+'}{'+b+'} \\cdot \\dfrac{'+d+'}{'+c+'} = \\dfrac{'+num+'}{'+den+'}$ (несократимо)';
else resHtml = '$\\dfrac{'+a+'}{'+b+'} : \\dfrac{'+c+'}{'+d+'} = \\dfrac{'+a+'}{'+b+'} \\cdot \\dfrac{'+d+'}{'+c+'} = \\dfrac{'+num+'}{'+den+'} = \\dfrac{'+n2+'}{'+d2+'}$';
out.innerHTML = resHtml;
renderMath(out);
feedback(fb, true, '&#10003; Готово! +10 XP');
solved++;
if(solved === 1){ addXp(10,'p4-iv2'); bumpProgress('p4', 15); }
}
go.addEventListener('click', calc);
calc();
})();
/* IV3 — Верно/Ошибка */
(function(){
const Q = [
{ expr:'$\\dfrac{x}{2} \\cdot \\dfrac{3}{y} = \\dfrac{3x}{2y}$', ok:true, why:'умножили числители и знаменатели' },
{ expr:'$\\dfrac{a}{b} : \\dfrac{c}{d} = \\dfrac{ad}{bc}$', ok:true, why:'это правило деления (умножение на обратную)' },
{ expr:'$\\dfrac{a}{b} \\cdot \\dfrac{c}{d} = \\dfrac{a+c}{b+d}$', ok:false, why:'при умножении числители <b>перемножаются</b>, а не складываются' },
{ expr:'$\\dfrac{2}{x} \\cdot \\dfrac{x}{2} = 1$', ok:true, why:'сократили $2x$ в числителе и $2x$ в знаменателе' },
{ expr:'$\\dfrac{a}{b} : \\dfrac{c}{d} = \\dfrac{a}{b} \\cdot \\dfrac{c}{d}$', ok:false, why:'нужно умножать на <b>обратную</b> дробь: $\\dfrac{d}{c}$, а не $\\dfrac{c}{d}$' },
{ expr:'$\\left(\\dfrac{2}{x}\\right)^2 = \\dfrac{2}{x^2}$', ok:false, why:'числитель тоже возводится в степень: правильно $\\dfrac{4}{x^2}$' },
];
let i = 0, score = 0;
function show(){
if(i >= Q.length){
document.getElementById('p4-iv3-q').innerHTML = '<b>Готово!</b> Результат: ' + score + ' / ' + Q.length;
if(score === Q.length){ addXp(15,'p4-iv3'); bumpProgress('p4', 25); }
else if(score >= Q.length - 2){ addXp(8,'p4-iv3'); bumpProgress('p4', 15); }
return;
}
document.getElementById('p4-iv3-i').textContent = (i+1);
document.getElementById('p4-iv3-s').textContent = score;
document.getElementById('p4-iv3-q').innerHTML = Q[i].expr;
renderMath(document.getElementById('p4-iv3-q'));
document.getElementById('p4-iv3-fb').style.display = 'none';
}
function answer(isOk){
if(i >= Q.length) return;
const fb = document.getElementById('p4-iv3-fb');
if(isOk === Q[i].ok){ score++; feedback(fb, true, '&#10003; Верно! '+Q[i].why+'. Дальше ▶'); }
else feedback(fb, false, '&#10007; Неверно. '+Q[i].why+'. Дальше ▶');
document.getElementById('p4-iv3-s').textContent = score;
i++;
setTimeout(show, 1300);
}
document.getElementById('p4-iv3-ok').addEventListener('click', ()=>answer(true));
document.getElementById('p4-iv3-err').addEventListener('click', ()=>answer(false));
show();
})();
/* IV4 — Тренажёр умножения/деления */
(function(){
const Q = [
{ q:'$\\dfrac{3}{4} \\cdot \\dfrac{8}{9}$', ans:2, prompt:'числитель =', hint:'$=\\dfrac{24}{36} = \\dfrac{2}{3}$', res:'\\dfrac{2}{3}' },
{ q:'$\\dfrac{x}{2} \\cdot \\dfrac{4}{x^2}$', ans:1, prompt:'показатель x в знам. =', hint:'$=\\dfrac{4x}{2x^2} = \\dfrac{2}{x}$', res:'\\dfrac{2}{x}' },
{ q:'$\\dfrac{6}{a} : \\dfrac{2}{a}$', ans:3, prompt:'число =', hint:'$=\\dfrac{6}{a} \\cdot \\dfrac{a}{2} = \\dfrac{6a}{2a} = 3$', res:'3' },
{ q:'$\\dfrac{5}{x+1} \\cdot \\dfrac{x+1}{10}$', ans:2, prompt:'знаменатель =', hint:'сократили $(x+1)$ и $5$: $\\dfrac{1}{2}$', res:'\\dfrac{1}{2}' },
{ q:'$\\left(\\dfrac{3}{x}\\right)^2$', ans:9, prompt:'числитель =', hint:'$=\\dfrac{3^2}{x^2} = \\dfrac{9}{x^2}$', res:'\\dfrac{9}{x^2}' },
{ q:'$\\dfrac{a^2}{b} : \\dfrac{a}{b^2}$', ans:1, prompt:'показатель a в ответе =', hint:'$=\\dfrac{a^2}{b} \\cdot \\dfrac{b^2}{a} = \\dfrac{a^2 b^2}{ab} = ab$ — у $a$ степень $1$', res:'ab' },
];
let i = 0, score = 0;
function show(){
if(i >= Q.length){
document.getElementById('p4-iv4-q').innerHTML = '<b>Готово!</b> Результат: ' + score + ' / ' + Q.length;
document.getElementById('p4-iv4-prompt').textContent = 'ответ =';
if(score === Q.length){ addXp(15,'p4-iv4'); bumpProgress('p4', 25); }
else if(score >= 4){ addXp(8,'p4-iv4'); bumpProgress('p4', 15); }
return;
}
document.getElementById('p4-iv4-i').textContent = (i+1);
document.getElementById('p4-iv4-s').textContent = score;
document.getElementById('p4-iv4-q').innerHTML = 'Вычисли: ' + Q[i].q;
document.getElementById('p4-iv4-prompt').textContent = Q[i].prompt;
document.getElementById('p4-iv4-ans').value = '';
renderMath(document.getElementById('p4-iv4-q'));
document.getElementById('p4-iv4-fb').style.display = 'none';
}
function go(){
if(i >= Q.length) return;
const fb = document.getElementById('p4-iv4-fb');
const ans = parseInt(document.getElementById('p4-iv4-ans').value, 10);
if(isNaN(ans)){ feedback(fb, false, '&#10007; Введи целое число.'); return; }
if(ans === Q[i].ans){ score++; feedback(fb, true, '&#10003; Верно! Ответ: $' + Q[i].res + '$ ('+Q[i].hint+'). Дальше ▶'); }
else feedback(fb, false, '&#10007; Неверно. Должно быть $' + Q[i].ans + '$. Ответ: $' + Q[i].res + '$ ('+Q[i].hint+'). Дальше ▶');
document.getElementById('p4-iv4-s').textContent = score;
i++;
setTimeout(show, 1400);
}
document.getElementById('p4-iv4-go').addEventListener('click', go);
document.getElementById('p4-iv4-ans').addEventListener('keydown', e=>{ if(e.key === 'Enter') go(); });
document.getElementById('p4-iv4-start').addEventListener('click', ()=>{ i=0; score=0; show(); });
show();
})();
wireReadBtn('p4');
}
function buildP5(){
const box = document.getElementById('p5-body');
let html = '';
html += makeCard('theory', 'Алгоритм многошагового упрощения', '5.1', `
<p>Сложное рациональное выражение упрощается по чёткому порядку действий:</p>
<ol style="padding-left:22px;line-height:2">
<li><b>Разложи</b> все числители и знаменатели на множители.</li>
<li>Выполни действия <b>в скобках</b> (сначала $+$ и $-$).</li>
<li>Выполни <b>умножение и деление</b> в порядке слева направо.</li>
<li><b>Сократи</b> результат.</li>
</ol>
<p style="background:var(--warn-bg);padding:8px 12px;border-radius:8px;border-left:3px solid var(--warn)"><b>Главное:</b> на каждом шаге следи за <b>ОДЗ</b> — никакой знаменатель не должен обращаться в ноль.</p>
<p>Помни также правила приоритета: сначала всё, что в скобках; затем умножение/деление; затем сложение/вычитание.</p>`);
html += makeCard('example', 'Пример: вся цепочка от начала до конца', '5.2', `
<p>Упростим выражение:</p>
\\[\\left( \\dfrac{1}{x-1} + \\dfrac{1}{x+1} \\right) \\cdot \\dfrac{x^2-1}{2x}.\\]
<p><b>Шаг 1 (скобка).</b> Общий знаменатель — $(x-1)(x+1)$:</p>
\\[\\dfrac{1}{x-1} + \\dfrac{1}{x+1} = \\dfrac{(x+1)+(x-1)}{(x-1)(x+1)} = \\dfrac{2x}{(x-1)(x+1)}.\\]
<p><b>Шаг 2 (умножение).</b> Разложим $x^2-1 = (x-1)(x+1)$:</p>
\\[\\dfrac{2x}{(x-1)(x+1)} \\cdot \\dfrac{(x-1)(x+1)}{2x}.\\]
<p><b>Шаг 3 (сокращение).</b> Сокращаем $2x$ и $(x-1)(x+1)$:</p>
\\[= 1.\\]
<p><b>ОДЗ:</b> $x \\ne 0,\\ x \\ne 1,\\ x \\ne -1$. На ОДЗ результат равен $1$.</p>`);
html += makeCard('rule', 'Полезные тождества для сокращений', '5.3', `
<p>Без этих формул многие дроби не упростить:</p>
<ul style="padding-left:22px;line-height:2">
<li><b>Разность квадратов:</b> $a^2 - b^2 = (a-b)(a+b)$</li>
<li><b>Квадрат суммы/разности:</b> $(a \\pm b)^2 = a^2 \\pm 2ab + b^2$</li>
<li><b>Сумма/разность кубов:</b> $a^3 \\pm b^3 = (a \\pm b)(a^2 \\mp ab + b^2)$</li>
<li><b>Вынесение общего множителя:</b> $ax \\pm ay = a(x \\pm y)$</li>
</ul>
<p>Перед сокращением всегда попробуй применить одно из этих тождеств — часто это единственный путь.</p>
<details class="spoiler"><summary>Подсказка: алгоритм поиска множителя</summary><div class="spoiler-body">
Если в числителе или знаменателе видишь два слагаемых — проверь: это разность квадратов? Если три слагаемых — может, полный квадрат? Если есть общий буквенный множитель — вынеси его. И только после этого сокращай.
</div></details>`);
/* INTERACTIVE 1 — Конвейер пошагового упрощения */
html += `<div class="wg" id="p5-iv1">
<div class="wg-header"><span class="wg-badge">ИНТЕРАКТИВ 1</span><div class="wg-title">Конвейер упрощения</div></div>
<div class="wg-help">Выбери задачу и раскрывай шаги один за другим. После последнего шага — «Готово!». 5 задач.</div>
<div class="sliders">
<label>Задача №<b id="p5-iv1-idx">1</b> / 5<input type="range" id="p5-iv1-sl" min="1" max="5" step="1" value="1"></label>
</div>
<div id="p5-iv1-before" style="text-align:center;font-size:1.18rem;padding:14px;background:var(--card);border-radius:9px;margin-bottom:10px;min-height:60px"></div>
<div id="p5-iv1-steps" style="padding:0;background:transparent;margin-bottom:10px"></div>
<div style="display:flex;gap:10px;justify-content:center;flex-wrap:wrap;margin-bottom:10px">
<button class="btn primary" id="p5-iv1-next">Следующий шаг ▶</button>
<button class="btn" id="p5-iv1-reset">Сначала</button>
</div>
<div class="feedback" id="p5-iv1-fb"></div>
</div>`;
/* INTERACTIVE 2 — DnD: разложи числитель и знаменатель */
html += `<div class="wg" id="p5-iv2">
<div class="wg-header"><span class="wg-badge">ИНТЕРАКТИВ 2</span><div class="wg-title">Сборщик: разложи и сократи</div></div>
<div class="wg-help">Дана дробь $\\dfrac{x^2-4}{x^2+4x+4}$. Перетащи правильные разложения в ящики «Числитель» и «Знаменатель». Лишние карточки оставь в пуле.</div>
<div class="dnd-hint"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M9 11V6a3 3 0 0 1 6 0v5"/><path d="M9 11h6v8a4 4 0 0 1-8 0z"/></svg> Подсказка: $a^2-b^2$ и $(a+b)^2$</div>
<div id="p5-iv2-pool"></div>
<div style="display:grid;grid-template-columns:repeat(auto-fit,minmax(220px,1fr));gap:10px;margin-top:8px">
<div class="drop-box"><h5 data-cat="num">Числитель: $x^2-4$</h5><div class="drop-items" data-cat="num"></div></div>
<div class="drop-box"><h5 data-cat="den">Знаменатель: $x^2+4x+4$</h5><div class="drop-items" data-cat="den"></div></div>
</div>
<div class="actions"><button class="btn primary" id="p5-iv2-check">Проверить</button><button class="btn" id="p5-iv2-reset">Сначала</button></div>
<div id="p5-iv2-result" style="margin-top:10px;text-align:center;font-size:1.05rem;display:none;padding:10px;background:var(--sec-acc-soft);border-radius:9px"></div>
<div class="feedback" id="p5-iv2-fb"></div>
</div>`;
/* INTERACTIVE 3 — Найди ошибку (квикфайр) */
html += `<div class="wg" id="p5-iv3">
<div class="wg-header"><span class="wg-badge">ИНТЕРАКТИВ 3</span><div class="wg-title">Найди ошибку в упрощении</div></div>
<div class="wg-help">6 цепочек преобразований. Реши: верно или ошибка?</div>
<div class="score-display"><span>Задача <b id="p5-iv3-i">1</b> / 6</span><span>Очки: <b id="p5-iv3-s">0</b> / 6</span></div>
<div id="p5-iv3-q" style="padding:14px;background:var(--sec-acc-soft);border-radius:10px;font-size:1.1rem;text-align:center;margin-bottom:10px;min-height:54px"></div>
<div style="display:flex;gap:10px;justify-content:center;flex-wrap:wrap">
<button class="btn primary" id="p5-iv3-ok" style="background:#10b981;border-color:#10b981">Верно</button>
<button class="btn primary" id="p5-iv3-err" style="background:#dc2626;border-color:#dc2626">Ошибка</button>
</div>
<div class="feedback" id="p5-iv3-fb"></div>
</div>`;
/* INTERACTIVE 4 — Тренажёр многошагового упрощения */
html += `<div class="wg" id="p5-iv4">
<div class="wg-header"><span class="wg-badge">ИНТЕРАКТИВ 4</span><div class="wg-title">Тренажёр многошагового упрощения</div></div>
<div class="wg-help">Упрости каждое выражение и введи число, которое спрашивают в подсказке.</div>
<div class="score-display"><span>Задача <b id="p5-iv4-i">1</b> / 5</span><span>Очки: <b id="p5-iv4-s">0</b> / 5</span></div>
<div id="p5-iv4-q" style="padding:14px;background:var(--sec-acc-soft);border-radius:10px;font-size:1.05rem;margin-bottom:10px;text-align:center;min-height:60px"></div>
<div style="display:flex;gap:10px;align-items:center;flex-wrap:wrap;justify-content:center">
<span id="p5-iv4-prompt" style="font-family:'JetBrains Mono',monospace;font-size:.95rem">ответ =</span>
<input type="number" id="p5-iv4-ans" class="tinp" style="width:110px;text-align:center">
<button class="btn primary" id="p5-iv4-go">Проверить</button>
<button class="btn" id="p5-iv4-start">Заново</button>
</div>
<div class="feedback" id="p5-iv4-fb"></div>
</div>`;
html += secNav('p4', 'final1');
html += readButton('p5');
box.innerHTML = html;
renderMath(box);
/* IV1 — Конвейер */
(function(){
const T = [
{
title:'\\dfrac{x^2-9}{x+3} : \\dfrac{x-3}{2}',
steps:[
'\\dfrac{x^2-9}{x+3} : \\dfrac{x-3}{2} = \\dfrac{(x-3)(x+3)}{x+3} \\cdot \\dfrac{2}{x-3}',
'= \\dfrac{(x-3)(x+3) \\cdot 2}{(x+3)(x-3)}',
'= 2'
],
notes:['разность квадратов и деление = умножение на обратную','перемножили','сократили $(x-3)(x+3)$']
},
{
title:'\\dfrac{1}{a} + \\dfrac{1}{a^2}',
steps:[
'\\dfrac{1}{a} + \\dfrac{1}{a^2} = \\dfrac{a}{a^2} + \\dfrac{1}{a^2}',
'= \\dfrac{a+1}{a^2}'
],
notes:['общий знаменатель $a^2$','сложили числители']
},
{
title:'\\dfrac{x^2-4}{x} \\cdot \\dfrac{1}{x+2}',
steps:[
'\\dfrac{x^2-4}{x} \\cdot \\dfrac{1}{x+2} = \\dfrac{(x-2)(x+2)}{x} \\cdot \\dfrac{1}{x+2}',
'= \\dfrac{(x-2)(x+2)}{x(x+2)}',
'= \\dfrac{x-2}{x}'
],
notes:['разложили $x^2-4$','перемножили','сократили $(x+2)$']
},
{
title:'\\dfrac{a}{a-b} - \\dfrac{b}{a-b}',
steps:[
'\\dfrac{a}{a-b} - \\dfrac{b}{a-b} = \\dfrac{a-b}{a-b}',
'= 1'
],
notes:['одинаковые знаменатели — вычли числители','сократили']
},
{
title:'\\dfrac{x+1}{x-1} : \\dfrac{x^2-1}{x-1}',
steps:[
'\\dfrac{x+1}{x-1} : \\dfrac{x^2-1}{x-1} = \\dfrac{x+1}{x-1} \\cdot \\dfrac{x-1}{(x-1)(x+1)}',
'= \\dfrac{(x+1)(x-1)}{(x-1)(x-1)(x+1)}',
'= \\dfrac{1}{x-1}'
],
notes:['обратная дробь + разложение $x^2-1$','перемножили','сократили $(x+1)(x-1)$']
},
];
const sl = document.getElementById('p5-iv1-sl');
const idx = document.getElementById('p5-iv1-idx');
const bEl = document.getElementById('p5-iv1-before');
const stEl = document.getElementById('p5-iv1-steps');
const next = document.getElementById('p5-iv1-next');
const rst = document.getElementById('p5-iv1-reset');
const fb = document.getElementById('p5-iv1-fb');
let step = 0;
const seen = new Set();
function show(){
const k = +sl.value; idx.textContent = k;
const t = T[k-1];
bEl.innerHTML = '$' + t.title + '$';
let out = '';
for(let i=0;i<step && i<t.steps.length;i++){
out += '<div style="padding:10px 14px;background:var(--card);border-radius:9px;margin-bottom:8px;border-left:3px solid var(--pri)"><div style="font-size:.85rem;color:var(--muted);margin-bottom:4px">Шаг '+(i+1)+': '+t.notes[i]+'</div><div style="text-align:center;font-size:1.05rem">$'+t.steps[i]+'$</div></div>';
}
stEl.innerHTML = out;
renderMath(bEl); renderMath(stEl);
if(step >= t.steps.length){
feedback(fb, true, '&#10003; Готово! Все шаги пройдены.');
seen.add(k);
if(seen.size === T.length && !seen.has('done')){ addXp(10,'p5-iv1'); bumpProgress('p5', 15); seen.add('done'); }
} else {
fb.style.display = 'none';
}
}
sl.addEventListener('input', ()=>{ step = 0; show(); });
next.addEventListener('click', ()=>{ const t = T[(+sl.value)-1]; if(step < t.steps.length){ step++; show(); } });
rst.addEventListener('click', ()=>{ step = 0; show(); });
show();
})();
/* IV2 — DnD: разложи числитель и знаменатель */
(function(){
const items = [
{ id:'s1', cat:'num', html:'$(x-2)(x+2)$' },
{ id:'s2', cat:'den', html:'$(x+2)^2$' },
{ id:'s3', cat:'trash', html:'$(x-2)^2$' },
{ id:'s4', cat:'trash', html:'$x^2-2$' },
{ id:'s5', cat:'num', html:'$(x+2)(x-2)$' },
{ id:'s6', cat:'trash', html:'$(x-4)(x+1)$' },
];
const sorter = setupSorter({
poolId:'p5-iv2-pool',
scopeSelector:'#p5-iv2',
items: items,
cats:['num','den'],
columnLayout:true,
});
let solved = false;
document.getElementById('p5-iv2-check').addEventListener('click', ()=>{
const fb = document.getElementById('p5-iv2-fb');
const result = document.getElementById('p5-iv2-result');
const placed = sorter.placed;
const inNum = items.filter(it => placed[it.id] === 'num');
const inDen = items.filter(it => placed[it.id] === 'den');
const trashPlaced = items.filter(it => it.cat === 'trash' && placed[it.id]);
if(inNum.length === 0 || inDen.length === 0){ feedback(fb, false, '&#10007; В каждый ящик нужно что-то положить.'); return; }
const numOk = inNum.every(it => it.cat === 'num');
const denOk = inDen.every(it => it.cat === 'den') && inDen.some(it => it.id === 's2');
if(numOk && denOk && trashPlaced.length === 0){
feedback(fb, true, '&#10003; Верно! Разложили правильно. +10 XP');
result.style.display = 'block';
result.innerHTML = 'После сокращения: $\\dfrac{x^2-4}{x^2+4x+4} = \\dfrac{(x-2)(x+2)}{(x+2)^2} = \\dfrac{x-2}{x+2}$';
renderMath(result);
if(!solved){ addXp(10,'p5-iv2'); bumpProgress('p5', 15); solved = true; }
} else if(trashPlaced.length > 0){
feedback(fb, false, '&#10007; Лишние карточки в ящиках — убери их в пул.');
} else {
feedback(fb, false, '&#10007; Не все разложения подходят. Подсказка: $x^2-4$ — разность квадратов, $x^2+4x+4 = (x+2)^2$.');
}
});
document.getElementById('p5-iv2-reset').addEventListener('click', ()=>{
sorter.reset();
document.getElementById('p5-iv2-fb').style.display = 'none';
document.getElementById('p5-iv2-result').style.display = 'none';
});
})();
/* IV3 — Найди ошибку */
(function(){
const Q = [
{ expr:'$\\dfrac{x+2}{x+3} = \\dfrac{2}{3}$', ok:false, why:'нельзя сокращать <b>слагаемые</b> — только общий <b>множитель</b>' },
{ expr:'$\\dfrac{(x+2)(x-1)}{(x+2)(x+3)} = \\dfrac{x-1}{x+3}$', ok:true, why:'сократили общий множитель $(x+2)$' },
{ expr:'$\\dfrac{x^2-9}{x-3} = x+3$', ok:true, why:'$\\dfrac{(x-3)(x+3)}{x-3} = x+3$' },
{ expr:'$\\dfrac{a^2+a}{a^2-a} = -1$', ok:false, why:'правильно $\\dfrac{a(a+1)}{a(a-1)} = \\dfrac{a+1}{a-1}$' },
{ expr:'$\\dfrac{2x-2}{x-1} = 2$', ok:true, why:'$\\dfrac{2(x-1)}{x-1} = 2$' },
{ expr:'$\\dfrac{x}{x+y} = \\dfrac{1}{1+y}$', ok:false, why:'нельзя сокращать $x$ со слагаемым $x$ в сумме $x+y$' },
];
let i = 0, score = 0;
function show(){
if(i >= Q.length){
document.getElementById('p5-iv3-q').innerHTML = '<b>Готово!</b> Результат: ' + score + ' / ' + Q.length;
if(score === Q.length){ addXp(15,'p5-iv3'); bumpProgress('p5', 25); }
else if(score >= Q.length - 1){ addXp(8,'p5-iv3'); bumpProgress('p5', 15); }
return;
}
document.getElementById('p5-iv3-i').textContent = (i+1);
document.getElementById('p5-iv3-s').textContent = score;
document.getElementById('p5-iv3-q').innerHTML = Q[i].expr;
renderMath(document.getElementById('p5-iv3-q'));
document.getElementById('p5-iv3-fb').style.display = 'none';
}
function answer(isOk){
if(i >= Q.length) return;
const fb = document.getElementById('p5-iv3-fb');
if(isOk === Q[i].ok){ score++; feedback(fb, true, '&#10003; Верно! '+Q[i].why+'. Дальше ▶'); }
else feedback(fb, false, '&#10007; Неверно. '+Q[i].why+'. Дальше ▶');
document.getElementById('p5-iv3-s').textContent = score;
i++;
setTimeout(show, 1500);
}
document.getElementById('p5-iv3-ok').addEventListener('click', ()=>answer(true));
document.getElementById('p5-iv3-err').addEventListener('click', ()=>answer(false));
show();
})();
/* IV4 — Тренажёр многошагового упрощения */
(function(){
const Q = [
{ q:'$\\dfrac{a-b}{a+b} + \\dfrac{2b}{a+b}$', ans:1, prompt:'значение =', hint:'$=\\dfrac{a-b+2b}{a+b} = \\dfrac{a+b}{a+b} = 1$', res:'1' },
{ q:'$\\dfrac{x^2-25}{x+5}$', ans:-5, prompt:'свободный член результата =', hint:'$=\\dfrac{(x-5)(x+5)}{x+5} = x-5$ — свободный член $-5$', res:'x-5' },
{ q:'$\\dfrac{2}{x-3} - \\dfrac{2}{x+3}$', ans:12, prompt:'числитель результата =', hint:'$=\\dfrac{2(x+3)-2(x-3)}{(x-3)(x+3)} = \\dfrac{12}{x^2-9}$', res:'\\dfrac{12}{x^2-9}' },
{ q:'$\\dfrac{a^2-1}{a+1} \\cdot \\dfrac{1}{a-1}$', ans:1, prompt:'значение =', hint:'$=\\dfrac{(a-1)(a+1)}{a+1} \\cdot \\dfrac{1}{a-1} = 1$', res:'1' },
{ q:'$\\dfrac{x}{x^2-1} : \\dfrac{x^2}{x-1}$', ans:-1, prompt:'сумма корней знаменателя $x(x+1)$ =', hint:'$=\\dfrac{x}{(x-1)(x+1)} \\cdot \\dfrac{x-1}{x^2} = \\dfrac{1}{x(x+1)}$. Корни $0$ и $-1$, сумма $-1$.', res:'\\dfrac{1}{x(x+1)}' },
];
let i = 0, score = 0;
function show(){
if(i >= Q.length){
document.getElementById('p5-iv4-q').innerHTML = '<b>Готово!</b> Результат: ' + score + ' / ' + Q.length;
document.getElementById('p5-iv4-prompt').textContent = 'ответ =';
if(score === Q.length){ addXp(15,'p5-iv4'); bumpProgress('p5', 25); }
else if(score >= 4){ addXp(8,'p5-iv4'); bumpProgress('p5', 15); }
return;
}
document.getElementById('p5-iv4-i').textContent = (i+1);
document.getElementById('p5-iv4-s').textContent = score;
document.getElementById('p5-iv4-q').innerHTML = 'Упрости: ' + Q[i].q;
document.getElementById('p5-iv4-prompt').textContent = Q[i].prompt;
document.getElementById('p5-iv4-ans').value = '';
renderMath(document.getElementById('p5-iv4-q'));
document.getElementById('p5-iv4-fb').style.display = 'none';
}
function go(){
if(i >= Q.length) return;
const fb = document.getElementById('p5-iv4-fb');
const ans = parseInt(document.getElementById('p5-iv4-ans').value, 10);
if(isNaN(ans)){ feedback(fb, false, '&#10007; Введи целое число.'); return; }
if(ans === Q[i].ans){ score++; feedback(fb, true, '&#10003; Верно! Ответ: $' + Q[i].res + '$ ('+Q[i].hint+'). Дальше ▶'); }
else feedback(fb, false, '&#10007; Неверно. Должно быть $' + Q[i].ans + '$. Ответ: $' + Q[i].res + '$ ('+Q[i].hint+'). Дальше ▶');
document.getElementById('p5-iv4-s').textContent = score;
i++;
setTimeout(show, 1600);
}
document.getElementById('p5-iv4-go').addEventListener('click', go);
document.getElementById('p5-iv4-ans').addEventListener('keydown', e=>{ if(e.key === 'Enter') go(); });
document.getElementById('p5-iv4-start').addEventListener('click', ()=>{ i=0; score=0; show(); });
show();
})();
wireReadBtn('p5');
}
function buildFinal1(){
const box = document.getElementById('final1-body');
let html = '';
/* Часть А — Шпаргалка главы (5 mini-карточек) */
html += `<div class="card">
<div class="card-header">
<span class="card-icon theory">${ICONS.theory}</span>
<span class="card-title">Шпаргалка главы 1</span>
<span class="card-num">Итог</span>
</div>
<div class="card-body">
<p>Все ключевые правила главы — в одном месте. Просмотри перед боссами!</p>
<div style="display:grid;grid-template-columns:repeat(auto-fit,minmax(220px,1fr));gap:12px;margin-top:10px">
<div style="padding:12px 14px;background:var(--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">§ 1 · ОДЗ</div>
<div style="font-size:.95rem">$\\dfrac{P(x)}{Q(x)},\\ Q(x) \\ne 0$. Решаем $Q(x)=0$ и исключаем корни.</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">§ 2 · Сокращение</div>
<div style="font-size:.95rem">$\\dfrac{AC}{BC} = \\dfrac{A}{B}$. Только общий <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">§ 3 · Сложение</div>
<div style="font-size:.95rem">Одинаковые знам. — числители $\\pm$. Разные — приводим к НОЗ.</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">§ 4 · Умножение и деление</div>
<div style="font-size:.95rem">$\\dfrac{A}{B} \\cdot \\dfrac{C}{D} = \\dfrac{AC}{BD}$. Деление — умножение на обратную.</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">§ 5 · Преобразование</div>
<div style="font-size:.95rem">Разложи → скобки → $\\cdot$ и $:$ слева направо → сократи.</div>
</div>
</div>
</div>
</div>`;
/* Часть Б — 5 боссов */
html += `<div class="card">
<div class="card-header">
<span class="card-icon rule">${ICONS.rule}</span>
<span class="card-title">Боссы главы 1</span>
<span class="card-num">5</span>
</div>
<div class="card-body">
<p>5 интегрированных задач. Каждая комбинирует несколько тем. За каждого побеждённого босса — <b>+10 XP</b>. Победишь всех — <b>+50 XP бонус</b> и ачивка «Магистр рациональных дробей»!</p>
</div>
</div>`;
html += '<div id="ch1-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="ch1-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="ch1-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="ch1-boss-overall-fill" style="height:100%;width:0%;background:linear-gradient(90deg,#0891b2,#22d3ee);transition:width .35s"></div>
</div>
<div id="ch1-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">Глава 1 пройдена! Все 5 боссов повержены. +50 XP бонус.</div>
<a class="btn primary" href="/textbook/algebra-9-ch2" style="text-decoration:none">Дальше: Глава 2 <svg class="ic" viewBox="0 0 24 24"><polyline points="9 18 15 12 9 6"/></svg></a>
</div>
</div>`;
html += secNav('p5', null);
box.innerHTML = html;
renderMath(box);
/* Боссы */
const BOSSES = [
{
n:1, color:'#10b981',
title:'Огр Сокращения',
tag:'§ 1 + § 2',
q:'Найди ОДЗ и сократи: $\\dfrac{x^2-16}{x^2-x-12}$. После сокращения получится $\\dfrac{x+4}{x+?}$. Введи число в знаменателе.',
ans:3,
hint:'$x^2-16 = (x-4)(x+4)$; $x^2-x-12 = (x-4)(x+3)$. Сократи $(x-4)$ — останется $\\dfrac{x+4}{x+3}$.'
},
{
n:2, color:'#0891b2',
title:'Дракон Сложения',
tag:'§ 3 + § 2',
q:'Упрости: $\\dfrac{1}{x-2} + \\dfrac{1}{x+2} - \\dfrac{4}{x^2-4}$. Результат имеет вид $\\dfrac{?}{x+2}$. Введи числитель.',
ans:2,
hint:'Общий знаменатель $x^2-4 = (x-2)(x+2)$. Числитель: $(x+2)+(x-2)-4 = 2x-4 = 2(x-2)$. После сокращения $(x-2)$: $\\dfrac{2}{x+2}$.'
},
{
n:3, color:'#7c3aed',
title:'Гидра Умножения',
tag:'§ 4 + § 5',
q:'Упрости: $\\dfrac{x^2-9}{x+5} \\cdot \\dfrac{x^2+10x+25}{x-3}$. Результат — квадратный трёхчлен $x^2+\\Box x+15$. Введи коэффициент при $x$.',
ans:8,
hint:'$(x^2-9)(x+5)^2 / ((x+5)(x-3)) = (x-3)(x+3) \\cdot (x+5)/(x-3) = (x+3)(x+5) = x^2+8x+15$.'
},
{
n:4, color:'#dc2626',
title:'Тёмный Лорд Деления',
tag:'§ 4 + § 1',
q:'Реши: $\\dfrac{x+1}{x^2-9} : \\dfrac{x+1}{x-3}$. Результат имеет вид $\\dfrac{1}{x+?}$. Введи число.',
ans:3,
hint:'ОДЗ: $x \\ne \\pm 3,\\ x \\ne -1$. $\\dfrac{x+1}{(x-3)(x+3)} \\cdot \\dfrac{x-3}{x+1} = \\dfrac{1}{x+3}$.'
},
{
n:5, color:'#f59e0b',
title:'Мастер Синтеза',
tag:'§ 5 — итог',
q:'Найди значение: $\\left(\\dfrac{1}{a-b} - \\dfrac{1}{a+b}\\right) : \\dfrac{2b}{a^2-b^2}$ при $a=5,\\ b=3$. Введи число.',
ans:1,
hint:'$\\dfrac{(a+b)-(a-b)}{(a-b)(a+b)} : \\dfrac{2b}{a^2-b^2} = \\dfrac{2b}{a^2-b^2} \\cdot \\dfrac{a^2-b^2}{2b} = 1$ — независимо от $a, b$.'
},
];
const cont = document.getElementById('ch1-bosses-container');
const STATE_KEY = 'algebra9_ch1_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="boss-'+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="boss-'+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="boss-'+b.n+'-ans" class="tinp" style="width:120px;text-align:center" placeholder="число">'
+'<button class="btn primary" id="boss-'+b.n+'-go" style="background:'+b.color+';border-color:'+b.color+'">Атаковать</button>'
+'<button class="btn" id="boss-'+b.n+'-hint">Подсказка</button>'
+'</div>'
+'<div class="feedback" id="boss-'+b.n+'-fb"></div>'
+'</div>';
}).join('');
renderMath(cont);
function refreshOverall(){
const won = BOSS_STATE.filter(s => s.defeated).length;
const txt = document.getElementById('ch1-boss-overall');
const fill = document.getElementById('ch1-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('ch1-final-reward');
if(reward && reward.style.display === 'none'){
reward.style.display = 'block';
if(!STATE.achievements.has('ch1_done')){
achievement('ch1_done','Магистр рациональных дробей');
addXp(50, 'ch1-bonus');
bumpProgress('final1', 30);
if(window.confetti){ try{ confetti(); }catch(e){} }
}
}
}
}
BOSSES.forEach((b, idx)=>{
const card = document.getElementById('boss-'+b.n+'-card');
const goBtn = document.getElementById('boss-'+b.n+'-go');
const hintBtn = document.getElementById('boss-'+b.n+'-hint');
const ansInp = document.getElementById('boss-'+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('boss-'+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-ch1-'+b.n);
bumpProgress('final1', 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('boss-'+b.n+'-fb');
fb.className = 'feedback ok';
fb.innerHTML = '<b>Подсказка:</b> '+b.hint;
fb.style.display = 'block';
fb.style.background = 'var(--warn-bg)';
fb.style.color = '#92400e';
fb.style.borderLeftColor = 'var(--warn)';
renderMath(fb);
});
ansInp.addEventListener('keydown', e=>{ if(e.key === 'Enter') goBtn.click(); });
});
refreshOverall();
}
/* ===== Search ===== */
const SEARCH_INDEX = (function(){
const arr=[];
PARAS.forEach(p=>arr.push({kind:'Параграф',title:p.num+' '+p.name,desc:p.sub||'',sec:p.id}));
return arr;
})();
function initSearch(){
const modal=document.getElementById('search-modal'),inp=document.getElementById('search-input'),out=document.getElementById('search-results'),btn=document.getElementById('search-btn');
if(!modal||!inp||!out) return;
let cur=0,rows=[];
function score(q,it){ const t=(it.title+' '+it.desc).toLowerCase(); if(t.includes(q)) return 100+(it.title.toLowerCase().startsWith(q)?50:0); let s=0; q.split(/\s+/).forEach(w=>{if(w&&t.includes(w))s+=10;}); return s; }
function rank(q){ q=q.trim().toLowerCase(); if(!q) return SEARCH_INDEX.slice(0,12); return SEARCH_INDEX.map(it=>({it,s:score(q,it)})).filter(x=>x.s>0).sort((a,b)=>b.s-a.s).slice(0,20).map(x=>x.it); }
function render(){ cur=0; if(!rows.length){out.innerHTML='<div class="search-empty">Ничего не найдено</div>';return;} out.innerHTML=rows.map((r,i)=>'<button class="search-row'+(i===0?' active':'')+'" data-i="'+i+'"><div class="sr-kind">'+r.kind+'</div><div class="sr-title">'+r.title+'</div>'+(r.desc?'<div class="sr-desc">'+(r.desc.length>90?r.desc.slice(0,90)+'…':r.desc)+'</div>':'')+'</button>').join(''); out.querySelectorAll('.search-row').forEach(b=>b.addEventListener('click',()=>{cur=+b.dataset.i;pick();})); }
function pick(){ const r=rows[cur]; if(!r) return; close(); goTo(r.sec); }
function move(d){ const items=out.querySelectorAll('.search-row'); if(!items.length) return; items[cur]&&items[cur].classList.remove('active'); cur=(cur+d+items.length)%items.length; items[cur].classList.add('active'); items[cur].scrollIntoView({block:'nearest'}); }
function open(){ modal.classList.add('show'); inp.value=''; rows=rank(''); render(); setTimeout(()=>inp.focus(),50); }
function close(){ modal.classList.remove('show'); }
btn&&btn.addEventListener('click',open);
modal.addEventListener('click',e=>{if(e.target===modal)close();});
inp.addEventListener('input',()=>{rows=rank(inp.value);render();});
inp.addEventListener('keydown',e=>{ if(e.key==='ArrowDown'){e.preventDefault();move(1);}else if(e.key==='ArrowUp'){e.preventDefault();move(-1);}else if(e.key==='Enter'){e.preventDefault();pick();}else if(e.key==='Escape'){e.preventDefault();close();} });
document.addEventListener('keydown',e=>{ if((e.ctrlKey||e.metaKey)&&(e.key==='k'||e.key==='K')){ e.preventDefault(); if(modal.classList.contains('show')) close(); else open(); } });
}
function initSidebarToggle(){
const side=document.getElementById('col-side'),back=document.getElementById('col-side-backdrop'),btn=document.getElementById('sidebar-btn');
if(!side||!btn) return;
function open(){ side.classList.add('open'); back.classList.add('show'); }
function close(){ side.classList.remove('open'); back.classList.remove('show'); }
btn.addEventListener('click',()=>{ if(side.classList.contains('open')) close(); else open(); });
back.addEventListener('click',close);
document.addEventListener('keydown',e=>{ if(e.key==='Escape') close(); });
}
function init(){
loadProgress(); initTheme(); initSidebarToggle(); initSearch();
buildParaSelector(); refreshProgressUI(); loadServerReadState(); goTo('p1');
setTimeout(()=>achievement('start'), 600);
if(window.LS&&window.LS.xp){
window.LS.xp.load().then(function(s){ if(s&&s.xp>STATE.xp){ STATE.xp=s.xp; STATE.level=calcLevel(STATE.xp); saveProgress(); refreshProgressUI(); if(STATE.current) buildSidebar(STATE.current); } });
}
}
document.addEventListener('DOMContentLoaded', init);
</script>
</body>
</html>