9d5a2959e1
На десктопе (>980px) .col-side уже видна как sticky-колонка справа в grid 1fr 280px. Клик по кнопке #sidebar-btn добавлял .col-side-backdrop.show — backdrop с z-index:9990 затемнял всю страницу, перекрывая sticky-aside. Со стороны выглядело как «ничего не открылось» — на самом деле появлялась чёрная вуаль. Фикс: @media(min-width:981px) скрывает #sidebar-btn и подавляет показ backdrop. На мобайле (≤980px) кнопка и overlay работают как раньше. Применено в 51 файле: physics 8/9/10 chN, algebra 7/9/10/11 chN + 8 ch2-3, geometry 7/8/9/11 chN, geometry_10 r1-4.
2053 lines
128 KiB
HTML
2053 lines
128 KiB
HTML
<!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(min-width:981px){#sidebar-btn{display:none}.col-side-backdrop.show{display:none}}
|
||
@media(max-width:980px){
|
||
.col-side{position:fixed;top:0;right:0;height:100vh;width:300px;max-width:88vw;background:var(--bg);box-shadow:-12px 0 24px rgba(0,0,0,.18);padding:18px 16px;overflow-y:auto;transform:translateX(100%);transition:transform .25s ease;z-index:9991;max-height:none}
|
||
.col-side.open{transform:none}
|
||
}
|
||
|
||
.gloss-term{border-bottom:1.5px dotted var(--sec-acc,var(--pri));cursor:help;color:var(--sec-acc-d,var(--pri2));font-weight:600;padding:0 1px}
|
||
.gloss-term:hover{background:var(--sec-acc-soft,var(--pri-soft));border-radius:3px}
|
||
.gloss-tip{position:fixed;max-width:320px;padding:11px 14px;background:var(--card);border:1.5px solid var(--sec-acc,var(--pri));border-radius:11px;font-size:.84rem;line-height:1.55;box-shadow:0 12px 32px rgba(0,0,0,.18);z-index:9994;display:none;pointer-events:none;color:var(--text)}
|
||
.gloss-tip.show{display:block}
|
||
.gloss-tip b{color:var(--sec-acc-d,var(--pri2));font-size:.92rem}
|
||
|
||
.search-modal{position:fixed;inset:0;background:rgba(15,23,42,.55);backdrop-filter:blur(4px);z-index:9993;display:none;align-items:flex-start;justify-content:center;padding-top:14vh}
|
||
.search-modal.show{display:flex}
|
||
.search-box{background:var(--bg);border:1px solid var(--border);border-radius:14px;width:560px;max-width:92vw;max-height:70vh;display:flex;flex-direction:column;overflow:hidden;box-shadow:0 24px 64px rgba(0,0,0,.4)}
|
||
.search-input{padding:14px 16px;font-size:1rem;border:0;border-bottom:1px solid var(--border);background:transparent;color:var(--text);outline:none}
|
||
.search-results{flex:1;overflow-y:auto;padding:6px 0}
|
||
.search-row{display:block;padding:8px 16px;cursor:pointer;border-bottom:1px solid var(--border);text-align:left;background:transparent;border:0;width:100%;color:var(--text)}
|
||
.search-row:hover,.search-row.active{background:var(--sec-acc-soft,var(--pri-soft))}
|
||
.search-row .sr-kind{font-size:.7rem;font-weight:800;color:var(--muted);text-transform:uppercase;letter-spacing:.06em;margin-bottom:2px}
|
||
.search-row .sr-title{font-weight:700;font-size:.92rem;color:var(--text)}
|
||
.search-row .sr-desc{font-size:.8rem;color:var(--muted);margin-top:2px}
|
||
.search-empty{padding:20px;text-align:center;color:var(--muted);font-size:.88rem}
|
||
.search-foot{padding:8px 14px;border-top:1px solid var(--border);font-size:.74rem;color:var(--muted);display:flex;gap:14px}
|
||
.search-foot kbd{padding:2px 6px;background:var(--card);border:1px solid var(--border);border-radius:4px;font-family:'JetBrains Mono',monospace;font-size:.72rem}
|
||
</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" data-gamified></div>
|
||
</div>
|
||
</section>
|
||
|
||
<section class="psel">
|
||
<div class="psel-title">Параграфы главы</div>
|
||
<div id="psel-grid" class="psel-grid"></div>
|
||
</section>
|
||
|
||
<section id="sec-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:[['§§6–9','теория главы 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" data-gamified><div class="xp-card-title" data-gamified><span>XP-прогресс</span><span class="xp-level">Ур. '+STATE.level+'</span></div><div class="xp-bar"><div class="xp-fill" style="width:'+xpPct+'%"></div></div><div class="xp-nums"><span>'+STATE.xp+' XP</span><span>'+xpNext+' XP</span></div></div>';
|
||
html+='<div class="sidecard"><h4>'+sb.title+'</h4>';
|
||
sb.rows.forEach(([k,v])=>{ html+='<div class="sidecard-row"><b>'+k+'</b>'+(v?' — '+v:'')+'</div>'; });
|
||
html+='</div>';
|
||
const tip=TIPS.find(t=>t.sec===id)||TIPS[0];
|
||
if(tip){
|
||
html+='<div class="sidecard" style="background:linear-gradient(135deg,var(--warn-bg,#fef3c7),var(--pri-soft));border-color:var(--warn,#f59e0b)"><h4 style="color:#92400e;display:flex;align-items:center;gap:6px"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" style="width:14px;height:14px"><polygon points="12,2 22,20 2,20"/></svg>Подсказка</h4><div class="sidecard-row" style="margin-bottom:0;font-size:.84rem;line-height:1.55">'+tip.html+'</div></div>';
|
||
}
|
||
if(STATE.achievements.size>0){
|
||
html+='<div class="sidecard"><h4>Достижения <span style="color:var(--warn);float:right">'+STATE.achievements.size+'</span></h4>';
|
||
[...STATE.achievements.values()].slice(-4).forEach(text=>{ html+='<div class="sidecard-row" style="font-size:.78rem;color:var(--ok)">✓ '+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?'✓ Верно!':'✗ Неверно'); 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 · Очки: <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?'✓ Верно. ':'✗ Неверно. ') + 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 ? '✓ Все верно! ' + correct + ' / ' + total : '✗ Правильно: ' + 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 · Очки: <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 · Очки: <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?'✓ Верно. ':'✗ Неверно. ') + 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?'✓ Верно. ':'✗ Неверно. ') + 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 ? '✓ Все верно! ' + correct + ' / ' + total : '✗ Правильно: ' + 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 · Очки: <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 · Очки: <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?'✓ Верно. ':'✗ Неверно. ') + 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?'✓ Верно. ':'✗ Неверно. ') + 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 ? '✓ Все верно! ' + correct + ' / ' + total : '✗ Правильно: ' + 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 · Очки: <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 · Очки: <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?'✓ Верно. ':'✗ Неверно. ') + 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?'✓ Верно. ':'✗ Неверно. ') + 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 ? '✓ Все верно! ' + correct + ' / ' + tasks.length : '✗ Правильно: ' + 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:'§§ 6–9 — синтез',
|
||
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, '✗ Введи целое число.'); return; }
|
||
if(val === b.ans){
|
||
BOSS_STATE[idx].defeated = true; saveBosses();
|
||
feedback(fb, true, '✓ Босс '+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, '✗ Промах. Попробуй ещё. Подсказка доступна.');
|
||
}
|
||
});
|
||
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>
|