Files
Learn_System/frontend/textbooks/algebra_9_ch1.html
T

1114 lines
82 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 root = document.getElementById('p3-body');
root.innerHTML = `
<div class="card">
<div class="card-header">
<span class="card-icon theory">${ICONS.theory}</span>
<span class="card-title">В разработке</span>
<span class="card-num">§ 3</span>
</div>
<div class="card-body">
<p>Содержание параграфа <b>«Сложение и вычитание»</b> будет добавлено в следующих обновлениях.</p>
<p style="color:var(--muted);font-size:.9rem">Раздел Phase 1.</p>
</div>
</div>` + secNav('p2', 'p4') + readButton('p3');
renderMath(root);
wireReadBtn('p3');
}
function buildP4(){
const root = document.getElementById('p4-body');
root.innerHTML = `
<div class="card">
<div class="card-header">
<span class="card-icon theory">${ICONS.theory}</span>
<span class="card-title">В разработке</span>
<span class="card-num">§ 4</span>
</div>
<div class="card-body">
<p>Содержание параграфа <b>«Умножение и деление»</b> будет добавлено в следующих обновлениях.</p>
<p style="color:var(--muted);font-size:.9rem">Раздел Phase 1.</p>
</div>
</div>` + secNav('p3', 'p5') + readButton('p4');
renderMath(root);
wireReadBtn('p4');
}
function buildP5(){
const root = document.getElementById('p5-body');
root.innerHTML = `
<div class="card">
<div class="card-header">
<span class="card-icon theory">${ICONS.theory}</span>
<span class="card-title">В разработке</span>
<span class="card-num">§ 5</span>
</div>
<div class="card-body">
<p>Содержание параграфа <b>«Преобразование выражений»</b> будет добавлено в следующих обновлениях.</p>
<p style="color:var(--muted);font-size:.9rem">Раздел Phase 1.</p>
</div>
</div>` + secNav('p4', 'final1') + readButton('p5');
renderMath(root);
wireReadBtn('p5');
}
function buildFinal1(){
const root = document.getElementById('final1-body');
root.innerHTML = `
<div class="card">
<div class="card-header">
<span class="card-icon theory">${ICONS.theory}</span>
<span class="card-title">В разработке</span>
<span class="card-num">★</span>
</div>
<div class="card-body">
<p>Содержание финала главы <b>Финал главы — в разработке</b> будет добавлено в следующих обновлениях.</p>
<p style="color:var(--muted);font-size:.9rem">Боссы и итоговые задания будут добавлены в Phase 1.</p>
</div>
</div>` + secNav('p5', null) + readButton('final1');
renderMath(root);
wireReadBtn('final1');
}
/* ===== 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>