Files
Learn_System/frontend/textbooks/algebra_7_ch2.html
T
Maxim Dolgolyov 660e7e2747 feat(gamification): Phase 1 — full kill-switch + textbook XP wrapping
Until now the 'gamification' feature flag did nothing: it had no row in
app_settings, the admin couldn't toggle it, awardXP/awardCoins ignored
it, and the CSS only hid three dashboard widgets — XP bars in textbooks
stayed visible regardless.

Phase 1 closes every hole.

Backend (source of truth):
  • migration 029 seeds feature_gamification_enabled=1
  • new isGamificationEnabled() helper in gamification/_shared.js with a
    30s cache + invalidateGamificationCache() for instant admin toggles
  • awardXP / awardCoins / updateStreak / unlockAchievement /
    checkAchievements all bail out when the flag is off
  • /api/gamification/* and /api/shop/* (user routes) return 404 when
    disabled; admin routes remain open so the switch itself is reachable
  • adminController.updateFeatures gains 'gamification' in the allow-list
    and invalidates the cache on flip

Frontend:
  • LS.isGamificationEnabled() (synchronous, populated by loadFeatures)
    so xp.js + applyCosmetics can bail without a round-trip
  • xp.js load/add/flush become no-ops when the flag is off
  • applyCosmetics skips the round-trip when off
  • CSS .no-gamification rule expanded to cover .hero-xp-badge, .po-xp,
    .xp-card, .xp-bar, #frames-section, and a universal [data-gamified]
    hook for future blocks

Textbooks (Variant 2 of the plan):
  • backend/scripts/wrap_textbook_xp.py — idempotent script that adds
    data-gamified to 167 XP tags across 63 textbook files (chapters +
    hubs, all subjects/grades). Single CSS rule now hides everything.

Verified end-to-end: with the flag off, awardXP/awardCoins write nothing;
flipping back restores normal behavior.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-29 19:43:24 +03:00

1920 lines
146 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>Алгебра 7 · Глава 2 · Выражения и преобразования</title>
<meta name="viewport" content="width=device-width,initial-scale=1">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/katex@0.16.9/dist/katex.min.css">
<script defer src="https://cdn.jsdelivr.net/npm/katex@0.16.9/dist/katex.min.js"></script>
<script defer src="https://cdn.jsdelivr.net/npm/katex@0.16.9/dist/contrib/auto-render.min.js"
onload="renderMathInElement(document.body,{delimiters:[{left:'$$',right:'$$',display:true},{left:'$',right:'$',display:false},{left:'\\[',right:'\\]',display:true},{left:'\\(',right:'\\)',display:false}],throwOnError:false})"></script>
<script src="/js/api.js" defer></script>
<script src="/js/xp.js" defer></script>
<link rel="stylesheet" href="/css/alg7-fx.css">
<script src="/js/alg7-fx.js" defer></script>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800&family=Manrope:wght@600;700;800;900&family=Unbounded:wght@700;800;900&family=JetBrains+Mono:wght@500;700&display=swap" rel="stylesheet">
<style>
:root{
--bg:#fafafa; --card:#fff; --card-soft:#f8fafc; --text:#0f172a; --ink:#0f172a; --muted:#64748b;
--border:#e2e8f0; --sh:0 1px 3px rgba(0,0,0,.06); --sh2:0 4px 14px rgba(0,0,0,.08);
--pri:#059669; --pri2:#047857; --pri-soft:#d1fae5;
--acc:#10b981; --acc2:#059669; --acc-soft:#a7f3d0;
--ok:#10b981; --ok-bg:#d1fae5; --warn:#f59e0b; --warn-bg:#fef3c7;
--bad:#ef4444; --fail:#dc2626; --fail-bg:#fee2e2;
}
.dark{--bg:#0a0e0c; --card:#0a1310; --card-soft:#0e1a14; --text:#e0fef0; --ink:#e0fef0; --muted:#7aa090; --border:#1a2a22}
*{margin:0;padding:0;box-sizing:border-box;-webkit-tap-highlight-color:transparent}
html,body{font-family:'Inter',system-ui,sans-serif;background:var(--bg);color:var(--text);line-height:1.55;font-size:15px}
button,input,select,textarea{font-family:inherit;font-size:inherit}
button{cursor:pointer;border:0;background:transparent;color:inherit}
a{color:inherit;text-decoration:none}
.ic{width:16px;height:16px;display:inline-block;flex-shrink:0;stroke:currentColor;fill:none;stroke-width:2;stroke-linecap:round;stroke-linejoin:round;vertical-align:middle}
.hdr{position:relative;background:linear-gradient(110deg,#064e3b 0%,#059669 55%,#34d399 100%);color:#fff;padding:46px 22px 30px;overflow:hidden;border-bottom:2px solid rgba(167,243,208,.2);min-height:130px}
.hdr::before{content:'ГЛАВА 2';position:absolute;right:-12px;top:50%;transform:translateY(-50%);font-family:'Unbounded',sans-serif;font-size:clamp(5rem,15vw,11rem);font-weight:900;letter-spacing:-.04em;color:transparent;-webkit-text-stroke:1.5px rgba(220,255,235,.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:'P(x)';position:absolute;right:-10px;top:-20px;font-size:clamp(2rem,8vw,5.5rem);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(5,150,105,.32)}
.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(5,150,105,.18);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(5,150,105,.22);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(160px,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:.85rem;font-weight:700;color:var(--text);line-height:1.3;margin-bottom:8px}
.psel-prog{height:4px;background:rgba(5,150,105,.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-p4"] { --sec-acc:#059669; --sec-acc-d:#047857; --sec-acc-soft:#d1fae5; }
.sec[id="sec-p5"] { --sec-acc:#10b981; --sec-acc-d:#059669; --sec-acc-soft:#a7f3d0; }
.sec[id="sec-p6"] { --sec-acc:#0d9488; --sec-acc-d:#0f766e; --sec-acc-soft:#ccfbf1; }
.sec[id="sec-p7"] { --sec-acc:#0891b2; --sec-acc-d:#0e7490; --sec-acc-soft:#cffafe; }
.sec[id="sec-p8"] { --sec-acc:#2563eb; --sec-acc-d:#1d4ed8; --sec-acc-soft:#dbeafe; }
.sec[id="sec-p9"] { --sec-acc:#7c3aed; --sec-acc-d:#6d28d9; --sec-acc-soft:#ede9fe; }
.sec[id="sec-p10"]{ --sec-acc:#c026d3; --sec-acc-d:#a21caf; --sec-acc-soft:#fae8ff; }
.sec[id="sec-p11"]{ --sec-acc:#db2777; --sec-acc-d:#9d174d; --sec-acc-soft:#fce7f3; }
.sec[id="sec-p12"]{ --sec-acc:#dc2626; --sec-acc-d:#b91c1c; --sec-acc-soft:#fee2e2; }
.sec[id="sec-p13"]{ --sec-acc:#ea580c; --sec-acc-d:#c2410c; --sec-acc-soft:#ffedd5; }
.sec[id="sec-p14"]{ --sec-acc:#d97706; --sec-acc-d:#b45309; --sec-acc-soft:#fef3c7; }
.sec[id="sec-final2"]{ --sec-acc:#059669; --sec-acc-d:#047857; --sec-acc-soft:#d1fae5; }
.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(5,150,105,.06);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(5,150,105,.12)}
.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}
.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}
.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))}
.btn.small{padding:5px 11px;font-size:.78rem}
.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))}
.feedback{padding:10px 14px;border-radius:9px;font-weight:600;font-size:.88rem;margin-top:8px;display:none}
.feedback.ok{display:block;background:var(--ok-bg);color:#065f46;border-left:4px solid var(--ok)}
.feedback.fail{display:block;background:var(--fail-bg);color:#7f1d1d;border-left:4px solid var(--fail)}
.col-side{position:sticky;top:14px;align-self:start;height:fit-content;max-height:calc(100vh - 28px);overflow-y:auto}
.sidecard{background:var(--card);border:1px solid var(--border);border-radius:14px;padding:16px;margin-bottom:14px;box-shadow:var(--sh)}
.sidecard h4{font-family:'Unbounded',sans-serif;font-size:.74rem;font-weight:800;color:var(--pri2);text-transform:uppercase;letter-spacing:.07em;margin-bottom:10px;padding-bottom:8px;border-bottom:1px solid var(--border)}
.sidecard-row{margin-bottom:8px;font-size:.86rem;line-height:1.6}
.sidecard-row b{color:var(--pri);font-weight:700}
.sidecard-row:last-child{margin-bottom:0}
@media(max-width:980px){.col-side{position:static;max-height:none}}
.xp-card{background:linear-gradient(135deg,var(--acc-soft),var(--pri-soft));border:1.5px solid var(--acc);border-radius:12px;padding:14px;margin-bottom:14px}
.xp-card-title{font-size:.68rem;font-weight:800;color:var(--acc2);text-transform:uppercase;letter-spacing:.07em;margin-bottom:8px;display:flex;align-items:center;justify-content:space-between}
.xp-level{font-size:1.1rem;font-weight:900;color:var(--acc2);font-family:'Unbounded',sans-serif}
.xp-bar{height:9px;background:rgba(16,185,129,.18);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}
.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}
.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}
.tbl{width:100%;border-collapse:collapse;margin:12px 0;font-size:.88rem}
.tbl th,.tbl td{padding:7px 10px;border:1px solid var(--border);text-align:center}
.tbl th{background:var(--sec-acc-soft,var(--pri-soft));color:var(--sec-acc-d,var(--pri2));font-weight:700}
.ach-popup{position:fixed;top:80px;right:18px;background:linear-gradient(135deg,#059669,#10b981);color:#fff;padding:12px 18px;border-radius:11px;font-weight:700;font-size:.9rem;box-shadow:0 8px 28px rgba(5,150,105,.45);z-index:1002;display:none;align-items:center;gap:8px;max-width:340px}
.ach-popup.show{display:flex}
.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-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.armed{border-color:var(--sec-acc,var(--pri));background:var(--sec-acc-soft,var(--pri-soft));box-shadow:0 0 0 3px rgba(5,150,105,.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}
.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}
.hp-boss{height:14px;background:rgba(220,38,38,.12);border-radius:9px;overflow:hidden;border:1px solid #fecaca;margin:8px 0}
.hp-boss-fill{height:100%;background:linear-gradient(90deg,#dc2626,#f59e0b);border-radius:9px;transition:width .5s cubic-bezier(.4,0,.2,1)}
.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;border-bottom:1px solid var(--border);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}
.boss-card{padding:16px;background:var(--card);border-radius:12px;border:2px solid var(--bad,#dc2626);margin-bottom:14px}
.boss-head{display:flex;align-items:center;gap:10px;margin-bottom:10px}
.boss-title{font-family:'Unbounded',sans-serif;font-weight:800;color:#7f1d1d;font-size:1.04rem;flex:1}
.boss-stage{font-size:.85rem;color:var(--muted)}
.boss-q{font-size:1rem;line-height:1.55;padding:11px 13px;background:var(--card-soft);border-radius:8px;margin-bottom:9px;border-left:3px solid var(--bad,#dc2626)}
</style>
</head>
<body>
<header class="hdr">
<div class="hdr-row">
<div>
<h1>Алгебра 7 · Глава 2</h1>
<div class="hdr-sub">Выражения, тождества, одночлены, многочлены, ФСУ, разложение на множители</div>
</div>
<div class="hdr-side">
<a href="/textbook/algebra-7" class="hdr-btn"><svg class="ic" viewBox="0 0 24 24"><polyline points="15 18 9 12 15 6"/></svg> К алгебре 7</a>
<button id="search-btn" class="hdr-btn" title="Поиск (Ctrl+K)"><svg class="ic" viewBox="0 0 24 24"><circle cx="11" cy="11" r="7"/><path d="m21 21-4-4"/></svg> Поиск</button>
<button id="sidebar-btn" class="hdr-btn"><svg class="ic" viewBox="0 0 24 24"><line x1="4" y1="6" x2="20" y2="6"/><line x1="4" y1="12" x2="20" y2="12"/><line x1="4" y1="18" x2="14" y2="18"/></svg> Шпаргалка</button>
<button id="theme-btn" class="hdr-btn"><svg class="ic" viewBox="0 0 24 24"><path d="M21 12.8A9 9 0 1 1 11.2 3a7 7 0 0 0 9.8 9.8z"/></svg><span id="theme-lab">Тёмная</span></button>
</div>
</div>
</header>
<main class="main">
<div class="col-main">
<section class="hero">
<h2>Преобразования выражений: от тождества до разложения на множители</h2>
<p>В этой главе мы изучаем <b>выражения с переменными</b> и <b>тождественные преобразования</b>. Знакомимся с <b>одночленами</b> и <b>многочленами</b>, осваиваем <b>ФСУ</b> (квадрат суммы/разности, разность квадратов) и три способа <b>разложения на множители</b>.</p>
<div class="hero-row">
<button class="btn-primary" onclick="goTo('p4')"><svg class="ic" viewBox="0 0 24 24"><polygon points="6 4 20 12 6 20 6 4" fill="currentColor" stroke="none"/></svg> Начать § 4</button>
<div class="hero-progress">
<span class="hp-label">Прогресс по главе</span>
<div class="hp-bar"><div id="hero-hp-fill" class="hp-fill"></div></div>
<span id="hero-hp-text" class="hp-text">0%</span>
</div>
<div id="hero-xp-badge" class="hero-xp-badge" data-gamified></div>
</div>
</section>
<section class="psel">
<div class="psel-title">Параграфы главы</div>
<div id="psel-grid" class="psel-grid"></div>
</section>
<section id="sec-p4" class="sec" data-watermark="E(x)"><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-p6" class="sec" data-watermark="axⁿ"><div class="sec-header"><span class="sec-num">§ 6</span><h2 class="sec-h">Одночлен. Стандартный вид. Степень</h2></div><div id="p6-body"></div></section>
<section id="sec-p7" class="sec" data-watermark="\xD7"><div class="sec-header"><span class="sec-num">§ 7</span><h2 class="sec-h">Действия с одночленами</h2></div><div id="p7-body"></div></section>
<section id="sec-p8" class="sec" data-watermark="P(x)"><div class="sec-header"><span class="sec-num">§ 8</span><h2 class="sec-h">Многочлен. Стандартный вид. Степень</h2></div><div id="p8-body"></div></section>
<section id="sec-p9" class="sec" data-watermark="+"><div class="sec-header"><span class="sec-num">§ 9</span><h2 class="sec-h">Сложение и вычитание многочленов</h2></div><div id="p9-body"></div></section>
<section id="sec-p10" class="sec" data-watermark="\xF7"><div class="sec-header"><span class="sec-num">§ 10</span><h2 class="sec-h">Умножение и деление многочлена на одночлен</h2></div><div id="p10-body"></div></section>
<section id="sec-p11" class="sec" data-watermark="P\xB7Q"><div class="sec-header"><span class="sec-num">§ 11</span><h2 class="sec-h">Умножение многочленов</h2></div><div id="p11-body"></div></section>
<section id="sec-p12" class="sec" data-watermark="(a+b)\xB2"><div class="sec-header"><span class="sec-num">§ 12</span><h2 class="sec-h">Квадрат суммы и квадрат разности</h2></div><div id="p12-body"></div></section>
<section id="sec-p13" class="sec" data-watermark="a\xB2b\xB2"><div class="sec-header"><span class="sec-num">§ 13</span><h2 class="sec-h">Разность квадратов: (ab)(a+b) = a\xB2b\xB2</h2></div><div id="p13-body"></div></section>
<section id="sec-p14" class="sec" data-watermark="∏"><div class="sec-header"><span class="sec-num">§ 14</span><h2 class="sec-h">Разложение многочлена на множители</h2></div><div id="p14-body"></div></section>
<section id="sec-final2" class="sec" data-watermark="★"><div class="sec-header"><span class="sec-num" style="background:linear-gradient(135deg,#059669,#10b981)">Финал главы</span><h2 class="sec-h">Итоги. 6 боссов главы 2</h2></div><div id="final2-body"></div></section>
</div>
<aside class="col-side" id="col-side"><div id="sidebar-content"></div></aside>
<div class="col-side-backdrop" id="col-side-backdrop"></div>
</main>
<footer class="foot">Интерактивный учебник «Алгебра 7» · Глава 2 · Выражения и преобразования · LearnSpace</footer>
<div id="ach-popup" class="ach-popup"><svg class="ic" viewBox="0 0 24 24" style="width:22px;height:22px"><polygon points="12,2 22,20 2,20"/></svg><span id="ach-text">Достижение!</span></div>
<div id="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:'p4', progress:{p4:0,p5:0,p6:0,p7:0,p8:0,p9:0,p10:0,p11:0,p12:0,p13:0,p14:0,final2:0}, achievements:new Map(), xp:0, level:1 };
const TOTAL_PARAS = 12;
const _TB_SLUG = 'algebra-7-ch2';
function calcLevel(xp){ return Math.floor(Math.sqrt((xp||0)/100))+1; }
function _xpForLevel(lv){ return (lv-1)*(lv-1)*100; }
const ACH_LABELS = {
start:'Начало главы 2!',
p6_done:'Одночлены покорены!',
p11_done:'Многочлены умеешь умножать!',
p13_done:'ФСУ — на автомате!',
p14_done:'Разложил всё на множители!',
ch2_done:'Глава 2 пройдена!',
};
function loadProgress(){
try{
const s=localStorage.getItem('algebra7_ch2_progress'); if(s) Object.assign(STATE.progress, JSON.parse(s));
const a=localStorage.getItem('algebra7_ch2_achievements');
if(a){ const p=JSON.parse(a); if(Array.isArray(p)) p.forEach(id=>STATE.achievements.set(id, ACH_LABELS[id]||id)); else if(p&&typeof p==='object'){ for(const[id,t] of Object.entries(p)) STATE.achievements.set(id,(t&&t!==id)?t:(ACH_LABELS[id]||id)); } }
STATE.xp=+(localStorage.getItem('algebra7_xp')||0); STATE.level=calcLevel(STATE.xp);
}catch(e){}
}
function saveProgress(){
try{
localStorage.setItem('algebra7_ch2_progress', JSON.stringify(STATE.progress));
localStorage.setItem('algebra7_ch2_achievements', JSON.stringify(Object.fromEntries(STATE.achievements)));
localStorage.setItem('algebra7_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);
if(STATE.progress[key]>=100){
if(key==='p6') achievement('p6_done');
else if(key==='p11') achievement('p11_done');
else if(key==='p13') achievement('p13_done');
else if(key==='p14') achievement('p14_done');
else if(key==='final2') achievement('ch2_done');
}
}
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,'algebra7-ch2-'+(src||'misc'));
if(STATE.level>prev){
const pop=document.getElementById('ach-popup');
if(pop){ document.getElementById('ach-text').textContent='Уровень '+STATE.level+'!'; pop.classList.add('show'); setTimeout(()=>pop.classList.remove('show'),2600); }
if(window.confetti) try{confetti();}catch(e){}
}
}
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:'p4', num:'§ 4', name:'Выражения', sub:'Числовые и с переменными' },
{ id:'p5', num:'§ 5', name:'Тождество', sub:'Тождественные преобразования' },
{ id:'p6', num:'§ 6', name:'Одночлен', sub:'Стандартный вид · степень' },
{ id:'p7', num:'§ 7', name:'Действия с одночленами', sub:'Умножение, возведение в степень' },
{ id:'p8', num:'§ 8', name:'Многочлен', sub:'Стандартный вид · степень' },
{ id:'p9', num:'§ 9', name:'Сложение и вычитание', sub:'Раскрытие скобок' },
{ id:'p10', num:'§ 10', name:'Умножение/деление на одночлен', sub:'(a+b+c)·m' },
{ id:'p11', num:'§ 11', name:'Умножение многочленов', sub:'(a+b)(c+d)' },
{ id:'p12', num:'§ 12', name:'Квадрат суммы и разности', sub:'(a\xB1b)\xB2' },
{ id:'p13', num:'§ 13', name:'Разность квадратов', sub:'a\xB2b\xB2 = (ab)(a+b)' },
{ id:'p14', num:'§ 14', name:'Разложение на множители', sub:'3 способа' },
{ id:'final2', num:'★', name:'Финал главы', sub:'Итоги \xB7 6 боссов', 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 = {
p4:()=>buildP4(), p5:()=>buildP5(), p6:()=>buildP6(), p7:()=>buildP7(),
p8:()=>buildP8(), p9:()=>buildP9(), p10:()=>buildP10(), p11:()=>buildP11(),
p12:()=>buildP12(), p13:()=>buildP13(), p14:()=>buildP14(), final2:()=>buildFinal2(),
};
function ensureBuilt(id){ if(BUILT.has(id)) return; const fn=BUILDERS[id]; if(fn){ fn(); BUILT.add(id); } }
function goTo(id){
STATE.current=id; ensureBuilt(id);
document.querySelectorAll('.sec').forEach(s=>s.classList.remove('active'));
const el=document.getElementById('sec-'+id); if(el) el.classList.add('active');
document.querySelectorAll('.psel-card').forEach(c=>c.classList.toggle('active', c.dataset.id===id));
buildSidebar(id);
window.scrollTo({top:0,behavior:'smooth'});
if((STATE.progress[id]||0)<10) bumpProgress(id, 10);
if(window.renderMathInElement) setTimeout(()=>renderMath(el), 0);
setTimeout(()=>{ try{ wrapGlossary(el); }catch(e){} }, 60);
markLastPara(id);
}
const SIDEBARS = {
p4: { title:'Шпаргалка \xA7 4', rows:[
['Числовое выражение','состоит из чисел и знаков действий'],
['Выражение с переменной','содержит буквы — переменные'],
['Значение','подставь число, вычисли'],
['Область определения','все значения переменных, при которых выражение имеет смысл'],
['Не имеет смысла','деление на ноль'],
]},
p5: { title:'Шпаргалка \xA7 5', rows:[
['Тождественно равные','равны при всех значениях переменных'],
['Тождество','равенство $\\equiv$ всегда'],
['Тождественное преобразование','замена выражения равным ему'],
['Основа','законы умножения и сложения'],
]},
p6: { title:'Шпаргалка \xA7 6', rows:[
['Одночлен','произведение чисел, переменных и их натуральных степеней'],
['Стандартный вид','коэффициент впереди, степени переменных собраны'],
['Коэффициент','число впереди (1 если нет)'],
['Степень одночлена','сумма показателей переменных'],
]},
p7: { title:'Шпаргалка \xA7 7', rows:[
['Произведение одночленов','перемножай коэффициенты и одинаковые буквы'],
['Возведение в степень','коэф. и каждая буква — в степень'],
['$(2x^2y)^3$','$= 8 x^6 y^3$'],
]},
p8: { title:'Шпаргалка \xA7 8', rows:[
['Многочлен','сумма одночленов'],
['Подобные','одночлены с одинаковой буквенной частью'],
['Стандартный вид','подобные приведены'],
['Степень','наибольшая степень одночлена'],
]},
p9: { title:'Шпаргалка \xA7 9', rows:[
['Перед скобкой $+$','скобки опускаем, знаки не меняем'],
['Перед скобкой $-$','скобки опускаем, знаки меняем'],
['Сложение/вычитание','раскрыть скобки + привести подобные'],
]},
p10: { title:'Шпаргалка \xA7 10', rows:[
['Одночлен × многочлен','умножай на каждый член, складывай'],
['$a(b+c) = ab+ac$',''],
['Многочлен : одночлен','каждый член : одночлен'],
]},
p11: { title:'Шпаргалка \xA7 11', rows:[
['(a+b)(c+d)','$= ac+ad+bc+bd$'],
['Алгоритм','каждый член 1-го на каждый член 2-го'],
['После','привести подобные'],
]},
p12: { title:'Шпаргалка \xA7 12', rows:[
['$(a+b)^2$','$= a^2 + 2ab + b^2$'],
['$(a-b)^2$','$= a^2 - 2ab + b^2$'],
['Удвоенное произведение','знак: + или −, тот же что в $\\pm$'],
['$(-a-b)^2$','$= (a+b)^2$'],
]},
p13: { title:'Шпаргалка \xA7 13', rows:[
['$(a+b)(a-b)$','$= a^2 - b^2$'],
['$a^2-b^2$','$= (a-b)(a+b)$'],
['Применение','$51 \\cdot 49 = 50^2 - 1^2 = 2499$'],
]},
p14: { title:'Шпаргалка \xA7 14', rows:[
['Способ 1','вынесение общего множителя за скобки'],
['Способ 2','группировка членов'],
['Способ 3','ФСУ: $a^2 \\pm 2ab + b^2 = (a\\pm b)^2$, $a^2-b^2 = (a-b)(a+b)$'],
['Часто','комбинация способов'],
]},
final2: { title:'Финал главы', rows:[
['\xA74\xA714','теория главы 2'],
['Боссов','6 итоговых задач'],
['Награда','+120 XP за полное прохождение'],
]},
};
const TIPS = [
{ sec:'p4', html:'Выражение не имеет смысла, если в знаменателе $0$ или под корнем отрицательное число. В 7 классе главное — <b>деление на 0</b>.' },
{ sec:'p5', html:'Чтобы доказать, что равенство <b>не</b> тождество — подставь конкретное значение и покажи, что левая часть $\\ne$ правой.' },
{ sec:'p6', html:'Степень одночлена — это <b>сумма показателей</b> всех букв. Коэффициент в степень <b>не</b> входит.' },
{ sec:'p7', html:'$(ab)^n = a^n b^n$. Не забудь возвести <b>коэффициент</b> в степень: $(2x)^3 = 8x^3$, а не $2x^3$.' },
{ sec:'p8', html:'Подобные одночлены — это те, у которых одинаковые буквы в одинаковых степенях. Их коэффициенты складываются.' },
{ sec:'p9', html:'Если перед скобками <b>«−»</b> — знаки <b>всех</b> членов внутри меняются. Это самая частая ошибка.' },
{ sec:'p10', html:'$(-2x)(3x^2-x+5)$ — умножай $(-2x)$ на каждый член, не забывая знак.' },
{ sec:'p11', html:'$(a+b)(c+d) = ac+ad+bc+bd$ — каждый с каждым. После — собери подобные.' },
{ sec:'p12', html:'$(a+b)^2 \\ne a^2 + b^2$. Не забудь про <b>$2ab$</b>! Это самая частая ошибка.' },
{ sec:'p13', html:'$a^2 - b^2 = (a-b)(a+b)$ — применяется как слева-направо (упрощение), так и справа-налево (разложение).' },
{ sec:'p14', html:'Алгоритм: 1) <b>вынеси</b> общий множитель; 2) если не получается — <b>группируй</b>; 3) посмотри на <b>ФСУ</b>.' },
{ sec:'final2', html:'6 боссов проверяют все темы главы. Подсказки доступны — пользуйся ими, чтобы не застрять.' },
];
function buildSidebar(id){
const box=document.getElementById('sidebar-content');
const sb=SIDEBARS[id]||SIDEBARS.p4;
let html='';
const xpForLv=_xpForLevel(STATE.level), xpNext=_xpForLevel(STATE.level+1);
const xpInLv=STATE.xp-xpForLv, xpRange=xpNext-xpForLv;
const xpPct=xpRange>0?Math.round(xpInLv/xpRange*100):100;
html+='<div class="xp-card" data-gamified><div class="xp-card-title" data-gamified><span>XP-прогресс</span><span class="xp-level">Ур. '+STATE.level+'</span></div><div class="xp-bar"><div class="xp-fill" style="width:'+xpPct+'%"></div></div><div class="xp-nums"><span>'+STATE.xp+' XP</span><span>'+xpNext+' XP</span></div></div>';
html+='<div class="sidecard"><h4>'+sb.title+'</h4>';
sb.rows.forEach(([k,v])=>{ html+='<div class="sidecard-row"><b>'+k+'</b>'+(v?' — '+v:'')+'</div>'; });
html+='</div>';
const tip=TIPS.find(t=>t.sec===id)||TIPS[0];
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)">✓ '+text+'</div>'; });
html+='</div>';
}
box.innerHTML=html;
if(window.renderMathInElement) try{ renderMath(box); }catch(e){}
}
function initTheme(){
const t=localStorage.getItem('algebra7_ch2_theme')||'light';
if(t==='dark') document.documentElement.classList.add('dark');
document.getElementById('theme-lab').textContent=t==='dark'?'Светлая':'Тёмная';
document.getElementById('theme-btn').addEventListener('click', ()=>{
document.documentElement.classList.toggle('dark');
const dark=document.documentElement.classList.contains('dark');
localStorage.setItem('algebra7_ch2_theme', dark?'dark':'light');
document.getElementById('theme-lab').textContent=dark?'Светлая':'Тёмная';
});
}
function renderMath(root){ if(window.renderMathInElement){ try{ renderMathInElement(root, {delimiters:[{left:'$$',right:'$$',display:true},{left:'$',right:'$',display:false},{left:'\\[',right:'\\]',display:true},{left:'\\(',right:'\\)',display:false}],throwOnError:false}); }catch(e){} } }
function feedback(elm, ok, text){ if(!elm) return; elm.className='feedback '+(ok?'ok':'fail'); elm.innerHTML=text||(ok?'&#10003; Верно!':'&#10007; Неверно'); elm.style.display='block'; try{renderMath(elm);}catch(e){} }
function fmt(n){ if(!isFinite(n)) return '?'; if(Number.isInteger(n)) return String(n); return Math.abs(n-Math.round(n))<1e-9?String(Math.round(n)):(+n.toFixed(6)).toString(); }
const ICONS = {
repeat:'<svg class="ic" viewBox="0 0 24 24"><polyline points="9 11 12 14 22 4"/><path d="M21 12v7a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h11"/></svg>',
theory:'<svg class="ic" viewBox="0 0 24 24"><path d="M4 19.5A2.5 2.5 0 0 1 6.5 17H20"/><path d="M6.5 2H20v20H6.5A2.5 2.5 0 0 1 4 19.5v-15A2.5 2.5 0 0 1 6.5 2z"/></svg>',
algo:'<svg class="ic" viewBox="0 0 24 24"><polyline points="17 11 21 7 17 3"/><line x1="21" y1="7" x2="9" y2="7"/><polyline points="7 13 3 17 7 21"/><line x1="3" y1="17" x2="15" y2="17"/></svg>',
rule:'<svg class="ic" viewBox="0 0 24 24"><path d="M6 8a6 6 0 0 1 12 0c0 7 3 9 3 9H3s3-2 3-9"/><path d="M10.3 21a1.94 1.94 0 0 0 3.4 0"/></svg>',
example:'<svg class="ic" viewBox="0 0 24 24"><path d="M9 18h6"/><path d="M10 22h4"/><path d="M12 2a7 7 0 0 0-4 13c1 1 2 2 2 4h4c0-2 1-3 2-4a7 7 0 0 0-4-13z"/></svg>',
oral:'<svg class="ic" viewBox="0 0 24 24"><circle cx="12" cy="12" r="10"/><path d="M9.09 9a3 3 0 0 1 5.83 1c0 2-3 3-3 3"/><line x1="12" y1="17" x2="12.01" y2="17"/></svg>',
};
function makeCard(kind, title, num, body){
const labels={repeat:'Повторение',theory:'Теория',algo:'Алгоритм',rule:'Правило',example:'Пример',oral:'Устно'};
return '<div class="card"><div class="card-header"><div class="card-icon '+kind+'">'+ICONS[kind]+'</div><div class="card-title">'+(labels[kind]||'')+(title&&title!==labels[kind]?' \xb7 '+title:'')+'</div>'+(num?'<div class="card-num">'+num+'</div>':'')+'</div><div class="card-body">'+body+'</div></div>';
}
function secNav(prev, next){
const NAMES={p4:'\xA74',p5:'\xA75',p6:'\xA76',p7:'\xA77',p8:'\xA78',p9:'\xA79',p10:'\xA710',p11:'\xA711',p12:'\xA712',p13:'\xA713',p14:'\xA714',final2:'Финал'};
let h='<div class="sec-nav">';
h+=prev?'<button class="btn" onclick="goTo(\''+prev+'\')"><svg class="ic" viewBox="0 0 24 24"><polyline points="15 18 9 12 15 6"/></svg> '+NAMES[prev]+'</button>':'<span></span>';
h+=next?'<button class="btn primary" onclick="goTo(\''+next+'\')">'+NAMES[next]+' <svg class="ic" viewBox="0 0 24 24"><polyline points="9 18 15 12 9 6"/></svg></button>':'<span></span>';
h+='</div>'; return h;
}
let _confettiCanvas=null, _confettiParticles=[], _confettiRaf=null;
function confetti(){
if(!_confettiCanvas){ _confettiCanvas=document.createElement('canvas'); _confettiCanvas.style.cssText='position:fixed;top:0;left:0;width:100%;height:100%;pointer-events:none;z-index:9999'; document.body.appendChild(_confettiCanvas); }
const c=_confettiCanvas; c.width=window.innerWidth; c.height=window.innerHeight;
const ctx=c.getContext('2d');
const colors=['#059669','#10b981','#34d399','#f59e0b','#8b5cf6'];
for(let i=0;i<80;i++){ _confettiParticles.push({x:window.innerWidth/2+(Math.random()-.5)*200,y:window.innerHeight/2,vx:(Math.random()-.5)*14,vy:-10-Math.random()*10,g:.4,life:100,color:colors[i%colors.length],r:4+Math.random()*4,rot:0,vRot:(Math.random()-.5)*.3}); }
if(_confettiRaf) cancelAnimationFrame(_confettiRaf);
function frame(){ ctx.clearRect(0,0,c.width,c.height); _confettiParticles=_confettiParticles.filter(p=>{p.x+=p.vx;p.y+=p.vy;p.vy+=p.g;p.life--;p.rot+=p.vRot;ctx.save();ctx.translate(p.x,p.y);ctx.rotate(p.rot);ctx.fillStyle=p.color;ctx.fillRect(-p.r,-p.r/2,p.r*2,p.r);ctx.restore();return p.life>0&&p.y<c.height+50;}); if(_confettiParticles.length>0) _confettiRaf=requestAnimationFrame(frame); else{ ctx.clearRect(0,0,c.width,c.height); _confettiRaf=null; } }
frame();
}
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 GLOSSARY = [
{ term:'выражение с переменной', def:'Алгебраическое выражение, содержащее буквы (переменные).', sec:'p4', aliases:['выражение с переменной','выражения с переменными','выражением с переменной'] },
{ term:'область определения', def:'Множество всех значений переменных, при которых выражение имеет смысл.', sec:'p4', aliases:['область определения','областью определения','области определения'] },
{ term:'тождество', def:'Равенство двух тождественно равных выражений.', sec:'p5', aliases:['тождество','тождества','тождеством','тождестве'] },
{ term:'тождественное преобразование', def:'Замена выражения другим, тождественно равным ему.', sec:'p5', aliases:['тождественное преобразование','тождественные преобразования','тождественного преобразования','тождественных преобразований','тождественным преобразованием'] },
{ term:'тождественно равные выражения', def:'Выражения, принимающие одинаковые значения при всех значениях переменных из их общей области определения.', sec:'p5', aliases:['тождественно равные выражения','тождественно равными выражениями','тождественно равных выражений','тождественно равные'] },
{ term:'одночлен', def:'Произведение чисел, переменных и натуральных степеней переменных.', sec:'p6', aliases:['одночлен','одночлена','одночлене','одночленов','одночлены','одночленами'] },
{ term:'стандартный вид одночлена', def:'Запись одночлена с числовым множителем впереди и степенями переменных с разными основаниями.', sec:'p6', aliases:['стандартный вид одночлена','стандартного вида одночлена'] },
{ term:'коэффициент', def:'Числовой множитель одночлена, записанный на первом месте.', sec:'p6', aliases:['коэффициент','коэффициента','коэффициенте','коэффициенты','коэффициентом'] },
{ term:'степень одночлена', def:'Сумма показателей степеней всех переменных одночлена.', sec:'p6', aliases:['степень одночлена','степени одночлена','степенью одночлена'] },
{ term:'многочлен', def:'Сумма одночленов (членов многочлена).', sec:'p8', aliases:['многочлен','многочлена','многочлене','многочленов','многочлены','многочленами'] },
{ term:'подобные одночлены', def:'Одночлены с одинаковой буквенной частью; различаются только коэффициентами.', sec:'p8', aliases:['подобные одночлены','подобных одночленов','подобными одночленами','подобные'] },
{ term:'степень многочлена', def:'Наибольшая степень среди степеней входящих в него одночленов после приведения к стандартному виду.', sec:'p8', aliases:['степень многочлена','степени многочлена','степенью многочлена'] },
{ term:'формула сокращённого умножения', def:'Готовая формула вида $(a\\pm b)^2$ или $(a-b)(a+b)$.', sec:'p12', aliases:['формула сокращённого умножения','формулы сокращённого умножения','формулу сокращённого умножения','ФСУ'] },
{ term:'квадрат суммы', def:'$(a+b)^2 = a^2 + 2ab + b^2$.', sec:'p12', aliases:['квадрат суммы','квадрата суммы','квадрату суммы','квадратом суммы'] },
{ term:'квадрат разности', def:'$(a-b)^2 = a^2 - 2ab + b^2$.', sec:'p12', aliases:['квадрат разности','квадрата разности','квадрату разности','квадратом разности'] },
{ term:'разность квадратов', def:'$a^2 - b^2 = (a-b)(a+b)$.', sec:'p13', aliases:['разность квадратов','разности квадратов','разностью квадратов'] },
{ term:'разложение на множители', def:'Представление многочлена в виде произведения одночлена и многочлена или нескольких многочленов.', sec:'p14', aliases:['разложение на множители','разложения на множители','разложением на множители','разложить на множители'] },
{ term:'вынесение общего множителя', def:'Способ разложения: $am + an = a(m+n)$.', sec:'p14', aliases:['вынесение общего множителя','вынесением общего множителя','вынести общий множитель'] },
{ term:'группировка', def:'Способ разложения: объединяем члены в группы, в каждой выносим множитель, потом — общую скобку.', sec:'p14', aliases:['группировка','группировки','группировкой','группировку'] },
];
function wrapGlossary(root){
if(!root||root.__glossDone) return;
const allAliases=[]; GLOSSARY.forEach((g,i)=>g.aliases.forEach(a=>allAliases.push({a,i})));
allAliases.sort((x,y)=>y.a.length-x.a.length);
const re=new RegExp('(?<![\\w\\u0400-\\u04ff-])('+allAliases.map(x=>x.a.replace(/[.*+?^${}()|[\]\\]/g,'\\$&')).join('|')+')(?![\\w\\u0400-\\u04ff-])', 'iu');
const walker=document.createTreeWalker(root, NodeFilter.SHOW_TEXT, { acceptNode(node){ const p=node.parentElement; if(!p) return NodeFilter.FILTER_REJECT; if(p.closest('.katex,.gloss-term,button,input,select,.wg-badge,.card-icon,.sec-num,.psel-num,.hdr,.ach-popup,script,style,.search-modal,.sidecard,.gloss-tip')) return NodeFilter.FILTER_REJECT; if(!re.test(node.nodeValue)) return NodeFilter.FILTER_REJECT; return NodeFilter.FILTER_ACCEPT; } });
const nodes=[]; let n; while((n=walker.nextNode())) nodes.push(n);
nodes.forEach(node=>{ const text=node.nodeValue; const out=document.createDocumentFragment(); let cursor=0; const global=new RegExp(re.source,'giu'); let m; while((m=global.exec(text))!==null){ if(m.index>cursor) out.appendChild(document.createTextNode(text.slice(cursor,m.index))); const found=m[0].toLowerCase(); const hit=allAliases.find(x=>x.a.toLowerCase()===found); const g=hit?GLOSSARY[hit.i]:null; const sp=document.createElement('span'); sp.className='gloss-term'; sp.dataset.gloss=g?g.term:''; sp.textContent=m[0]; out.appendChild(sp); cursor=m.index+m[0].length; } if(cursor<text.length) out.appendChild(document.createTextNode(text.slice(cursor))); node.parentNode.replaceChild(out,node); });
root.__glossDone=true;
}
function initGlossaryTip(){
const tip=document.getElementById('gloss-tip'); if(!tip) return;
let lockOpen=null;
function show(elm){ const g=GLOSSARY.find(x=>x.term===elm.dataset.gloss); if(!g) return; tip.innerHTML='<b>'+g.term[0].toUpperCase()+g.term.slice(1)+'</b><div style="margin-top:4px">'+g.def+'</div><div style="margin-top:6px;font-size:.72rem;color:var(--muted);text-transform:uppercase;letter-spacing:.06em">См. \xA7 '+g.sec.replace('p','')+'</div>'; if(window.renderMathInElement) renderMath(tip); const r=elm.getBoundingClientRect(); tip.classList.add('show'); const tw=tip.offsetWidth,th=tip.offsetHeight; let left=r.left,top=r.bottom+8; if(left+tw>window.innerWidth-12) left=window.innerWidth-tw-12; if(top+th>window.innerHeight-12) top=r.top-th-8; tip.style.left=Math.max(8,left)+'px'; tip.style.top=Math.max(8,top)+'px'; }
function hide(){ tip.classList.remove('show'); }
document.addEventListener('mouseover',e=>{ const elm=e.target.closest&&e.target.closest('.gloss-term'); if(elm&&!lockOpen) show(elm); });
document.addEventListener('mouseout',e=>{ const elm=e.target.closest&&e.target.closest('.gloss-term'); if(elm&&!lockOpen) hide(); });
document.addEventListener('click',e=>{ const elm=e.target.closest&&e.target.closest('.gloss-term'); if(elm){ if(lockOpen===elm){lockOpen=null;hide();}else{lockOpen=elm;show(elm);} }else if(lockOpen&&!e.target.closest('.gloss-tip')){lockOpen=null;hide();} });
}
const SEARCH_INDEX = (function(){
const arr=[];
PARAS.forEach(p=>arr.push({kind:'Параграф',title:p.num+' '+p.name,desc:p.sub||'',sec:p.id}));
GLOSSARY.forEach(g=>arr.push({kind:'Понятие',title:g.term,desc:g.def.replace(/\$/g,''),sec:g.sec}));
[
['Формула','(a+b)\xB2 = a\xB2 + 2ab + b\xB2','\xA712','p12'],
['Формула','(a-b)\xB2 = a\xB2 - 2ab + b\xB2','\xA712','p12'],
['Формула','a\xB2 - b\xB2 = (a-b)(a+b)','\xA713','p13'],
['Формула','a(b+c) = ab + ac — распределительный закон','\xA710','p10'],
['Формула','(a+b)(c+d) = ac+ad+bc+bd','\xA711','p11'],
].forEach(([k,t,d,s])=>arr.push({kind:k,title:t,desc:d,sec:s}));
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(); initGlossaryTip(); initSearch();
buildParaSelector(); refreshProgressUI(); loadServerReadState(); goTo('p4');
setTimeout(()=>achievement('start','Начало главы 2!'), 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);
/* Quick trainer helper: takes array of {q,a} questions where a is a number or function */
function makeTrainer(opts){
/* opts: { containerId, idPrefix, questions, onComplete?, parser? } */
let i=0, score=0;
const Q=opts.questions;
const parser = opts.parser || (v => parseFloat(String(v).replace(',','.')));
function show(){
if(i >= Q.length){
document.getElementById(opts.idPrefix+'-q').innerHTML = '<b>Готово!</b> Результат: '+score+' / '+Q.length;
if(opts.onComplete) opts.onComplete(score, Q.length);
return;
}
document.getElementById(opts.idPrefix+'-i').textContent = (i+1);
document.getElementById(opts.idPrefix+'-s').textContent = score;
document.getElementById(opts.idPrefix+'-q').innerHTML = Q[i].q;
document.getElementById(opts.idPrefix+'-ans').value = '';
renderMath(document.getElementById(opts.idPrefix+'-q'));
document.getElementById(opts.idPrefix+'-fb').style.display = 'none';
}
function go(){
if(i >= Q.length) return;
const fb = document.getElementById(opts.idPrefix+'-fb');
const raw = document.getElementById(opts.idPrefix+'-ans').value.trim();
if(raw === ''){ feedback(fb, false, '&#10007; Введи ответ.'); return; }
const expected = Q[i].a;
let ok = false;
if(typeof expected === 'function') ok = expected(raw);
else { const got = parser(raw); ok = !isNaN(got) && Math.abs(got - expected) < 1e-6; }
if(ok){ score++; feedback(fb, true, '&#10003; Верно! Дальше ▶'); }
else feedback(fb, false, '&#10007; Неверно. Правильно: <b>'+(Q[i].show||expected)+'</b>. Дальше ▶');
document.getElementById(opts.idPrefix+'-s').textContent = score;
i++; setTimeout(show, 1100);
}
document.getElementById(opts.idPrefix+'-go').addEventListener('click', go);
document.getElementById(opts.idPrefix+'-ans').addEventListener('keydown', e=>{ if(e.key==='Enter') go(); });
const restart = document.getElementById(opts.idPrefix+'-start');
if(restart) restart.addEventListener('click', ()=>{ i=0; score=0; show(); });
show();
}
function trainerHTML(idPrefix, total, placeholder){
return '<div class="score-display"><span>Задача <b id="'+idPrefix+'-i">1</b> / '+total+'</span><span>Очки: <b id="'+idPrefix+'-s">0</b> / '+total+'</span></div>'
+'<div id="'+idPrefix+'-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">'
+'<input type="text" id="'+idPrefix+'-ans" class="tinp" placeholder="'+(placeholder||'Ответ')+'" style="width:140px;text-align:center">'
+'<button class="btn primary" id="'+idPrefix+'-go">Проверить</button>'
+'<button class="btn" id="'+idPrefix+'-start">Заново</button>'
+'</div><div class="feedback" id="'+idPrefix+'-fb"></div>';
}
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>'
+' Я прочитал \xA7'+paraId.replace('p','')+' (+10 XP)'
+'</button></div>';
}
function wireReadBtn(paraId){
document.getElementById(paraId+'-read-btn').addEventListener('click', ()=>{
addXp(10, paraId+'-read'); bumpProgress(paraId, 30);
const b=document.getElementById(paraId+'-read-btn'); b.textContent='Прочитано! +10 XP'; b.disabled=true; b.style.opacity=.6;
});
}
/* BUILDERS — stubs filled below */
function buildP4(){
const box = document.getElementById('p4-body');
let html = '';
html += makeCard('theory', 'Числовые выражения и выражения с переменными', '4.1', `
<p><b>Числовое выражение</b> — запись из чисел, соединённых знаками действий и скобок. Его всегда можно вычислить.</p>
<p>Например: $2 + 3 \\cdot 5$, $(7 - 4) : 3$, $0{,}25 \\cdot 2{,}56$.</p>
<p><b>Выражение с переменными</b> — содержит буквы (переменные), например: $7a + 1$, $x - y$, $0{,}25 m - n^2$.</p>
<p>Чтобы найти <b>значение</b> выражения с переменной, нужно подставить вместо буквы число и вычислить.</p>
<details class="spoiler"><summary>Пример</summary><div class="spoiler-body">
Найдём $7a + 1$ при $a = 4$: $\\;7 \\cdot 4 + 1 = 29$.<br>
При $a = -3$: $\\;7 \\cdot (-3) + 1 = -20$.
</div></details>`);
html += makeCard('rule', 'Область определения', '4.2', `
<p><b>Областью определения</b> выражения с переменной называют все значения переменной, при которых выражение <b>имеет смысл</b>.</p>
<p>Главное правило: <b>нельзя делить на ноль</b>. Поэтому выражение $\\dfrac{1}{x-3}$ не имеет смысла при $x = 3$.</p>
<p>У целых выражений (без знаменателя с переменной) область определения — <b>все числа</b>.</p>
<p>У дробных — нужно <b>исключить</b> значения, при которых знаменатель равен нулю.</p>`);
html += makeCard('example', 'Поиск области определения', '4.3', `
<p><b>а)</b> $x^2 - 2x + 6$ — все числа (нет деления на переменную).</p>
<p><b>б)</b> $(x - 8) : 3$ — все числа (делим на 3, не на $x$).</p>
<p><b>в)</b> $12 : (x - 4)$ — все числа, <b>кроме</b> $x = 4$ (иначе деление на 0).</p>
<p><b>г)</b> $(x + 1) : (2x + 5)$ — все числа, кроме $x = -2{,}5$.</p>`);
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">Подставь $x$ и найди значение выражения. Введи число.</div>'
+'<div class="sliders"><label>$x$ = <b id="p4-x-val">2</b><input type="range" id="p4-x-sl" min="-5" max="10" step="1" value="2"></label></div>'
+'<div id="p4-iv1-out" style="padding:14px;background:var(--sec-acc-soft);border-radius:10px;font-size:1rem;line-height:1.9;text-align:center"></div>'
+'</div>';
html += '<div class="wg" id="p4-iv2">'
+'<div class="wg-header"><span class="wg-badge">ИНТЕРАКТИВ 2</span><div class="wg-title">Тренажёр: найди значение</div></div>'
+'<div class="wg-help">Вычисли значение выражения при данных значениях переменных. Введи число (десятичная дробь через точку или запятую).</div>'
+trainerHTML('p4-iv2', 6, 'Значение')
+'</div>';
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">Для каждого выражения найди <b>исключённое</b> значение переменной (или напиши «нет», если ограничений нет).</div>'
+trainerHTML('p4-iv3', 5, 'число или «нет»')
+'</div>';
html += secNav(null, 'p5') + readButton('p4');
box.innerHTML = html; renderMath(box);
/* IV1: slider for 3x - 7 */
(function(){
const sl=document.getElementById('p4-x-sl'), v=document.getElementById('p4-x-val'), out=document.getElementById('p4-iv1-out');
function update(){
const x=+sl.value; v.textContent=x;
const val1 = 3*x - 7;
const val2 = 2*x*x - 5;
out.innerHTML = '<div>$3x - 7$ при $x = '+x+'$: $\\;3 \\cdot '+x+' - 7 = '+val1+'$</div>'
+'<div style="margin-top:6px">$2x^2 - 5$ при $x = '+x+'$: $\\;2 \\cdot '+(x*x)+' - 5 = '+val2+'$</div>';
renderMath(out);
}
sl.addEventListener('input', update); update();
})();
makeTrainer({
idPrefix:'p4-iv2',
questions:[
{ q:'$7a + 1$ при $a = 4$', a:29 },
{ q:'$15 - 2b$ при $b = 6$', a:3 },
{ q:'$x - y$ при $x = -8$, $y = \\dfrac{1}{3}$', a:-25/3, show:'$-8\\tfrac{1}{3}$', parser:(v)=>parseFloat(String(v).replace(',','.').replace(/(\d+)\s*\/\s*(\d+)/, (m,a,b)=>String(+a/+b))) },
{ q:'$0{,}25m - n^2$ при $m = 8$, $n = -5$', a:-23 },
{ q:'$100a^3$ при $a = 2$', a:800 },
{ q:'$b^4 - 8b$ при $b = -1$', a:9 },
],
onComplete:(s,n)=>{ if(s===n){ addXp(15,'p4-iv2'); bumpProgress('p4',25); } else if(s>=4){ addXp(8,'p4-iv2'); bumpProgress('p4',15); } }
});
makeTrainer({
idPrefix:'p4-iv3',
parser:(v)=>{ const t=String(v).trim().toLowerCase(); if(t==='нет'||t==='all'||t==='—') return 'нет'; return parseFloat(t.replace(',','.')); },
questions:[
{ q:'$x^2 - 2x + 6$ — какое значение $x$ исключить?', a:(v)=>String(v).trim().toLowerCase()==='нет', show:'нет' },
{ q:'$12 : (x - 4)$ — исключить $x = ?$', a:4 },
{ q:'$8 : (a - b)$ — при $a - b = ?$ выражение не имеет смысла', a:0 },
{ q:'$(x - 8) : 3$ — есть ли исключения?', a:(v)=>String(v).trim().toLowerCase()==='нет', show:'нет (делим на 3, а не на $x$)' },
{ q:'$(2x + 1) : (x + 7)$ — исключить $x = ?$', a:-7 },
],
onComplete:(s,n)=>{ if(s===n){ addXp(12,'p4-iv3'); bumpProgress('p4',20); } else if(s>=3){ addXp(6,'p4-iv3'); bumpProgress('p4',12); } }
});
wireReadBtn('p4');
}
function buildP5(){
const box = document.getElementById('p5-body');
let html = '';
html += makeCard('theory', 'Тождественно равные выражения', '5.1', `
<p>Сравним значения $7x + 2x$ и $9x$:</p>
<ul style="padding-left:22px;line-height:1.8">
<li>При $x = -1$: $7\\cdot(-1)+2\\cdot(-1) = -9$ и $9\\cdot(-1) = -9$ — равны.</li>
<li>При $x = 0$: оба равны $0$.</li>
<li>При $x = \\dfrac{1}{3}$: оба равны $3$.</li>
</ul>
<p>Эти выражения равны при <b>любых</b> значениях $x$ — на основании распределительного закона $7x + 2x = (7+2)x = 9x$. Такие выражения называют <b>тождественно равными</b>.</p>
<p><b>Определение.</b> Два выражения называются тождественно равными, если они принимают одинаковые значения при всех значениях переменных из их общей области определения.</p>`);
html += makeCard('rule', 'Тождество', '5.2', `
<p><b>Тождеством</b> называют равенство двух тождественно равных выражений.</p>
<p>Примеры тождеств:</p>
<p>$a + 3 = 3 + a$ (переместительный закон сложения)</p>
<p>$a(b + c) = ab + ac$ (распределительный закон)</p>
<p>$m^4 = m \\cdot m \\cdot m \\cdot m$ (определение степени)</p>
<p>Равенство $a^2 - 9 = (a - 3)^2$ — <b>не</b> тождество. Проверим при $a = 0$: $-9 \\ne 9$.</p>`);
html += makeCard('rule', 'Тождественные преобразования', '5.3', `
<p><b>Тождественным преобразованием</b> выражения называется замена одного выражения другим, тождественно равным ему.</p>
<p>Примеры:</p>
<p>$3x + 2x = 5x$ — на основании распределительного закона.</p>
<p>$x^8 \\cdot x^{10} = x^{18}$ — на основании свойства степени.</p>
<p>$a(b - c + d) = ab - ac + ad$ — раскрытие скобок.</p>
<p><b>Как доказать, что равенство не тождество?</b> Подставь конкретное значение и покажи, что левая часть $\\ne$ правой.</p>`);
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">Определи, является ли равенство тождеством. Нажми «Да» или «Нет».</div>'
+'<div class="score-display"><span>Задача <b id="p5-iv1-i">1</b> / 8</span><span>Очки: <b id="p5-iv1-s">0</b> / 8</span></div>'
+'<div id="p5-iv1-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"><button class="btn primary" id="p5-iv1-yes" style="background:#10b981;border-color:#10b981">Да, тождество</button><button class="btn primary" id="p5-iv1-no" style="background:#dc2626;border-color:#dc2626">Нет</button></div>'
+'<div class="feedback" id="p5-iv1-fb"></div></div>';
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">Перетащи каждое выражение к тождественно равному ему.</div>'
+'<div id="p5-iv2-pool"></div>'
+'<div style="display:grid;grid-template-columns:repeat(auto-fit,minmax(170px,1fr));gap:10px;margin-top:8px">'
+'<div class="drop-box"><h5 data-cat="c1">$5a$</h5><div class="drop-items" data-cat="c1"></div></div>'
+'<div class="drop-box"><h5 data-cat="c2">$2a - 2b$</h5><div class="drop-items" data-cat="c2"></div></div>'
+'<div class="drop-box"><h5 data-cat="c3">$a^5$</h5><div class="drop-items" data-cat="c3"></div></div>'
+'<div class="drop-box"><h5 data-cat="c4">$-3a - 15$</h5><div class="drop-items" data-cat="c4"></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 class="feedback" id="p5-iv2-fb"></div></div>';
html += secNav('p4', 'p6') + readButton('p5');
box.innerHTML = html; renderMath(box);
/* IV1 */
(function(){
const Q=[
{ expr:'$3x - 7x = -4x$', isId:true },
{ expr:'$(a+b)^2 = a^2 + b^2$', isId:false },
{ expr:'$a \\cdot b = b \\cdot a$', isId:true },
{ expr:'$a - b = b - a$', isId:false },
{ expr:'$5a^{10} : 5a^{10} = 1$', isId:true },
{ expr:'$(-a)^2 = a^2$', isId:true },
{ expr:'$a^2 - 9 = (a-3)^2$', isId:false },
{ expr:'$a^5 \\cdot a^{10} = (a^3)^5$', isId:true },
];
let i=0,score=0;
function show(){
if(i>=Q.length){
document.getElementById('p5-iv1-q').innerHTML='<b>Готово!</b> Результат: '+score+' / '+Q.length;
if(score===Q.length){ addXp(15,'p5-iv1'); bumpProgress('p5',25); }
else if(score>=6){ addXp(8,'p5-iv1'); bumpProgress('p5',15); }
return;
}
document.getElementById('p5-iv1-i').textContent=(i+1);
document.getElementById('p5-iv1-s').textContent=score;
document.getElementById('p5-iv1-q').innerHTML=Q[i].expr;
renderMath(document.getElementById('p5-iv1-q'));
document.getElementById('p5-iv1-fb').style.display='none';
}
function answer(isYes){
if(i>=Q.length) return;
const fb=document.getElementById('p5-iv1-fb');
if(isYes===Q[i].isId){ score++; feedback(fb,true,'&#10003; Верно! Дальше ▶'); }
else feedback(fb,false,'&#10007; Неверно. '+(Q[i].isId?'Это <b>тождество</b>.':'Это <b>не</b> тождество.')+' Дальше ▶');
document.getElementById('p5-iv1-s').textContent=score;
i++; setTimeout(show,1000);
}
document.getElementById('p5-iv1-yes').addEventListener('click',()=>answer(true));
document.getElementById('p5-iv1-no').addEventListener('click',()=>answer(false));
show();
})();
/* IV2 */
(function(){
const items=[
{ id:'a1', cat:'c1', html:'$3a + 2a$' },
{ id:'a2', cat:'c1', html:'$a + 4a$' },
{ id:'a3', cat:'c2', html:'$2(a - b)$' },
{ id:'a4', cat:'c3', html:'$a \\cdot a^2 \\cdot a^2$' },
{ id:'a5', cat:'c3', html:'$(a^5)^1$' },
{ id:'a6', cat:'c4', html:'$-3(a + 5)$' },
{ id:'a7', cat:'c4', html:'$-15 - 3a$' },
{ id:'a8', cat:'c2', html:'$-2(b - a)$' },
];
const sorter=setupSorter({ poolId:'p5-iv2-pool', scopeSelector:'#p5-iv2', items, cats:['c1','c2','c3','c4'] });
document.getElementById('p5-iv2-check').addEventListener('click',()=>{
const fb=document.getElementById('p5-iv2-fb');
const placed=items.filter(it=>sorter.placed[it.id]).length;
if(placed<items.length){ feedback(fb,false,'&#10007; Размести все 8 карточек.'); return; }
const correct=items.filter(it=>sorter.placed[it.id]===it.cat).length;
if(correct===items.length){ feedback(fb,true,'&#10003; Все 8 правильно! +12 XP'); addXp(12,'p5-iv2'); bumpProgress('p5',25); }
else feedback(fb,false,'&#10007; Правильно '+correct+' из '+items.length+'.');
});
document.getElementById('p5-iv2-reset').addEventListener('click',()=>{ sorter.reset(); document.getElementById('p5-iv2-fb').style.display='none'; });
})();
wireReadBtn('p5');
}
function buildP6(){
const box = document.getElementById('p6-body');
let html = '';
html += makeCard('theory', 'Определение одночлена', '6.1', `
<p><b>Одночленом</b> называется произведение чисел, переменных и натуральных степеней переменных.</p>
<p>Числа, переменные и натуральные степени переменных также являются одночленами.</p>
<p><b>Примеры одночленов:</b> $\\;2a^2 b^3 c, \\quad \\tfrac{2}{7} x^6 y, \\quad -3{,}5 a^2, \\quad 18, \\quad m, \\quad k^4$.</p>
<p><b>Не одночлены:</b> $0{,}7 : x^2 + y$ (есть сложение и деление на переменную), $12 a^2 x^4 - 3 c^3 y^7$ (есть вычитание), $5k : p$ (деление на переменную).</p>`);
html += makeCard('rule', 'Стандартный вид. Коэффициент', '6.2', `
<p>Чтобы привести одночлен к <b>стандартному виду</b>, нужно перемножить все числовые множители и собрать степени переменных.</p>
<p><b>Стандартный вид</b> — запись одночлена в виде произведения числового множителя (на первом месте) и степеней переменных с разными основаниями.</p>
<p>Числовой множитель называется <b>коэффициентом</b> одночлена.</p>
<p>Если в записи коэффициент не виден — он равен $1$ (или $-1$, если перед одночленом «−»).</p>
<details class="spoiler"><summary>Примеры</summary><div class="spoiler-body">
$4x \\cdot 2xy = 8 x^2 y$, коэф. $8$.<br>
$-a^2 y^2 \\cdot (-a^3 y^5) = a^5 y^7$, коэф. $1$.<br>
$12 a^2 x^4 \\cdot (-3 c^3 y^7) = -36 a^2 c^3 x^4 y^7$, коэф. $-36$.
</div></details>`);
html += makeCard('rule', 'Степень одночлена', '6.3', `
<p><b>Степенью одночлена</b> с коэффициентом, отличным от нуля, называется сумма показателей степеней входящих в него переменных.</p>
<p>Если одночлен не содержит переменных, его степень равна $0$.</p>
<ul style="padding-left:22px;line-height:1.85">
<li>$5 a^2 b^3 c$ — шестая степень $(2+3+1)$</li>
<li>$5 a^2$ — вторая степень</li>
<li>$5 a$ — первая степень</li>
<li>$5$ — нулевая степень</li>
</ul>`);
html += '<div class="wg" id="p6-iv1">'
+'<div class="wg-header"><span class="wg-badge">ИНТЕРАКТИВ 1</span><div class="wg-title">Это одночлен?</div></div>'
+'<div class="wg-help">Определи, является ли выражение одночленом. Нажми «Да» или «Нет».</div>'
+'<div class="score-display"><span>Задача <b id="p6-iv1-i">1</b> / 8</span><span>Очки: <b id="p6-iv1-s">0</b> / 8</span></div>'
+'<div id="p6-iv1-q" style="padding:14px;background:var(--sec-acc-soft);border-radius:10px;font-size:1.2rem;text-align:center;margin-bottom:10px"></div>'
+'<div style="display:flex;gap:10px;justify-content:center"><button class="btn primary" id="p6-iv1-yes" style="background:#10b981;border-color:#10b981">Да</button><button class="btn primary" id="p6-iv1-no" style="background:#dc2626;border-color:#dc2626">Нет</button></div>'
+'<div class="feedback" id="p6-iv1-fb"></div></div>';
html += '<div class="wg" id="p6-iv2">'
+'<div class="wg-header"><span class="wg-badge">ИНТЕРАКТИВ 2</span><div class="wg-title">Тренажёр: коэффициент и степень</div></div>'
+'<div class="wg-help">Приведи одночлен к стандартному виду. Введи через запятую: <b>коэффициент,степень</b> (например: <code>-36,12</code>).</div>'
+trainerHTML('p6-iv2', 6, 'коэф,степень')
+'</div>';
html += secNav('p5', 'p7') + readButton('p6');
box.innerHTML = html; renderMath(box);
/* IV1 */
(function(){
const Q=[
{ e:'$4{,}5 a^7$', is:true },
{ e:'$x^3 + y$', is:false },
{ e:'$-12$', is:true },
{ e:'$5 a^4 b - 1$', is:false },
{ e:'$2 y \\cdot 5{,}1 a$', is:true },
{ e:'$\\dfrac{m^2}{3}$', is:true },
{ e:'$5k : p$', is:false },
{ e:'$a a a b^8$', is:true },
];
let i=0,score=0;
function show(){
if(i>=Q.length){ document.getElementById('p6-iv1-q').innerHTML='<b>Готово!</b> '+score+' / '+Q.length; if(score===Q.length){ addXp(12,'p6-iv1'); bumpProgress('p6',20); } else if(score>=6){ addXp(6,'p6-iv1'); bumpProgress('p6',10); } return; }
document.getElementById('p6-iv1-i').textContent=(i+1);
document.getElementById('p6-iv1-s').textContent=score;
document.getElementById('p6-iv1-q').innerHTML=Q[i].e;
renderMath(document.getElementById('p6-iv1-q'));
document.getElementById('p6-iv1-fb').style.display='none';
}
function answer(isYes){
if(i>=Q.length) return;
const fb=document.getElementById('p6-iv1-fb');
if(isYes===Q[i].is){ score++; feedback(fb,true,'&#10003; Верно! Дальше ▶'); }
else feedback(fb,false,'&#10007; Неверно. '+(Q[i].is?'Это <b>одночлен</b>.':'Это <b>не</b> одночлен.')+' Дальше ▶');
document.getElementById('p6-iv1-s').textContent=score;
i++; setTimeout(show,1000);
}
document.getElementById('p6-iv1-yes').addEventListener('click',()=>answer(true));
document.getElementById('p6-iv1-no').addEventListener('click',()=>answer(false));
show();
})();
/* IV2 */
makeTrainer({
idPrefix:'p6-iv2',
parser:(v)=>v,
questions:[
{ q:'$3 a^7 a^2$', a:(v)=>{const m=String(v).trim().split(/[,;\s]+/); return +m[0]===3 && +m[1]===9;}, show:'3, 9' },
{ q:'$0{,}25 x^2 y \\cdot 4 y$', a:(v)=>{const m=String(v).trim().replace(',','.').split(/[;\s]+/); return Math.abs(+m[0]-1)<1e-6 && +m[1]===3;}, show:'1, 3' },
{ q:'$-2 a^3 \\cdot (-a^2) \\cdot a b$', a:(v)=>{const m=String(v).trim().split(/[,;\s]+/); return +m[0]===2 && +m[1]===7;}, show:'2, 7' },
{ q:'$0{,}1 m^6 n \\cdot 65 m^7 n^2$', a:(v)=>{const m=String(v).trim().replace(',','.').split(/[;\s]+/); return Math.abs(+m[0]-6.5)<1e-3 && +m[1]===16;}, show:'6.5, 16' },
{ q:'$-\\dfrac{5}{7} x^5 \\cdot 1{,}4 x y^2$', a:(v)=>{const m=String(v).trim().replace(',','.').split(/[;\s]+/); return Math.abs(+m[0]+1)<1e-3 && +m[1]===8;}, show:'-1, 8' },
{ q:'$-a^3 b^2 c \\cdot (-a b)$', a:(v)=>{const m=String(v).trim().split(/[,;\s]+/); return +m[0]===1 && +m[1]===8;}, show:'1, 8' },
],
onComplete:(s,n)=>{ if(s===n){ addXp(18,'p6-iv2'); bumpProgress('p6',30); } else if(s>=4){ addXp(10,'p6-iv2'); bumpProgress('p6',18); } }
});
wireReadBtn('p6');
}
function buildP7(){
const box = document.getElementById('p7-body');
let html = '';
html += makeCard('rule', 'Произведение одночленов', '7.1', `
<p>Чтобы найти произведение двух одночленов, нужно:</p>
<ol style="padding-left:22px;line-height:1.9">
<li>перемножить коэффициенты;</li>
<li>перемножить одинаковые буквы (сложить показатели);</li>
<li>записать ответ в стандартном виде.</li>
</ol>
<p>Например, $3 a^2 b \\cdot (-5 a b^4) = (3 \\cdot (-5)) \\cdot (a^2 \\cdot a) \\cdot (b \\cdot b^4) = -15 a^3 b^5$.</p>`);
html += makeCard('rule', 'Возведение одночлена в степень', '7.2', `
<p>По правилу $(ab)^n = a^n b^n$ — каждый множитель возводится в степень.</p>
<p>Чтобы возвести одночлен в степень, нужно:</p>
<ol style="padding-left:22px;line-height:1.9">
<li>возвести в эту степень коэффициент;</li>
<li>каждую переменную возвести в степень, перемножив показатели.</li>
</ol>
<p>Например, $(2 x^2 y)^3 = 2^3 \\cdot x^{2 \\cdot 3} \\cdot y^3 = 8 x^6 y^3$.</p>`);
html += makeCard('example', 'Применение', '7.3', `
<p><b>а)</b> $5 m^3 \\cdot (-4 m^2 n) = -20 m^5 n$</p>
<p><b>б)</b> $(-3 a b^2)^2 = 9 a^2 b^4$</p>
<p><b>в)</b> $(2 x y)^3 \\cdot x = 8 x^4 y^3$</p>
<p><b>г)</b> $-\\dfrac{1}{2} c^3 \\cdot 6 c d^4 = -3 c^4 d^4$</p>`);
html += '<div class="wg" id="p7-iv1">'
+'<div class="wg-header"><span class="wg-badge">ИНТЕРАКТИВ 1</span><div class="wg-title">Тренажёр: произведение одночленов</div></div>'
+'<div class="wg-help">Найди произведение и введи через запятую: <b>коэффициент, степень одночлена</b>.</div>'
+trainerHTML('p7-iv1', 6, 'коэф,степень')
+'</div>';
html += '<div class="wg" id="p7-iv2">'
+'<div class="wg-header"><span class="wg-badge">ИНТЕРАКТИВ 2</span><div class="wg-title">Тренажёр: возведение в степень</div></div>'
+'<div class="wg-help">Возведи одночлен в степень и введи: <b>коэффициент, степень одночлена</b>.</div>'
+trainerHTML('p7-iv2', 5, 'коэф,степень')
+'</div>';
html += secNav('p6', 'p8') + readButton('p7');
box.innerHTML = html; renderMath(box);
function parsePair(v){ return String(v).trim().replace(',','.').split(/[,;\s]+/).filter(Boolean); }
makeTrainer({
idPrefix:'p7-iv1',
parser:(v)=>v,
questions:[
{ q:'$3 a^2 b \\cdot (-5 a b^4)$', a:(v)=>{const m=parsePair(v); return +m[0]===-15 && +m[1]===8;}, show:'-15, 8 → $-15 a^3 b^5$' },
{ q:'$5 m^3 \\cdot (-4 m^2 n)$', a:(v)=>{const m=parsePair(v); return +m[0]===-20 && +m[1]===6;}, show:'-20, 6 → $-20 m^5 n$' },
{ q:'$2 x \\cdot 5 x^3 y^2$', a:(v)=>{const m=parsePair(v); return +m[0]===10 && +m[1]===6;}, show:'10, 6 → $10 x^4 y^2$' },
{ q:'$-\\dfrac{1}{2} c^3 \\cdot 6 c d^4$', a:(v)=>{const m=parsePair(v); return +m[0]===-3 && +m[1]===8;}, show:'-3, 8 → $-3 c^4 d^4$' },
{ q:'$0{,}4 a^2 b \\cdot 25 a^3 b^2$', a:(v)=>{const m=parsePair(v); return +m[0]===10 && +m[1]===8;}, show:'10, 8 → $10 a^5 b^3$' },
{ q:'$-x y \\cdot (-3 x^2 y^4)$', a:(v)=>{const m=parsePair(v); return +m[0]===3 && +m[1]===8;}, show:'3, 8 → $3 x^3 y^5$' },
],
onComplete:(s,n)=>{ if(s===n){ addXp(15,'p7-iv1'); bumpProgress('p7',30); } else if(s>=4){ addXp(8,'p7-iv1'); bumpProgress('p7',18); } }
});
makeTrainer({
idPrefix:'p7-iv2',
parser:(v)=>v,
questions:[
{ q:'$(2 x^2 y)^3$', a:(v)=>{const m=parsePair(v); return +m[0]===8 && +m[1]===9;}, show:'8, 9 → $8 x^6 y^3$' },
{ q:'$(-3 a b^2)^2$', a:(v)=>{const m=parsePair(v); return +m[0]===9 && +m[1]===6;}, show:'9, 6 → $9 a^2 b^4$' },
{ q:'$(-2 m^4)^3$', a:(v)=>{const m=parsePair(v); return +m[0]===-8 && +m[1]===12;}, show:'-8, 12 → $-8 m^{12}$' },
{ q:'$\\left(\\dfrac{1}{2} x y^3\\right)^2$', a:(v)=>{const m=parsePair(v); return Math.abs(+m[0]-0.25)<1e-6 && +m[1]===8;}, show:'0.25, 8 → $0{,}25 x^2 y^6$' },
{ q:'$(-c d^5)^4$', a:(v)=>{const m=parsePair(v); return +m[0]===1 && +m[1]===24;}, show:'1, 24 → $c^4 d^{20}$' },
],
onComplete:(s,n)=>{ if(s===n){ addXp(15,'p7-iv2'); bumpProgress('p7',30); } else if(s>=3){ addXp(7,'p7-iv2'); bumpProgress('p7',15); } }
});
wireReadBtn('p7');
}
function buildP8(){
const box = document.getElementById('p8-body');
let html = '';
html += makeCard('theory', 'Многочлен', '8.1', `
<p><b>Многочленом</b> называется сумма одночленов. Сами одночлены, входящие в эту сумму, называются <b>членами многочлена</b>.</p>
<p>Примеры: $5 a^2 b - 3 a + 7$ (трёхчлен), $x^2 + 2 x y + y^2$ (тоже трёхчлен), $a + b$ (двучлен), $7 x^3$ (одночлен — тоже многочлен).</p>
<p><b>Подобные одночлены</b> (подобные члены многочлена) — это одночлены с одинаковыми буквенными частями. Они отличаются только коэффициентами.</p>
<p>Подобные слагаемые приводятся (объединяются в один): $3 a^2 + 5 a^2 = 8 a^2$, $7 x y - 2 x y = 5 x y$.</p>`);
html += makeCard('rule', 'Стандартный вид многочлена', '8.2', `
<p>Многочлен записан в <b>стандартном виде</b>, если:</p>
<ol style="padding-left:22px;line-height:1.85">
<li>каждый его член — одночлен стандартного вида;</li>
<li>среди его членов нет подобных.</li>
</ol>
<p>Чтобы привести многочлен к стандартному виду, нужно:</p>
<ol style="padding-left:22px;line-height:1.85">
<li>привести каждый одночлен к стандартному виду;</li>
<li>привести подобные члены (сложить коэффициенты).</li>
</ol>
<p>Например, $8 a + 7 a^2 b - 7 a + a^2 b = a + 8 a^2 b$.</p>`);
html += makeCard('rule', 'Степень многочлена', '8.3', `
<p><b>Степенью многочлена</b> стандартного вида называется наибольшая из степеней входящих в него одночленов.</p>
<p>Пример: $a + 8 a^2 b$ — степени слагаемых: $1$ и $3$. Степень многочлена равна $3$.</p>
<p>Многочлен $x^4 - 5 x^2 + 6 x - 7$ имеет степень $4$.</p>`);
html += '<div class="wg" id="p8-iv1">'
+'<div class="wg-header"><span class="wg-badge">ИНТЕРАКТИВ 1</span><div class="wg-title">Приведи подобные</div></div>'
+'<div class="wg-help">Упрости многочлен и введи коэффициент при $a$ (или другой переменной — в задаче указано).</div>'
+trainerHTML('p8-iv1', 6, 'коэффициент')
+'</div>';
html += '<div class="wg" id="p8-iv2">'
+'<div class="wg-header"><span class="wg-badge">ИНТЕРАКТИВ 2</span><div class="wg-title">Степень многочлена</div></div>'
+'<div class="wg-help">Найди степень многочлена (после приведения к стандартному виду).</div>'
+trainerHTML('p8-iv2', 5, 'степень')
+'</div>';
html += secNav('p7', 'p9') + readButton('p8');
box.innerHTML = html; renderMath(box);
makeTrainer({
idPrefix:'p8-iv1',
questions:[
{ q:'$3a + 5a - 2a$ — коэффициент при $a$', a:6 },
{ q:'$4 a^2 - 6 a - 3 a^2 - a$ — коэффициент при $a^2$', a:1 },
{ q:'$8n + 9m - 8n - 2m$ — коэффициент при $m$', a:7 },
{ q:'$1{,}6 x^3 + 5xy - 0{,}6 x^3 - 4xy$ — коэффициент при $xy$', a:1 },
{ q:'$8a + 7a^2 b - 7a + a^2 b$ — коэффициент при $a^2 b$', a:8 },
{ q:'$2m^2 n - 7m + 3m^2 n - 3m - m^2 n$ — коэффициент при $m^2 n$', a:4 },
],
onComplete:(s,n)=>{ if(s===n){ addXp(15,'p8-iv1'); bumpProgress('p8',25); } else if(s>=4){ addXp(8,'p8-iv1'); bumpProgress('p8',15); } }
});
makeTrainer({
idPrefix:'p8-iv2',
questions:[
{ q:'$3 a^4 - 5 a^2 + 6 a - 7$', a:4 },
{ q:'$2 x^2 y + 5 x y^2 - 3 x y$', a:3 },
{ q:'$8 - 5 a - a^2$', a:2 },
{ q:'$10 a^5 y^7$', a:12 },
{ q:'$m^4 - 5 n + 6 m^4 - 3 n + 8 n$ (после привед.)', a:4 },
],
onComplete:(s,n)=>{ if(s===n){ addXp(12,'p8-iv2'); bumpProgress('p8',20); } else if(s>=3){ addXp(6,'p8-iv2'); bumpProgress('p8',12); } }
});
wireReadBtn('p8');
}
function buildP9(){
const box = document.getElementById('p9-body');
let html = '';
html += makeCard('rule', 'Раскрытие скобок', '9.1', `
<p>При сложении и вычитании многочленов нужно <b>раскрыть скобки</b>.</p>
<p><b>Если перед скобками стоит знак «+»</b>:</p>
<ol style="padding-left:22px;line-height:1.85">
<li>скобки опускают;</li>
<li>опускают знак «+»;</li>
<li>все знаки слагаемых в скобках остаются без изменения.</li>
</ol>
<p>$a + (b - c - d) = a + b - c - d$</p>
<p><b>Если перед скобками стоит знак «−»</b>:</p>
<ol style="padding-left:22px;line-height:1.85">
<li>скобки опускают;</li>
<li>опускают знак «−»;</li>
<li>все знаки слагаемых в скобках заменяют на противоположные.</li>
</ol>
<p>$a - (b - c - d) = a - b + c + d$</p>`);
html += makeCard('algo', 'Чтобы сложить (вычесть) многочлены', '9.2', `
<ol style="padding-left:22px;line-height:1.9">
<li>раскрыть скобки;</li>
<li>привести подобные слагаемые в полученном многочлене.</li>
</ol>
<p>Например, $3 a^2 - 5 a - (2 a^2 - a + 1) = 3 a^2 - 5 a - 2 a^2 + a - 1 = a^2 - 4 a - 1$.</p>`);
html += makeCard('example', 'Сумма и разность', '9.3', `
<p><b>Сумма:</b> $(2 a^2 + a b - c^2) + (-2 a^2 + a b - c^2) = 2 a b - 2 c^2$</p>
<p><b>Разность:</b> $(2 a^2 + a b - c^2) - (-2 a^2 + a b - c^2) = 2 a^2 + a b - c^2 + 2 a^2 - a b + c^2 = 4 a^2$</p>`);
html += '<div class="wg" id="p9-iv1">'
+'<div class="wg-header"><span class="wg-badge">ИНТЕРАКТИВ 1</span><div class="wg-title">Раскрытие скобок</div></div>'
+'<div class="wg-help">Раскрой скобки и приведи подобные. Введи коэффициент при указанной переменной.</div>'
+trainerHTML('p9-iv1', 6, 'коэффициент')
+'</div>';
html += '<div class="wg" id="p9-iv2">'
+'<div class="wg-header"><span class="wg-badge">ИНТЕРАКТИВ 2</span><div class="wg-title">Сумма / разность многочленов</div></div>'
+'<div class="wg-help">Найди сумму или разность многочленов. Введи свободный член (число без буквы).</div>'
+trainerHTML('p9-iv2', 5, 'свободный член')
+'</div>';
html += secNav('p8', 'p10') + readButton('p9');
box.innerHTML = html; renderMath(box);
makeTrainer({
idPrefix:'p9-iv1',
questions:[
{ q:'$5x + (3x + 7y)$ — коэф. при $x$', a:8 },
{ q:'$9b + (8c - b)$ — коэф. при $b$', a:8 },
{ q:'$7a - (-9b + 7a)$ — коэф. при $a$', a:0 },
{ q:'$8x - (-7x - 2y)$ — коэф. при $x$', a:15 },
{ q:'$10ab - (ab - 2c) - (3ab + 4c)$ — коэф. при $ab$', a:6 },
{ q:'$3a - 2b - (-2b + 4c) + (5c - 2a)$ — коэф. при $c$', a:1 },
],
onComplete:(s,n)=>{ if(s===n){ addXp(15,'p9-iv1'); bumpProgress('p9',25); } else if(s>=4){ addXp(8,'p9-iv1'); bumpProgress('p9',15); } }
});
makeTrainer({
idPrefix:'p9-iv2',
questions:[
{ q:'$(7b^5 - 3b^4 + 5b^2 - b - 8) + 0$ — свободный член', a:-8 },
{ q:'$(-3n^4 + 2{,}1 n^2 - 8) - (2 n^4 - n^2 + 1)$ — свободный член', a:-9 },
{ q:'$(5 - x^2 - 4x + 2x^2) + (7 + 4x - x^2)$ — свободный член', a:12 },
{ q:'$(6a^2 - 5a) + (3a - 7a^2)$ — свободный член', a:0 },
{ q:'$(y^2 - 4y - 6) - (-3y^2 + 4y - 6)$ — свободный член', a:0 },
],
onComplete:(s,n)=>{ if(s===n){ addXp(12,'p9-iv2'); bumpProgress('p9',20); } else if(s>=3){ addXp(6,'p9-iv2'); bumpProgress('p9',12); } }
});
wireReadBtn('p9');
}
function buildP10(){
const box = document.getElementById('p10-body');
let html = '';
html += makeCard('rule', 'Умножение одночлена на многочлен', '10.1', `
<p>Применяется <b>распределительный закон</b>: $a(b + c + d) = ab + ac + ad$.</p>
<p>Чтобы умножить одночлен на многочлен, нужно:</p>
<ol style="padding-left:22px;line-height:1.85">
<li>умножить одночлен на каждый член многочлена;</li>
<li>полученные произведения сложить.</li>
</ol>
<p>Например, $2 a (a^2 + 3 a - 1) = 2 a^3 + 6 a^2 - 2 a$.</p>
<p>Не забываем про <b>знаки</b>: $-5 c (2 a^2 c + 3 a c - 4 a) = -10 a^2 c^2 - 15 a c^2 + 20 a c$.</p>`);
html += makeCard('rule', 'Деление многочлена на одночлен', '10.2', `
<p>Чтобы разделить многочлен на одночлен, нужно:</p>
<ol style="padding-left:22px;line-height:1.85">
<li>разделить каждый член многочлена на этот одночлен;</li>
<li>полученные частные сложить.</li>
</ol>
<p>$(4 x^4 - 6 x^3 + 8 x^2) : (2 x^2) = 2 x^2 - 3 x + 4$</p>
<p>Результатом не всегда будет многочлен: например, $(5 x^4 y^3 + 3 x^3 y - x y) : (x^3 y) = 5 x y^2 + 3 - x^{-2}$ — не многочлен (есть отрицательный показатель).</p>`);
html += makeCard('example', 'Применение', '10.3', `
<p><b>а)</b> $3 (a - b) + 2 (a + b) = 3a - 3b + 2a + 2b = 5a - b$</p>
<p><b>б)</b> $5 a b (a + b) - 3 m^2 (m + n) = 5 a^2 b + 5 a b^2 - 3 m^3 - 3 m^2 n$</p>
<p><b>в)</b> $(27 x^{10} + 3 x^5 - 24 x^3) : (3 x^3) = 9 x^7 + x^2 - 8$</p>`);
html += '<div class="wg" id="p10-iv1">'
+'<div class="wg-header"><span class="wg-badge">ИНТЕРАКТИВ 1</span><div class="wg-title">Умножение одночлена на многочлен</div></div>'
+'<div class="wg-help">Выполни умножение. Введи коэффициент при указанной переменной.</div>'
+trainerHTML('p10-iv1', 6, 'коэффициент')
+'</div>';
html += '<div class="wg" id="p10-iv2">'
+'<div class="wg-header"><span class="wg-badge">ИНТЕРАКТИВ 2</span><div class="wg-title">Деление многочлена на одночлен</div></div>'
+'<div class="wg-help">Раздели и введи свободный член результата (число).</div>'
+trainerHTML('p10-iv2', 5, 'свободный член')
+'</div>';
html += secNav('p9', 'p11') + readButton('p10');
box.innerHTML = html; renderMath(box);
makeTrainer({
idPrefix:'p10-iv1',
questions:[
{ q:'$3 (a - b) + 2 (a + b)$ — коэф. при $a$', a:5 },
{ q:'$5 (a - 6b) - 2 (8a - b)$ — коэф. при $a$', a:-11 },
{ q:'$5 x y (x - y)$ — коэф. при $x^2 y$', a:5 },
{ q:'$2 a (a^2 + 3a - 1)$ — коэф. при $a^2$', a:6 },
{ q:'$8 (2a - 3b) - 3 (3a - 2b)$ — коэф. при $b$', a:-18 },
{ q:'$-7 (3x + 1) - 5 (1 - 3x) - 6 (x - 2)$ — коэф. при $x$', a:2 },
],
onComplete:(s,n)=>{ if(s===n){ addXp(15,'p10-iv1'); bumpProgress('p10',28); } else if(s>=4){ addXp(8,'p10-iv1'); bumpProgress('p10',15); } }
});
makeTrainer({
idPrefix:'p10-iv2',
questions:[
{ q:'$(15 a^4 - 10 a^3 - 5 a) : (5 a)$ — свободный член (коэф. при $a^0$)', a:-1 },
{ q:'$(8 x^5 + 4 x^4 - 2 x^2) : (-2 x^2)$ — свободный член (без переменной)', a:1 },
{ q:'$(27 x^{10} + 3 x^5 - 24 x^3) : (3 x^3)$ — свободный член', a:-8 },
{ q:'$(4 x^4 - 6 x^3 + 8 x^2) : (2 x^2)$ — свободный член', a:4 },
{ q:'$(18 a^4 b^3 - 24 a^5 b^4 + 6 a^2 b^3) : (-6 a^2 b^3)$ — свободный член', a:-1 },
],
onComplete:(s,n)=>{ if(s===n){ addXp(12,'p10-iv2'); bumpProgress('p10',22); } else if(s>=3){ addXp(6,'p10-iv2'); bumpProgress('p10',12); } }
});
wireReadBtn('p10');
}
function buildP11(){
const box = document.getElementById('p11-body');
let html = '';
html += makeCard('rule', 'Умножение многочленов', '11.1', `
<p>Чтобы умножить многочлен на многочлен, нужно:</p>
<ol style="padding-left:22px;line-height:1.85">
<li>умножить каждый член одного многочлена на каждый член другого;</li>
<li>полученные произведения сложить и привести подобные.</li>
</ol>
<p>Базовая формула: $\\;(a + b)(c + d) = ac + ad + bc + bd$.</p>`);
html += makeCard('example', 'Применение', '11.2', `
<p><b>а)</b> $(x + 3)(4 x - 2) = 4 x^2 - 2 x + 12 x - 6 = 4 x^2 + 10 x - 6$</p>
<p><b>б)</b> $(5 m - n)(m - 3 n) = 5 m^2 - 15 m n - m n + 3 n^2 = 5 m^2 - 16 m n + 3 n^2$</p>
<p><b>в)</b> $(x + 3)(x^2 - 4 x + 1) = x^3 - 4 x^2 + x + 3 x^2 - 12 x + 3 = x^3 - x^2 - 11 x + 3$</p>`);
html += makeCard('rule', 'Сколько членов получится?', '11.3', `
<p>При умножении двучлена на двучлен сначала получается <b>4</b> произведения. После приведения подобных может остаться <b>2, 3 или 4</b> члена.</p>
<p>Пример: $(a + b)(a - b) = a^2 - ab + ab - b^2 = a^2 - b^2$ — осталось <b>2</b> члена (об этом — §13).</p>`);
html += '<div class="wg" id="p11-iv1">'
+'<div class="wg-header"><span class="wg-badge">ИНТЕРАКТИВ 1</span><div class="wg-title">Тренажёр умножения многочленов</div></div>'
+'<div class="wg-help">Перемножь и введи коэффициент при указанной переменной/одночлене.</div>'
+trainerHTML('p11-iv1', 6, 'коэффициент')
+'</div>';
html += '<div class="wg" id="p11-iv2">'
+'<div class="wg-header"><span class="wg-badge">ИНТЕРАКТИВ 2</span><div class="wg-title">Свободный член произведения</div></div>'
+'<div class="wg-help">Введи свободный член многочлена-результата (без переменной).</div>'
+trainerHTML('p11-iv2', 5, 'свободный член')
+'</div>';
html += secNav('p10', 'p12') + readButton('p11');
box.innerHTML = html; renderMath(box);
makeTrainer({
idPrefix:'p11-iv1',
questions:[
{ q:'$(x + 3)(4x - 2)$ — коэф. при $x$', a:10 },
{ q:'$(5m - n)(m - 3n)$ — коэф. при $mn$', a:-16 },
{ q:'$(a + b)(2c - d)$ — коэф. при $ac$', a:2 },
{ q:'$(2x + y)(3x - 5y)$ — коэф. при $y^2$', a:-5 },
{ q:'$(x + 3)(x^2 - 4x + 1)$ — коэф. при $x^2$', a:-1 },
{ q:'$(3n - 1)(5 - 3n)$ — коэф. при $n^2$', a:-9 },
],
onComplete:(s,n)=>{ if(s===n){ addXp(18,'p11-iv1'); bumpProgress('p11',30); } else if(s>=4){ addXp(10,'p11-iv1'); bumpProgress('p11',18); } }
});
makeTrainer({
idPrefix:'p11-iv2',
questions:[
{ q:'$(x + 3)(4x - 2)$', a:-6 },
{ q:'$(x + 4)(x + 1)$', a:4 },
{ q:'$(x - 2)(x - 5)$', a:10 },
{ q:'$(2x + 1)(3x + 2)$', a:2 },
{ q:'$(x - 3)(x + 3)$', a:-9 },
],
onComplete:(s,n)=>{ if(s===n){ addXp(12,'p11-iv2'); bumpProgress('p11',22); } else if(s>=3){ addXp(6,'p11-iv2'); bumpProgress('p11',12); } }
});
wireReadBtn('p11');
}
function buildP12(){
const box = document.getElementById('p12-body');
let html = '';
html += makeCard('theory', 'Квадрат суммы', '12.1', `
<p>Рассмотрим $(a + b)^2 = (a + b)(a + b)$. По правилу умножения многочленов:</p>
<p>$(a + b)(a + b) = a \\cdot a + a \\cdot b + b \\cdot a + b \\cdot b = a^2 + 2ab + b^2$</p>
<p>Получили <b>формулу сокращённого умножения квадрата суммы</b>:</p>
\\[(a + b)^2 = a^2 + 2ab + b^2\\]
<p>На словах: <b>квадрат суммы двух выражений равен квадрату первого, плюс удвоенное произведение первого на второе, плюс квадрат второго</b>.</p>`);
html += makeCard('theory', 'Квадрат разности', '12.2', `
<p>Аналогично $(a - b)^2 = (a - b)(a - b) = a^2 - ab - ab + b^2 = a^2 - 2ab + b^2$:</p>
\\[(a - b)^2 = a^2 - 2ab + b^2\\]
<p>Отличается от квадрата суммы только знаком при $2ab$.</p>
<p>Заметим: $(a - b)^2 = (b - a)^2$ и $(-a - b)^2 = (a + b)^2$.</p>`);
html += makeCard('example', 'Применение формул', '12.3', `
<p><b>а)</b> $(3n - 1)^2 = (3n)^2 - 2 \\cdot 3n \\cdot 1 + 1^2 = 9n^2 - 6n + 1$</p>
<p><b>б)</b> $(y^3 - 2)^2 = y^6 - 4y^3 + 4$</p>
<p><b>в)</b> $(2 a + 3 b)^2 = 4 a^2 + 12 a b + 9 b^2$</p>
<p><b>г)</b> <b>Быстрый счёт:</b> $1001^2 = (1000 + 1)^2 = 1000000 + 2000 + 1 = 1002001$.</p>
<p><b>д)</b> $7{,}8^2 = (8 - 0{,}2)^2 = 64 - 3{,}2 + 0{,}04 = 60{,}84$.</p>`);
html += makeCard('algo', 'Свернуть трёхчлен в квадрат двучлена', '12.4', `
<p>Чтобы записать трёхчлен в виде квадрата двучлена, нужно:</p>
<ol style="padding-left:22px;line-height:1.9">
<li>найти два члена, являющиеся квадратами выражений;</li>
<li>определить эти выражения;</li>
<li>проверить: удвоенное произведение совпадает с третьим членом?</li>
<li>если да — записать $(a \\pm b)^2$.</li>
</ol>
<p><b>Пример:</b> $x^2 - 10xy + 25 y^2$. Квадраты: $x^2$ и $25y^2$. Выражения: $x$ и $5y$. Удвоенное произведение: $2 \\cdot x \\cdot 5y = 10 xy$ — совпало! Значит $x^2 - 10xy + 25y^2 = (x - 5y)^2$.</p>`);
html += '<div class="wg" id="p12-iv1">'
+'<div class="wg-header"><span class="wg-badge">ИНТЕРАКТИВ 1</span><div class="wg-title">Раскрой $(a \\pm b)^2$</div></div>'
+'<div class="wg-help">Раскрой квадрат и введи коэффициент при $2ab$ — среднем члене (с учётом знака).</div>'
+trainerHTML('p12-iv1', 6, '2ab-коэф.')
+'</div>';
html += '<div class="wg" id="p12-iv2">'
+'<div class="wg-header"><span class="wg-badge">ИНТЕРАКТИВ 2</span><div class="wg-title">Сверни трёхчлен в квадрат двучлена</div></div>'
+'<div class="wg-help">Запиши результат в виде $(p \\pm q)^2$. Введи через запятую: первое выражение, второе, знак (1 или -1).</div>'
+trainerHTML('p12-iv2', 5, 'p,q,знак')
+'</div>';
html += '<div class="wg" id="p12-iv3">'
+'<div class="wg-header"><span class="wg-badge">ИНТЕРАКТИВ 3</span><div class="wg-title">Быстрый счёт</div></div>'
+'<div class="wg-help">Используй формулу квадрата суммы или разности для устного счёта. Введи число.</div>'
+trainerHTML('p12-iv3', 4, 'результат')
+'</div>';
/* INTERACTIVE 4: Геометрическая визуализация квадрата суммы */
html += '<div class="wg" id="p12-iv4-viz">'
+'<div class="wg-header"><span class="wg-badge">ИНТЕРАКТИВ 4</span><div class="wg-title">Геометрическая визуализация $(a \\pm b)^2$</div></div>'
+'<div class="wg-help">Большой квадрат со стороной $(a+b)$ разделён на 4 цветные области. Подвигай $a, b$ — увидишь живое доказательство формулы! Кликни цвет → подсветка в формуле.</div>'
+'<div id="p12-iv4-viz-host"></div>'
+'</div>';
html += secNav('p11', 'p13') + readButton('p12');
box.innerHTML = html; renderMath(box);
makeTrainer({
idPrefix:'p12-iv1',
questions:[
{ q:'$(x + 3)^2$ — коэф. среднего члена', a:6 },
{ q:'$(7n - 1)^2$ — коэф. при $n$', a:-14 },
{ q:'$(5a + 2b)^2$ — коэф. при $ab$', a:20 },
{ q:'$(3a - 4b)^2$ — коэф. при $ab$', a:-24 },
{ q:'$(-5a - 2b)^2$ — коэф. при $ab$ (помни знак!)', a:20 },
{ q:'$(6a + b)^2$ — коэф. при $ab$', a:12 },
],
onComplete:(s,n)=>{ if(s===n){ addXp(18,'p12-iv1'); bumpProgress('p12',30); } else if(s>=4){ addXp(10,'p12-iv1'); bumpProgress('p12',18); } }
});
makeTrainer({
idPrefix:'p12-iv2',
parser:(v)=>v,
questions:[
{ q:'$x^2 - 10x + 25 = (?)^2$ — введи: <i>выраж.1, выраж.2, знак(\\pm 1)</i>', a:(v)=>{const m=String(v).trim().split(/[,;\s]+/); return (m[0]==='x'||m[0]==='X') && +m[1]===5 && +m[2]===-1;}, show:'x, 5, -1 → $(x-5)^2$' },
{ q:'$n^2 + 2n + 1$', a:(v)=>{const m=String(v).trim().split(/[,;\s]+/); return (m[0]==='n'||m[0]==='N') && +m[1]===1 && +m[2]===1;}, show:'n, 1, 1 → $(n+1)^2$' },
{ q:'$16a^2 + 8ab + b^2$', a:(v)=>{const m=String(v).trim().split(/[,;\s]+/); return m[0]==='4a' && m[1]==='b' && +m[2]===1;}, show:'4a, b, 1 → $(4a+b)^2$' },
{ q:'$25m^2 - 20mn + 4n^2$', a:(v)=>{const m=String(v).trim().split(/[,;\s]+/); return m[0]==='5m' && m[1]==='2n' && +m[2]===-1;}, show:'5m, 2n, -1 → $(5m-2n)^2$' },
{ q:'$y^4 - 12y^2 + 36$', a:(v)=>{const m=String(v).trim().split(/[,;\s]+/); return (m[0]==='y^2'||m[0]==='y2') && +m[1]===6 && +m[2]===-1;}, show:'y^2, 6, -1 → $(y^2-6)^2$' },
],
onComplete:(s,n)=>{ if(s===n){ addXp(15,'p12-iv2'); bumpProgress('p12',25); } else if(s>=3){ addXp(8,'p12-iv2'); bumpProgress('p12',15); } }
});
makeTrainer({
idPrefix:'p12-iv3',
questions:[
{ q:'$1001^2$ = ?', a:1002001 },
{ q:'$7{,}8^2$ = ?', a:60.84 },
{ q:'$99^2$ = ?', a:9801 },
{ q:'$31^2$ = ?', a:961 },
],
onComplete:(s,n)=>{ if(s===n){ addXp(12,'p12-iv3'); bumpProgress('p12',20); } else if(s>=2){ addXp(5,'p12-iv3'); bumpProgress('p12',10); } }
});
/* Init quadrat suммы viz from alg7-fx.js */
if(window.ALG7 && window.ALG7.buildQuadSumViz){
window.ALG7.buildQuadSumViz(document.getElementById('p12-iv4-viz-host'));
} else {
/* defer if not loaded yet */
const _try = ()=>{ if(window.ALG7 && window.ALG7.buildQuadSumViz){ window.ALG7.buildQuadSumViz(document.getElementById('p12-iv4-viz-host')); } else setTimeout(_try, 100); };
_try();
}
wireReadBtn('p12');
}
function buildP13(){
const box = document.getElementById('p13-body');
let html = '';
html += makeCard('theory', 'Произведение суммы и разности', '13.1', `
<p>Рассмотрим $(a + b)(a - b)$. По правилу умножения многочленов:</p>
<p>$(a + b)(a - b) = a^2 - ab + ab - b^2 = a^2 - b^2$</p>
<p>Получили <b>формулу сокращённого умножения</b>:</p>
\\[(a + b)(a - b) = a^2 - b^2\\]
<p>На словах: <b>произведение суммы и разности двух выражений равно разности их квадратов</b>.</p>`);
html += makeCard('rule', 'Применение справа налево', '13.2', `
<p>Формула применяется как <b>слева направо</b> (для упрощения произведений), так и <b>справа налево</b> (для разложения разности квадратов на множители):</p>
\\[a^2 - b^2 = (a - b)(a + b)\\]
<p>Например, $x^2 - 4 = x^2 - 2^2 = (x - 2)(x + 2)$.</p>
<p>$36 m^2 - 25 = (6m)^2 - 5^2 = (6m - 5)(6m + 5)$.</p>`);
html += makeCard('example', 'Применение формулы', '13.3', `
<p><b>а)</b> $(3b + 7c)(3b - 7c) = (3b)^2 - (7c)^2 = 9 b^2 - 49 c^2$</p>
<p><b>б)</b> $(x + 8y)(x - 8y) = x^2 - 64 y^2$</p>
<p><b>в)</b> $b^4 - 25 a^4 = (b^2)^2 - (5 a^2)^2 = (b^2 - 5 a^2)(b^2 + 5 a^2)$</p>
<p><b>г) Быстрый счёт:</b> $199 \\cdot 201 = (200 - 1)(200 + 1) = 200^2 - 1 = 39999$.</p>
<p><b>д)</b> $49 \\cdot 51 = (50 - 1)(50 + 1) = 2500 - 1 = 2499$.</p>`);
html += '<div class="wg" id="p13-iv1">'
+'<div class="wg-header"><span class="wg-badge">ИНТЕРАКТИВ 1</span><div class="wg-title">Произведение → разность квадратов</div></div>'
+'<div class="wg-help">Раскрой произведение по формуле и введи коэффициент при $b^2$ (или другой переменной) в ответе. Знак учитывается!</div>'
+trainerHTML('p13-iv1', 6, 'коэффициент')
+'</div>';
html += '<div class="wg" id="p13-iv2">'
+'<div class="wg-header"><span class="wg-badge">ИНТЕРАКТИВ 2</span><div class="wg-title">Быстрый счёт</div></div>'
+'<div class="wg-help">Вычисли произведение, применяя формулу разности квадратов.</div>'
+trainerHTML('p13-iv2', 5, 'результат')
+'</div>';
/* INTERACTIVE 3: Анимированная визуализация $a^2 - b^2 = (a-b)(a+b)$ */
html += '<div class="wg" id="p13-iv3-viz">'
+'<div class="wg-header"><span class="wg-badge">ИНТЕРАКТИВ 3</span><div class="wg-title">Анимация: $a^2 - b^2 = (a-b)(a+b)$</div></div>'
+'<div class="wg-help">Из квадрата $a^2$ вырезали малый квадрат $b^2$. Жми кнопку <b>«Шаг»</b> — L-форма «расклеится» и соберётся в прямоугольник со сторонами $(a-b)$ и $(a+b)$. Это геометрическое доказательство формулы!</div>'
+'<div id="p13-iv3-viz-host"></div>'
+'</div>';
html += secNav('p12', 'p14') + readButton('p13');
box.innerHTML = html; renderMath(box);
makeTrainer({
idPrefix:'p13-iv1',
questions:[
{ q:'$(3b + 7c)(3b - 7c)$ — коэф. при $c^2$', a:-49 },
{ q:'$(4x - 5)(4x + 5)$ — свободный член', a:-25 },
{ q:'$(8n + \\tfrac{1}{4} m)(8n - \\tfrac{1}{4} m)$ — коэф. при $n^2$', a:64 },
{ q:'$(x - 4y)(x + 4y)$ — коэф. при $y^2$', a:-16 },
{ q:'$(5y^2 - 1)(5y^2 + 1)$ — свободный член', a:-1 },
{ q:'$(2a - 0{,}3b^2)(2a + 0{,}3b^2)$ — коэф. при $b^4$', a:-0.09 },
],
onComplete:(s,n)=>{ if(s===n){ addXp(15,'p13-iv1'); bumpProgress('p13',30); } else if(s>=4){ addXp(8,'p13-iv1'); bumpProgress('p13',18); } }
});
makeTrainer({
idPrefix:'p13-iv2',
questions:[
{ q:'$199 \\cdot 201$', a:39999 },
{ q:'$49 \\cdot 51$', a:2499 },
{ q:'$98 \\cdot 102$', a:9996 },
{ q:'$67^2 - 33^2$ (используй $a^2-b^2$)', a:3400 },
{ q:'$59^2 - 41^2$', a:1800 },
],
onComplete:(s,n)=>{ if(s===n){ addXp(12,'p13-iv2'); bumpProgress('p13',22); } else if(s>=3){ addXp(6,'p13-iv2'); bumpProgress('p13',12); } }
});
/* Init diff-squares viz from alg7-fx.js */
if(window.ALG7 && window.ALG7.buildDiffSquaresViz){
window.ALG7.buildDiffSquaresViz(document.getElementById('p13-iv3-viz-host'));
} else {
const _try = ()=>{ if(window.ALG7 && window.ALG7.buildDiffSquaresViz){ window.ALG7.buildDiffSquaresViz(document.getElementById('p13-iv3-viz-host')); } else setTimeout(_try, 100); };
_try();
}
wireReadBtn('p13');
}
function buildP14(){
const box = document.getElementById('p14-body');
let html = '';
html += makeCard('theory', 'Что такое разложение на множители', '14.1', `
<p><b>Разложить многочлен на множители</b> — значит представить его в виде произведения одночлена и многочлена или нескольких многочленов.</p>
<p>Это <b>обратная задача</b> к умножению. Если умножение даёт многочлен, то разложение возвращает множители.</p>
<p>В 7 классе изучают три основных способа: <b>вынесение общего множителя</b>, <b>группировка</b>, применение <b>ФСУ</b>.</p>`);
html += makeCard('algo', 'Способ 1. Вынесение общего множителя', '14.2', `
<p>Если все члены многочлена имеют общий множитель — его выносят за скобки.</p>
<p><b>Алгоритм:</b></p>
<ol style="padding-left:22px;line-height:1.85">
<li>определить общий множитель у всех членов;</li>
<li>записать его и открыть скобку;</li>
<li>разделить каждый член на общий множитель, записать результат в скобке.</li>
</ol>
<p>Пример: $15 x^2 y + 10 x y^2 = 5 x y (3 x + 2 y)$.</p>
<p>Пример: $12 m^5 n^2 - 18 m^8 n = 6 m^5 n (2 n - 3 m^3)$.</p>
<p><b>Внимание!</b> Если общий множитель совпадает с одним из членов, на его месте остаётся <b>1</b>: $4 x^3 + 3 x^2 - x = x(4 x^2 + 3 x - 1)$.</p>`);
html += makeCard('algo', 'Способ 2. Группировка', '14.3', `
<p>Если общего множителя у всех членов нет — попробуй разбить многочлен на <b>группы</b>, в каждой из которых есть общий множитель.</p>
<p>Пример: $xy - 3x + 2y - 6$. Сгруппируем 1-й и 2-й, 3-й и 4-й:</p>
<p>$(xy - 3x) + (2y - 6) = x(y - 3) + 2(y - 3) = (y - 3)(x + 2)$</p>
<p>Получилась общая скобка $(y - 3)$ — её и выносим.</p>
<p>Можно группировать <b>по-разному</b>; не каждая группировка приводит к ответу.</p>`);
html += makeCard('algo', 'Способ 3. Применение ФСУ', '14.4', `
<p>Если многочлен подходит под формулу:</p>
<ul style="padding-left:22px;line-height:1.85">
<li><b>разность квадратов:</b> $a^2 - b^2 = (a - b)(a + b)$, например $36 a^2 - b^2 = (6a - b)(6a + b)$;</li>
<li><b>квадрат суммы:</b> $a^2 + 2ab + b^2 = (a + b)^2$, например $25 x^2 + 10 x + 1 = (5x + 1)^2$;</li>
<li><b>квадрат разности:</b> $a^2 - 2ab + b^2 = (a - b)^2$, например $x^2 - 8x + 16 = (x - 4)^2$.</li>
</ul>
<p><b>Часто</b> приходится <b>комбинировать</b> способы. Сначала — общий множитель, потом — ФСУ.</p>
<p>Пример: $25 a^3 - a = a(25 a^2 - 1) = a(5a - 1)(5a + 1)$ — вынесли $a$, потом разность квадратов.</p>`);
html += '<div class="wg" id="p14-iv1">'
+'<div class="wg-header"><span class="wg-badge">ИНТЕРАКТИВ 1</span><div class="wg-title">Какой способ применить?</div></div>'
+'<div class="wg-help">Перетащи каждый многочлен в нужную категорию: какой способ разложения сработает <b>первым</b>?</div>'
+'<div id="p14-iv1-pool"></div>'
+'<div style="display:grid;grid-template-columns:repeat(auto-fit,minmax(180px,1fr));gap:10px;margin-top:8px">'
+'<div class="drop-box"><h5 data-cat="cm">Общий множитель</h5><div class="drop-items" data-cat="cm"></div></div>'
+'<div class="drop-box"><h5 data-cat="gp">Группировка</h5><div class="drop-items" data-cat="gp"></div></div>'
+'<div class="drop-box"><h5 data-cat="fsu">ФСУ</h5><div class="drop-items" data-cat="fsu"></div></div>'
+'</div>'
+'<div class="actions"><button class="btn primary" id="p14-iv1-check">Проверить</button><button class="btn" id="p14-iv1-reset">Сначала</button></div>'
+'<div class="feedback" id="p14-iv1-fb"></div></div>';
html += '<div class="wg" id="p14-iv2">'
+'<div class="wg-header"><span class="wg-badge">ИНТЕРАКТИВ 2</span><div class="wg-title">Тренажёр: вынеси общий множитель</div></div>'
+'<div class="wg-help">Введи общий множитель (без знака — просто число и буквы, например <code>5xy</code>).</div>'
+trainerHTML('p14-iv2', 5, 'общий множитель')
+'</div>';
html += '<div class="wg" id="p14-iv3">'
+'<div class="wg-header"><span class="wg-badge">ИНТЕРАКТИВ 3</span><div class="wg-title">Тренажёр: ФСУ</div></div>'
+'<div class="wg-help">Разложи на множители по ФСУ. Введи свободный коэффициент в одном из множителей (например, для $(2x-5)(2x+5)$ — введи 5).</div>'
+trainerHTML('p14-iv3', 5, 'коэффициент')
+'</div>';
html += secNav('p13', 'final2') + readButton('p14');
box.innerHTML = html; renderMath(box);
/* IV1 DnD */
(function(){
const items=[
{ id:'p1', cat:'cm', html:'$6 a^3 + 9 a^2$' },
{ id:'p2', cat:'cm', html:'$15 x^2 y + 10 x y^2$' },
{ id:'p3', cat:'gp', html:'$xy - 3x + 2y - 6$' },
{ id:'p4', cat:'gp', html:'$2ab - 4a + bc - 2c$' },
{ id:'p5', cat:'fsu', html:'$x^2 - 49$' },
{ id:'p6', cat:'fsu', html:'$y^2 + 6y + 9$' },
{ id:'p7', cat:'fsu', html:'$25 m^2 - 10 m + 1$' },
{ id:'p8', cat:'cm', html:'$8 c^4 - 12 c^2$' },
];
const sorter=setupSorter({ poolId:'p14-iv1-pool', scopeSelector:'#p14-iv1', items, cats:['cm','gp','fsu'] });
document.getElementById('p14-iv1-check').addEventListener('click',()=>{
const fb=document.getElementById('p14-iv1-fb');
const placed=items.filter(it=>sorter.placed[it.id]).length;
if(placed<items.length){ feedback(fb,false,'&#10007; Размести все 8 многочленов.'); return; }
const correct=items.filter(it=>sorter.placed[it.id]===it.cat).length;
if(correct===items.length){ feedback(fb,true,'&#10003; Все 8 правильно! +14 XP'); addXp(14,'p14-iv1'); bumpProgress('p14',25); }
else feedback(fb,false,'&#10007; Правильно '+correct+' из '+items.length+'.');
});
document.getElementById('p14-iv1-reset').addEventListener('click',()=>{ sorter.reset(); document.getElementById('p14-iv1-fb').style.display='none'; });
})();
/* IV2 */
makeTrainer({
idPrefix:'p14-iv2',
parser:(v)=>v,
questions:[
{ q:'$15 x^2 y + 10 x y^2$', a:(v)=>String(v).trim().toLowerCase().replace(/\s/g,'')==='5xy', show:'$5xy$' },
{ q:'$12 m^5 n^2 - 18 m^8 n$', a:(v)=>{const t=String(v).trim().toLowerCase().replace(/\s/g,''); return t==='6m^5n'||t==='6m5n';}, show:'$6 m^5 n$' },
{ q:'$8 c^4 - 12 c^2$', a:(v)=>{const t=String(v).trim().toLowerCase().replace(/\s/g,''); return t==='4c^2'||t==='4c2';}, show:'$4 c^2$' },
{ q:'$6 a^3 b^4 + 9 a^2 b^2 c$', a:(v)=>{const t=String(v).trim().toLowerCase().replace(/\s/g,''); return t==='3a^2b^2'||t==='3a2b2';}, show:'$3 a^2 b^2$' },
{ q:'$2 a b + b$', a:(v)=>String(v).trim().toLowerCase().replace(/\s/g,'')==='b', show:'$b$' },
],
onComplete:(s,n)=>{ if(s===n){ addXp(15,'p14-iv2'); bumpProgress('p14',25); } else if(s>=3){ addXp(8,'p14-iv2'); bumpProgress('p14',15); } }
});
/* IV3 */
makeTrainer({
idPrefix:'p14-iv3',
questions:[
{ q:'$x^2 - 49 = (x - ?)(x + ?)$', a:7 },
{ q:'$4 x^2 - 25 = (2x - ?)(2x + ?)$', a:5 },
{ q:'$x^2 + 6x + 9 = (x + ?)^2$', a:3 },
{ q:'$y^2 - 8y + 16 = (y - ?)^2$', a:4 },
{ q:'$9 a^2 + 6 a + 1 = (3a + ?)^2$', a:1 },
],
onComplete:(s,n)=>{ if(s===n){ addXp(15,'p14-iv3'); bumpProgress('p14',25); } else if(s>=3){ addXp(8,'p14-iv3'); bumpProgress('p14',15); } }
});
wireReadBtn('p14');
}
const BOSSES_CH2 = [
{
n:1, title:'Босс \xA74-5 — Выражения и тождества', color:'#059669',
steps:[
{ q:'Найди значение $7a + 1$ при $a = 4$.', verify:(v)=>+v===29, hint:'Подставь и вычисли.' },
{ q:'При каком $x$ выражение $\\dfrac{1}{x-3}$ не имеет смысла?', verify:(v)=>+v===3, hint:'Знаменатель = 0.' },
{ q:'Является ли тождеством $a + b = b + a$? Введи «да» или «нет».', verify:(v)=>['да','yes','y','1','+'].includes(String(v).trim().toLowerCase()), hint:'Переместительный закон сложения.' },
{ q:'Упрости $3x + 2x - 5x$. Введи коэффициент при $x$.', verify:(v)=>+v===0, hint:'$3+2-5=0$.' },
{ q:'Является ли тождеством $(a - b)^2 = a^2 - b^2$? «да»/«нет».', verify:(v)=>['нет','no','n','0','-'].includes(String(v).trim().toLowerCase()), hint:'Проверь при $a=1, b=1$.' },
]
},
{
n:2, title:'Босс \xA76-7 — Одночлены', color:'#0d9488',
steps:[
{ q:'Приведи $4 x \\cdot 2 x y$ к стандартному виду. Введи коэффициент.', verify:(v)=>+v===8, hint:'$4 \\cdot 2 = 8$.' },
{ q:'Какова степень одночлена $5 a^2 b^3 c$?', verify:(v)=>+v===6, hint:'$2+3+1=6$.' },
{ q:'Коэффициент одночлена $-15 a^3 x^4 c^3$ равен?', verify:(v)=>+v===-15, hint:'Это число впереди.' },
{ q:'$(2 x^2 y)^3$ — введи коэффициент результата.', verify:(v)=>+v===8, hint:'$2^3 = 8$.' },
{ q:'Степень одночлена $-7 x y^2 z$ равна?', verify:(v)=>+v===4, hint:'$1+2+1=4$.' },
]
},
{
n:3, title:'Босс \xA78-9 — Многочлены и раскрытие скобок', color:'#2563eb',
steps:[
{ q:'Степень многочлена $3 a^4 - 5 a^2 + 6 a - 7$.', verify:(v)=>+v===4, hint:'Наибольшая степень.' },
{ q:'Упрости $5a - (3a - 2b) + 4b$. Коэф. при $a$.', verify:(v)=>+v===2, hint:'$5 - 3 = 2$.' },
{ q:'Упрости $5a - (3a - 2b) + 4b$. Коэф. при $b$.', verify:(v)=>+v===6, hint:'$2 + 4 = 6$.' },
{ q:'$(2 x^2 - 3x + 5) + (9 - 2 x^2)$. Введи свободный член.', verify:(v)=>+v===14, hint:'$5 + 9 = 14$.' },
{ q:'$(7 b^5 - 3 b^4 + 5 b^2 - b - 8)$ — какой свободный член?', verify:(v)=>+v===-8, hint:'Член без $b$.' },
]
},
{
n:4, title:'Босс \xA710-11 — Умножение', color:'#7c3aed',
steps:[
{ q:'$3 (a - b) + 2 (a + b)$. Коэф. при $a$.', verify:(v)=>+v===5, hint:'$3 + 2 = 5$.' },
{ q:'$(x + 4)(x + 1)$. Свободный член.', verify:(v)=>+v===4, hint:'$4 \\cdot 1 = 4$.' },
{ q:'$(2x + 1)(3x + 2)$. Коэф. при $x$.', verify:(v)=>+v===7, hint:'$2 \\cdot 2 + 1 \\cdot 3 = 7$.' },
{ q:'$(15 a^4 - 10 a^3 - 5 a) : (5 a)$. Коэф. при $a^0$ (свободный).', verify:(v)=>+v===-1, hint:'$-5a : 5a = -1$.' },
{ q:'$(x - 2)(x - 5)$. Свободный член.', verify:(v)=>+v===10, hint:'$(-2)(-5)=10$.' },
]
},
{
n:5, title:'Босс \xA712-13 — ФСУ', color:'#dc2626',
steps:[
{ q:'$(x + 3)^2$. Коэф. при $x$.', verify:(v)=>+v===6, hint:'$2 \\cdot 1 \\cdot 3 = 6$.' },
{ q:'$(5a - 2b)^2$. Коэф. при $ab$.', verify:(v)=>+v===-20, hint:'$-2 \\cdot 5 \\cdot 2 = -20$.' },
{ q:'$(x - 7)(x + 7)$. Свободный член.', verify:(v)=>+v===-49, hint:'$-7^2 = -49$.' },
{ q:'$199 \\cdot 201$ — вычисли по формуле разности квадратов.', verify:(v)=>+v===39999, hint:'$200^2 - 1$.' },
{ q:'Сверни $a^2 - 8a + 16$ в квадрат двучлена $(a - ?)^2$. Введи число.', verify:(v)=>+v===4, hint:'$8 = 2 \\cdot 4$.' },
]
},
{
n:6, title:'Финальный босс — Разложение на множители', color:'#d97706',
steps:[
{ q:'$15 x^2 y + 10 x y^2$ — общий множитель = ?', verify:(v)=>{const t=String(v).trim().toLowerCase().replace(/\s/g,''); return t==='5xy';}, hint:'НОД(15,10)=5; общая буква — xy.' },
{ q:'$x^2 - 25$. Разложение $(x - ?)(x + ?)$. Введи число.', verify:(v)=>+v===5, hint:'$25 = 5^2$.' },
{ q:'$x^2 + 10x + 25 = (x + ?)^2$.', verify:(v)=>+v===5, hint:'$10 = 2 \\cdot 5$.' },
{ q:'$xy - 3x + 2y - 6$ — разложение группировкой даёт $(? - 3)(x + 2)$. Введи букву.', verify:(v)=>String(v).trim().toLowerCase()==='y', hint:'Первая группа: $x(y-3)$.' },
{ q:'$25 a^3 - a = a(5a - 1)(5a + ?)$. Введи число.', verify:(v)=>+v===1, hint:'Разность квадратов $25a^2 - 1$.' },
]
},
];
function buildFinal2(){
const box = document.getElementById('final2-body');
let html = '';
html += makeCard('theory', 'Что мы изучили', 'Итог', `
<p>В этой главе мы:</p>
<ul style="padding-left:22px;line-height:1.85">
<li>научились работать с <b>выражениями с переменными</b> и доказывать <b>тождества</b>;</li>
<li>освоили <b>одночлены</b> (коэффициент, степень, стандартный вид) и <b>многочлены</b> (приведение подобных, стандартный вид);</li>
<li>научились <b>складывать, вычитать, умножать и делить</b> многочлены и одночлены;</li>
<li>выучили <b>ФСУ</b>: $(a \\pm b)^2$ и $(a-b)(a+b)$;</li>
<li>освоили <b>три способа разложения</b> на множители: вынесение, группировка, ФСУ.</li>
</ul>
<p>Теперь — <b>6 боссов</b>. Каждый — серия из 5 этапов.</p>`);
html += '<div id="bosses-container-ch2"></div>';
html += '<div style="margin-top:22px;padding:18px 20px;background:linear-gradient(135deg,var(--pri-soft),var(--acc-soft));border-radius:14px;border:1.5px solid var(--pri);text-align:center">'
+'<div style="font-family:\'Unbounded\',sans-serif;font-weight:800;color:var(--pri2);font-size:1.1rem;margin-bottom:6px">Прогресс по боссам</div>'
+'<div id="boss-overall-ch2" style="font-size:.95rem;color:var(--text);margin-bottom:10px">0 / 6 боссов побеждено</div>'
+'<div class="hp-boss"><div class="hp-boss-fill" id="boss-overall-fill-ch2" style="width:0%;background:linear-gradient(90deg,#059669,#10b981)"></div></div>'
+'</div>';
html += secNav('p14', null);
box.innerHTML = html; renderMath(box);
const cont = document.getElementById('bosses-container-ch2');
const BOSS_STATE = (function(){
try{ const s=localStorage.getItem('algebra7_ch2_bosses'); if(s) return JSON.parse(s); }catch(e){}
return BOSSES_CH2.map(()=>({stage:0,defeated:false}));
})();
function saveBosses(){ try{ localStorage.setItem('algebra7_ch2_bosses', JSON.stringify(BOSS_STATE)); }catch(e){} }
function refreshOverall(){
const won=BOSS_STATE.filter(b=>b.defeated).length;
const txt=document.getElementById('boss-overall-ch2'); if(txt) txt.textContent=won+' / '+BOSSES_CH2.length+' боссов побеждено';
const fill=document.getElementById('boss-overall-fill-ch2'); if(fill) fill.style.width=(won*100/BOSSES_CH2.length)+'%';
if(won>=BOSSES_CH2.length){ bumpProgress('final2',60); achievement('ch2_done','Глава 2 пройдена!'); }
}
cont.innerHTML = BOSSES_CH2.map((b,idx)=>{
return '<div class="boss-card" id="boss-card-'+idx+'" style="border-color:'+b.color+'">'
+'<div class="boss-head">'
+'<svg viewBox="0 0 24 24" fill="none" stroke="'+b.color+'" stroke-width="2.2" style="width:28px;height:28px"><polygon points="12,2 22,20 2,20"/></svg>'
+'<div class="boss-title" style="color:'+b.color+'">'+b.title+'</div>'
+'<div class="boss-stage" id="boss-'+idx+'-stage">Этап 1 / '+b.steps.length+'</div>'
+'</div>'
+'<div class="hp-boss" style="border-color:'+b.color+'66;background:'+b.color+'1a"><div class="hp-boss-fill" id="boss-'+idx+'-fill" style="width:0%;background:linear-gradient(90deg,'+b.color+',#f59e0b)"></div></div>'
+'<div class="boss-q" id="boss-'+idx+'-q" style="border-color:'+b.color+'"></div>'
+'<div style="display:flex;gap:8px;align-items:center;flex-wrap:wrap">'
+'<input type="text" id="boss-'+idx+'-input" class="tinp" placeholder="Ответ" style="width:160px;text-align:center">'
+'<button class="btn primary" id="boss-'+idx+'-go" style="background:'+b.color+';border-color:'+b.color+'">Атака</button>'
+'<button class="btn" id="boss-'+idx+'-hint">Подсказка</button>'
+'<button class="btn" id="boss-'+idx+'-restart">↻</button>'
+'</div>'
+'<div class="feedback" id="boss-'+idx+'-fb"></div>'
+'</div>';
}).join('');
BOSSES_CH2.forEach((b,idx)=>{
function show(){
const st=BOSS_STATE[idx];
const stageEl=document.getElementById('boss-'+idx+'-stage');
const fill=document.getElementById('boss-'+idx+'-fill');
const q=document.getElementById('boss-'+idx+'-q');
const fb=document.getElementById('boss-'+idx+'-fb');
if(st.defeated){
stageEl.textContent='✓ Побеждён'; fill.style.width='100%';
q.innerHTML='<b style="color:'+b.color+'">Босс повержен!</b> Можно перезапустить.';
document.getElementById('boss-'+idx+'-go').disabled=true;
document.getElementById('boss-'+idx+'-go').style.opacity=.5;
return;
}
stageEl.textContent='Этап '+(st.stage+1)+' / '+b.steps.length;
fill.style.width=(st.stage*100/b.steps.length)+'%';
q.innerHTML=b.steps[st.stage].q;
document.getElementById('boss-'+idx+'-input').value='';
fb.style.display='none';
renderMath(q);
}
document.getElementById('boss-'+idx+'-go').addEventListener('click',()=>{
const st=BOSS_STATE[idx]; if(st.defeated) return;
const step=b.steps[st.stage];
const val=document.getElementById('boss-'+idx+'-input').value;
const fb=document.getElementById('boss-'+idx+'-fb');
if(!val.trim()){ feedback(fb,false,'&#10007; Введи ответ.'); return; }
if(step.verify(val)){
st.stage++;
if(st.stage>=b.steps.length){
st.defeated=true; saveBosses();
feedback(fb,true,'&#10003; Босс '+b.n+' побеждён! +20 XP');
addXp(20,'boss-'+b.n); bumpProgress('final2',18); refreshOverall();
setTimeout(show,1400);
if(window.confetti) try{confetti();}catch(e){}
}else{
saveBosses();
feedback(fb,true,'&#10003; Верно! Удар попал. +3 XP');
addXp(3,'boss-step'); setTimeout(show,1100);
}
}else{
feedback(fb,false,'&#10007; Промах. Попробуй ещё раз.');
}
});
document.getElementById('boss-'+idx+'-hint').addEventListener('click',()=>{
const st=BOSS_STATE[idx]; if(st.defeated) return;
const fb=document.getElementById('boss-'+idx+'-fb');
fb.className='feedback ok';
fb.innerHTML='<span style="color:#92400e">\u{1F4A1} Подсказка:</span> '+b.steps[st.stage].hint;
fb.style.display='block';
fb.style.background='var(--warn-bg)'; fb.style.color='#92400e'; fb.style.borderLeftColor='var(--warn)';
renderMath(fb);
});
document.getElementById('boss-'+idx+'-restart').addEventListener('click',()=>{
BOSS_STATE[idx]={stage:0,defeated:false}; saveBosses();
document.getElementById('boss-'+idx+'-go').disabled=false;
document.getElementById('boss-'+idx+'-go').style.opacity=1;
show(); refreshOverall();
});
show();
});
refreshOverall();
}
</script>
</body>
</html>