Files
Learn_System/frontend/textbooks/algebra_9_ch2.html
T

2052 lines
128 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!doctype html>
<html lang="ru">
<head>
<meta charset="UTF-8">
<meta http-equiv="Cache-Control" content="no-cache, no-store, must-revalidate">
<meta http-equiv="Pragma" content="no-cache">
<meta http-equiv="Expires" content="0">
<title>Алгебра 9 · Глава 2 · Функции</title>
<meta name="viewport" content="width=device-width,initial-scale=1">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/katex@0.16.9/dist/katex.min.css">
<script defer src="https://cdn.jsdelivr.net/npm/katex@0.16.9/dist/katex.min.js"></script>
<script defer src="https://cdn.jsdelivr.net/npm/katex@0.16.9/dist/contrib/auto-render.min.js"
onload="renderMathInElement(document.body,{delimiters:[{left:'$$',right:'$$',display:true},{left:'$',right:'$',display:false},{left:'\\[',right:'\\]',display:true},{left:'\\(',right:'\\)',display:false}],throwOnError:false})"></script>
<script src="/js/api.js" defer></script>
<script src="/js/xp.js" defer></script>
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800&family=Manrope:wght@600;700;800;900&family=Unbounded:wght@700;800;900&family=JetBrains+Mono:wght@500;700&display=swap" rel="stylesheet">
<style>
:root{
--bg:#fafafa; --card:#fff; --card-soft:#f8fafc; --text:#0f172a; --ink:#0f172a; --muted:#64748b;
--border:#e2e8f0; --sh:0 1px 3px rgba(0,0,0,.06); --sh2:0 4px 14px rgba(0,0,0,.08);
--pri:#059669; --pri2:#047857; --pri-soft:#d1fae5;
--acc:#10b981; --acc2:#059669; --acc-soft:#ecfdf5;
--ok:#10b981; --ok-bg:#d1fae5; --warn:#f59e0b; --warn-bg:#fef3c7;
--bad:#ef4444; --fail:#dc2626; --fail-bg:#fee2e2;
}
.dark{--bg:#021410; --card:#0a1f1a; --card-soft:#0d2620; --text:#e0fcf3; --ink:#e0fcf3; --muted:#7aa896; --border:#163d2f}
*{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(209,250,229,.12);line-height:1;pointer-events:none;user-select:none;z-index:0}
.hdr-row{position:relative;z-index:1;display:flex;align-items:center;gap:14px;flex-wrap:wrap}
.hdr h1{font-family:'Unbounded',sans-serif;font-size:1.5rem;font-weight:900;letter-spacing:-.01em;line-height:1.3;padding-top:4px}
.hdr-sub{font-size:.85rem;opacity:.88;margin-top:6px;font-weight:500;line-height:1.4}
.hdr-side{margin-left:auto;display:flex;gap:8px;align-items:center;flex-wrap:wrap}
.hdr-btn{padding:7px 12px;border-radius:9px;background:rgba(255,255,255,.14);color:#fff;font-weight:600;font-size:.82rem;display:inline-flex;align-items:center;gap:6px;transition:background .15s;text-decoration:none}
.hdr-btn:hover{background:rgba(255,255,255,.24)}
.main{max-width:1240px;margin:0 auto;padding:22px;width:100%;display:grid;grid-template-columns:1fr 280px;gap:24px}
@media(max-width:980px){.main{grid-template-columns:1fr;padding:14px}}
.col-main{min-width:0}
.hero{background:linear-gradient(135deg,var(--pri-soft) 0%,var(--acc-soft) 50%,var(--pri-soft) 100%);background-size:200% 200%;animation:heroShift 12s ease-in-out infinite;border:1px solid var(--border);border-radius:18px;padding:24px 22px;margin-bottom:24px;position:relative;overflow:hidden}
@keyframes heroShift{0%,100%{background-position:0% 50%}50%{background-position:100% 50%}}
.hero::before{content:'f(x)';position:absolute;right:0;top:-30px;font-size:clamp(2rem,12vw,8rem);font-weight:900;color:var(--pri);opacity:.10;line-height:1;pointer-events:none;font-family:'Unbounded',sans-serif}
.hero h2{font-family:'Unbounded',sans-serif;font-size:1.55rem;font-weight:800;color:var(--pri2);margin-bottom:10px;letter-spacing:-.01em}
.hero p{font-size:.95rem;color:var(--text);opacity:.88;margin-bottom:14px;max-width:640px}
.hero-row{display:flex;gap:14px;flex-wrap:wrap;align-items:center}
.btn-primary{padding:11px 22px;background:linear-gradient(135deg,var(--pri),var(--pri2));color:#fff;border-radius:11px;font-weight:700;font-size:.92rem;display:inline-flex;align-items:center;gap:8px;box-shadow:var(--sh2);transition:transform .15s,box-shadow .15s}
.btn-primary:hover{transform:translateY(-1px);box-shadow:0 8px 28px rgba(0,0,0,.18)}
.hero-progress{flex:1;min-width:200px;max-width:280px}
.hp-label{font-size:.74rem;font-weight:700;color:var(--muted);text-transform:uppercase;letter-spacing:.06em;display:block;margin-bottom:5px}
.hp-bar{height:8px;background:rgba(0,0,0,.12);border-radius:5px;overflow:hidden}
.hp-fill{height:100%;background:linear-gradient(90deg,var(--pri),var(--acc));border-radius:5px;width:0%;transition:width .6s cubic-bezier(.16,1,.3,1)}
.hp-text{font-size:.78rem;color:var(--muted);font-weight:700;margin-top:4px;display:block}
.hero-xp-badge{display:inline-flex;align-items:center;gap:6px;padding:6px 12px;background:linear-gradient(135deg,var(--warn,#f59e0b),var(--pri));color:#fff;border-radius:99px;font-size:.82rem;font-weight:800;letter-spacing:.02em;box-shadow:0 4px 12px rgba(0,0,0,.18);font-family:'Unbounded',sans-serif}
.psel{margin-bottom:24px}
.psel-title{font-size:.72rem;font-weight:800;color:var(--muted);text-transform:uppercase;letter-spacing:.08em;margin-bottom:10px}
.psel-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(180px,1fr));gap:10px}
.psel-card{background:var(--card);border:1.5px solid var(--border);border-radius:13px;padding:14px;cursor:pointer;transition:transform .2s,box-shadow .2s,border-color .2s;text-align:left;position:relative}
.psel-card:hover{transform:translateY(-3px);box-shadow:var(--sh2);border-color:var(--pri)}
.psel-card.active{border-color:var(--pri);background:linear-gradient(135deg,var(--pri-soft),var(--card));box-shadow:var(--sh2)}
.psel-card.active::after{content:'';position:absolute;top:0;left:0;right:0;height:3px;background:linear-gradient(90deg,var(--pri),var(--acc));border-radius:13px 13px 0 0}
.psel-num{font-family:'Unbounded',sans-serif;font-size:.72rem;font-weight:800;color:var(--pri);text-transform:uppercase;letter-spacing:.08em;margin-bottom:5px}
.psel-name{font-size:.86rem;font-weight:700;color:var(--text);line-height:1.3;margin-bottom:8px}
.psel-prog{height:4px;background:rgba(0,0,0,.10);border-radius:3px;overflow:hidden}
.psel-prog-fill{height:100%;background:var(--pri);width:0%;transition:width .4s}
.psel-card.final{background:linear-gradient(135deg,#fff5e1,#fef3c7)}
.psel-card.final .psel-num{color:var(--warn)}
.sec[id="sec-p6"]{ --sec-acc:#059669; --sec-acc-d:#047857; --sec-acc-soft:#d1fae5; }
.sec[id="sec-p7"]{ --sec-acc:#10b981; --sec-acc-d:#059669; --sec-acc-soft:#ecfdf5; }
.sec[id="sec-p8"]{ --sec-acc:#0891b2; --sec-acc-d:#0e7490; --sec-acc-soft:#cffafe; }
.sec[id="sec-p9"]{ --sec-acc:#7c3aed; --sec-acc-d:#6d28d9; --sec-acc-soft:#ede9fe; }
.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(0,0,0,.04);position:relative;z-index:1;transition:transform .25s cubic-bezier(.16,1,.3,1),box-shadow .25s}
.card:hover{transform:translateY(-2px);box-shadow:0 4px 10px rgba(0,0,0,.06),0 16px 36px rgba(0,0,0,.08)}
.card-header{display:flex;align-items:center;gap:10px;margin-bottom:12px;padding-bottom:10px;border-bottom:1px dashed var(--border)}
.card-icon{width:32px;height:32px;border-radius:9px;display:flex;align-items:center;justify-content:center;flex-shrink:0;color:#fff}
.card-icon.repeat{background:#0ea5e9}.card-icon.theory{background:#8b5cf6}.card-icon.algo{background:#f59e0b}.card-icon.rule{background:#ec4899}.card-icon.example{background:#10b981}.card-icon.oral{background:#06b6d4}
.card-icon .ic{width:18px;height:18px}
.card-title{font-family:'Unbounded',sans-serif;font-size:.82rem;font-weight:800;text-transform:uppercase;letter-spacing:.06em;color:var(--muted);flex:1}
.card-num{font-size:.74rem;font-weight:700;color:var(--muted);background:var(--sec-acc-soft,var(--pri-soft));padding:3px 7px;border-radius:5px}
.card-body{font-size:.94rem;line-height:1.65}
.card-body p{margin-bottom:8px}
.card-body p:last-child{margin-bottom:0}
.btn{padding:8px 16px;border-radius:8px;background:var(--card);color:var(--text);border:1.5px solid var(--border);font-weight:600;font-size:.88rem;transition:background .15s,border-color .15s,transform .1s}
.btn:hover{background:var(--sec-acc-soft,var(--pri-soft));border-color:var(--sec-acc,var(--pri))}
.btn:active{transform:scale(.96)}
.btn.primary{background:var(--sec-acc,var(--pri));color:#fff;border-color:var(--sec-acc,var(--pri))}
.btn.primary:hover{background:var(--sec-acc-d,var(--pri2));border-color:var(--sec-acc-d,var(--pri2))}
.feedback{padding:10px 14px;border-radius:9px;font-weight:600;font-size:.88rem;margin-top:8px;display:none}
.feedback.ok{display:block;background:var(--ok-bg);color:#065f46;border-left:4px solid var(--ok)}
.feedback.fail{display:block;background:var(--fail-bg);color:#7f1d1d;border-left:4px solid var(--fail)}
.wg{background:linear-gradient(135deg,var(--card),var(--sec-acc-soft,var(--pri-soft)));border:1.5px solid var(--sec-acc,var(--pri));border-radius:14px;padding:18px 20px;margin-bottom:18px;box-shadow:var(--sh2);position:relative;z-index:1}
.wg-header{display:flex;align-items:center;gap:8px;margin-bottom:14px}
.wg-badge{padding:4px 9px;background:var(--sec-acc,var(--pri));color:#fff;border-radius:6px;font-family:'Unbounded',sans-serif;font-size:.68rem;font-weight:800;text-transform:uppercase;letter-spacing:.06em}
.wg-title{font-family:'Unbounded',sans-serif;font-size:1.05rem;font-weight:800;color:var(--sec-acc-d,var(--pri2));flex:1}
.wg-help{font-size:.88rem;color:var(--text);margin-bottom:12px;line-height:1.55;background:linear-gradient(135deg,var(--warn-bg,#fef3c7),var(--sec-acc-soft,var(--pri-soft)));border-left:4px solid var(--warn,#f59e0b);padding:9px 14px;border-radius:9px}
.tinp{padding:8px 12px;border:1.5px solid var(--border);border-radius:8px;background:var(--card);color:var(--text);transition:border-color .15s;font-family:'JetBrains Mono',monospace}
.tinp:focus{outline:0;border-color:var(--sec-acc,var(--pri));box-shadow:0 0 0 3px var(--sec-acc-soft,var(--pri-soft))}
.actions{display:flex;gap:8px;flex-wrap:wrap;margin-top:10px}
.sliders{display:grid;grid-template-columns:repeat(auto-fit,minmax(180px,1fr));gap:10px;margin-bottom:10px}
.sliders label{display:block;font-size:.92rem;color:var(--muted);background:var(--card);padding:8px 12px;border-radius:8px;border:1px solid var(--border);line-height:1.5}
.sliders label b{font-family:'JetBrains Mono',monospace;font-size:1.05rem;color:var(--sec-acc-d,var(--pri2));margin-left:4px}
.sliders label input[type="range"]{display:block;width:100%;margin-top:6px;accent-color:var(--sec-acc,var(--pri))}
.score-display{display:flex;gap:14px;flex-wrap:wrap;align-items:center;padding:10px 14px;background:var(--sec-acc-soft,var(--pri-soft));border-radius:10px;margin-bottom:12px}
.score-display b{color:var(--sec-acc-d,var(--pri2));font-size:1.15rem}
.spoiler{border:1px solid var(--border);border-radius:10px;background:var(--card);margin:10px 0;overflow:hidden}
.spoiler summary{padding:8px 14px;background:var(--sec-acc-soft,var(--pri-soft));font-weight:700;cursor:pointer;font-size:.88rem;color:var(--sec-acc-d,var(--pri2));list-style:none;display:flex;align-items:center;gap:8px}
.spoiler summary::-webkit-details-marker{display:none}
.spoiler summary::before{content:'+';font-size:1.2rem;font-weight:900;color:var(--sec-acc,var(--pri));width:18px}
.spoiler[open] summary::before{content:'\2212'}
.spoiler-body{padding:10px 14px;font-size:.92rem;line-height:1.6}
.dnd-pool{display:flex;flex-wrap:wrap;gap:8px;margin-bottom:14px;padding:10px;border:1.5px dashed var(--border);border-radius:10px;min-height:54px;transition:border-color .18s,background .18s}
.dnd-pool.over{border-color:var(--sec-acc,var(--pri));background:var(--sec-acc-soft,var(--pri-soft));border-style:solid}
.dnd-pool.col{flex-direction:column;align-items:stretch}
.dnd-pool.col .dnd-chip{width:auto}
.dnd-chip{display:inline-flex;align-items:center;gap:6px;padding:6px 12px;background:var(--card);border:1.5px solid var(--border);border-radius:10px;cursor:grab;user-select:none;font-size:.92rem;line-height:1.4;transition:transform .12s,box-shadow .12s,border-color .12s;touch-action:none;max-width:100%}
.dnd-chip:hover{transform:translateY(-1px);border-color:var(--sec-acc,var(--pri));box-shadow:var(--sh)}
.dnd-chip:active{cursor:grabbing}
.dnd-chip.armed{border-color:var(--sec-acc,var(--pri));background:var(--sec-acc-soft,var(--pri-soft));box-shadow:0 0 0 3px rgba(217,119,6,.22);transform:translateY(-1px)}
.dnd-chip.dragging{opacity:.28}
.dnd-chip.placed{background:var(--sec-acc-soft,var(--pri-soft));border-color:var(--sec-acc,var(--pri))}
.dnd-chip .dnd-x{padding:0 5px;color:var(--muted);font-weight:700;font-size:1.05rem;border-radius:4px;cursor:pointer}
.dnd-chip .dnd-x:hover{color:var(--bad,var(--fail));background:var(--fail-bg)}
.drop-box{background:var(--card);border:1.5px dashed var(--border);border-radius:10px;padding:10px;min-height:90px;transition:border-color .15s,background .15s}
.drop-box:hover{border-color:var(--sec-acc,var(--pri));background:var(--sec-acc-soft,var(--pri-soft))}
.drop-box h5{font-family:'Unbounded',sans-serif;font-size:.78rem;color:var(--sec-acc-d,var(--pri2));margin-bottom:8px;text-transform:uppercase;letter-spacing:.05em}
.drop-box.over{border-color:var(--sec-acc,var(--pri));background:var(--sec-acc-soft,var(--pri-soft));border-style:solid;transform:scale(1.015)}
.drop-items{display:flex;flex-wrap:wrap;gap:6px;min-height:32px}
.dnd-hint{font-size:.78rem;color:var(--muted);margin-bottom:8px;display:flex;align-items:center;gap:6px}
.dnd-hint svg{width:14px;height:14px;flex-shrink:0}
.col-side{position:sticky;top:14px;align-self:start;height:fit-content;max-height:calc(100vh - 28px);overflow-y:auto}
.sidecard{background:var(--card);border:1px solid var(--border);border-radius:14px;padding:16px;margin-bottom:14px;box-shadow:var(--sh)}
.sidecard h4{font-family:'Unbounded',sans-serif;font-size:.74rem;font-weight:800;color:var(--pri2);text-transform:uppercase;letter-spacing:.07em;margin-bottom:10px;padding-bottom:8px;border-bottom:1px solid var(--border)}
.sidecard-row{margin-bottom:8px;font-size:.86rem;line-height:1.6}
.sidecard-row b{color:var(--pri);font-weight:700}
.sidecard-row:last-child{margin-bottom:0}
@media(max-width:980px){.col-side{position:static;max-height:none}}
.xp-card{background:linear-gradient(135deg,var(--acc-soft),var(--pri-soft));border:1.5px solid var(--acc);border-radius:12px;padding:14px;margin-bottom:14px}
.xp-card-title{font-size:.68rem;font-weight:800;color:var(--acc2);text-transform:uppercase;letter-spacing:.07em;margin-bottom:8px;display:flex;align-items:center;justify-content:space-between}
.xp-level{font-size:1.1rem;font-weight:900;color:var(--acc2);font-family:'Unbounded',sans-serif}
.xp-bar{height:9px;background:rgba(0,0,0,.10);border-radius:6px;overflow:hidden;margin:7px 0}
.xp-fill{height:100%;background:linear-gradient(90deg,var(--acc),var(--pri));border-radius:6px;transition:width .5s cubic-bezier(.4,0,.2,1)}
.xp-nums{font-size:.74rem;color:var(--muted);display:flex;justify-content:space-between}
.sec-nav{display:flex;gap:10px;margin-top:24px;padding-top:20px;border-top:1px solid var(--border);justify-content:space-between;flex-wrap:wrap}
.foot{text-align:center;padding:30px 16px;color:var(--muted);font-size:.78rem;border-top:1px solid var(--border);margin-top:30px}
.ach-popup{position:fixed;top:80px;right:18px;background:linear-gradient(135deg,var(--pri),var(--acc));color:#fff;padding:12px 18px;border-radius:11px;font-weight:700;font-size:.9rem;box-shadow:0 8px 28px rgba(0,0,0,.32);z-index:1002;display:none;align-items:center;gap:8px;max-width:340px}
.ach-popup.show{display:flex}
.col-side-backdrop{position:fixed;inset:0;background:rgba(0,0,0,.42);z-index:9990;display:none}
.col-side-backdrop.show{display:block}
@media(max-width:980px){
.col-side{position:fixed;top:0;right:0;height:100vh;width:300px;max-width:88vw;background:var(--bg);box-shadow:-12px 0 24px rgba(0,0,0,.18);padding:18px 16px;overflow-y:auto;transform:translateX(100%);transition:transform .25s ease;z-index:9991;max-height:none}
.col-side.open{transform:none}
}
.gloss-term{border-bottom:1.5px dotted var(--sec-acc,var(--pri));cursor:help;color:var(--sec-acc-d,var(--pri2));font-weight:600;padding:0 1px}
.gloss-term:hover{background:var(--sec-acc-soft,var(--pri-soft));border-radius:3px}
.gloss-tip{position:fixed;max-width:320px;padding:11px 14px;background:var(--card);border:1.5px solid var(--sec-acc,var(--pri));border-radius:11px;font-size:.84rem;line-height:1.55;box-shadow:0 12px 32px rgba(0,0,0,.18);z-index:9994;display:none;pointer-events:none;color:var(--text)}
.gloss-tip.show{display:block}
.gloss-tip b{color:var(--sec-acc-d,var(--pri2));font-size:.92rem}
.search-modal{position:fixed;inset:0;background:rgba(15,23,42,.55);backdrop-filter:blur(4px);z-index:9993;display:none;align-items:flex-start;justify-content:center;padding-top:14vh}
.search-modal.show{display:flex}
.search-box{background:var(--bg);border:1px solid var(--border);border-radius:14px;width:560px;max-width:92vw;max-height:70vh;display:flex;flex-direction:column;overflow:hidden;box-shadow:0 24px 64px rgba(0,0,0,.4)}
.search-input{padding:14px 16px;font-size:1rem;border:0;border-bottom:1px solid var(--border);background:transparent;color:var(--text);outline:none}
.search-results{flex:1;overflow-y:auto;padding:6px 0}
.search-row{display:block;padding:8px 16px;cursor:pointer;border-bottom:1px solid var(--border);text-align:left;background:transparent;border:0;width:100%;color:var(--text)}
.search-row:hover,.search-row.active{background:var(--sec-acc-soft,var(--pri-soft))}
.search-row .sr-kind{font-size:.7rem;font-weight:800;color:var(--muted);text-transform:uppercase;letter-spacing:.06em;margin-bottom:2px}
.search-row .sr-title{font-weight:700;font-size:.92rem;color:var(--text)}
.search-row .sr-desc{font-size:.8rem;color:var(--muted);margin-top:2px}
.search-empty{padding:20px;text-align:center;color:var(--muted);font-size:.88rem}
.search-foot{padding:8px 14px;border-top:1px solid var(--border);font-size:.74rem;color:var(--muted);display:flex;gap:14px}
.search-foot kbd{padding:2px 6px;background:var(--card);border:1px solid var(--border);border-radius:4px;font-family:'JetBrains Mono',monospace;font-size:.72rem}
</style>
</head>
<body>
<header class="hdr">
<div class="hdr-row">
<div>
<h1>Алгебра 9 · Глава 2</h1>
<div class="hdr-sub">Числовой аргумент · свойства · чётность · сдвиги</div>
</div>
<div class="hdr-side">
<a href="/textbook/algebra-9" class="hdr-btn"><svg class="ic" viewBox="0 0 24 24"><polyline points="15 18 9 12 15 6"/></svg> К алгебре 9</a>
<button id="search-btn" class="hdr-btn"><svg class="ic" viewBox="0 0 24 24"><circle cx="11" cy="11" r="7"/><path d="m21 21-4-4"/></svg> Поиск</button>
<button id="sidebar-btn" class="hdr-btn"><svg class="ic" viewBox="0 0 24 24"><line x1="4" y1="6" x2="20" y2="6"/><line x1="4" y1="12" x2="20" y2="12"/><line x1="4" y1="18" x2="14" y2="18"/></svg> Шпаргалка</button>
<button id="theme-btn" class="hdr-btn"><svg class="ic" viewBox="0 0 24 24"><path d="M21 12.8A9 9 0 1 1 11.2 3a7 7 0 0 0 9.8 9.8z"/></svg><span id="theme-lab">Тёмная</span></button>
</div>
</div>
</header>
<main class="main">
<div class="col-main">
<section class="hero">
<h2>Функции — изучаем поведение и графики</h2>
<p>Здесь мы знакомимся с <b>функцией числового аргумента</b>: область определения $D(f)$, область значений $E(f)$, <b>возрастание/убывание</b>, нули, наибольшее и наименьшее значения, <b>чётность</b> и <b>сдвиги</b> графиков $y = f(x) + b$, $y = f(x \pm a)$.</p>
<div class="hero-row">
<button class="btn-primary" onclick="goTo('p6')"><svg class="ic" viewBox="0 0 24 24"><polygon points="6 4 20 12 6 20 6 4" fill="currentColor" stroke="none"/></svg> Начать § 6</button>
<div class="hero-progress">
<span class="hp-label">Прогресс по главе</span>
<div class="hp-bar"><div id="hero-hp-fill" class="hp-fill"></div></div>
<span id="hero-hp-text" class="hp-text">0%</span>
</div>
<div id="hero-xp-badge" class="hero-xp-badge"></div>
</div>
</section>
<section class="psel">
<div class="psel-title">Параграфы главы</div>
<div id="psel-grid" class="psel-grid"></div>
</section>
<section id="sec-p6" class="sec" data-watermark="D/E"><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="↗"><div class="sec-header"><span class="sec-num">§ 7</span><h2 class="sec-h">Свойства функции</h2></div><div id="p7-body"></div></section>
<section id="sec-p8" class="sec" data-watermark="±"><div class="sec-header"><span class="sec-num">§ 8</span><h2 class="sec-h">Чётные и нечётные функции</h2></div><div id="p8-body"></div></section>
<section id="sec-p9" class="sec" data-watermark="→"><div class="sec-header"><span class="sec-num">§ 9</span><h2 class="sec-h">Сдвиги графиков</h2></div><div id="p9-body"></div></section>
<section id="sec-final2" class="sec" data-watermark="★"><div class="sec-header"><span class="sec-num" style="background:linear-gradient(135deg,#059669,#10b981)">Финал главы</span><h2 class="sec-h">Итоги. 5 боссов главы 2</h2></div><div id="final2-body"></div></section>
</div>
<aside class="col-side" id="col-side"><div id="sidebar-content"></div></aside>
<div class="col-side-backdrop" id="col-side-backdrop"></div>
</main>
<footer class="foot">Интерактивный учебник «Алгебра 9» · Глава 2 · Функции · LearnSpace</footer>
<div id="ach-popup" class="ach-popup"><svg class="ic" viewBox="0 0 24 24" style="width:22px;height:22px"><polygon points="12,2 22,20 2,20"/></svg><span id="ach-text">Достижение!</span></div>
<div id="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:'p6', progress:{p6:0,p7:0,p8:0,p9:0,final2:0}, achievements:new Map(), xp:0, level:1 };
const TOTAL_PARAS = 5;
const _TB_SLUG = 'algebra-9-ch2';
function calcLevel(xp){ return Math.floor(Math.sqrt((xp||0)/100))+1; }
function _xpForLevel(lv){ return (lv-1)*(lv-1)*100; }
const ACH_LABELS = {
start:'Начало главы 2!',
p7_done:'Свойства функции освоены!',
p8_done:'Чётность освоена!',
p9_done:'Сдвиги графиков освоены!',
ch2_done:'Глава 2 пройдена!'
};
function loadProgress(){
try{
const s=localStorage.getItem('algebra9_ch2_progress'); if(s) Object.assign(STATE.progress, JSON.parse(s));
const a=localStorage.getItem('algebra9_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('algebra9_xp')||0); STATE.level=calcLevel(STATE.xp);
}catch(e){}
}
function saveProgress(){
try{
localStorage.setItem('algebra9_ch2_progress', JSON.stringify(STATE.progress));
localStorage.setItem('algebra9_ch2_achievements', JSON.stringify(Object.fromEntries(STATE.achievements)));
localStorage.setItem('algebra9_xp', String(STATE.xp));
}catch(e){}
}
function bumpProgress(key, delta){
STATE.progress[key]=Math.max(0,Math.min(100,(STATE.progress[key]||0)+delta));
saveProgress(); refreshProgressUI();
if(STATE.progress[key]>=50) markParaRead(key);
}
const _markedRead=new Set();
let _pendingProgressBody=null, _progressTimer=null;
function _flushProgress(){
const body=_pendingProgressBody; _pendingProgressBody=null; if(!body) return;
const tok=(window.LS&&LS.getToken)?LS.getToken():''; if(!tok) return;
fetch('/api/textbooks/'+_TB_SLUG+'/progress',{method:'POST',headers:{'Content-Type':'application/json','Authorization':'Bearer '+tok},body:JSON.stringify(body),keepalive:true}).catch(()=>{});
}
function _queueProgress(patch){ _pendingProgressBody=Object.assign(_pendingProgressBody||{},patch); if(_progressTimer) clearTimeout(_progressTimer); _progressTimer=setTimeout(_flushProgress, 600); }
function markLastPara(id){ _queueProgress({last_para:id}); }
function markParaRead(id){ if(_markedRead.has(id)) return; _markedRead.add(id); _queueProgress({mark_read:id}); }
window.addEventListener('beforeunload', _flushProgress);
function loadServerReadState(){
const tok=(window.LS&&LS.getToken)?LS.getToken():''; if(!tok) return;
fetch('/api/textbooks/'+_TB_SLUG,{headers:{'Authorization':'Bearer '+tok}}).then(r=>r.ok?r.json():null).then(d=>{
if(!d||!d.progress) return;
(d.progress.read||[]).forEach(k=>{_markedRead.add(k); if((STATE.progress[k]||0)<50) STATE.progress[k]=100;});
saveProgress(); refreshProgressUI();
}).catch(()=>{});
}
function addXp(n,src){
if(!n) return;
const prev=STATE.level; STATE.xp=Math.max(0,(STATE.xp||0)+n); STATE.level=calcLevel(STATE.xp);
saveProgress(); refreshProgressUI();
if(window.LS&&window.LS.xp) window.LS.xp.add(n,'algebra9-ch2-'+(src||'misc'));
if(STATE.level>prev){
const pop=document.getElementById('ach-popup');
if(pop){ document.getElementById('ach-text').textContent='Уровень '+STATE.level+'!'; pop.classList.add('show'); setTimeout(()=>pop.classList.remove('show'),2600); }
}
}
function refreshProgressUI(){
const total=Math.round(Object.values(STATE.progress).reduce((a,b)=>a+b,0)/TOTAL_PARAS);
const f=document.getElementById('hero-hp-fill'); if(f) f.style.width=total+'%';
const t=document.getElementById('hero-hp-text'); if(t) t.textContent=total+'% пройдено';
document.querySelectorAll('[data-prog-card]').forEach(el=>{ const k=el.dataset.progCard; const fl=el.querySelector('.psel-prog-fill'); if(fl) fl.style.width=(STATE.progress[k]||0)+'%'; });
const xpBadge=document.getElementById('hero-xp-badge');
if(xpBadge){ xpBadge.innerHTML='<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" style="width:13px;height:13px"><polygon points="12 2 22 20 2 20"/></svg> Ур. '+STATE.level+' \xb7 '+(STATE.xp||0)+' XP'; }
if(STATE.current && document.getElementById('sidebar-content')){ try{ buildSidebar(STATE.current); }catch(e){} }
}
function achievement(id,text){
if(STATE.achievements.has(id)) return;
STATE.achievements.set(id, text||ACH_LABELS[id]||id); saveProgress();
const pop=document.getElementById('ach-popup');
if(pop){ document.getElementById('ach-text').textContent=text||ACH_LABELS[id]||id; pop.classList.add('show'); setTimeout(()=>pop.classList.remove('show'),3300); }
addXp(20,'ach-'+id);
}
const PARAS = [
{ id:'p6', num:'§ 6', name:'Функция числового аргумента', sub:'$D(f)$, $E(f)$' },
{ id:'p7', num:'§ 7', name:'Свойства функции', sub:'нули, монотонность, экстр.' },
{ id:'p8', num:'§ 8', name:'Чётные и нечётные функции', sub:'симметрия графика' },
{ id:'p9', num:'§ 9', name:'Сдвиги графиков', sub:'$y=f(x)+b$, $y=f(x \\pm a)$' },
{ id:'final2', num:'★', name:'Финал главы', sub:'Итоги · 5 боссов', final:true }
];
function buildParaSelector(){
const g=document.getElementById('psel-grid'); g.innerHTML='';
PARAS.forEach(p=>{
const card=document.createElement('div');
card.className='psel-card'+(p.final?' final':'');
card.dataset.id=p.id; card.dataset.progCard=p.id;
card.innerHTML='<div class="psel-num">'+p.num+'</div><div class="psel-name">'+p.name+'</div><div class="psel-prog"><div class="psel-prog-fill"></div></div>';
card.addEventListener('click', ()=>goTo(p.id));
g.appendChild(card);
});
}
const BUILT=new Set();
const BUILDERS = { p6:()=>buildP6(), p7:()=>buildP7(), p8:()=>buildP8(), p9:()=>buildP9(), final2:()=>buildFinal2() };
function ensureBuilt(id){ if(BUILT.has(id)) return; const fn=BUILDERS[id]; if(fn){ fn(); BUILT.add(id); } }
function goTo(id){
STATE.current=id; ensureBuilt(id);
document.querySelectorAll('.sec').forEach(s=>s.classList.remove('active'));
const el=document.getElementById('sec-'+id); if(el) el.classList.add('active');
document.querySelectorAll('.psel-card').forEach(c=>c.classList.toggle('active', c.dataset.id===id));
buildSidebar(id);
window.scrollTo({top:0,behavior:'smooth'});
if((STATE.progress[id]||0)<10) bumpProgress(id, 10);
if(window.renderMathInElement) setTimeout(()=>renderMath(el), 0);
markLastPara(id);
}
const SIDEBARS = {
p6:{title:'Шпаргалка \xA76',rows:[['Функция','правило $x \\to y$'],['$D(f)$','область определения'],['$E(f)$','область значений']]},
p7:{title:'Шпаргалка \xA77',rows:[['Нуль','$f(x_0) = 0$'],['Возрастает','при бо́льшем $x$ — бо́льшее $f(x)$'],['Убывает','при бо́льшем $x$ — меньшее $f(x)$'],['$y_{max}$','наиб. значение на промежутке']]},
p8:{title:'Шпаргалка \xA78',rows:[['Чётная','$f(-x) = f(x)$ — симм. отн. $Oy$'],['Нечётная','$f(-x) = -f(x)$ — симм. отн. $O$'],['Ни та, ни др.','общий случай']]},
p9:{title:'Шпаргалка \xA79',rows:[['$f(x) + b$','сдвиг вверх на $b$'],['$f(x) - b$','сдвиг вниз на $b$'],['$f(x - a)$','сдвиг вправо на $a$'],['$f(x + a)$','сдвиг влево на $a$']]},
final2:{title:'Финал главы',rows:[['§§69','теория главы 2'],['Боссов','5'],['Награда','+100 XP']]}
};
const TIPS=[
{sec:'p6',html:'Функция — это <b>правило</b>: каждому $x$ из $D(f)$ соответствует ровно одно $y$.'},
{sec:'p7',html:'<b>Нули</b> функции — это решения уравнения $f(x) = 0$.'},
{sec:'p8',html:'Чётная функция: $f(-x) = f(x)$. Нечётная: $f(-x) = -f(x)$.'},
{sec:'p9',html:'$y = f(x) + b$ — сдвиг по $Oy$. $y = f(x - a)$ — сдвиг по $Ox$ вправо на $a$.'},
{sec:'final2',html:'5 боссов главы 2.'}
];
function buildSidebar(id){
const box=document.getElementById('sidebar-content');
const sb=SIDEBARS[id]||SIDEBARS['p6'];
let html='';
const xpForLv=_xpForLevel(STATE.level), xpNext=_xpForLevel(STATE.level+1);
const xpInLv=STATE.xp-xpForLv, xpRange=xpNext-xpForLv;
const xpPct=xpRange>0?Math.round(xpInLv/xpRange*100):100;
html+='<div class="xp-card"><div class="xp-card-title"><span>XP-прогресс</span><span class="xp-level">Ур. '+STATE.level+'</span></div><div class="xp-bar"><div class="xp-fill" style="width:'+xpPct+'%"></div></div><div class="xp-nums"><span>'+STATE.xp+' XP</span><span>'+xpNext+' XP</span></div></div>';
html+='<div class="sidecard"><h4>'+sb.title+'</h4>';
sb.rows.forEach(([k,v])=>{ html+='<div class="sidecard-row"><b>'+k+'</b>'+(v?' — '+v:'')+'</div>'; });
html+='</div>';
const tip=TIPS.find(t=>t.sec===id)||TIPS[0];
if(tip){
html+='<div class="sidecard" style="background:linear-gradient(135deg,var(--warn-bg,#fef3c7),var(--pri-soft));border-color:var(--warn,#f59e0b)"><h4 style="color:#92400e;display:flex;align-items:center;gap:6px"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" style="width:14px;height:14px"><polygon points="12,2 22,20 2,20"/></svg>Подсказка</h4><div class="sidecard-row" style="margin-bottom:0;font-size:.84rem;line-height:1.55">'+tip.html+'</div></div>';
}
if(STATE.achievements.size>0){
html+='<div class="sidecard"><h4>Достижения <span style="color:var(--warn);float:right">'+STATE.achievements.size+'</span></h4>';
[...STATE.achievements.values()].slice(-4).forEach(text=>{ html+='<div class="sidecard-row" style="font-size:.78rem;color:var(--ok)">&#10003; '+text+'</div>'; });
html+='</div>';
}
box.innerHTML=html;
if(window.renderMathInElement) try{ renderMath(box); }catch(e){}
}
function initTheme(){
const t=localStorage.getItem('algebra9_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('algebra9_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 secNav(prev, next){
const NAMES={p6:'\xA76',p7:'\xA77',p8:'\xA78',p9:'\xA79',final2:'Финал'};
let h='<div class="sec-nav">';
h+=prev?'<button class="btn" onclick="goTo(\''+prev+'\')"><svg class="ic" viewBox="0 0 24 24"><polyline points="15 18 9 12 15 6"/></svg> '+NAMES[prev]+'</button>':'<span></span>';
h+=next?'<button class="btn primary" onclick="goTo(\''+next+'\')">'+NAMES[next]+' <svg class="ic" viewBox="0 0 24 24"><polyline points="9 18 15 12 9 6"/></svg></button>':'<span></span>';
h+='</div>'; return h;
}
function readButton(paraId){
return '<div style="margin-top:18px;display:flex;justify-content:center">'
+'<button class="btn primary" id="'+paraId+'-read-btn">'
+'<svg class="ic" viewBox="0 0 24 24"><path d="M4 19.5A2.5 2.5 0 0 1 6.5 17H20"/><path d="M6.5 2H20v20H6.5A2.5 2.5 0 0 1 4 19.5v-15A2.5 2.5 0 0 1 6.5 2z"/></svg>'
+' Я прочитал — '+(paraId.startsWith('final')?'финал':'\xA7'+paraId.replace('p',''))+' (+10 XP)'
+'</button></div>';
}
function wireReadBtn(paraId){
const btn = document.getElementById(paraId+'-read-btn'); if(!btn) return;
btn.addEventListener('click', ()=>{
addXp(10, paraId+'-read'); bumpProgress(paraId, 30);
btn.textContent='Прочитано! +10 XP'; btn.disabled=true; btn.style.opacity=.6;
if(paraId==='p7') achievement('p7_done');
if(paraId==='p8') achievement('p8_done');
if(paraId==='p9') achievement('p9_done');
if(paraId==='final2') achievement('ch2_done');
});
}
function gcd(a,b){ a=Math.abs(a|0); b=Math.abs(b|0); while(b){ const t=b; b=a%b; a=t; } return a||1; }
function makeCard(kind, title, num, body){
const labels = {repeat:'Повторение',theory:'Теория',algo:'Алгоритм',rule:'Правило',example:'Пример',oral:'Устно'};
return '<div class="card"><div class="card-header"><div class="card-icon '+kind+'">'+ICONS[kind]+'</div><div class="card-title">'+(labels[kind]||'')+(title&&title!==labels[kind]?' \xb7 '+title:'')+'</div>'+(num?'<div class="card-num">'+num+'</div>':'')+'</div><div class="card-body">'+body+'</div></div>';
}
function setupSorter(cfg){
const placed = {}; const pool = document.getElementById(cfg.poolId); const scope = document.querySelector(cfg.scopeSelector);
if(!pool||!scope) return {placed,render:()=>{},reset:()=>{}};
pool.classList.add('dnd-pool'); if(cfg.columnLayout) pool.classList.add('col');
let armed = null;
function buildChip(it,isPlaced){ const e=document.createElement('div'); e.className='dnd-chip'+(isPlaced?' placed':''); e.dataset.id=it.id; e.innerHTML='<span class="dnd-txt">'+it.html+'</span><span class="dnd-x" title="Убрать">\xd7</span>'; attach(e,it.id); return e; }
function attach(elm,itId){ let ghost=null,dragging=false,sx=0,sy=0; elm.addEventListener('pointerdown',ev=>{ if(ev.button!==undefined&&ev.button!==0) return;
ev.preventDefault(); if(ev.target.classList&&ev.target.classList.contains('dnd-x')){ ev.stopPropagation(); if(placed[itId]){delete placed[itId];render();}else if(armed===itId){armed=null;render();} return; } sx=ev.clientX;sy=ev.clientY; const r=elm.getBoundingClientRect(); const ox=ev.clientX-r.left,oy=ev.clientY-r.top; try{elm.setPointerCapture(ev.pointerId);}catch(e){} function onMove(e){ const dx=e.clientX-sx,dy=e.clientY-sy; if(!dragging&&Math.hypot(dx,dy)>8){ dragging=true; ghost=elm.cloneNode(true); ghost.classList.remove('armed'); ghost.style.cssText='position:fixed;z-index:9999;pointer-events:none;opacity:.9;transform:rotate(-2.5deg);box-shadow:0 14px 36px rgba(0,0,0,.32);width:'+r.width+'px;left:'+(e.clientX-ox)+'px;top:'+(e.clientY-oy)+'px'; document.body.appendChild(ghost); elm.classList.add('dragging'); } if(dragging&&ghost){ ghost.style.left=(e.clientX-ox)+'px';ghost.style.top=(e.clientY-oy)+'px'; const under=document.elementsFromPoint(e.clientX,e.clientY); scope.querySelectorAll('.drop-box.over,.dnd-pool.over').forEach(n=>n.classList.remove('over')); const tgt=under.find(n=>n.classList&&(n.classList.contains('drop-box')||n.classList.contains('dnd-pool'))); if(tgt)tgt.classList.add('over'); } } function onUp(e){ elm.removeEventListener('pointermove',onMove);elm.removeEventListener('pointerup',onUp);elm.removeEventListener('pointercancel',onUp);elm.classList.remove('dragging'); if(ghost){ghost.remove();ghost=null;} scope.querySelectorAll('.drop-box.over,.dnd-pool.over').forEach(n=>n.classList.remove('over')); if(dragging){ const under=document.elementsFromPoint(e.clientX,e.clientY); const box=under.find(n=>n.classList&&n.classList.contains('drop-box')); const pl=under.find(n=>n.classList&&n.classList.contains('dnd-pool')); if(box){const di=box.querySelector('[data-cat]');if(di){placed[itId]=di.dataset.cat;armed=null;render();return;}}else if(pl){delete placed[itId];armed=null;render();return;} }else{ if(placed[itId]){delete placed[itId];armed=null;render();}else{armed=(armed===itId)?null:itId;render();} } dragging=false; } elm.addEventListener('pointermove',onMove);elm.addEventListener('pointerup',onUp);elm.addEventListener('pointercancel',onUp); }); }
function attachBoxTaps(){ scope.querySelectorAll('.drop-box').forEach(box=>{ box.addEventListener('click',ev=>{ if(!armed)return; if(ev.target.closest('.dnd-chip'))return; const di=box.querySelector('[data-cat]'); if(di){placed[armed]=di.dataset.cat;armed=null;render();} }); }); }
function render(){ pool.innerHTML=''; cfg.items.forEach(it=>{if(placed[it.id])return;const c=buildChip(it,false);if(armed===it.id)c.classList.add('armed');pool.appendChild(c);}); cfg.cats.forEach(cat=>{const box=scope.querySelector('.drop-items[data-cat="'+cat+'"]');if(!box)return;box.innerHTML='';cfg.items.forEach(it=>{if(placed[it.id]!==cat)return;box.appendChild(buildChip(it,true));});}); if(window.renderMathInElement)try{renderMath(scope);}catch(_){} }
attachBoxTaps(); render();
return {placed,render,reset(){ for(const k in placed)delete placed[k];armed=null;render(); }};
}
/* Координатная плоскость в SVG */
function axes2D(W, H, pad, xmin, xmax, ymin, ymax){
const ux = (W - 2*pad) / (xmax - xmin);
const uy = (H - 2*pad) / (ymax - ymin);
const toX = v => pad + (v - xmin) * ux;
const toY = v => H - pad - (v - ymin) * uy;
let g = '';
g += '<g stroke="#e5e7eb" stroke-width="1">';
for (let x = Math.ceil(xmin); x <= xmax; x++){
g += '<line x1="'+toX(x)+'" y1="'+pad+'" x2="'+toX(x)+'" y2="'+(H-pad)+'"/>';
}
for (let y = Math.ceil(ymin); y <= ymax; y++){
g += '<line x1="'+pad+'" y1="'+toY(y)+'" x2="'+(W-pad)+'" y2="'+toY(y)+'"/>';
}
g += '</g>';
const y0 = toY(0), x0 = toX(0);
g += '<line x1="'+pad+'" y1="'+y0+'" x2="'+(W-pad)+'" y2="'+y0+'" stroke="#0f172a" stroke-width="1.5"/>';
g += '<line x1="'+x0+'" y1="'+pad+'" x2="'+x0+'" y2="'+(H-pad)+'" stroke="#0f172a" stroke-width="1.5"/>';
g += '<text x="'+(W-pad+2)+'" y="'+(y0-4)+'" font-size="11" fill="#0f172a">x</text>';
g += '<text x="'+(x0+4)+'" y="'+(pad-2)+'" font-size="11" fill="#0f172a">y</text>';
g += '<g font-size="10" fill="#64748b">';
for (let x = Math.ceil(xmin); x <= xmax; x++){
if (x !== 0) g += '<text x="'+(toX(x)-3)+'" y="'+(y0+12)+'">'+x+'</text>';
}
for (let y = Math.ceil(ymin); y <= ymax; y++){
if (y !== 0) g += '<text x="'+(x0+4)+'" y="'+(toY(y)+3)+'">'+y+'</text>';
}
g += '<text x="'+(x0+4)+'" y="'+(y0+12)+'">0</text>';
g += '</g>';
return { content: g, toX, toY, ux, uy };
}
/* График функции y=f(x) — возвращает строку <path ...> */
function plotFunc(f, xmin, xmax, toX, toY, color, N){
N = N || 200;
let d = '';
let prevValid = false;
for (let i = 0; i <= N; i++){
const x = xmin + (xmax - xmin) * i / N;
let y;
try { y = f(x); } catch(e){ y = NaN; }
if (!isFinite(y) || isNaN(y) || y < -1e4 || y > 1e4){ prevValid = false; continue; }
d += (prevValid ? ' L' : ' M') + toX(x).toFixed(2) + ',' + toY(y).toFixed(2);
prevValid = true;
}
return '<path d="'+d+'" stroke="'+color+'" stroke-width="2" fill="none" stroke-linecap="round" stroke-linejoin="round"/>';
}
/* ===== STUB BUILDERS — наполнение в Phase 1+ ===== */
function buildP6(){
const box = document.getElementById('p6-body');
let html = '';
html += makeCard('theory', 'Определение функции', '6.1', `
<p>Если каждому значению $x$ из некоторого множества $X$ поставлено в соответствие <b>единственное</b> значение $y$, то говорят, что $y$ есть <b>функция</b> от $x$. Записывают $y = f(x)$.</p>
<p>Переменную $x$ называют <b>аргументом</b> (или независимой переменной), а $y$ — <b>значением функции</b> (зависимой переменной).</p>
<ul style="padding-left:22px;line-height:1.9">
<li>Множество $X$ — <b>область определения</b> функции, обозначается $D(f)$.</li>
<li>Множество всех значений $y = f(x)$ — <b>область значений</b>, обозначается $E(f)$.</li>
</ul>
<details class="spoiler"><summary>А что если двум разным $x$ соответствует одно $y$?</summary><div class="spoiler-body">
Это нормально! Главное требование: одному $x$ — ровно одно $y$. А разные $x$ могут давать одно и то же $y$ — например, $f(x) = x^2$: при $x = -2$ и $x = 2$ получаем одно значение $y = 4$.
</div></details>`);
html += makeCard('rule', 'Три способа задания функции', '6.2', `
<p>Функцию можно задать одним из трёх основных способов:</p>
<ol style="padding-left:22px;line-height:2">
<li><b>Формулой (аналитически):</b> $y = 2x + 1$, $y = \\dfrac{1}{x}$, $y = \\sqrt{x}$.</li>
<li><b>Таблицей:</b> две строки — значения $x$ и соответствующие $y$.</li>
<li><b>Графиком:</b> точки $(x; f(x))$ в координатной плоскости.</li>
</ol>
<p>Иногда функцию задают <b>словесным описанием</b>: «каждому натуральному числу поставлен в соответствие его последний разряд».</p>`);
html += makeCard('example', 'Нахождение области определения', '6.3', `
<p><b>а)</b> $f(x) = \\dfrac{1}{x - 3}$. Знаменатель не равен нулю: $x - 3 \\ne 0 \\Rightarrow x \\ne 3$. <b>$D(f) = (-\\infty; 3) \\cup (3; +\\infty)$.</b></p>
<p><b>б)</b> $f(x) = \\sqrt{x - 2}$. Подкоренное выражение неотрицательно: $x - 2 \\ge 0 \\Rightarrow x \\ge 2$. <b>$D(f) = [2; +\\infty)$.</b></p>
<p><b>в)</b> $f(x) = x^2 + 1$. Никаких ограничений нет. <b>$D(f) = \\mathbb{R}$.</b></p>
<p><b>г)</b> $f(x) = \\dfrac{\\sqrt{x}}{x - 5}$. Сразу два условия: $x \\ge 0$ и $x \\ne 5$. <b>$D(f) = [0; 5) \\cup (5; +\\infty)$.</b></p>`);
/* INTERACTIVE 1 — график + точка */
html += `<div class="wg" id="p6-iv1">
<div class="wg-header"><span class="wg-badge">ИНТЕРАКТИВ 1</span><div class="wg-title">График и точка $(x_0; f(x_0))$</div></div>
<div class="wg-help">Выбери функцию ползунком, а потом — значение $x_0$. На графике появится точка $(x_0; f(x_0))$ и пунктирные линии к осям.</div>
<div class="sliders">
<label>Функция №<b id="p6-iv1-fi">1</b> / 5<input type="range" id="p6-iv1-fn" min="1" max="5" step="1" value="1"></label>
<label>$x_0$ =<b id="p6-iv1-xv">0</b><input type="range" id="p6-iv1-xs" min="-5" max="5" step="0.25" value="0"></label>
</div>
<div id="p6-iv1-formula" style="text-align:center;font-size:1.1rem;padding:10px;background:var(--card);border-radius:9px;margin-bottom:10px"></div>
<div style="background:var(--card);border-radius:10px;padding:10px;overflow-x:auto">
<svg id="p6-iv1-svg" viewBox="0 0 360 280" style="width:100%;max-width:480px;height:auto;display:block;margin:0 auto"></svg>
</div>
<div id="p6-iv1-out" style="margin-top:10px;padding:10px 14px;background:var(--sec-acc-soft);border-radius:9px;font-size:.95rem;text-align:center"></div>
</div>`;
/* INTERACTIVE 2 — функция или нет */
html += `<div class="wg" id="p6-iv2">
<div class="wg-header"><span class="wg-badge">ИНТЕРАКТИВ 2</span><div class="wg-title">Функция или нет?</div></div>
<div class="wg-help">Помни главное правило: одному $x$ — ровно одно $y$. Если хотя бы одному $x$ соответствует два разных $y$ — это уже не функция.</div>
<div class="score-display">Задача: <b id="p6-iv2-idx">1</b> / 6 &middot; Очки: <b id="p6-iv2-sc">0</b></div>
<div id="p6-iv2-q" style="text-align:center;font-size:1.05rem;padding:14px;background:var(--card);border-radius:9px;margin-bottom:12px;min-height:60px"></div>
<div class="actions" style="justify-content:center">
<button class="btn primary" id="p6-iv2-y">Да, функция</button>
<button class="btn" id="p6-iv2-n">Нет, не функция</button>
</div>
<div class="feedback" id="p6-iv2-fb"></div>
</div>`;
/* INTERACTIVE 3 — DnD сортер D(f) */
html += `<div class="wg" id="p6-iv3">
<div class="wg-header"><span class="wg-badge">ИНТЕРАКТИВ 3</span><div class="wg-title">Найди $D(f)$</div></div>
<div class="wg-help">Перетащи каждую функцию в подходящий ящик с её областью определения.</div>
<div id="p6-iv3-pool"></div>
<div style="display:grid;grid-template-columns:repeat(auto-fit,minmax(180px,1fr));gap:10px">
<div class="drop-box"><h5>$\\mathbb{R}$</h5><div class="drop-items" data-cat="R"></div></div>
<div class="drop-box"><h5>Все, кроме точки</h5><div class="drop-items" data-cat="hole"></div></div>
<div class="drop-box"><h5>Луч $[a; +\\infty)$</h5><div class="drop-items" data-cat="ray"></div></div>
</div>
<div class="actions"><button class="btn primary" id="p6-iv3-check">Проверить</button><button class="btn" id="p6-iv3-reset">Сбросить</button></div>
<div class="feedback" id="p6-iv3-fb"></div>
</div>`;
/* INTERACTIVE 4 — калькулятор f(x) */
html += `<div class="wg" id="p6-iv4">
<div class="wg-header"><span class="wg-badge">ИНТЕРАКТИВ 4</span><div class="wg-title">Калькулятор $f(x)$</div></div>
<div class="wg-help">Выбери функцию из списка и значение $x$. Калькулятор проверит принадлежность $D(f)$ и посчитает $y$.</div>
<div style="display:flex;gap:10px;align-items:center;flex-wrap:wrap;justify-content:center;margin-bottom:10px">
<select id="p6-iv4-fn" class="tinp" style="min-width:200px">
<option value="0">y = x²</option>
<option value="1">y = 2x + 5</option>
<option value="2">y = 1/x</option>
<option value="3">y = √x</option>
<option value="4">y = |x|</option>
<option value="5">y = (x+1)/(x-3)</option>
</select>
<span style="font-family:'JetBrains Mono',monospace">$x$ =</span>
<input type="number" id="p6-iv4-x" class="tinp" style="width:90px;text-align:center" value="2" step="0.5">
<button class="btn primary" id="p6-iv4-go">Вычислить</button>
</div>
<div id="p6-iv4-out" style="padding:12px 14px;background:var(--card);border-radius:9px;text-align:center;font-size:1rem;min-height:48px"></div>
</div>`;
box.innerHTML = html + secNav(null, 'p7') + readButton('p6');
renderMath(box);
/* ===== IV1 wiring ===== */
(function(){
const fns = [
{ label: 'y = 2x - 1', f: x=>2*x-1, ok: x=>true },
{ label: 'y = x²', f: x=>x*x, ok: x=>true },
{ label: 'y = -x + 3', f: x=>-x+3, ok: x=>true },
{ label: 'y = 6/x', f: x=>6/x, ok: x=>x!==0 },
{ label: 'y = √x', f: x=>Math.sqrt(x), ok: x=>x>=0 }
];
const svg = document.getElementById('p6-iv1-svg');
const fnSl = document.getElementById('p6-iv1-fn');
const xSl = document.getElementById('p6-iv1-xs');
const fi = document.getElementById('p6-iv1-fi');
const xv = document.getElementById('p6-iv1-xv');
const formula = document.getElementById('p6-iv1-formula');
const out = document.getElementById('p6-iv1-out');
let bumped = false;
function redraw(){
const idx = (+fnSl.value)-1;
const x0 = +xSl.value;
const fobj = fns[idx];
fi.textContent = (idx+1);
xv.textContent = fmt(x0);
const ax = axes2D(360, 280, 28, -6, 6, -6, 6);
let g = ax.content;
g += plotFunc(fobj.f, -6, 6, ax.toX, ax.toY, '#059669', 300);
let outHtml = '';
if (fobj.ok(x0)){
const y0 = fobj.f(x0);
if (isFinite(y0) && y0>=-6 && y0<=6){
const px = ax.toX(x0), py = ax.toY(y0);
g += '<line x1="'+px+'" y1="'+py+'" x2="'+px+'" y2="'+ax.toY(0)+'" stroke="#ef4444" stroke-width="1.2" stroke-dasharray="4 3"/>';
g += '<line x1="'+px+'" y1="'+py+'" x2="'+ax.toX(0)+'" y2="'+py+'" stroke="#ef4444" stroke-width="1.2" stroke-dasharray="4 3"/>';
g += '<circle cx="'+px+'" cy="'+py+'" r="5" fill="#ef4444" stroke="#fff" stroke-width="2"/>';
}
outHtml = '$f(' + fmt(x0) + ') = ' + fmt(+y0.toFixed(4)) + '$';
} else {
outHtml = '$f(' + fmt(x0) + ')$ — не определена';
}
svg.innerHTML = g;
formula.innerHTML = '$' + fobj.label.replace('y =','y =').replace('²','^2').replace('√','\\sqrt') + '$';
// отрисовать формулу из набора: подменим на корректные KaTeX-варианты
const tex = ['y = 2x - 1','y = x^2','y = -x + 3','y = \\dfrac{6}{x}','y = \\sqrt{x}'];
formula.innerHTML = '$' + tex[idx] + '$';
out.innerHTML = outHtml;
renderMath(formula); renderMath(out);
if (!bumped){ bumped = true; bumpProgress('p6', 15); addXp(10,'p6-iv1'); }
}
fnSl.addEventListener('input', redraw);
xSl.addEventListener('input', redraw);
redraw();
})();
/* ===== IV2 wiring ===== */
(function(){
const items = [
{ q: 'Соответствие $\\{(1,2),\\ (2,4),\\ (3,6)\\}$', ans: true, hint: 'Каждому $x$ — один $y$. Это функция.' },
{ q: 'Соответствие $\\{(1,2),\\ (1,3),\\ (2,4)\\}$', ans: false, hint: 'При $x = 1$ получаем сразу 2 и 3. Не функция.' },
{ q: '$y = x^2$', ans: true, hint: 'Каждому $x$ соответствует ровно одно $y$.' },
{ q: '$y^2 = x$', ans: false, hint: 'При $x = 4$ получаем $y = 2$ и $y = -2$. Не функция.' },
{ q: '$y = |x|$', ans: true, hint: 'Один $x$ → один $y$.' },
{ q: 'Окружность $x^2 + y^2 = 9$', ans: false, hint: 'При $x = 0$ есть $y = 3$ и $y = -3$. Не функция.' }
];
let i = 0, sc = 0;
const idxEl = document.getElementById('p6-iv2-idx');
const scEl = document.getElementById('p6-iv2-sc');
const qEl = document.getElementById('p6-iv2-q');
const fb = document.getElementById('p6-iv2-fb');
const yBtn = document.getElementById('p6-iv2-y');
const nBtn = document.getElementById('p6-iv2-n');
let bumped = false;
function render(){
idxEl.textContent = Math.min(i+1, items.length);
scEl.textContent = sc;
if (i >= items.length){
qEl.innerHTML = '<b>Готово!</b> Результат: ' + sc + ' / ' + items.length;
yBtn.disabled = true; nBtn.disabled = true;
yBtn.style.opacity = .5; nBtn.style.opacity = .5;
if (!bumped){ bumped = true; bumpProgress('p6', 15); addXp(10,'p6-iv2'); }
return;
}
qEl.innerHTML = items[i].q;
fb.style.display = 'none';
renderMath(qEl);
}
function answer(v){
if (i >= items.length) return;
const it = items[i];
const ok = (v === it.ans);
if (ok) sc++;
feedback(fb, ok, (ok?'&#10003; Верно. ':'&#10007; Неверно. ') + it.hint);
i++;
setTimeout(render, 900);
}
yBtn.addEventListener('click', ()=>answer(true));
nBtn.addEventListener('click', ()=>answer(false));
render();
})();
/* ===== IV3 wiring — DnD sorter ===== */
(function(){
const items = [
{ id:'a', html:'$f(x) = x^2 + 1$', cat:'R' },
{ id:'b', html:'$f(x) = \\dfrac{1}{x}$', cat:'hole' },
{ id:'c', html:'$f(x) = \\sqrt{x}$', cat:'ray' },
{ id:'d', html:'$f(x) = \\dfrac{1}{x-2}$', cat:'hole' },
{ id:'e', html:'$f(x) = \\sqrt{x-3}$', cat:'ray' },
{ id:'f', html:'$f(x) = 5x - 7$', cat:'R' }
];
const sorter = setupSorter({
poolId: 'p6-iv3-pool',
scopeSelector: '#p6-iv3',
cats: ['R','hole','ray'],
items: items
});
let bumped = false;
document.getElementById('p6-iv3-check').addEventListener('click', ()=>{
const fb = document.getElementById('p6-iv3-fb');
const total = items.length;
let correct = 0, placed = 0;
items.forEach(it=>{ if(sorter.placed[it.id]){ placed++; if(sorter.placed[it.id]===it.cat) correct++; } });
if (placed < total){
feedback(fb, false, 'Размещены не все: ' + placed + ' / ' + total + '.');
return;
}
const ok = (correct === total);
feedback(fb, ok, ok ? '&#10003; Все верно! ' + correct + ' / ' + total : '&#10007; Правильно: ' + correct + ' / ' + total);
if (ok && !bumped){ bumped = true; bumpProgress('p6', 25); addXp(15,'p6-iv3'); }
});
document.getElementById('p6-iv3-reset').addEventListener('click', ()=>{
sorter.reset();
const fb = document.getElementById('p6-iv3-fb'); fb.style.display='none';
});
})();
/* ===== IV4 wiring — calculator ===== */
(function(){
const fns = [
{ tex:'y = x^2', f:x=>x*x, ok:x=>true, bad:'' },
{ tex:'y = 2x + 5', f:x=>2*x+5, ok:x=>true, bad:'' },
{ tex:'y = \\dfrac{1}{x}', f:x=>1/x, ok:x=>x!==0, bad:'$x = 0$' },
{ tex:'y = \\sqrt{x}', f:x=>Math.sqrt(x), ok:x=>x>=0, bad:'$x < 0$' },
{ tex:'y = |x|', f:x=>Math.abs(x), ok:x=>true, bad:'' },
{ tex:'y = \\dfrac{x+1}{x-3}', f:x=>(x+1)/(x-3), ok:x=>x!==3, bad:'$x = 3$' }
];
const out = document.getElementById('p6-iv4-out');
let bumped = false;
document.getElementById('p6-iv4-go').addEventListener('click', ()=>{
const idx = +document.getElementById('p6-iv4-fn').value;
const x = +document.getElementById('p6-iv4-x').value;
const fn = fns[idx];
if (!fn.ok(x)){
out.innerHTML = 'Функция $' + fn.tex + '$ <b>не определена</b> при ' + fn.bad + '.';
} else {
const y = fn.f(x);
out.innerHTML = '$' + fn.tex + '$, при $x = ' + fmt(x) + '$: $y = ' + fmt(+y.toFixed(6)) + '$.';
}
renderMath(out);
if (!bumped){ bumped = true; bumpProgress('p6', 25); addXp(15,'p6-iv4'); }
});
})();
wireReadBtn('p6');
}
function buildP7(){
const box = document.getElementById('p7-body');
let html = '';
html += makeCard('theory', 'Возрастание и убывание', '7.1', `
<p>Функция $f$ <b>возрастает</b> на промежутке $I$, если для любых $x_1, x_2 \\in I$ из $x_1 < x_2$ следует $f(x_1) < f(x_2)$ (бо́льшему аргументу — бо́льшее значение).</p>
<p>Функция $f$ <b>убывает</b> на промежутке $I$, если для любых $x_1, x_2 \\in I$ из $x_1 < x_2$ следует $f(x_1) > f(x_2)$ (бо́льшему аргументу — меньшее значение).</p>
<p>Функция называется <b>монотонной</b> на $I$, если она возрастает или убывает на $I$.</p>
<details class="spoiler"><summary>Пример</summary><div class="spoiler-body">
$y = x^2$ убывает на $(-\\infty; 0]$ и возрастает на $[0; +\\infty)$. На всём $\\mathbb{R}$ — не монотонна.
</div></details>`);
html += makeCard('rule', 'Нули и знакопостоянство', '7.2', `
<p><b>Нуль</b> функции — такое значение $x_0$, при котором $f(x_0) = 0$. Графически — точка пересечения графика с осью $Ox$.</p>
<p>Чтобы найти нули, решают уравнение $f(x) = 0$.</p>
<p><b>Промежутки знакопостоянства</b> — те, на которых функция сохраняет знак:</p>
<ul style="padding-left:22px;line-height:1.9">
<li>$f(x) > 0$ — график над осью $Ox$;</li>
<li>$f(x) < 0$ — график под осью $Ox$.</li>
</ul>
<p>Пример. Для $f(x) = x^2 - 4$: нули $x = \\pm 2$; $f(x) > 0$ на $(-\\infty;-2) \\cup (2;+\\infty)$; $f(x) < 0$ на $(-2; 2)$.</p>`);
html += makeCard('example', 'Экстремумы функции', '7.3', `
<p>Точка $x_0$ — <b>точка максимума</b>, если в некоторой её окрестности $f(x_0) \\ge f(x)$ для всех $x$. Значение $f(x_0)$ называют <b>максимумом</b> и пишут $f_{\\max}$.</p>
<p>Аналогично, $x_0$ — <b>точка минимума</b>, если $f(x_0) \\le f(x)$ в окрестности. Значение — $f_{\\min}$.</p>
<p>Примеры:</p>
<ul style="padding-left:22px;line-height:1.9">
<li>$y = x^2$ — точка минимума $x = 0$, $f_{\\min} = 0$.</li>
<li>$y = -x^2 + 1$ — точка максимума $x = 0$, $f_{\\max} = 1$.</li>
<li>$y = x^3$ — экстремумов нет (функция возрастает всюду).</li>
</ul>`);
/* INTERACTIVE 1 — анализ графика */
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">Выбери функцию ползунком. На графике видны нули (красные точки) и экстремум (синяя звёздочка), а под графиком — все свойства функции.</div>
<div class="sliders">
<label>Функция №<b id="p7-iv1-fi">1</b> / 5<input type="range" id="p7-iv1-fn" min="1" max="5" step="1" value="1"></label>
</div>
<div id="p7-iv1-formula" style="text-align:center;font-size:1.1rem;padding:10px;background:var(--card);border-radius:9px;margin-bottom:10px"></div>
<div style="background:var(--card);border-radius:10px;padding:10px;overflow-x:auto">
<svg id="p7-iv1-svg" viewBox="0 0 400 280" style="width:100%;max-width:520px;height:auto;display:block;margin:0 auto"></svg>
</div>
<div id="p7-iv1-out" style="margin-top:10px;padding:12px 14px;background:var(--sec-acc-soft);border-radius:9px;font-size:.92rem;line-height:1.7"></div>
</div>`;
/* INTERACTIVE 2 — нули функции */
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> всех нулей функции. Если нулей нет — введи <b>-999</b>. Если бесконечно много — введи <b>999</b>.</div>
<div class="score-display">Задача: <b id="p7-iv2-idx">1</b> / 6 &middot; Очки: <b id="p7-iv2-sc">0</b></div>
<div id="p7-iv2-q" style="text-align:center;font-size:1.1rem;padding:14px;background:var(--card);border-radius:9px;margin-bottom:12px"></div>
<div style="display:flex;gap:10px;justify-content:center;flex-wrap:wrap;align-items:center">
<span>Сумма нулей =</span>
<input type="number" id="p7-iv2-in" class="tinp" style="width:110px;text-align:center" step="1">
<button class="btn primary" id="p7-iv2-go">Проверить</button>
</div>
<div class="feedback" id="p7-iv2-fb"></div>
</div>`;
/* INTERACTIVE 3 — возрастает или убывает */
html += `<div class="wg" id="p7-iv3">
<div class="wg-header"><span class="wg-badge">ИНТЕРАКТИВ 3</span><div class="wg-title">Возрастает или убывает?</div></div>
<div class="wg-help">Для каждой функции и промежутка выбери: возрастает она или убывает.</div>
<div class="score-display">Задача: <b id="p7-iv3-idx">1</b> / 6 &middot; Очки: <b id="p7-iv3-sc">0</b></div>
<div id="p7-iv3-q" style="text-align:center;font-size:1.05rem;padding:14px;background:var(--card);border-radius:9px;margin-bottom:12px;min-height:60px"></div>
<div class="actions" style="justify-content:center">
<button class="btn primary" id="p7-iv3-u">Возрастает</button>
<button class="btn" id="p7-iv3-d">Убывает</button>
</div>
<div class="feedback" id="p7-iv3-fb"></div>
</div>`;
/* INTERACTIVE 4 — DnD сортер */
html += `<div class="wg" id="p7-iv4">
<div class="wg-header"><span class="wg-badge">ИНТЕРАКТИВ 4</span><div class="wg-title">Свойства: возрастает / убывает</div></div>
<div class="wg-help">Перетащи каждую карточку с функцией и промежутком в нужный ящик.</div>
<div id="p7-iv4-pool"></div>
<div style="display:grid;grid-template-columns:1fr 1fr;gap:10px">
<div class="drop-box"><h5>Возрастает</h5><div class="drop-items" data-cat="up"></div></div>
<div class="drop-box"><h5>Убывает</h5><div class="drop-items" data-cat="down"></div></div>
</div>
<div class="actions"><button class="btn primary" id="p7-iv4-check">Проверить</button><button class="btn" id="p7-iv4-reset">Сбросить</button></div>
<div class="feedback" id="p7-iv4-fb"></div>
</div>`;
box.innerHTML = html + secNav('p6', 'p8') + readButton('p7');
renderMath(box);
/* ===== IV1 wiring — анализ графика ===== */
(function(){
const fns = [
{
tex: 'y = x^2 - 4',
f: x=>x*x - 4,
zeros: [-2, 2],
ext: {x:0, y:-4, kind:'min'},
desc: 'Нули: $x = -2,\\ x = 2$. Точка минимума $x = 0$, $f_{\\min} = -4$. Убывает на $(-\\infty; 0]$, возрастает на $[0; +\\infty)$.'
},
{
tex: 'y = -x^2 + 3',
f: x=>-x*x + 3,
zeros: [-Math.sqrt(3), Math.sqrt(3)],
ext: {x:0, y:3, kind:'max'},
desc: 'Нули: $x = \\pm\\sqrt{3}$. Точка максимума $x = 0$, $f_{\\max} = 3$. Возрастает на $(-\\infty; 0]$, убывает на $[0; +\\infty)$.'
},
{
tex: 'y = x^3',
f: x=>x*x*x,
zeros: [0],
ext: null,
desc: 'Нуль: $x = 0$. Экстремумов нет. Возрастает на всей числовой прямой $\\mathbb{R}$.'
},
{
tex: 'y = (x - 2)^2 - 1',
f: x=>(x-2)*(x-2) - 1,
zeros: [1, 3],
ext: {x:2, y:-1, kind:'min'},
desc: 'Нули: $x = 1,\\ x = 3$. Точка минимума $x = 2$, $f_{\\min} = -1$. Убывает на $(-\\infty; 2]$, возрастает на $[2; +\\infty)$.'
},
{
tex: 'y = -|x| + 2',
f: x=>-Math.abs(x) + 2,
zeros: [-2, 2],
ext: {x:0, y:2, kind:'max'},
desc: 'Нули: $x = \\pm 2$. Точка максимума $x = 0$, $f_{\\max} = 2$. Возрастает на $(-\\infty; 0]$, убывает на $[0; +\\infty)$.'
}
];
const sl = document.getElementById('p7-iv1-fn');
const fi = document.getElementById('p7-iv1-fi');
const svg = document.getElementById('p7-iv1-svg');
const formula = document.getElementById('p7-iv1-formula');
const out = document.getElementById('p7-iv1-out');
let bumped = false;
function redraw(){
const idx = (+sl.value)-1;
const fobj = fns[idx];
fi.textContent = (idx+1);
const ax = axes2D(400, 280, 30, -5, 5, -4, 6);
let g = ax.content;
g += plotFunc(fobj.f, -5, 5, ax.toX, ax.toY, '#10b981', 300);
// нули
fobj.zeros.forEach(z=>{
if (z>=-5 && z<=5){
const cx = ax.toX(z), cy = ax.toY(0);
g += '<circle cx="'+cx+'" cy="'+cy+'" r="5" fill="#ef4444" stroke="#fff" stroke-width="2"/>';
}
});
// экстремум — звёздочка
if (fobj.ext){
const ex = fobj.ext;
if (ex.x>=-5 && ex.x<=5 && ex.y>=-4 && ex.y<=6){
const cx = ax.toX(ex.x), cy = ax.toY(ex.y);
// 5-конечная звезда
let pts = '';
for (let i=0;i<10;i++){
const r = (i%2===0) ? 7 : 3.2;
const a = -Math.PI/2 + i*Math.PI/5;
pts += (cx + r*Math.cos(a)).toFixed(2) + ',' + (cy + r*Math.sin(a)).toFixed(2) + ' ';
}
g += '<polygon points="'+pts.trim()+'" fill="#2563eb" stroke="#fff" stroke-width="1.5"/>';
}
}
svg.innerHTML = g;
formula.innerHTML = '$' + fobj.tex + '$';
out.innerHTML = fobj.desc;
renderMath(formula); renderMath(out);
if (!bumped){ bumped = true; bumpProgress('p7', 15); addXp(10,'p7-iv1'); }
}
sl.addEventListener('input', redraw);
redraw();
})();
/* ===== IV2 wiring — нули ===== */
(function(){
const items = [
{ q: '$f(x) = x - 5$', ans: 5 },
{ q: '$f(x) = x^2 - 9$', ans: 0 },
{ q: '$f(x) = (x+1)(x-4)$', ans: 3 },
{ q: '$f(x) = x^2 + 1$', ans: -999 },
{ q: '$f(x) = x^2 - 6x + 9$', ans: 3 },
{ q: '$f(x) = x^3 - 8$', ans: 2 }
];
let i = 0, sc = 0;
const idxEl = document.getElementById('p7-iv2-idx');
const scEl = document.getElementById('p7-iv2-sc');
const qEl = document.getElementById('p7-iv2-q');
const inEl = document.getElementById('p7-iv2-in');
const fb = document.getElementById('p7-iv2-fb');
const goBtn = document.getElementById('p7-iv2-go');
let bumped = false;
function render(){
idxEl.textContent = Math.min(i+1, items.length);
scEl.textContent = sc;
if (i >= items.length){
qEl.innerHTML = '<b>Готово!</b> Результат: ' + sc + ' / ' + items.length;
inEl.disabled = true; goBtn.disabled = true; goBtn.style.opacity = .5;
if (!bumped){ bumped = true; bumpProgress('p7', 15); addXp(10,'p7-iv2'); }
return;
}
qEl.innerHTML = 'Функция: ' + items[i].q;
inEl.value = '';
fb.style.display = 'none';
renderMath(qEl);
inEl.focus();
}
function check(){
if (i >= items.length) return;
const v = +inEl.value;
if (!Number.isFinite(v) || inEl.value === '') { feedback(fb, false, 'Введи число.'); return; }
const it = items[i];
const ok = (v === it.ans);
if (ok) sc++;
let hint = '';
if (it.ans === -999) hint = 'Нулей нет.';
else if (it.ans === 999) hint = 'Нулей бесконечно много.';
else hint = 'Сумма нулей = ' + it.ans + '.';
feedback(fb, ok, (ok?'&#10003; Верно. ':'&#10007; Неверно. ') + hint);
i++;
setTimeout(render, 1100);
}
goBtn.addEventListener('click', check);
inEl.addEventListener('keydown', e=>{ if (e.key === 'Enter') check(); });
render();
})();
/* ===== IV3 wiring — возрастает / убывает ===== */
(function(){
const items = [
{ q: '$y = 2x + 1$ на $\\mathbb{R}$', up: true, hint: 'Линейная с положительным $k$. Возрастает.' },
{ q: '$y = -3x + 5$ на $\\mathbb{R}$', up: false, hint: 'Линейная с отрицательным $k$. Убывает.' },
{ q: '$y = x^2$ на $(-\\infty; 0]$', up: false, hint: 'Левая ветвь параболы. Убывает.' },
{ q: '$y = x^2$ на $[0; +\\infty)$', up: true, hint: 'Правая ветвь параболы. Возрастает.' },
{ q: '$y = \\dfrac{1}{x}$ на $(0; +\\infty)$', up: false, hint: 'Гипербола в I четверти. Убывает.' },
{ q: '$y = x^3$ на $\\mathbb{R}$', up: true, hint: 'Кубическая. Возрастает всюду.' }
];
let i = 0, sc = 0;
const idxEl = document.getElementById('p7-iv3-idx');
const scEl = document.getElementById('p7-iv3-sc');
const qEl = document.getElementById('p7-iv3-q');
const fb = document.getElementById('p7-iv3-fb');
const uBtn = document.getElementById('p7-iv3-u');
const dBtn = document.getElementById('p7-iv3-d');
let bumped = false;
function render(){
idxEl.textContent = Math.min(i+1, items.length);
scEl.textContent = sc;
if (i >= items.length){
qEl.innerHTML = '<b>Готово!</b> Результат: ' + sc + ' / ' + items.length;
uBtn.disabled = true; dBtn.disabled = true;
uBtn.style.opacity = .5; dBtn.style.opacity = .5;
if (!bumped){ bumped = true; bumpProgress('p7', 25); addXp(15,'p7-iv3'); }
return;
}
qEl.innerHTML = items[i].q;
fb.style.display = 'none';
renderMath(qEl);
}
function answer(v){
if (i >= items.length) return;
const it = items[i];
const ok = (v === it.up);
if (ok) sc++;
feedback(fb, ok, (ok?'&#10003; Верно. ':'&#10007; Неверно. ') + it.hint);
i++;
setTimeout(render, 900);
}
uBtn.addEventListener('click', ()=>answer(true));
dBtn.addEventListener('click', ()=>answer(false));
render();
})();
/* ===== IV4 wiring — DnD sorter ===== */
(function(){
const items = [
{ id:'a', html:'$y = 5x$ на $\\mathbb{R}$', cat:'up' },
{ id:'b', html:'$y = -2x$ на $\\mathbb{R}$', cat:'down' },
{ id:'c', html:'$y = x^2$ на $[0;\\,3]$', cat:'up' },
{ id:'d', html:'$y = x^2$ на $[-3;\\,0]$', cat:'down' },
{ id:'e', html:'$y = x^3$ на $\\mathbb{R}$', cat:'up' },
{ id:'f', html:'$y = \\dfrac{1}{x}$ на $(0;\\,+\\infty)$', cat:'down' }
];
const sorter = setupSorter({
poolId: 'p7-iv4-pool',
scopeSelector: '#p7-iv4',
cats: ['up','down'],
items: items
});
let bumped = false;
document.getElementById('p7-iv4-check').addEventListener('click', ()=>{
const fb = document.getElementById('p7-iv4-fb');
const total = items.length;
let correct = 0, placed = 0;
items.forEach(it=>{ if(sorter.placed[it.id]){ placed++; if(sorter.placed[it.id]===it.cat) correct++; } });
if (placed < total){ feedback(fb, false, 'Размещены не все: ' + placed + ' / ' + total + '.'); return; }
const ok = (correct === total);
feedback(fb, ok, ok ? '&#10003; Все верно! ' + correct + ' / ' + total : '&#10007; Правильно: ' + correct + ' / ' + total);
if (ok && !bumped){ bumped = true; bumpProgress('p7', 25); addXp(15,'p7-iv4'); }
});
document.getElementById('p7-iv4-reset').addEventListener('click', ()=>{
sorter.reset();
const fb = document.getElementById('p7-iv4-fb'); fb.style.display='none';
});
})();
wireReadBtn('p7');
}
function buildP8(){
const box = document.getElementById('p8-body');
let html = '';
html += makeCard('theory', 'Определения', '8.1', `
<p>Функция $y = f(x)$ называется <b>чётной</b>, если её область определения $D(f)$ симметрична относительно нуля и для всех $x \\in D(f)$ выполняется равенство:</p>
<p style="text-align:center;font-size:1.05rem">$f(-x) = f(x)$</p>
<p>Функция $y = f(x)$ называется <b>нечётной</b>, если её область определения $D(f)$ симметрична относительно нуля и для всех $x \\in D(f)$ выполняется равенство:</p>
<p style="text-align:center;font-size:1.05rem">$f(-x) = -f(x)$</p>
<p>Если ни одно из этих условий не выполняется — функцию называют <b>функцией общего вида</b>.</p>
<details class="spoiler"><summary>Что значит «$D(f)$ симметрична относительно нуля»?</summary><div class="spoiler-body">
Это значит: если $x \\in D(f)$, то и $-x \\in D(f)$. Например, $D(f) = [-3; 3]$ — симметрична, а $D(f) = [0; +\\infty)$ — нет.
</div></details>`);
html += makeCard('rule', 'Графическая симметрия', '8.2', `
<p>Свойство чётности или нечётности имеет наглядный геометрический смысл:</p>
<ul style="padding-left:22px;line-height:1.9">
<li>График <b>чётной</b> функции симметричен относительно <b>оси $Oy$</b> (зеркальное отражение).</li>
<li>График <b>нечётной</b> функции симметричен относительно <b>начала координат</b> (центральная симметрия — точка $O$).</li>
</ul>
<p>Поэтому достаточно построить график на $[0; +\\infty)$ — а на $(-\\infty; 0]$ его можно восстановить отражением.</p>`);
html += makeCard('example', 'Примеры функций', '8.3', `
<p><b>Чётные:</b> $y = x^2$, $y = x^4$, $y = |x|$, $y = \\cos x$, $y = x^2 + 1$.</p>
<p><b>Нечётные:</b> $y = x$, $y = x^3$, $y = \\dfrac{1}{x}$, $y = \\sin x$, $y = x^5 - x$.</p>
<p><b>Общего вида:</b></p>
<ul style="padding-left:22px;line-height:1.9">
<li>$y = x + 1$ — линейная без чётности;</li>
<li>$y = x^2 + x$ — смесь чётной и нечётной части;</li>
<li>$y = \\sqrt{x}$ — $D(f) = [0; +\\infty)$ не симметрична относительно нуля.</li>
</ul>`);
/* INTERACTIVE 1 — симметрия графика */
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">Выбери функцию ползунком. На графике появятся линии симметрии: для чётной — относительно оси $Oy$, для нечётной — относительно точки $O$.</div>
<div class="sliders">
<label>Функция №<b id="p8-iv1-fi">1</b> / 6<input type="range" id="p8-iv1-fn" min="1" max="6" step="1" value="1"></label>
</div>
<div id="p8-iv1-formula" style="text-align:center;font-size:1.1rem;padding:10px;background:var(--card);border-radius:9px;margin-bottom:10px"></div>
<div style="background:var(--card);border-radius:10px;padding:10px;overflow-x:auto">
<svg id="p8-iv1-svg" viewBox="0 0 380 280" style="width:100%;max-width:500px;height:auto;display:block;margin:0 auto"></svg>
</div>
<div id="p8-iv1-out" style="margin-top:10px;padding:12px 14px;background:var(--sec-acc-soft);border-radius:9px;font-size:.95rem;line-height:1.7"></div>
</div>`;
/* INTERACTIVE 2 — квикфайр */
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>
<div class="score-display">Задача: <b id="p8-iv2-idx">1</b> / 8 &middot; Очки: <b id="p8-iv2-sc">0</b></div>
<div id="p8-iv2-q" style="text-align:center;font-size:1.15rem;padding:14px;background:var(--card);border-radius:9px;margin-bottom:12px;min-height:60px"></div>
<div class="actions" style="justify-content:center;flex-wrap:wrap">
<button class="btn primary" id="p8-iv2-e">Чётная</button>
<button class="btn" id="p8-iv2-o">Нечётная</button>
<button class="btn" id="p8-iv2-g">Общая</button>
</div>
<div class="feedback" id="p8-iv2-fb"></div>
</div>`;
/* INTERACTIVE 3 — подставь -x */
html += `<div class="wg" id="p8-iv3">
<div class="wg-header"><span class="wg-badge">ИНТЕРАКТИВ 3</span><div class="wg-title">Подставь $-x$</div></div>
<div class="wg-help">Подставь в формулу $-x$ вместо $x$. Сравни с исходной: получилась $f(x)$? — чётная. Получилась $-f(x)$? — нечётная. Что-то другое? — общая.</div>
<div class="score-display">Задача: <b id="p8-iv3-idx">1</b> / 6 &middot; Очки: <b id="p8-iv3-sc">0</b></div>
<div id="p8-iv3-q" style="text-align:center;font-size:1.05rem;padding:14px;background:var(--card);border-radius:9px;margin-bottom:12px;min-height:60px"></div>
<div class="actions" style="justify-content:center;flex-wrap:wrap">
<button class="btn primary" id="p8-iv3-a">$f(-x) = f(x)$</button>
<button class="btn" id="p8-iv3-b">$f(-x) = -f(x)$</button>
<button class="btn" id="p8-iv3-c">Другое</button>
</div>
<div class="feedback" id="p8-iv3-fb"></div>
</div>`;
/* INTERACTIVE 4 — DnD сортер */
html += `<div class="wg" id="p8-iv4">
<div class="wg-header"><span class="wg-badge">ИНТЕРАКТИВ 4</span><div class="wg-title">Сортер: какой тип?</div></div>
<div class="wg-help">Перетащи каждую функцию в подходящий ящик.</div>
<div id="p8-iv4-pool"></div>
<div style="display:grid;grid-template-columns:repeat(auto-fit,minmax(160px,1fr));gap:10px">
<div class="drop-box"><h5>Чётная</h5><div class="drop-items" data-cat="even"></div></div>
<div class="drop-box"><h5>Нечётная</h5><div class="drop-items" data-cat="odd"></div></div>
<div class="drop-box"><h5>Общая</h5><div class="drop-items" data-cat="gen"></div></div>
</div>
<div class="actions"><button class="btn primary" id="p8-iv4-check">Проверить</button><button class="btn" id="p8-iv4-reset">Сбросить</button></div>
<div class="feedback" id="p8-iv4-fb"></div>
</div>`;
box.innerHTML = html + secNav('p7', 'p9') + readButton('p8');
renderMath(box);
/* ===== IV1 wiring — симметрия графика ===== */
(function(){
const fns = [
{ tex:'y = x^2', f:x=>x*x, type:'even', xmin:-4, xmax:4 },
{ tex:'y = x^3', f:x=>x*x*x, type:'odd', xmin:-4, xmax:4 },
{ tex:'y = |x|', f:x=>Math.abs(x),type:'even', xmin:-4, xmax:4 },
{ tex:'y = x', f:x=>x, type:'odd', xmin:-4, xmax:4 },
{ tex:'y = x^2 + x', f:x=>x*x+x, type:'gen', xmin:-4, xmax:4 },
{ tex:'y = \\dfrac{1}{x}', f:x=>1/x, type:'odd', xmin:-4, xmax:4 }
];
const sl = document.getElementById('p8-iv1-fn');
const fi = document.getElementById('p8-iv1-fi');
const svg = document.getElementById('p8-iv1-svg');
const formula = document.getElementById('p8-iv1-formula');
const out = document.getElementById('p8-iv1-out');
let bumped = false;
function redraw(){
const idx = (+sl.value)-1;
const fobj = fns[idx];
fi.textContent = (idx+1);
const ax = axes2D(380, 280, 28, -4, 4, -4, 4);
let g = ax.content;
// линии симметрии под графиком
if (fobj.type === 'even'){
// подсветим ось Oy
const x0 = ax.toX(0);
g += '<line x1="'+x0+'" y1="'+ax.toY(-4)+'" x2="'+x0+'" y2="'+ax.toY(4)+'" stroke="#a855f7" stroke-width="2.5" stroke-dasharray="6 4" opacity=".7"/>';
} else if (fobj.type === 'odd'){
// выделим точку O маркером
const cx = ax.toX(0), cy = ax.toY(0);
g += '<circle cx="'+cx+'" cy="'+cy+'" r="10" fill="none" stroke="#a855f7" stroke-width="2.5" stroke-dasharray="4 3"/>';
// диагонали через O
g += '<line x1="'+ax.toX(-4)+'" y1="'+ax.toY(4)+'" x2="'+ax.toX(4)+'" y2="'+ax.toY(-4)+'" stroke="#a855f7" stroke-width="1" stroke-dasharray="3 5" opacity=".4"/>';
g += '<line x1="'+ax.toX(-4)+'" y1="'+ax.toY(-4)+'" x2="'+ax.toX(4)+'" y2="'+ax.toY(4)+'" stroke="#a855f7" stroke-width="1" stroke-dasharray="3 5" opacity=".4"/>';
}
// график
if (fobj.type === 'odd' && fobj.tex.indexOf('1') !== -1 && fobj.tex.indexOf('x') !== -1 && fobj.tex.indexOf('dfrac') !== -1){
// гипербола: рисуем две ветви
g += plotFunc(fobj.f, -4, -0.05, ax.toX, ax.toY, '#059669', 200);
g += plotFunc(fobj.f, 0.05, 4, ax.toX, ax.toY, '#059669', 200);
} else {
g += plotFunc(fobj.f, fobj.xmin, fobj.xmax, ax.toX, ax.toY, '#059669', 300);
}
svg.innerHTML = g;
formula.innerHTML = '$' + fobj.tex + '$';
// подсчёт f(-x)
const xprobe = 1.5;
let fnegX_tex = '';
if (idx === 0) fnegX_tex = '(-x)^2 = x^2 = f(x)';
else if (idx === 1) fnegX_tex = '(-x)^3 = -x^3 = -f(x)';
else if (idx === 2) fnegX_tex = '|-x| = |x| = f(x)';
else if (idx === 3) fnegX_tex = '-x = -f(x)';
else if (idx === 4) fnegX_tex = '(-x)^2 + (-x) = x^2 - x \\ne \\pm f(x)';
else if (idx === 5) fnegX_tex = '\\dfrac{1}{-x} = -\\dfrac{1}{x} = -f(x)';
const typeLabel = (fobj.type==='even')?'<b style="color:#059669">чётная</b>':(fobj.type==='odd')?'<b style="color:#2563eb">нечётная</b>':'<b style="color:#ef4444">общего вида</b>';
const symLabel = (fobj.type==='even')?'ось $Oy$':(fobj.type==='odd')?'точка $O$ (начало координат)':'нет симметрии';
out.innerHTML =
'<div>$f(-x) = ' + fnegX_tex + '$</div>' +
'<div style="margin-top:6px">Тип: ' + typeLabel + '</div>' +
'<div style="margin-top:4px">Симметрия: ' + symLabel + '</div>';
renderMath(formula); renderMath(out);
if (!bumped){ bumped = true; bumpProgress('p8', 15); addXp(10,'p8-iv1'); }
}
sl.addEventListener('input', redraw);
redraw();
})();
/* ===== IV2 wiring — квикфайр 8 ===== */
(function(){
const items = [
{ q:'$f(x) = x^4$', ans:'even', hint:'$(-x)^4 = x^4$ — чётная.' },
{ q:'$f(x) = x^5$', ans:'odd', hint:'$(-x)^5 = -x^5$ — нечётная.' },
{ q:'$f(x) = x^2 + 3$', ans:'even', hint:'$(-x)^2 + 3 = x^2 + 3 = f(x)$ — чётная.' },
{ q:'$f(x) = x^3 + x$', ans:'odd', hint:'$(-x)^3 + (-x) = -x^3 - x = -(x^3+x) = -f(x)$ — нечётная.' },
{ q:'$f(x) = x^3 + 1$', ans:'gen', hint:'$(-x)^3 + 1 = -x^3 + 1$ — не равно ни $f(x)$, ни $-f(x)$. Общая.' },
{ q:'$f(x) = |x| - 5$', ans:'even', hint:'$|-x| - 5 = |x| - 5 = f(x)$ — чётная.' },
{ q:'$f(x) = \\dfrac{1}{x^2}$', ans:'even', hint:'$\\dfrac{1}{(-x)^2} = \\dfrac{1}{x^2} = f(x)$ — чётная.' },
{ q:'$f(x) = \\dfrac{1}{x^3}$', ans:'odd', hint:'$\\dfrac{1}{(-x)^3} = -\\dfrac{1}{x^3} = -f(x)$ — нечётная.' }
];
let i = 0, sc = 0;
const idxEl = document.getElementById('p8-iv2-idx');
const scEl = document.getElementById('p8-iv2-sc');
const qEl = document.getElementById('p8-iv2-q');
const fb = document.getElementById('p8-iv2-fb');
const eBtn = document.getElementById('p8-iv2-e');
const oBtn = document.getElementById('p8-iv2-o');
const gBtn = document.getElementById('p8-iv2-g');
let bumped = false;
function render(){
idxEl.textContent = Math.min(i+1, items.length);
scEl.textContent = sc;
if (i >= items.length){
qEl.innerHTML = '<b>Готово!</b> Результат: ' + sc + ' / ' + items.length;
eBtn.disabled = true; oBtn.disabled = true; gBtn.disabled = true;
eBtn.style.opacity = .5; oBtn.style.opacity = .5; gBtn.style.opacity = .5;
if (!bumped){ bumped = true; bumpProgress('p8', 15); addXp(10,'p8-iv2'); }
return;
}
qEl.innerHTML = items[i].q;
fb.style.display = 'none';
renderMath(qEl);
}
function answer(v){
if (i >= items.length) return;
const it = items[i];
const ok = (v === it.ans);
if (ok) sc++;
feedback(fb, ok, (ok?'&#10003; Верно. ':'&#10007; Неверно. ') + it.hint);
i++;
setTimeout(render, 1000);
}
eBtn.addEventListener('click', ()=>answer('even'));
oBtn.addEventListener('click', ()=>answer('odd'));
gBtn.addEventListener('click', ()=>answer('gen'));
render();
})();
/* ===== IV3 wiring — подставь -x ===== */
(function(){
const items = [
{ q:'$f(x) = 2x^2 - 7$', ans:'a', hint:'$f(-x) = 2(-x)^2 - 7 = 2x^2 - 7 = f(x)$. Чётная.' },
{ q:'$f(x) = x^5 - 2x$', ans:'b', hint:'$f(-x) = -x^5 + 2x = -(x^5 - 2x) = -f(x)$. Нечётная.' },
{ q:'$f(x) = x + 1$', ans:'c', hint:'$f(-x) = -x + 1$ — не равно ни $f(x)$, ни $-f(x)$. Общая.' },
{ q:'$f(x) = -3x^2 + |x|$', ans:'a', hint:'$f(-x) = -3x^2 + |x| = f(x)$. Чётная.' },
{ q:'$f(x) = x^3 - 3x$', ans:'b', hint:'$f(-x) = -x^3 + 3x = -(x^3 - 3x) = -f(x)$. Нечётная.' },
{ q:'$f(x) = (x + 2)^2$', ans:'c', hint:'$f(-x) = (-x + 2)^2 = (x - 2)^2$ — не равно ни $f(x)$, ни $-f(x)$. Общая.' }
];
let i = 0, sc = 0;
const idxEl = document.getElementById('p8-iv3-idx');
const scEl = document.getElementById('p8-iv3-sc');
const qEl = document.getElementById('p8-iv3-q');
const fb = document.getElementById('p8-iv3-fb');
const aBtn = document.getElementById('p8-iv3-a');
const bBtn = document.getElementById('p8-iv3-b');
const cBtn = document.getElementById('p8-iv3-c');
let bumped = false;
function render(){
idxEl.textContent = Math.min(i+1, items.length);
scEl.textContent = sc;
if (i >= items.length){
qEl.innerHTML = '<b>Готово!</b> Результат: ' + sc + ' / ' + items.length;
aBtn.disabled = true; bBtn.disabled = true; cBtn.disabled = true;
aBtn.style.opacity = .5; bBtn.style.opacity = .5; cBtn.style.opacity = .5;
if (!bumped){ bumped = true; bumpProgress('p8', 25); addXp(15,'p8-iv3'); }
return;
}
qEl.innerHTML = items[i].q;
fb.style.display = 'none';
renderMath(qEl);
}
function answer(v){
if (i >= items.length) return;
const it = items[i];
const ok = (v === it.ans);
if (ok) sc++;
feedback(fb, ok, (ok?'&#10003; Верно. ':'&#10007; Неверно. ') + it.hint);
i++;
setTimeout(render, 1100);
}
aBtn.addEventListener('click', ()=>answer('a'));
bBtn.addEventListener('click', ()=>answer('b'));
cBtn.addEventListener('click', ()=>answer('c'));
render();
})();
/* ===== IV4 wiring — DnD сортер ===== */
(function(){
const items = [
{ id:'a', html:'$y = 5x^2$', cat:'even' },
{ id:'b', html:'$y = 7x^3$', cat:'odd' },
{ id:'c', html:'$y = 4x + 1$', cat:'gen' },
{ id:'d', html:'$y = x^2 - 9$', cat:'even' },
{ id:'e', html:'$y = x^3 - 2x$', cat:'odd' },
{ id:'f', html:'$y = \\sqrt{x}$', cat:'gen' }
];
const sorter = setupSorter({
poolId: 'p8-iv4-pool',
scopeSelector: '#p8-iv4',
cats: ['even','odd','gen'],
items: items
});
let bumped = false;
document.getElementById('p8-iv4-check').addEventListener('click', ()=>{
const fb = document.getElementById('p8-iv4-fb');
const total = items.length;
let correct = 0, placed = 0;
items.forEach(it=>{ if(sorter.placed[it.id]){ placed++; if(sorter.placed[it.id]===it.cat) correct++; } });
if (placed < total){ feedback(fb, false, 'Размещены не все: ' + placed + ' / ' + total + '.'); return; }
const ok = (correct === total);
feedback(fb, ok, ok ? '&#10003; Все верно! ' + correct + ' / ' + total : '&#10007; Правильно: ' + correct + ' / ' + total);
if (ok && !bumped){ bumped = true; bumpProgress('p8', 25); addXp(15,'p8-iv4'); }
});
document.getElementById('p8-iv4-reset').addEventListener('click', ()=>{
sorter.reset();
const fb = document.getElementById('p8-iv4-fb'); fb.style.display='none';
});
})();
wireReadBtn('p8');
}
function buildP9(){
const box = document.getElementById('p9-body');
let html = '';
html += makeCard('theory', 'Вертикальный сдвиг $y = f(x) + b$', '9.1', `
<p>График функции $y = f(x) + b$ получается из графика $y = f(x)$ <b>параллельным переносом</b> вдоль оси $Oy$:</p>
<ul style="padding-left:22px;line-height:1.9">
<li>если $b > 0$ — <b>вверх</b> на $b$ единиц;</li>
<li>если $b < 0$ — <b>вниз</b> на $|b|$ единиц.</li>
</ul>
<p><b>Пример.</b> График $y = x^2 + 3$ — это парабола $y = x^2$, сдвинутая <b>вверх</b> на 3 единицы. Её вершина: $(0;\\ 3)$.</p>
<details class="spoiler"><summary>Почему так?</summary><div class="spoiler-body">
При том же $x$ значение $y$ увеличивается на $b$ — то есть каждая точка поднимается на $b$ вверх. Это и есть вертикальный перенос.
</div></details>`);
html += makeCard('rule', 'Горизонтальный сдвиг $y = f(x \\pm a)$', '9.2', `
<p>График функции $y = f(x - a)$ получается из графика $y = f(x)$ параллельным переносом вдоль оси $Ox$:</p>
<ul style="padding-left:22px;line-height:1.9">
<li>если $a > 0$ — <b>вправо</b> на $a$ единиц;</li>
<li>если $a < 0$ — <b>влево</b> на $|a|$ единиц.</li>
</ul>
<p><b>Внимание!</b> Знак <b>минус</b> внутри аргумента — сдвиг <b>вправо</b>. Знак <b>плюс</b> (то есть $f(x + a)$) — сдвиг <b>влево</b>. Это самая частая ошибка!</p>
<p><b>Пример.</b> График $y = (x - 2)^2$ — парабола $y = x^2$, сдвинутая <b>вправо</b> на 2. Её вершина: $(2;\\ 0)$.</p>
<p>А $y = (x + 5)^2$ — сдвиг <b>влево</b> на 5. Вершина: $(-5;\\ 0)$.</p>`);
html += makeCard('example', 'Комбинированный сдвиг', '9.3', `
<p>График $y = f(x - a) + b$ получается комбинацией двух переносов: сдвиг на $a$ по оси $Ox$ и на $b$ по оси $Oy$.</p>
<ul style="padding-left:22px;line-height:1.9">
<li>$y = (x - 1)^2 + 3$ — парабола с вершиной в $(1;\\ 3)$.</li>
<li>$y = (x + 2)^2 - 4$ — парабола с вершиной в $(-2;\\ -4)$.</li>
<li>$y = \\sqrt{x - 3} + 1$ — график $y = \\sqrt{x}$, сдвинутый вправо на 3 и вверх на 1.</li>
<li>$y = |x + 1| - 2$ — график $y = |x|$, сдвинутый влево на 1 и вниз на 2.</li>
</ul>
<p><b>Лайфхак.</b> Для параболы $y = (x - a)^2 + b$ вершина всегда в точке $(a;\\ b)$.</p>`);
/* INTERACTIVE 1 — слайдер сдвигов */
html += `<div class="wg" id="p9-iv1">
<div class="wg-header"><span class="wg-badge">ИНТЕРАКТИВ 1</span><div class="wg-title">Слайдер сдвигов</div></div>
<div class="wg-help">Выбери базовую функцию и крути ползунки $a$ и $b$. Синяя кривая — сдвинутый график $y = f(x - a) + b$, серая — исходный.</div>
<div class="sliders">
<label>Функция №<b id="p9-iv1-fi">1</b> / 4<input type="range" id="p9-iv1-fn" min="1" max="4" step="1" value="1"></label>
<label>$a$ =<b id="p9-iv1-av">0</b><input type="range" id="p9-iv1-a" min="-4" max="4" step="1" value="0"></label>
<label>$b$ =<b id="p9-iv1-bv">0</b><input type="range" id="p9-iv1-b" min="-4" max="4" step="1" value="0"></label>
</div>
<div id="p9-iv1-formula" style="text-align:center;font-size:1.1rem;padding:10px;background:var(--card);border-radius:9px;margin-bottom:10px"></div>
<div style="background:var(--card);border-radius:10px;padding:10px;overflow-x:auto">
<svg id="p9-iv1-svg" viewBox="0 0 420 320" style="width:100%;max-width:560px;height:auto;display:block;margin:0 auto"></svg>
</div>
<div id="p9-iv1-out" style="margin-top:10px;padding:12px 14px;background:var(--sec-acc-soft);border-radius:9px;font-size:.95rem;line-height:1.7;text-align:center"></div>
</div>`;
/* INTERACTIVE 2 — вершина параболы */
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">Для параболы $y = (x - a)^2 + b$ вершина — в точке $(a;\\ b)$. Введи <b>сумму</b> координат вершины: $a + b$.</div>
<div class="score-display">Задача: <b id="p9-iv2-idx">1</b> / 6 &middot; Очки: <b id="p9-iv2-sc">0</b></div>
<div id="p9-iv2-q" style="text-align:center;font-size:1.1rem;padding:14px;background:var(--card);border-radius:9px;margin-bottom:12px"></div>
<div style="display:flex;gap:10px;justify-content:center;flex-wrap:wrap;align-items:center">
<span>$a + b$ =</span>
<input type="number" id="p9-iv2-in" class="tinp" style="width:110px;text-align:center" step="1">
<button class="btn primary" id="p9-iv2-go">Проверить</button>
</div>
<div class="feedback" id="p9-iv2-fb"></div>
</div>`;
/* INTERACTIVE 3 — куда сдвиг */
html += `<div class="wg" id="p9-iv3">
<div class="wg-header"><span class="wg-badge">ИНТЕРАКТИВ 3</span><div class="wg-title">В какую сторону сдвиг?</div></div>
<div class="wg-help">Определи направление сдвига графика относительно исходной функции.</div>
<div class="score-display">Задача: <b id="p9-iv3-idx">1</b> / 8 &middot; Очки: <b id="p9-iv3-sc">0</b></div>
<div id="p9-iv3-q" style="text-align:center;font-size:1.15rem;padding:14px;background:var(--card);border-radius:9px;margin-bottom:12px;min-height:60px"></div>
<div class="actions" style="justify-content:center;flex-wrap:wrap">
<button class="btn primary" id="p9-iv3-u">Вверх</button>
<button class="btn" id="p9-iv3-d">Вниз</button>
<button class="btn" id="p9-iv3-r">Вправо</button>
<button class="btn" id="p9-iv3-l">Влево</button>
</div>
<div class="feedback" id="p9-iv3-fb"></div>
</div>`;
/* INTERACTIVE 4 — конструктор формулы */
html += `<div class="wg" id="p9-iv4">
<div class="wg-header"><span class="wg-badge">ИНТЕРАКТИВ 4</span><div class="wg-title">Конструктор формулы по графику</div></div>
<div class="wg-help">Для каждого описания выбери верную формулу из выпадающего списка. Нажми «Проверить» в конце.</div>
<div id="p9-iv4-rows" style="display:flex;flex-direction:column;gap:12px"></div>
<div class="actions"><button class="btn primary" id="p9-iv4-check">Проверить</button><button class="btn" id="p9-iv4-reset">Сбросить</button></div>
<div class="feedback" id="p9-iv4-fb"></div>
</div>`;
box.innerHTML = html + secNav('p8', 'final2') + readButton('p9');
renderMath(box);
/* ===== IV1 wiring — слайдер сдвигов ===== */
(function(){
const fns = [
{ tex:'x^2', f:x=>x*x, dom:[-6,6] },
{ tex:'|x|', f:x=>Math.abs(x), dom:[-6,6] },
{ tex:'\\sqrt{x}', f:x=>Math.sqrt(x), dom:[0,6], shiftDom:true },
{ tex:'x^3', f:x=>x*x*x, dom:[-6,6] }
];
const fnSl = document.getElementById('p9-iv1-fn');
const aSl = document.getElementById('p9-iv1-a');
const bSl = document.getElementById('p9-iv1-b');
const fi = document.getElementById('p9-iv1-fi');
const av = document.getElementById('p9-iv1-av');
const bv = document.getElementById('p9-iv1-bv');
const svg = document.getElementById('p9-iv1-svg');
const formula = document.getElementById('p9-iv1-formula');
const out = document.getElementById('p9-iv1-out');
let bumped = false;
function redraw(){
const idx = (+fnSl.value)-1;
const a = +aSl.value, b = +bSl.value;
const fobj = fns[idx];
fi.textContent = (idx+1);
av.textContent = a;
bv.textContent = b;
const ax = axes2D(420, 320, 30, -6, 6, -6, 6);
let g = ax.content;
// исходная (серая)
g += plotFunc(fobj.f, fobj.dom[0], fobj.dom[1], ax.toX, ax.toY, '#94a3b8', 250);
// сдвинутая (синяя) — y = f(x - a) + b
const shifted = x => fobj.f(x - a) + b;
const xmin2 = fobj.dom[0] + a, xmax2 = fobj.dom[1] + a;
g += plotFunc(shifted, Math.max(xmin2,-6), Math.min(xmax2,6), ax.toX, ax.toY, '#2563eb', 280);
// маркеры начальных точек
// исходная: для x^2, |x|, x^3 — это (0;0); для sqrt — тоже (0;0)
const ox = 0, oy = fobj.f(0);
if (oy>=-6 && oy<=6){
const cx = ax.toX(ox), cy = ax.toY(oy);
g += '<circle cx="'+cx+'" cy="'+cy+'" r="4" fill="#94a3b8" stroke="#fff" stroke-width="1.5"/>';
}
// сдвинутая
const sx = ox + a, sy = oy + b;
if (sx>=-6 && sx<=6 && sy>=-6 && sy<=6){
const cx = ax.toX(sx), cy = ax.toY(sy);
g += '<circle cx="'+cx+'" cy="'+cy+'" r="5" fill="#2563eb" stroke="#fff" stroke-width="2"/>';
// стрелка от (0;0) до (a;b)
if (a !== 0 || b !== 0){
const sx0 = ax.toX(ox), sy0 = ax.toY(oy);
g += '<line x1="'+sx0+'" y1="'+sy0+'" x2="'+cx+'" y2="'+cy+'" stroke="#a855f7" stroke-width="1.6" stroke-dasharray="5 3" marker-end=""/>';
}
}
svg.innerHTML = g;
// формула
const aStr = (a >= 0) ? ('- ' + a) : ('+ ' + (-a));
const bStr = (b >= 0) ? ('+ ' + b) : ('- ' + (-b));
let inner;
if (a === 0) inner = fobj.tex;
else if (fobj.tex === '\\sqrt{x}') inner = '\\sqrt{x ' + aStr + '}';
else if (fobj.tex === '|x|') inner = '|x ' + aStr + '|';
else if (fobj.tex === 'x^2') inner = '(x ' + aStr + ')^2';
else if (fobj.tex === 'x^3') inner = '(x ' + aStr + ')^3';
else inner = fobj.tex;
let full = 'y = ' + inner;
if (b !== 0) full += ' ' + bStr;
formula.innerHTML = '$' + full + '$';
// направление
let dir = [];
if (a > 0) dir.push('вправо на ' + a);
else if (a < 0) dir.push('влево на ' + (-a));
if (b > 0) dir.push('вверх на ' + b);
else if (b < 0) dir.push('вниз на ' + (-b));
const dirText = dir.length ? 'Сдвиг: ' + dir.join(', ') + '.' : 'Сдвига нет — графики совпадают.';
out.innerHTML = dirText;
renderMath(formula); renderMath(out);
if (!bumped){ bumped = true; bumpProgress('p9', 15); addXp(10,'p9-iv1'); }
}
fnSl.addEventListener('input', redraw);
aSl.addEventListener('input', redraw);
bSl.addEventListener('input', redraw);
redraw();
})();
/* ===== IV2 wiring — вершина параболы ===== */
(function(){
const items = [
{ q:'$y = (x - 2)^2 + 3$', ans:5, hint:'Вершина $(2;\\ 3)$, $a + b = 5$.' },
{ q:'$y = (x + 1)^2 - 4$', ans:-5, hint:'Вершина $(-1;\\ -4)$, $a + b = -5$.' },
{ q:'$y = (x - 5)^2$', ans:5, hint:'Вершина $(5;\\ 0)$, $a + b = 5$.' },
{ q:'$y = x^2 + 7$', ans:7, hint:'Вершина $(0;\\ 7)$, $a + b = 7$.' },
{ q:'$y = (x + 3)^2 + 2$', ans:-1, hint:'Вершина $(-3;\\ 2)$, $a + b = -1$.' },
{ q:'$y = (x - 4)^2 - 6$', ans:-2, hint:'Вершина $(4;\\ -6)$, $a + b = -2$.' }
];
let i = 0, sc = 0;
const idxEl = document.getElementById('p9-iv2-idx');
const scEl = document.getElementById('p9-iv2-sc');
const qEl = document.getElementById('p9-iv2-q');
const inEl = document.getElementById('p9-iv2-in');
const fb = document.getElementById('p9-iv2-fb');
const goBtn = document.getElementById('p9-iv2-go');
let bumped = false;
function render(){
idxEl.textContent = Math.min(i+1, items.length);
scEl.textContent = sc;
if (i >= items.length){
qEl.innerHTML = '<b>Готово!</b> Результат: ' + sc + ' / ' + items.length;
inEl.disabled = true; goBtn.disabled = true; goBtn.style.opacity = .5;
if (!bumped){ bumped = true; bumpProgress('p9', 15); addXp(10,'p9-iv2'); }
return;
}
qEl.innerHTML = items[i].q;
inEl.value = '';
fb.style.display = 'none';
renderMath(qEl);
inEl.focus();
}
function check(){
if (i >= items.length) return;
const v = +inEl.value;
if (!Number.isFinite(v) || inEl.value === ''){ feedback(fb, false, 'Введи число.'); return; }
const it = items[i];
const ok = (v === it.ans);
if (ok) sc++;
feedback(fb, ok, (ok?'&#10003; Верно. ':'&#10007; Неверно. ') + it.hint);
i++;
setTimeout(render, 1100);
}
goBtn.addEventListener('click', check);
inEl.addEventListener('keydown', e=>{ if (e.key === 'Enter') check(); });
render();
})();
/* ===== IV3 wiring — направление сдвига ===== */
(function(){
const items = [
{ q:'$y = x^2 + 5$', ans:'u', hint:'$b = 5 > 0$ — вверх.' },
{ q:'$y = x^2 - 3$', ans:'d', hint:'$b = -3 < 0$ — вниз.' },
{ q:'$y = (x - 2)^2$', ans:'r', hint:'$x - 2$ → сдвиг вправо на 2.' },
{ q:'$y = (x + 4)^2$', ans:'l', hint:'$x + 4$ → сдвиг влево на 4.' },
{ q:'$y = |x| + 1$', ans:'u', hint:'$b = 1 > 0$ — вверх.' },
{ q:'$y = |x - 7|$', ans:'r', hint:'$x - 7$ → сдвиг вправо на 7.' },
{ q:'$y = \\sqrt{x + 2}$', ans:'l', hint:'$x + 2$ → сдвиг влево на 2.' },
{ q:'$y = x^3 - 1$', ans:'d', hint:'$b = -1 < 0$ — вниз.' }
];
let i = 0, sc = 0;
const idxEl = document.getElementById('p9-iv3-idx');
const scEl = document.getElementById('p9-iv3-sc');
const qEl = document.getElementById('p9-iv3-q');
const fb = document.getElementById('p9-iv3-fb');
const uBtn = document.getElementById('p9-iv3-u');
const dBtn = document.getElementById('p9-iv3-d');
const rBtn = document.getElementById('p9-iv3-r');
const lBtn = document.getElementById('p9-iv3-l');
let bumped = false;
function render(){
idxEl.textContent = Math.min(i+1, items.length);
scEl.textContent = sc;
if (i >= items.length){
qEl.innerHTML = '<b>Готово!</b> Результат: ' + sc + ' / ' + items.length;
[uBtn,dBtn,rBtn,lBtn].forEach(b=>{ b.disabled=true; b.style.opacity=.5; });
if (!bumped){ bumped = true; bumpProgress('p9', 25); addXp(15,'p9-iv3'); }
return;
}
qEl.innerHTML = items[i].q;
fb.style.display = 'none';
renderMath(qEl);
}
function answer(v){
if (i >= items.length) return;
const it = items[i];
const ok = (v === it.ans);
if (ok) sc++;
feedback(fb, ok, (ok?'&#10003; Верно. ':'&#10007; Неверно. ') + it.hint);
i++;
setTimeout(render, 900);
}
uBtn.addEventListener('click', ()=>answer('u'));
dBtn.addEventListener('click', ()=>answer('d'));
rBtn.addEventListener('click', ()=>answer('r'));
lBtn.addEventListener('click', ()=>answer('l'));
render();
})();
/* ===== IV4 wiring — конструктор формулы ===== */
(function(){
const tasks = [
{
desc: 'Парабола $y = x^2$ сдвинута на 2 <b>влево</b>',
opts: [
{ v:'a', tex:'y = (x - 2)^2' },
{ v:'b', tex:'y = (x + 2)^2' },
{ v:'c', tex:'y = x^2 + 2' },
{ v:'d', tex:'y = x^2 - 2' }
],
ans: 'b'
},
{
desc: '$y = |x|$ сдвинута на 4 <b>вверх</b>',
opts: [
{ v:'a', tex:'y = |x| + 4' },
{ v:'b', tex:'y = |x| - 4' },
{ v:'c', tex:'y = |x - 4|' },
{ v:'d', tex:'y = |x + 4|' }
],
ans: 'a'
},
{
desc: '$y = \\sqrt{x}$ сдвинута на 1 <b>вниз</b>',
opts: [
{ v:'a', tex:'y = \\sqrt{x} + 1' },
{ v:'b', tex:'y = \\sqrt{x} - 1' },
{ v:'c', tex:'y = \\sqrt{x - 1}' },
{ v:'d', tex:'y = \\sqrt{x + 1}' }
],
ans: 'b'
},
{
desc: '$y = x^2$ сдвинута на 3 <b>вправо</b> и 2 <b>вверх</b>',
opts: [
{ v:'a', tex:'y = (x + 3)^2 + 2' },
{ v:'b', tex:'y = (x - 3)^2 - 2' },
{ v:'c', tex:'y = (x - 3)^2 + 2' },
{ v:'d', tex:'y = (x + 3)^2 - 2' }
],
ans: 'c'
},
{
desc: '$y = x^3$ сдвинута на 5 <b>влево</b>',
opts: [
{ v:'a', tex:'y = (x - 5)^3' },
{ v:'b', tex:'y = (x + 5)^3' },
{ v:'c', tex:'y = x^3 + 5' },
{ v:'d', tex:'y = x^3 - 5' }
],
ans: 'b'
},
{
desc: '$y = |x|$ сдвинута на 1 <b>вправо</b> и 4 <b>вниз</b>',
opts: [
{ v:'a', tex:'y = |x - 1| - 4' },
{ v:'b', tex:'y = |x + 1| - 4' },
{ v:'c', tex:'y = |x - 1| + 4' },
{ v:'d', tex:'y = |x + 1| + 4' }
],
ans: 'a'
}
];
const rowsBox = document.getElementById('p9-iv4-rows');
let rowsHtml = '';
tasks.forEach((t, i)=>{
let optsHtml = '<option value="">— выбери —</option>';
t.opts.forEach(o=>{ optsHtml += '<option value="'+o.v+'">'+o.tex+'</option>'; });
rowsHtml +=
'<div style="background:var(--card);padding:10px 12px;border-radius:9px;display:flex;flex-direction:column;gap:8px">' +
'<div style="font-size:.98rem"><b>'+(i+1)+'.</b> '+t.desc+'</div>' +
'<select id="p9-iv4-s'+i+'" class="tinp" style="min-width:200px">'+optsHtml+'</select>' +
'</div>';
});
rowsBox.innerHTML = rowsHtml;
renderMath(rowsBox);
let bumped = false;
document.getElementById('p9-iv4-check').addEventListener('click', ()=>{
const fb = document.getElementById('p9-iv4-fb');
let correct = 0, answered = 0;
tasks.forEach((t,i)=>{
const v = document.getElementById('p9-iv4-s'+i).value;
if (v) answered++;
if (v === t.ans) correct++;
});
if (answered < tasks.length){
feedback(fb, false, 'Отвечены не все: ' + answered + ' / ' + tasks.length + '.');
return;
}
const ok = (correct === tasks.length);
feedback(fb, ok, ok ? '&#10003; Все верно! ' + correct + ' / ' + tasks.length : '&#10007; Правильно: ' + correct + ' / ' + tasks.length);
if (ok && !bumped){ bumped = true; bumpProgress('p9', 25); addXp(15,'p9-iv4'); }
});
document.getElementById('p9-iv4-reset').addEventListener('click', ()=>{
tasks.forEach((t,i)=>{ document.getElementById('p9-iv4-s'+i).value = ''; });
const fb = document.getElementById('p9-iv4-fb'); fb.style.display='none';
});
})();
wireReadBtn('p9');
}
function buildFinal2(){
const box = document.getElementById('final2-body');
let html = '';
/* Часть А — Шпаргалка главы (4 mini-карточки) */
html += `<div class="card">
<div class="card-header">
<span class="card-icon theory">${ICONS.theory}</span>
<span class="card-title">Шпаргалка главы 2</span>
<span class="card-num">Итог</span>
</div>
<div class="card-body">
<p>Все ключевые правила главы — в одном месте. Просмотри перед боссами!</p>
<div style="display:grid;grid-template-columns:repeat(auto-fit,minmax(220px,1fr));gap:12px;margin-top:10px">
<div style="padding:12px 14px;background:var(--sec-acc-soft);border-radius:11px;border-left:3px solid var(--pri)">
<div style="font-family:'Unbounded',sans-serif;font-weight:700;color:var(--pri2);margin-bottom:6px;font-size:.92rem">§ 6 · Функция</div>
<div style="font-size:.95rem">$y = f(x)$. $D(f)$ — область определения, $E(f)$ — область значений. 3 способа: формула, таблица, график.</div>
</div>
<div style="padding:12px 14px;background:var(--sec-acc-soft);border-radius:11px;border-left:3px solid var(--pri)">
<div style="font-family:'Unbounded',sans-serif;font-weight:700;color:var(--pri2);margin-bottom:6px;font-size:.92rem">§ 7 · Свойства</div>
<div style="font-size:.95rem">Возрастает / убывает; нули — $f(x) = 0$; знакопостоянство; экстремумы ($y_{max}$, $y_{min}$).</div>
</div>
<div style="padding:12px 14px;background:var(--sec-acc-soft);border-radius:11px;border-left:3px solid var(--pri)">
<div style="font-family:'Unbounded',sans-serif;font-weight:700;color:var(--pri2);margin-bottom:6px;font-size:.92rem">§ 8 · Чётность</div>
<div style="font-size:.95rem">Чётная: $f(-x) = f(x)$ — симм. отн. $Oy$. Нечётная: $f(-x) = -f(x)$ — симм. отн. $O$.</div>
</div>
<div style="padding:12px 14px;background:var(--sec-acc-soft);border-radius:11px;border-left:3px solid var(--pri)">
<div style="font-family:'Unbounded',sans-serif;font-weight:700;color:var(--pri2);margin-bottom:6px;font-size:.92rem">§ 9 · Сдвиги</div>
<div style="font-size:.95rem">$y = f(x - a) + b$ — вправо на $a$, вверх на $b$. Минус в скобке — вправо!</div>
</div>
</div>
</div>
</div>`;
/* Часть Б — 5 боссов */
html += `<div class="card">
<div class="card-header">
<span class="card-icon rule">${ICONS.rule}</span>
<span class="card-title">Боссы главы 2</span>
<span class="card-num">5</span>
</div>
<div class="card-body">
<p>5 интегрированных задач. Каждая комбинирует несколько тем главы 2. За каждого побеждённого босса — <b>+10 XP</b>. Победишь всех — <b>+50 XP бонус</b> и ачивка «Магистр функций»!</p>
</div>
</div>`;
html += '<div id="ch2-bosses-container"></div>';
html += `<div style="margin-top:18px;padding:18px 20px;background:linear-gradient(135deg,var(--pri-soft),var(--sec-acc-soft));border-radius:14px;border:1.5px solid var(--pri);text-align:center" id="ch2-final-summary">
<div style="font-family:'Unbounded',sans-serif;font-weight:800;color:var(--pri2);font-size:1.1rem;margin-bottom:6px">Прогресс по боссам</div>
<div id="ch2-boss-overall" style="font-size:.95rem;color:var(--text);margin-bottom:10px">0 / 5 боссов побеждено</div>
<div style="height:12px;background:var(--card);border-radius:8px;overflow:hidden;border:1px solid var(--border)">
<div id="ch2-boss-overall-fill" style="height:100%;width:0%;background:linear-gradient(90deg,#059669,#10b981);transition:width .35s"></div>
</div>
<div id="ch2-final-reward" style="margin-top:14px;display:none;padding:14px;background:var(--card);border-radius:11px;border:2px solid #f59e0b">
<div style="font-family:'Unbounded',sans-serif;font-weight:800;color:#92400e;font-size:1.05rem;margin-bottom:6px">Магистр функций</div>
<div style="font-size:.92rem;margin-bottom:10px">Глава 2 пройдена! Все 5 боссов повержены. +50 XP бонус.</div>
<a class="btn primary" href="/textbook/algebra-9-ch3" style="text-decoration:none">Дальше: Глава 3 <svg class="ic" viewBox="0 0 24 24"><polyline points="9 18 15 12 9 6"/></svg></a>
</div>
</div>`;
html += secNav('p9', null);
box.innerHTML = html;
renderMath(box);
/* Боссы */
const BOSSES = [
{
n:1, color:'#10b981',
title:'Грифон Областей',
tag:'§ 6 + § 7',
q:'Дана функция $f(x) = \\dfrac{1}{x - 4}$. Сколько <b>целых</b> значений $x$ из промежутка $[1;\\ 7]$ принадлежат $D(f)$?',
ans:6,
hint:'$D(f):\\ x \\ne 4$. На $[1;7]$ целые: $1, 2, 3, 4, 5, 6, 7$ — исключаем $4$. Остаётся <b>6</b> значений.'
},
{
n:2, color:'#0891b2',
title:'Феникс Симметрии',
tag:'§ 7 + § 8',
q:'Функция $f(x) = x^3 - 3x$. Найди $f(2)$, затем $f(-2)$. Введи значение $f(-2)$.',
ans:-2,
hint:'$f(2) = 8 - 6 = 2$. Функция нечётная: $f(-x) = -f(x)$. Значит $f(-2) = -f(2) = -2$.'
},
{
n:3, color:'#7c3aed',
title:'Кракен Сдвигов',
tag:'§ 9 + § 6',
q:'График $y = (x - 3)^2 - 5$ — парабола. Найди её <b>вершину</b> $(x_0;\\ y_0)$ и введи сумму $x_0 + y_0$.',
ans:-2,
hint:'$y = (x - 3)^2 - 5$ — сдвиг параболы $y=x^2$ вправо на $3$ и вниз на $5$. Вершина $(3;\\ -5)$. Сумма: $3 + (-5) = -2$.'
},
{
n:4, color:'#dc2626',
title:'Минотавр Свойств',
tag:'§ 7 + § 9',
q:'Сколько нулей у функции $y = (x + 2)^2 - 1$?',
ans:2,
hint:'$(x+2)^2 - 1 = 0 \\Rightarrow (x+2)^2 = 1 \\Rightarrow x + 2 = \\pm 1 \\Rightarrow x = -1$ или $x = -3$. <b>2 нуля</b>.'
},
{
n:5, color:'#f59e0b',
title:'Мастер Функций',
tag:'§§ 69 — синтез',
q:'Дана $f(x) = (x + 1)^2 - 4$. Найди: а) $f(0)$; б) оба нуля функции. Введи сумму всех трёх чисел: $f(0) + x_1 + x_2$.',
ans:-5,
hint:'$f(0) = 1 - 4 = -3$. Нули: $(x+1)^2 = 4 \\Rightarrow x = 1$ или $x = -3$. Сумма: $-3 + 1 + (-3) = -5$.'
},
];
const cont = document.getElementById('ch2-bosses-container');
const STATE_KEY = 'algebra9_ch2_bosses';
const BOSS_STATE = (function(){
try{ const s = localStorage.getItem(STATE_KEY); if(s) return JSON.parse(s); }catch(e){}
return BOSSES.map(()=>({defeated:false}));
})();
function saveBosses(){ try{ localStorage.setItem(STATE_KEY, JSON.stringify(BOSS_STATE)); }catch(e){} }
cont.innerHTML = BOSSES.map((b, idx)=>{
return '<div class="boss-card" id="boss2-'+b.n+'-card" style="padding:16px;background:var(--card);border-radius:12px;border:2px solid '+b.color+';margin-bottom:14px">'
+'<div style="display:flex;align-items:center;gap:10px;margin-bottom:10px;flex-wrap:wrap">'
+'<svg viewBox="0 0 24 24" fill="none" stroke="'+b.color+'" stroke-width="2.2" style="width:28px;height:28px;flex-shrink:0"><polygon points="12,2 22,20 2,20"/></svg>'
+'<div style="font-family:\'Unbounded\',sans-serif;font-weight:800;color:'+b.color+';font-size:1.05rem">Босс '+b.n+': '+b.title+'</div>'
+'<div style="margin-left:auto;font-size:.78rem;color:var(--muted);padding:3px 8px;background:var(--sec-acc-soft);border-radius:6px">'+b.tag+'</div>'
+'</div>'
+'<div id="boss2-'+b.n+'-q" style="padding:12px 14px;background:var(--sec-acc-soft);border-radius:9px;font-size:1rem;line-height:1.5;margin-bottom:10px">'+b.q+'</div>'
+'<div style="display:flex;gap:8px;align-items:center;flex-wrap:wrap">'
+'<span style="font-family:\'JetBrains Mono\',monospace;font-size:.92rem">ответ =</span>'
+'<input type="number" id="boss2-'+b.n+'-ans" class="tinp" style="width:120px;text-align:center" placeholder="число">'
+'<button class="btn primary" id="boss2-'+b.n+'-go" style="background:'+b.color+';border-color:'+b.color+'">Атаковать</button>'
+'<button class="btn" id="boss2-'+b.n+'-hint">Подсказка</button>'
+'</div>'
+'<div class="feedback" id="boss2-'+b.n+'-fb"></div>'
+'</div>';
}).join('');
renderMath(cont);
function refreshOverall(){
const won = BOSS_STATE.filter(s => s.defeated).length;
const txt = document.getElementById('ch2-boss-overall');
const fill = document.getElementById('ch2-boss-overall-fill');
if(txt) txt.textContent = won + ' / ' + BOSSES.length + ' боссов побеждено';
if(fill) fill.style.width = (won * 100 / BOSSES.length) + '%';
if(won >= BOSSES.length){
const reward = document.getElementById('ch2-final-reward');
if(reward && reward.style.display === 'none'){
reward.style.display = 'block';
if(!STATE.achievements.has('ch2_done')){
achievement('ch2_done','Магистр функций');
addXp(50, 'ch2-bonus');
bumpProgress('final2', 30);
if(window.confetti){ try{ confetti(); }catch(e){} }
}
}
}
}
BOSSES.forEach((b, idx)=>{
const card = document.getElementById('boss2-'+b.n+'-card');
const goBtn = document.getElementById('boss2-'+b.n+'-go');
const hintBtn = document.getElementById('boss2-'+b.n+'-hint');
const ansInp = document.getElementById('boss2-'+b.n+'-ans');
if(BOSS_STATE[idx].defeated){
card.style.background = 'linear-gradient(135deg,var(--sec-acc-soft),var(--pri-soft))';
goBtn.disabled = true; goBtn.style.opacity = .55; goBtn.textContent = '✓ Повержен';
ansInp.disabled = true;
}
goBtn.addEventListener('click', ()=>{
if(BOSS_STATE[idx].defeated) return;
const fb = document.getElementById('boss2-'+b.n+'-fb');
const val = parseInt(ansInp.value, 10);
if(isNaN(val)){ feedback(fb, false, '&#10007; Введи целое число.'); return; }
if(val === b.ans){
BOSS_STATE[idx].defeated = true; saveBosses();
feedback(fb, true, '&#10003; Босс '+b.n+' повержен! +10 XP. '+b.hint);
addXp(10, 'boss-ch2-'+b.n);
bumpProgress('final2', 18);
goBtn.disabled = true; goBtn.style.opacity = .55; goBtn.textContent = '✓ Повержен';
ansInp.disabled = true;
card.style.background = 'linear-gradient(135deg,var(--sec-acc-soft),var(--pri-soft))';
refreshOverall();
} else {
feedback(fb, false, '&#10007; Промах. Попробуй ещё. Подсказка доступна.');
}
});
hintBtn.addEventListener('click', ()=>{
const fb = document.getElementById('boss2-'+b.n+'-fb');
fb.className = 'feedback ok';
fb.innerHTML = '<b>Подсказка:</b> '+b.hint;
fb.style.display = 'block';
fb.style.background = 'var(--warn-bg)';
fb.style.color = '#92400e';
fb.style.borderLeftColor = 'var(--warn)';
renderMath(fb);
});
ansInp.addEventListener('keydown', e=>{ if(e.key === 'Enter') goBtn.click(); });
});
refreshOverall();
}
/* ===== Search ===== */
const SEARCH_INDEX = (function(){
const arr=[];
PARAS.forEach(p=>arr.push({kind:'Параграф',title:p.num+' '+p.name,desc:p.sub||'',sec:p.id}));
return arr;
})();
function initSearch(){
const modal=document.getElementById('search-modal'),inp=document.getElementById('search-input'),out=document.getElementById('search-results'),btn=document.getElementById('search-btn');
if(!modal||!inp||!out) return;
let cur=0,rows=[];
function score(q,it){ const t=(it.title+' '+it.desc).toLowerCase(); if(t.includes(q)) return 100+(it.title.toLowerCase().startsWith(q)?50:0); let s=0; q.split(/\s+/).forEach(w=>{if(w&&t.includes(w))s+=10;}); return s; }
function rank(q){ q=q.trim().toLowerCase(); if(!q) return SEARCH_INDEX.slice(0,12); return SEARCH_INDEX.map(it=>({it,s:score(q,it)})).filter(x=>x.s>0).sort((a,b)=>b.s-a.s).slice(0,20).map(x=>x.it); }
function render(){ cur=0; if(!rows.length){out.innerHTML='<div class="search-empty">Ничего не найдено</div>';return;} out.innerHTML=rows.map((r,i)=>'<button class="search-row'+(i===0?' active':'')+'" data-i="'+i+'"><div class="sr-kind">'+r.kind+'</div><div class="sr-title">'+r.title+'</div>'+(r.desc?'<div class="sr-desc">'+(r.desc.length>90?r.desc.slice(0,90)+'…':r.desc)+'</div>':'')+'</button>').join(''); out.querySelectorAll('.search-row').forEach(b=>b.addEventListener('click',()=>{cur=+b.dataset.i;pick();})); }
function pick(){ const r=rows[cur]; if(!r) return; close(); goTo(r.sec); }
function move(d){ const items=out.querySelectorAll('.search-row'); if(!items.length) return; items[cur]&&items[cur].classList.remove('active'); cur=(cur+d+items.length)%items.length; items[cur].classList.add('active'); items[cur].scrollIntoView({block:'nearest'}); }
function open(){ modal.classList.add('show'); inp.value=''; rows=rank(''); render(); setTimeout(()=>inp.focus(),50); }
function close(){ modal.classList.remove('show'); }
btn&&btn.addEventListener('click',open);
modal.addEventListener('click',e=>{if(e.target===modal)close();});
inp.addEventListener('input',()=>{rows=rank(inp.value);render();});
inp.addEventListener('keydown',e=>{ if(e.key==='ArrowDown'){e.preventDefault();move(1);}else if(e.key==='ArrowUp'){e.preventDefault();move(-1);}else if(e.key==='Enter'){e.preventDefault();pick();}else if(e.key==='Escape'){e.preventDefault();close();} });
document.addEventListener('keydown',e=>{ if((e.ctrlKey||e.metaKey)&&(e.key==='k'||e.key==='K')){ e.preventDefault(); if(modal.classList.contains('show')) close(); else open(); } });
}
function initSidebarToggle(){
const side=document.getElementById('col-side'),back=document.getElementById('col-side-backdrop'),btn=document.getElementById('sidebar-btn');
if(!side||!btn) return;
function open(){ side.classList.add('open'); back.classList.add('show'); }
function close(){ side.classList.remove('open'); back.classList.remove('show'); }
btn.addEventListener('click',()=>{ if(side.classList.contains('open')) close(); else open(); });
back.addEventListener('click',close);
document.addEventListener('keydown',e=>{ if(e.key==='Escape') close(); });
}
function init(){
loadProgress(); initTheme(); initSidebarToggle(); initSearch();
buildParaSelector(); refreshProgressUI(); loadServerReadState(); goTo('p6');
setTimeout(()=>achievement('start'), 600);
if(window.LS&&window.LS.xp){
window.LS.xp.load().then(function(s){ if(s&&s.xp>STATE.xp){ STATE.xp=s.xp; STATE.level=calcLevel(STATE.xp); saveProgress(); refreshProgressUI(); if(STATE.current) buildSidebar(STATE.current); } });
}
}
document.addEventListener('DOMContentLoaded', init);
</script>
</body>
</html>