dc201f28ff
Глава 3 «Неравенства с одной переменной» по программе Арефьевой/Пирютко. Палитра: индиго → фиолетовый → бирюза. 6 параграфов + финал. Скелет (общая инфраструктура, копия паттернов из ch2): - 7 параграфов: §13–§18 + final3 - LocalStorage 'algebra8_ch3_*', shared XP 'algebra8_xp' - DnD-хелпер setupSorter, glossary с 12 терминами, поиск Ctrl+K - XP-карта + бейдж + 7 контекстных подсказок + ачивки - Server sync прогресса (markLastPara/markParaRead, debounce 600мс) § 13 «Числовые неравенства и их свойства»: - Теория, 5 главных свойств, примеры - INTERACT 1: Drag-сортировка 5 чисел по возрастанию (5 наборов) - INTERACT 2: «Знак меняется или нет» (8 операций) - INTERACT 3: Конструктор a, b, k + операция → live-сравнение - INTERACT 4: Цепочка свойств (5 шагов выбора) - INTERACT 5: Drag-классификация (8 переходов по 4 свойствам) - INTERACT 6: Тренажёр «Что больше?» (10 случайных задач) § 14 «Сложение, умножение, оценка»: - Теория, таблица 4 операций для оценки, пример - INTERACT 1: Калькулятор оценок (live x+y, x-y, xy, x/y) - INTERACT 2: Тренажёр границ (8 задач) - INTERACT 3: Drag «Можно сложить / перемножить / нельзя» - INTERACT 4: Пошаговое сложение (5 шагов) - INTERACT 5: Сложи неравенства (6 multiple-choice) DB: миграция 013 — slug 'algebra-8-ch3', sort_order=5, бамп physics-8 на 6. Главы 1 и 2 теперь имеют кнопку «Глава 3 →» в шапке.
3378 lines
215 KiB
HTML
3378 lines
215 KiB
HTML
<!DOCTYPE html>
|
||
<html lang="ru">
|
||
<head>
|
||
<meta charset="UTF-8">
|
||
<meta name="viewport" content="width=device-width,initial-scale=1.0">
|
||
<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>Алгебра 8 · Глава 2 · Квадратные уравнения</title>
|
||
<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>
|
||
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800;900&family=Manrope:wght@400;500;600;700;800&family=Unbounded:wght@400;700;800;900&display=swap" rel="stylesheet">
|
||
<script src="/js/api.js" defer></script>
|
||
<script src="/js/xp.js" defer></script>
|
||
<style>
|
||
:root{
|
||
--pri:#e91e63; --pri2:#c2185b; --pri-soft:#fce7f3;
|
||
--acc:#03a9f4; --acc2:#0288d1; --acc-soft:#e0f4ff;
|
||
--ok:#10b981; --ok-bg:#d1fae5;
|
||
--fail:#ef4444; --fail-bg:#fee2e2;
|
||
--warn:#f59e0b; --warn-bg:#fef3c7;
|
||
--bg:#fdf2f8; --card:#fff;
|
||
--text:#1a1a2e; --muted:#6b5b73;
|
||
--border:#fce7f3;
|
||
--sh:0 2px 12px rgba(233,30,99,.07);
|
||
--sh2:0 6px 24px rgba(233,30,99,.10);
|
||
}
|
||
.dark{
|
||
--bg:#1a0f1a; --card:#2a1929;
|
||
--text:#f5e6f0; --muted:#b0a0b0;
|
||
--border:#5a2a5a; --pri-soft:#4a1a3a;
|
||
--acc-soft:#1a3a4a;
|
||
--sh:0 2px 12px rgba(0,0,0,.4);
|
||
--sh2:0 6px 24px rgba(0,0,0,.5);
|
||
}
|
||
*{margin:0;padding:0;box-sizing:border-box}
|
||
html,body{height:100%}
|
||
body{font-family:'Inter','Manrope',system-ui,sans-serif;background:var(--bg);color:var(--text);display:flex;flex-direction:column;min-height:100vh;line-height:1.5;transition:background .25s,color .25s}
|
||
button{font-family:inherit;cursor:pointer;border:none;background:none;color:inherit}
|
||
input,select,textarea{font-family:inherit}
|
||
.ic{width:1em;height:1em;display:inline-block;vertical-align:-.125em;flex-shrink:0;stroke:currentColor;fill:none;stroke-width:2;stroke-linecap:round;stroke-linejoin:round}
|
||
|
||
/* HEADER */
|
||
.hdr{position:relative;background:linear-gradient(110deg,#c2185b 0%,#e91e63 55%,#ec407a 100%);color:#fff;padding:46px 22px 30px;overflow:hidden;border-bottom:2px solid rgba(255,180,210,.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(255,220,235,.08);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}
|
||
.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 */
|
||
.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 */
|
||
.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:'ax² + bx + c = 0';position:absolute;right:-10px;top:-20px;font-family:'JetBrains Mono',monospace;font-size:clamp(2rem,8vw,5.5rem);font-weight:900;color:var(--pri);opacity:.08;line-height:1;pointer-events:none}
|
||
.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(233,30,99,.28)}
|
||
.hero-progress{flex:1;min-width:200px;max-width:280px}
|
||
.hp-label{font-size:.7rem;font-weight:700;color:var(--pri2);text-transform:uppercase;letter-spacing:.06em;margin-bottom:4px;display:block}
|
||
.hp-bar{height:8px;background:rgba(233,30,99,.12);border-radius:6px;overflow:hidden}
|
||
.hp-fill{height:100%;background:linear-gradient(90deg,var(--pri),var(--acc));border-radius:6px;width:0%;transition:width .4s}
|
||
.hp-text{font-size:.72rem;color:var(--muted);margin-top:3px;display:block}
|
||
|
||
/* PARA SELECTOR */
|
||
.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(170px,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:.88rem;font-weight:700;color:var(--text);line-height:1.3;margin-bottom:8px}
|
||
.psel-prog{height:4px;background:rgba(233,30,99,.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,#fce7f3)}
|
||
.dark .psel-card.final{background:linear-gradient(135deg,#3a2818,#4a1a3a)}
|
||
.psel-card.final .psel-num{color:var(--warn)}
|
||
|
||
/* SECTION COLORS */
|
||
.sec[id="sec-p7"] { --sec-acc:#f43f5e; --sec-acc-d:#be123c; --sec-acc-soft:#ffe4e6; }
|
||
.sec[id="sec-p8"] { --sec-acc:#06b6d4; --sec-acc-d:#0e7490; --sec-acc-soft:#cffafe; }
|
||
.sec[id="sec-p9"] { --sec-acc:#d97706; --sec-acc-d:#b45309; --sec-acc-soft:#fef3c7; }
|
||
.sec[id="sec-p10"] { --sec-acc:#6366f1; --sec-acc-d:#4338ca; --sec-acc-soft:#e0e7ff; }
|
||
.sec[id="sec-p11"] { --sec-acc:#059669; --sec-acc-d:#047857; --sec-acc-soft:#d1fae5; }
|
||
.sec[id="sec-p12"] { --sec-acc:#a21caf; --sec-acc-d:#86198f; --sec-acc-soft:#fae8ff; }
|
||
.sec[id="sec-final2"] { --sec-acc:#d97706; --sec-acc-d:#b45309; --sec-acc-soft:#fef3c7; }
|
||
|
||
/* SECTIONS */
|
||
.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.7rem;font-weight:800;color:var(--sec-acc-d,var(--pri2));letter-spacing:-.01em;line-height:1.25}
|
||
|
||
/* CARDS */
|
||
.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(233,30,99,.06);position:relative;z-index:1;transition:transform .25s cubic-bezier(.16,1,.3,1),box-shadow .25s}
|
||
.card:hover{transform:translateY(-2px);box-shadow:0 4px 10px rgba(0,0,0,.06),0 16px 36px rgba(233,30,99,.12)}
|
||
.card-header{display:flex;align-items:center;gap:10px;margin-bottom:12px;padding-bottom:10px;border-bottom:1px dashed var(--border)}
|
||
.card-icon{width:32px;height:32px;border-radius:9px;display:flex;align-items:center;justify-content:center;flex-shrink:0;color:#fff;outline:2px solid var(--sec-acc-soft,transparent);outline-offset:1px}
|
||
.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.class{background:#3b82f6}
|
||
.card-icon.home{background:#f97316}
|
||
.card-icon.prev{background:#6366f1}
|
||
.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:.95rem;line-height:1.65}
|
||
.card-body p{margin-bottom:10px}
|
||
.card-body p:last-child{margin-bottom:0}
|
||
.card-body b{color:var(--sec-acc-d,var(--pri2));font-weight:700}
|
||
.card-body ul,.card-body ol{padding-left:22px;margin:8px 0}
|
||
.card-body li{margin-bottom:4px}
|
||
.formula-box{background:var(--sec-acc-soft,var(--pri-soft));border-left:4px solid var(--sec-acc,var(--pri));border-radius:8px;padding:12px 16px;margin:10px 0;font-size:1.05rem}
|
||
.def-box{background:linear-gradient(135deg,var(--acc-soft),var(--card));border:1.5px solid var(--acc);border-radius:11px;padding:14px 16px;margin:12px 0}
|
||
.def-box-title{font-size:.78rem;font-weight:800;color:var(--acc2);text-transform:uppercase;letter-spacing:.06em;margin-bottom:6px;font-family:'Unbounded',sans-serif}
|
||
.note-warn{background:var(--warn-bg);border-left:4px solid var(--warn);border-radius:7px;padding:10px 14px;font-size:.9rem;margin:10px 0}
|
||
.dark .note-warn{background:rgba(245,158,11,.15);color:#fef3c7}
|
||
|
||
/* WIDGETS */
|
||
.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;transition:box-shadow .25s}
|
||
.wg:hover{box-shadow:0 4px 12px rgba(0,0,0,.08),0 16px 40px rgba(233,30,99,.18)}
|
||
.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;position:relative}
|
||
.wg-help::before{content:'?';position:absolute;left:-13px;top:50%;transform:translateY(-50%);width:22px;height:22px;border-radius:50%;background:var(--warn,#f59e0b);color:#fff;display:flex;align-items:center;justify-content:center;font-weight:900;font-size:.78rem;box-shadow:0 2px 6px rgba(0,0,0,.18)}
|
||
.dark .wg-help{background:linear-gradient(135deg,rgba(245,158,11,.18),rgba(233,30,99,.10))}
|
||
|
||
.btn{padding:8px 16px;border-radius:8px;background:var(--card);color:var(--text);border:1.5px solid var(--border);font-weight:600;font-size:.88rem;transition:background .15s,border-color .15s,transform .1s}
|
||
.btn:hover{background:var(--sec-acc-soft,var(--pri-soft));border-color:var(--sec-acc,var(--pri))}
|
||
.btn:active{transform:scale(.96)}
|
||
.btn.primary{background:var(--sec-acc,var(--pri));color:#fff;border-color:var(--sec-acc,var(--pri))}
|
||
.btn.primary:hover{background:var(--sec-acc-d,var(--pri2));border-color:var(--sec-acc-d,var(--pri2))}
|
||
.btn.acc{background:var(--acc);color:#fff;border-color:var(--acc)}
|
||
.btn.ok{background:var(--ok);color:#fff;border-color:var(--ok)}
|
||
.btn.small{padding:5px 11px;font-size:.78rem}
|
||
|
||
.inp{padding:7px 12px;border-radius:8px;background:var(--card);border:1.5px solid var(--border);font-size:.92rem;color:var(--text);font-family:'JetBrains Mono',monospace;width:auto;min-width:60px}
|
||
.inp:focus{outline:none;border-color:var(--sec-acc,var(--pri));background:var(--sec-acc-soft,var(--pri-soft))}
|
||
.inp.num{width:100px;text-align:center}
|
||
.slider{appearance:none;-webkit-appearance:none;height:6px;background:rgba(233,30,99,.18);border-radius:5px;width:100%;outline:none}
|
||
.slider::-webkit-slider-thumb{appearance:none;-webkit-appearance:none;width:18px;height:18px;background:var(--sec-acc,var(--pri));border-radius:50%;cursor:pointer;box-shadow:0 0 0 3px rgba(233,30,99,.25),0 2px 5px rgba(0,0,0,.18)}
|
||
.slider::-moz-range-thumb{width:18px;height:18px;background:var(--sec-acc,var(--pri));border-radius:50%;cursor:pointer;border:none}
|
||
.row{display:flex;gap:10px;align-items:center;flex-wrap:wrap;margin-bottom:10px}
|
||
.row-c{display:flex;gap:10px;align-items:center;justify-content:center;flex-wrap:wrap;margin-bottom:10px}
|
||
.lab{font-size:.85rem;font-weight:600;color:var(--text);margin-right:4px}
|
||
.lab-mono{font-family:'JetBrains Mono',monospace;font-weight:700;color:var(--sec-acc-d,var(--pri2))}
|
||
.chip{display:inline-flex;align-items:center;gap:5px;padding:5px 10px;background:var(--sec-acc-soft,var(--pri-soft));border-radius:7px;font-size:.84rem;font-weight:600;color:var(--sec-acc-d,var(--pri2))}
|
||
.chip.acc{background:var(--acc-soft);color:var(--acc2)}
|
||
.chip.ok{background:var(--ok-bg);color:#065f46}
|
||
.chip.fail{background:var(--fail-bg);color:#7f1d1d}
|
||
.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)}
|
||
|
||
/* DRAG-DROP */
|
||
.dz{min-height:60px;padding:12px;border:2px dashed var(--border);border-radius:10px;background:var(--card);display:flex;flex-wrap:wrap;gap:6px;align-content:flex-start;transition:border-color .15s,background .15s}
|
||
.dz.over{border-color:var(--sec-acc,var(--pri));background:var(--sec-acc-soft,var(--pri-soft))}
|
||
.dz-label{font-size:.78rem;font-weight:700;color:var(--muted);margin-bottom:6px;text-transform:uppercase;letter-spacing:.06em}
|
||
.drag-item{padding:6px 12px;background:linear-gradient(135deg,var(--acc),var(--acc2));color:#fff;border-radius:8px;font-weight:600;font-size:.88rem;cursor:grab;user-select:none;font-family:'JetBrains Mono',monospace;box-shadow:var(--sh);transition:transform .12s}
|
||
.drag-item:hover{transform:translateY(-1px)}
|
||
.drag-item:active{cursor:grabbing}
|
||
.drag-item.dragging{opacity:.5}
|
||
.drag-item.selected{outline:3px solid #FFD166;outline-offset:2px}
|
||
|
||
/* SIDEBAR */
|
||
.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}
|
||
@media(max-width:980px){.col-side{position:static;max-height:none}}
|
||
|
||
/* ACHIEVEMENT POPUP */
|
||
.ach-popup{position:fixed;top:18px;right:18px;background:linear-gradient(135deg,#fcd34d,#f59e0b);color:#451a03;padding:13px 18px;border-radius:11px;font-weight:700;font-size:.9rem;font-family:'Unbounded',sans-serif;box-shadow:0 8px 32px rgba(245,158,11,.4);z-index:1000;display:none;align-items:center;gap:10px;animation:slideIn .35s ease}
|
||
.ach-popup.show{display:flex}
|
||
@keyframes slideIn{from{transform:translateX(110%);opacity:0}to{transform:none;opacity:1}}
|
||
|
||
.spoiler{margin:10px 0;border:1px dashed var(--sec-acc,var(--pri));border-radius:8px;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:'−'}
|
||
.spoiler-body{padding:10px 14px;font-size:.92rem;line-height:1.6}
|
||
|
||
.sec-nav{display:flex;gap:10px;margin-top:24px;padding-top:20px;border-top:1px solid var(--border);justify-content:space-between;flex-wrap:wrap}
|
||
|
||
.foot{text-align:center;padding:30px 16px;color:var(--muted);font-size:.78rem;border-top:1px solid var(--border);margin-top:30px}
|
||
|
||
/* QUIZ */
|
||
.quiz{background:var(--card);border:1.5px solid var(--border);border-radius:12px;padding:14px 16px;margin-bottom:12px}
|
||
.quiz-q{font-weight:600;margin-bottom:10px;font-size:.94rem}
|
||
.quiz-opts{display:flex;flex-wrap:wrap;gap:6px}
|
||
.quiz-opt{padding:6px 12px;border:1.5px solid var(--border);border-radius:8px;font-size:.86rem;font-weight:600;cursor:pointer;background:var(--card);transition:background .12s,border-color .12s}
|
||
.quiz-opt:hover{background:var(--sec-acc-soft,var(--pri-soft));border-color:var(--sec-acc,var(--pri))}
|
||
.quiz-opt.correct{background:var(--ok-bg);border-color:var(--ok);color:#065f46}
|
||
.quiz-opt.wrong{background:var(--fail-bg);border-color:var(--fail);color:#7f1d1d}
|
||
|
||
.tbl{width:100%;border-collapse:collapse;margin:12px 0;font-size:.88rem}
|
||
.tbl th,.tbl td{padding:7px 10px;border:1px solid var(--border);text-align:center;font-family:'JetBrains Mono',monospace}
|
||
.tbl th{background:var(--sec-acc-soft,var(--pri-soft));color:var(--sec-acc-d,var(--pri2));font-weight:700}
|
||
|
||
/* SPECIFIC WIDGETS — added per § */
|
||
.live-ind{display:inline-block;margin-left:6px;font-weight:900;font-size:1.1rem}
|
||
.live-ind.ok{color:var(--ok)}
|
||
.live-ind.fail{color:var(--fail)}
|
||
|
||
/* CONSTRUCTOR uravnenia (§7) */
|
||
.uconstr-display{font-family:'JetBrains Mono',monospace;font-size:1.8rem;font-weight:800;text-align:center;padding:18px;background:var(--card);border-radius:11px;border:1px solid var(--border);margin:14px 0;color:var(--sec-acc-d,var(--pri2))}
|
||
.uconstr-type{display:inline-block;padding:6px 14px;border-radius:99px;font-size:.85rem;font-weight:700;margin-top:8px}
|
||
.uconstr-type.full{background:var(--ok-bg);color:#065f46}
|
||
.uconstr-type.incomp{background:var(--warn-bg);color:#92400e}
|
||
.uconstr-type.notq{background:var(--fail-bg);color:#7f1d1d}
|
||
|
||
/* BOOK PAGE задача */
|
||
.bookpage-svg{display:block;margin:0 auto;max-width:280px}
|
||
|
||
/* PIPELINE — пошаговый решатель */
|
||
.pipe-step{margin:8px 0;padding:10px 14px;background:var(--card);border-left:3px solid var(--sec-acc,var(--pri));border-radius:7px;font-family:'JetBrains Mono',monospace;font-size:.95rem;opacity:0;transform:translateX(-12px);transition:opacity .35s,transform .35s}
|
||
.pipe-step.show{opacity:1;transform:none}
|
||
.pipe-step b{color:var(--sec-acc-d,var(--pri2));font-family:'Inter',sans-serif;display:block;font-size:.78rem;margin-bottom:4px}
|
||
|
||
/* PARABOLA (§8) */
|
||
.parab-wrap{background:var(--card);border-radius:11px;border:1px solid var(--border);padding:8px;margin:14px 0}
|
||
|
||
/* CASES (§8) */
|
||
.case-card{background:var(--card);border:1.5px solid var(--border);border-radius:11px;padding:12px;text-align:center;cursor:pointer;transition:transform .15s,box-shadow .15s,border-color .15s}
|
||
.case-card:hover{transform:translateY(-2px);border-color:var(--sec-acc,var(--pri));box-shadow:var(--sh)}
|
||
.case-card.active{border-color:var(--sec-acc,var(--pri));background:var(--sec-acc-soft,var(--pri-soft));box-shadow:var(--sh2)}
|
||
.case-card h5{font-family:'Unbounded',sans-serif;font-size:.85rem;color:var(--sec-acc-d,var(--pri2));margin-bottom:4px}
|
||
|
||
/* SCORE display */
|
||
.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}
|
||
|
||
/* SLIDERS / DROP / ACTIONS */
|
||
.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 .katex{font-size:1em}
|
||
.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))}
|
||
.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-items{display:flex;flex-wrap:wrap;gap:6px;min-height:32px}
|
||
.drop-items .chip{cursor:pointer}
|
||
#p7s-pool .chip{cursor:pointer}
|
||
.actions{display:flex;gap:8px;flex-wrap:wrap;margin-top:10px}
|
||
.eq-show{font-family:'JetBrains Mono',monospace}
|
||
.pipe-tabs .btn.active{background:var(--sec-acc,var(--pri));color:#fff;border-color:var(--sec-acc,var(--pri))}
|
||
|
||
/* XP badge in hero */
|
||
.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(233,30,99,.22);font-family:'Unbounded',sans-serif}
|
||
.hero-xp-badge svg{flex-shrink:0}
|
||
/* XP card — единый стиль с главой 1 */
|
||
.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(3,169,244,.15);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}
|
||
|
||
/* GLOSSARY tooltip */
|
||
.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;animation:tipIn .15s ease}
|
||
.gloss-tip b{color:var(--sec-acc-d,var(--pri2));font-size:.92rem}
|
||
@keyframes tipIn{from{opacity:0;transform:translateY(4px)}to{opacity:1;transform:none}}
|
||
|
||
/* SEARCH MODAL */
|
||
.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;animation:fadeIn .15s ease}
|
||
.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-left:0;border-right:0;border-top: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;background:var(--card-soft,transparent)}
|
||
.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}
|
||
|
||
/* DRAG & DROP — sortable chips */
|
||
.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(244,63,94,.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.over{border-color:var(--sec-acc,var(--pri));background:var(--sec-acc-soft,var(--pri-soft));border-style:solid;transform:scale(1.015)}
|
||
.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}
|
||
|
||
/* SIDEBAR DRAWER for narrow viewports */
|
||
.col-side-backdrop{position:fixed;inset:0;background:rgba(0,0,0,.42);z-index:9990;display:none;animation:fadeIn .18s ease}
|
||
.col-side-backdrop.show{display:block}
|
||
@media(max-width:980px){
|
||
.col-side{position:fixed;top:0;right:0;height:100vh;width:300px;max-width:88vw;background:var(--bg);box-shadow:-12px 0 24px rgba(0,0,0,.18);padding:18px 16px;overflow-y:auto;transform:translateX(100%);transition:transform .25s ease;z-index:9991;max-height:none}
|
||
.col-side.open{transform:none}
|
||
}
|
||
</style>
|
||
</head>
|
||
<body>
|
||
|
||
<header class="hdr">
|
||
<div class="hdr-row">
|
||
<div>
|
||
<h1>Алгебра 8 · Глава 2</h1>
|
||
<div class="hdr-sub">Квадратные уравнения</div>
|
||
</div>
|
||
<div class="hdr-side">
|
||
<a href="/textbook/algebra-8" class="hdr-btn" title="К Главе 1">
|
||
<svg class="ic" viewBox="0 0 24 24"><polyline points="15 18 9 12 15 6"/></svg>
|
||
Глава 1
|
||
</a>
|
||
<a href="/textbook/algebra-8-ch3" class="hdr-btn" title="К Главе 3">
|
||
Глава 3
|
||
<svg class="ic" viewBox="0 0 24 24"><polyline points="9 18 15 12 9 6"/></svg>
|
||
</a>
|
||
<button id="search-btn" class="hdr-btn" title="Поиск (Ctrl+K)">
|
||
<svg class="ic" viewBox="0 0 24 24"><circle cx="11" cy="11" r="7"/><path d="m21 21-4-4"/></svg>
|
||
<span>Поиск</span>
|
||
</button>
|
||
<button id="sidebar-btn" class="hdr-btn" title="Шпаргалка">
|
||
<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" title="Сменить тему">
|
||
<svg class="ic" viewBox="0 0 24 24"><path d="M21 12.8A9 9 0 1 1 11.2 3a7 7 0 0 0 9.8 9.8z"/></svg>
|
||
<span id="theme-lab">Тёмная</span>
|
||
</button>
|
||
</div>
|
||
</div>
|
||
</header>
|
||
|
||
<main class="main">
|
||
<div class="col-main">
|
||
|
||
<section class="hero">
|
||
<h2>Самый знаменитый тип уравнений</h2>
|
||
<p>Квадратные уравнения встречаются везде: от расчёта траектории брошенного камня до планировки сада. Эта глава покажет полный арсенал: <b>дискриминант</b>, <b>теорему Виета</b>, разложение на множители, решение через подстановку. После неё вы сможете решить любое квадратное уравнение.</p>
|
||
<div class="hero-row">
|
||
<button class="btn-primary" onclick="goTo('p7')">
|
||
<svg class="ic" viewBox="0 0 24 24"><polygon points="6 4 20 12 6 20 6 4" fill="currentColor" stroke="none"/></svg>
|
||
Начать § 7
|
||
</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" title="Опыт"></div>
|
||
</div>
|
||
</section>
|
||
|
||
<section class="psel">
|
||
<div class="psel-title">Параграфы главы</div>
|
||
<div id="psel-grid" class="psel-grid"></div>
|
||
</section>
|
||
|
||
<section id="sec-p7" class="sec" data-watermark="ax²">
|
||
<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="D">
|
||
<div class="sec-header"><span class="sec-num">§ 8</span><h2 class="sec-h">Формулы корней квадратного уравнения</h2></div>
|
||
<div id="p8-body"></div>
|
||
</section>
|
||
|
||
<section id="sec-p9" class="sec" data-watermark="Виета">
|
||
<div class="sec-header"><span class="sec-num">§ 9</span><h2 class="sec-h">Теорема Виета</h2></div>
|
||
<div id="p9-body"></div>
|
||
</section>
|
||
|
||
<section id="sec-p10" class="sec" data-watermark="( )( )">
|
||
<div class="sec-header"><span class="sec-num">§ 10</span><h2 class="sec-h">Квадратный трёхчлен. Разложение на множители</h2></div>
|
||
<div id="p10-body"></div>
|
||
</section>
|
||
|
||
<section id="sec-p11" class="sec" data-watermark="задачи">
|
||
<div class="sec-header"><span class="sec-num">§ 11</span><h2 class="sec-h">Решение текстовых задач с помощью квадратных уравнений</h2></div>
|
||
<div id="p11-body"></div>
|
||
</section>
|
||
|
||
<section id="sec-p12" class="sec" data-watermark="t = x²">
|
||
<div class="sec-header"><span class="sec-num">§ 12</span><h2 class="sec-h">Целые рациональные уравнения, сводящиеся к квадратным</h2></div>
|
||
<div id="p12-body"></div>
|
||
</section>
|
||
|
||
<section id="sec-final2" class="sec" data-watermark="★">
|
||
<div class="sec-header"><span class="sec-num" style="background:linear-gradient(135deg,#f59e0b,#ec4899)">Финал главы</span><h2 class="sec-h">Итоги. Практическая и увлекательная математика</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">Интерактивный учебник «Алгебра 8» · Глава 2 · LearnSpace · версия 1.0</footer>
|
||
|
||
<div id="ach-popup" class="ach-popup">
|
||
<svg class="ic" viewBox="0 0 24 24" style="width:22px;height:22px"><circle cx="12" cy="8" r="5"/><path d="M8 13l-2 8 6-4 6 4-2-8"/></svg>
|
||
<span id="ach-text">Достижение!</span>
|
||
</div>
|
||
|
||
<div id="gloss-tip" class="gloss-tip"></div>
|
||
|
||
<div id="search-modal" class="search-modal" role="dialog" aria-label="Поиск по главе">
|
||
<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';
|
||
|
||
/* STATE & PROGRESS */
|
||
const STATE = {
|
||
current: 'p7',
|
||
progress: { p7:0, p8:0, p9:0, p10:0, p11:0, p12:0, final2:0 },
|
||
achievements: new Map(),
|
||
xp: 0,
|
||
level: 1,
|
||
};
|
||
|
||
/* Уровни — единая формула с сервером (xpToLevel из _shared.js) */
|
||
const XP_LEVELS = null; // legacy — теперь уровень считается формулой
|
||
|
||
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_constr: 'Конструктор уравнений',
|
||
p7_sort: 'Сортировка по типам',
|
||
p7_pipeline: 'Решил неполное по шагам',
|
||
p7_book: 'Задача про страницу',
|
||
p7_train: 'Тренажёр неполных',
|
||
p7_roots: 'Угадал есть ли корни',
|
||
p8_disc: 'Калькулятор дискриминанта',
|
||
p8_parab: 'Парабола и корни',
|
||
p8_cases: '3 случая дискриминанта',
|
||
p8_disc_train: 'Тренажёр дискриминанта',
|
||
p8_steps: 'Пошаговое решение',
|
||
p8_graph: 'Знак D по графику',
|
||
p9_vieta: 'Подбор по Виета',
|
||
p9_constr: 'Корни → уравнение',
|
||
p9_signs: 'Знаки корней',
|
||
p9_check: 'Проверка по Виета',
|
||
p9_nonpriv: 'Виета для a≠1',
|
||
p10_constr: 'Разложение на множители',
|
||
p10_steps: 'Шаговое разложение',
|
||
p10_train: 'Тренажёр разложения',
|
||
p10_fraction: 'Сокращение дробей',
|
||
p10_sort: 'Разложимо или нет',
|
||
p11_steps: 'Решил по 4 шагам',
|
||
p11_train: 'Тренажёр текстовых задач',
|
||
p11_move: 'Движение по реке',
|
||
p11_digit: 'Двузначное число',
|
||
p11_class: 'Классификатор задач',
|
||
p12_bi: 'Биквадратное решено',
|
||
p12_train: 'Тренажёр биквадратных',
|
||
p12_frac: 'Дробное → квадратное',
|
||
p12_subst: 'Замена переменной',
|
||
p12_odz: 'Посторонний корень',
|
||
boss_b1: 'Босс §7 повержен',
|
||
boss_b2: 'Босс §8 повержен',
|
||
boss_b3: 'Босс §9 повержен',
|
||
boss_b4: 'Босс §10 повержен',
|
||
boss_b5: 'Босс §11 повержен',
|
||
boss_b6: 'Босс §12 повержен',
|
||
boss_b7: 'Магистр алгебры',
|
||
all_bosses: 'Все 7 боссов побеждены!',
|
||
prac_streak: 'Серия из 5 верных',
|
||
};
|
||
|
||
function loadProgress(){
|
||
try{
|
||
const s = localStorage.getItem('algebra8_ch2_progress');
|
||
if(s) Object.assign(STATE.progress, JSON.parse(s));
|
||
const a = localStorage.getItem('algebra8_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));
|
||
}
|
||
}
|
||
// Общий XP для всех глав. Если ещё нет — собираем из старых ch1/ch2 ключей (single-shot миграция).
|
||
let xp = localStorage.getItem('algebra8_xp');
|
||
if(xp === null){
|
||
const c1 = +(localStorage.getItem('algebra8_ch1_xp') || 0);
|
||
const c2 = +(localStorage.getItem('algebra8_ch2_xp') || 0);
|
||
xp = c1 + c2;
|
||
try { localStorage.setItem('algebra8_xp', String(xp)); } catch(e){}
|
||
}
|
||
STATE.xp = +xp || 0; STATE.level = calcLevel(STATE.xp);
|
||
}catch(e){}
|
||
}
|
||
function saveProgress(){
|
||
try{
|
||
localStorage.setItem('algebra8_ch2_progress', JSON.stringify(STATE.progress));
|
||
localStorage.setItem('algebra8_ch2_achievements', JSON.stringify(Object.fromEntries(STATE.achievements)));
|
||
localStorage.setItem('algebra8_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);
|
||
}
|
||
|
||
/* Server sync of read/last_para — пишем в БД, чтобы каталог /textbooks показывал прогресс */
|
||
const _TB_SLUG = 'algebra-8-ch2';
|
||
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();
|
||
if(window.LS && window.LS.xp) window.LS.xp.add(n, 'algebra8-ch2-' + (src || 'misc'));
|
||
refreshProgressUI();
|
||
if(STATE.level > prev){
|
||
const pop = document.getElementById('ach-popup');
|
||
if(pop){
|
||
document.getElementById('ach-text').textContent = 'Уровень ' + STATE.level + '!';
|
||
pop.classList.add('show');
|
||
setTimeout(()=>pop.classList.remove('show'), 2600);
|
||
}
|
||
if(window.confetti) try { confetti(); } catch(e){}
|
||
}
|
||
}
|
||
function refreshProgressUI(){
|
||
const total = Math.round(Object.values(STATE.progress).reduce((a,b)=>a+b,0) / 7);
|
||
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 15.09 8.26 22 9.27 17 14.14 18.18 21.02 12 17.77 5.82 21.02 7 14.14 2 9.27 8.91 8.26 12 2"/></svg> Ур. ' + STATE.level + ' · ' + (STATE.xp || 0) + ' XP';
|
||
}
|
||
// sidebar XP card sync
|
||
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');
|
||
}
|
||
|
||
/* PARA SELECTOR */
|
||
const PARAS = [
|
||
{ id:'p7', num:'§ 7', name:'Квадратные уравнения', sub:'Неполные' },
|
||
{ id:'p8', num:'§ 8', name:'Формулы корней', sub:'Дискриминант' },
|
||
{ id:'p9', num:'§ 9', name:'Теорема Виета', sub:'Подбор корней' },
|
||
{ id:'p10', num:'§ 10', name:'Квадратный трёхчлен', sub:'Разложение' },
|
||
{ id:'p11', num:'§ 11', name:'Текстовые задачи', sub:'4 шага' },
|
||
{ id:'p12', num:'§ 12', name:'Сводящиеся к квадратным', sub:'Биквадратные' },
|
||
{ id:'final2', num:'★', name:'Финал главы', sub:'Итоги · Практика', 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 = {
|
||
p7:()=>buildP7(), p8:()=>buildP8(),
|
||
p9:()=>buildP9stub(), p10:()=>buildP10stub(),
|
||
p11:()=>buildP11stub(), p12:()=>buildP12stub(),
|
||
final2:()=>buildFinal2stub(),
|
||
};
|
||
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);
|
||
// glossary wrap — пройти по тексту секции и обернуть термины
|
||
setTimeout(()=>{ try { wrapGlossary(el); } catch(e){} }, 60);
|
||
markLastPara(id);
|
||
}
|
||
|
||
/* SIDEBAR */
|
||
const SIDEBARS = {
|
||
p7: { title:'Шпаргалка § 7', rows:[
|
||
['$ax^2+bx+c=0$','квадратное, $a \\neq 0$'],
|
||
['$ax^2+bx=0$','неполное: $x(ax+b)=0$'],
|
||
['$ax^2+c=0$','неполное: $x^2 = -c/a$'],
|
||
['$ax^2=0$','неполное: $x=0$'],
|
||
['Свойство','$ab=0 \\Leftrightarrow a=0$ или $b=0$'],
|
||
]},
|
||
p8: { title:'Шпаргалка § 8', rows:[
|
||
['$D = b^2 - 4ac$','дискриминант'],
|
||
['$D > 0$','два корня: $x_{1,2} = \\dfrac{-b \\pm \\sqrt{D}}{2a}$'],
|
||
['$D = 0$','один корень: $x = \\dfrac{-b}{2a}$'],
|
||
['$D < 0$','корней нет'],
|
||
]},
|
||
p9: { title:'Шпаргалка § 9', rows:[
|
||
['Виета','$x_1+x_2=-p,\\ x_1 x_2 = q$ для $x^2+px+q=0$'],
|
||
['Общий','$x_1+x_2 = -b/a,\\ x_1 x_2 = c/a$'],
|
||
['$q > 0$','корни одного знака'],
|
||
['$q < 0$','корни разных знаков'],
|
||
['Обратная','если $x_1+x_2=-p$ и $x_1 x_2=q$ — это корни'],
|
||
]},
|
||
p10:{ title:'Шпаргалка § 10', rows:[
|
||
['Разложение','$ax^2+bx+c = a(x-x_1)(x-x_2)$'],
|
||
['Условие','$D \\geq 0$ (иначе нельзя)'],
|
||
['Корни','через дискриминант или Виета'],
|
||
['Сокращение','через разложение числителя и знаменателя'],
|
||
]},
|
||
p11:{ title:'Шпаргалка § 11', rows:[
|
||
['4 шага','анализ → модель → решение → проверка'],
|
||
['Движение','$s = v t$; общее $\\dfrac{s_1}{v_1} + \\dfrac{s_2}{v_2}$'],
|
||
['Работа','$A = p \\cdot t$; вместе: $p_1 + p_2 = \\dfrac{1}{t}$'],
|
||
['Числа','$\\overline{ab} = 10a + b$'],
|
||
]},
|
||
p12:{ title:'Шпаргалка § 12', rows:[
|
||
['Биквадр','$ax^4+bx^2+c=0$ → $t=x^2$'],
|
||
['Условие','$t \\geq 0$'],
|
||
['$x = \\pm\\sqrt{t}$','для каждого $t \\geq 0$'],
|
||
['Дробное','умножить на ОЗ, проверить ОДЗ'],
|
||
['ОДЗ','знаменатель $\\neq 0$'],
|
||
]},
|
||
final2:{ title:'Финал главы', rows:[
|
||
['7 боссов','один на каждый параграф + общий'],
|
||
['Тип задач','select / yes-no / input'],
|
||
['Награда','«Магистр квадратных уравнений»'],
|
||
['Практика','случайные задачи всей главы'],
|
||
['Серия 5×','+ достижение «Серия из 5 верных»'],
|
||
]},
|
||
};
|
||
const TIPS = [
|
||
{ sec:'p7', html:'Если в неполном <b>ax² + c = 0</b> знаки <i>a</i> и <i>c</i> одинаковые — корней нет. Проверяй знак $-c/a$.' },
|
||
{ sec:'p8', html:'$D = b^2 - 4ac$ — даже не считай корни, если $D < 0$.' },
|
||
{ sec:'p9', html:'Перед использованием Виета убедись, что <b>a = 1</b>. Иначе формулы дают $-b/a$ и $c/a$.' },
|
||
{ sec:'p10', html:'Если $D < 0$, на множители первой степени <b>не</b> раскладывается. Не пытайся!' },
|
||
{ sec:'p11', html:'В задачах на движение часто помогает «общее время»: $\\dfrac{s_1}{v_1} + \\dfrac{s_2}{v_2}$.' },
|
||
{ sec:'p12', html:'После $t = x^2$ всегда проверяй $t \\geq 0$ — отрицательные $t$ дают пустое множество.' },
|
||
{ sec:'final2', html:'Не бойся пробовать боссов несколько раз: ошибка не «съедает» прогресс.' },
|
||
];
|
||
function buildSidebar(id){
|
||
const box = document.getElementById('sidebar-content');
|
||
const sb = SIDEBARS[id] || SIDEBARS.p7;
|
||
let html = '';
|
||
|
||
// XP card — единый стиль с главой 1
|
||
const xpForLv = _xpForLevel(STATE.level);
|
||
const xpNext = _xpForLevel(STATE.level + 1);
|
||
const xpInLv = STATE.xp - xpForLv;
|
||
const xpRange = xpNext - xpForLv;
|
||
const xpPct = xpRange > 0 ? Math.round(xpInLv / xpRange * 100) : 100;
|
||
html += `<div class="xp-card">
|
||
<div class="xp-card-title">
|
||
<span>XP-прогресс</span>
|
||
<span class="xp-level">Ур. ${STATE.level}</span>
|
||
</div>
|
||
<div class="xp-bar"><div class="xp-fill" style="width:${xpPct}%"></div></div>
|
||
<div class="xp-nums"><span>${STATE.xp} XP</span><span>${STATE.level < 30 ? xpNext + ' XP' : 'MAX'}</span></div>
|
||
</div>`;
|
||
|
||
// Шпаргалка
|
||
html += `<div class="sidecard"><h4>${sb.title}</h4>`;
|
||
sb.rows.forEach(([k,v])=>{
|
||
html += `<div class="sidecard-row"><b>${k}</b> ${v ? '— ' + v : ''}</div>`;
|
||
});
|
||
html += '</div>';
|
||
|
||
// Совет дня
|
||
const tip = TIPS.find(t => t.sec === id) || TIPS[0];
|
||
html += `<div class="sidecard tip-card" 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"><path d="M12 2v2M12 20v2M4.93 4.93l1.41 1.41M17.66 17.66l1.41 1.41M2 12h2M20 12h2M4.93 19.07l1.41-1.41M17.66 6.34l1.41-1.41"/><circle cx="12" cy="12" r="4"/></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){}
|
||
}
|
||
|
||
/* THEME */
|
||
function initTheme(){
|
||
const t = localStorage.getItem('algebra8_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('algebra8_ch2_theme', dark ? 'dark' : 'light');
|
||
document.getElementById('theme-lab').textContent = dark ? 'Светлая' : 'Тёмная';
|
||
});
|
||
}
|
||
|
||
/* HELPERS */
|
||
function $(sel, root){ return (root||document).querySelector(sel); }
|
||
function $$(sel, root){ return [...(root||document).querySelectorAll(sel)]; }
|
||
function el(tag, attrs, html){
|
||
const e = document.createElement(tag);
|
||
if(attrs) Object.entries(attrs).forEach(([k,v])=>{
|
||
if(k === 'class') e.className = v;
|
||
else if(k === 'style') e.style.cssText = v;
|
||
else if(k.startsWith('on') && typeof v === 'function') e.addEventListener(k.slice(2), v);
|
||
else e.setAttribute(k, v);
|
||
});
|
||
if(html != null) e.innerHTML = html;
|
||
return e;
|
||
}
|
||
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){
|
||
elm.className = 'feedback ' + (ok ? 'ok' : 'fail');
|
||
elm.innerHTML = text || (ok ? '✓ Верно!' : '✗ Неверно');
|
||
}
|
||
function sleep(ms){ return new Promise(r => setTimeout(r, ms)); }
|
||
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(4)).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>',
|
||
class: '<svg class="ic" viewBox="0 0 24 24"><rect x="3" y="3" width="18" height="14" rx="1"/><line x1="3" y1="21" x2="21" y2="21"/><polyline points="7 14 10 11 13 14 17 10"/></svg>',
|
||
home: '<svg class="ic" viewBox="0 0 24 24"><path d="M3 9l9-7 9 7v11a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z"/><polyline points="9 22 9 12 15 12 15 22"/></svg>',
|
||
};
|
||
function makeCard(kind, title, num, body){
|
||
const labels = {repeat:'Повторение',theory:'Теория',algo:'Алгоритм',rule:'Правило',example:'Пример',oral:'Устно',class:'Класс',home:'Домашка'};
|
||
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] ? '· ' + title : ''}</div>
|
||
${num ? `<div class="card-num">${num}</div>` : ''}
|
||
</div>
|
||
<div class="card-body">${body}</div>
|
||
</div>`;
|
||
}
|
||
function widget(title, badge, helpText, body){
|
||
return `<div class="wg">
|
||
<div class="wg-header"><span class="wg-badge">${badge||'INTERACT'}</span><div class="wg-title">${title}</div></div>
|
||
${helpText ? `<div class="wg-help">${helpText}</div>` : ''}
|
||
${body}
|
||
</div>`;
|
||
}
|
||
function secNav(prev, next){
|
||
const NAMES = {p7:'§7',p8:'§8',p9:'§9',p10:'§10',p11:'§11',p12:'§12',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;
|
||
}
|
||
|
||
/* CONFETTI (simplified) */
|
||
let _confettiCanvas = null, _confettiParticles = [], _confettiRaf = null;
|
||
function confetti(){
|
||
if(!_confettiCanvas){
|
||
_confettiCanvas = document.createElement('canvas');
|
||
_confettiCanvas.id = 'confetti-canvas';
|
||
_confettiCanvas.style.cssText = 'position:fixed;top:0;left:0;width:100%;height:100%;pointer-events:none;z-index:9999';
|
||
document.body.appendChild(_confettiCanvas);
|
||
}
|
||
const c = _confettiCanvas;
|
||
c.width = window.innerWidth; c.height = window.innerHeight;
|
||
const ctx = c.getContext('2d');
|
||
const colors = ['#e91e63','#03a9f4','#f59e0b','#10b981','#a855f7'];
|
||
for(let i = 0; i < 80; i++){
|
||
_confettiParticles.push({
|
||
x: window.innerWidth/2 + (Math.random()-0.5)*200,
|
||
y: window.innerHeight/2,
|
||
vx: (Math.random()-0.5)*14,
|
||
vy: -10 - Math.random()*10,
|
||
g: 0.4, life: 100, color: colors[i%colors.length], r: 4+Math.random()*4, rot: 0, vRot: (Math.random()-0.5)*0.3,
|
||
});
|
||
}
|
||
if(_confettiRaf) cancelAnimationFrame(_confettiRaf);
|
||
function frame(){
|
||
ctx.clearRect(0,0,c.width,c.height);
|
||
_confettiParticles = _confettiParticles.filter(p=>{
|
||
p.x += p.vx; p.y += p.vy; p.vy += p.g; p.life--; p.rot += p.vRot;
|
||
ctx.save(); ctx.translate(p.x, p.y); ctx.rotate(p.rot);
|
||
ctx.fillStyle = p.color;
|
||
ctx.fillRect(-p.r, -p.r/2, p.r*2, p.r);
|
||
ctx.restore();
|
||
return p.life > 0 && p.y < c.height + 50;
|
||
});
|
||
if(_confettiParticles.length > 0) _confettiRaf = requestAnimationFrame(frame);
|
||
else { ctx.clearRect(0,0,c.width,c.height); _confettiRaf = null; }
|
||
}
|
||
frame();
|
||
}
|
||
|
||
/* ============================================================
|
||
DRAG & DROP SORTER — shared helper
|
||
============================================================
|
||
- desktop: drag chip → drop on .drop-box (or back on .dnd-pool)
|
||
- mobile/tap: tap chip → armed; tap a box → placed; tap × → remove
|
||
- re-rendering keeps it simple; setupSorter returns { placed, render } */
|
||
function setupSorter(cfg){
|
||
// cfg: { poolId, cats:[...], items:[{id,html,cat}], scopeSelector, columnLayout?:bool }
|
||
const placed = {};
|
||
const pool = document.getElementById(cfg.poolId);
|
||
const scope = document.querySelector(cfg.scopeSelector);
|
||
if(!pool || !scope) return { placed, render: ()=>{} };
|
||
pool.classList.add('dnd-pool');
|
||
if(cfg.columnLayout) pool.classList.add('col');
|
||
let armed = null;
|
||
|
||
function buildChip(it, isPlaced){
|
||
const el = document.createElement('div');
|
||
el.className = 'dnd-chip' + (isPlaced ? ' placed' : '');
|
||
el.dataset.id = it.id;
|
||
el.innerHTML = '<span class="dnd-txt">' + it.html + '</span><span class="dnd-x" title="Убрать">×</span>';
|
||
attachHandlers(el, it.id);
|
||
return el;
|
||
}
|
||
|
||
function attachHandlers(el, itId){
|
||
let ghost = null, dragging = false, startX = 0, startY = 0, captured = false;
|
||
el.addEventListener('pointerdown', ev => {
|
||
if(ev.button !== undefined && ev.button !== 0) return;
|
||
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;
|
||
}
|
||
startX = ev.clientX; startY = ev.clientY;
|
||
const rect = el.getBoundingClientRect();
|
||
const ox = ev.clientX - rect.left, oy = ev.clientY - rect.top;
|
||
try { el.setPointerCapture(ev.pointerId); captured = true; } catch(e){}
|
||
function onMove(e){
|
||
const dx = e.clientX - startX, dy = e.clientY - startY;
|
||
if(!dragging && Math.hypot(dx, dy) > 8){
|
||
dragging = true;
|
||
ghost = el.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:' + rect.width + 'px;left:' + (e.clientX - ox) + 'px;top:' + (e.clientY - oy) + 'px';
|
||
document.body.appendChild(ghost);
|
||
el.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){
|
||
el.removeEventListener('pointermove', onMove);
|
||
el.removeEventListener('pointerup', onUp);
|
||
el.removeEventListener('pointercancel', onUp);
|
||
if(captured){ try { el.releasePointerCapture(ev.pointerId); } catch(_){} }
|
||
el.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; }
|
||
// fell outside — revert
|
||
} else {
|
||
// tap fallback
|
||
if(placed[itId]){ delete placed[itId]; armed = null; render(); }
|
||
else { armed = (armed === itId) ? null : itId; render(); }
|
||
}
|
||
dragging = false;
|
||
}
|
||
el.addEventListener('pointermove', onMove);
|
||
el.addEventListener('pointerup', onUp);
|
||
el.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(); }
|
||
});
|
||
});
|
||
pool.addEventListener('click', ev => {
|
||
if(!armed) return;
|
||
if(ev.target.closest('.dnd-chip')) return;
|
||
// empty pool click also de-arms
|
||
armed = null; render();
|
||
});
|
||
}
|
||
|
||
function render(){
|
||
pool.innerHTML = '';
|
||
cfg.items.forEach(it => {
|
||
if(placed[it.id]) return;
|
||
const chip = buildChip(it, false);
|
||
if(armed === it.id) chip.classList.add('armed');
|
||
pool.appendChild(chip);
|
||
});
|
||
cfg.cats.forEach(cat => {
|
||
const box = scope.querySelector('.drop-items[data-cat="' + cat + '"]');
|
||
if(!box) return;
|
||
box.innerHTML = '';
|
||
cfg.items.forEach(it => {
|
||
if(placed[it.id] !== cat) return;
|
||
box.appendChild(buildChip(it, true));
|
||
});
|
||
});
|
||
if(window.renderMathInElement) try { renderMath(scope); } catch(_){}
|
||
}
|
||
|
||
attachBoxTaps();
|
||
render();
|
||
return { placed, render, reset(){ for(const k in placed) delete placed[k]; armed = null; render(); } };
|
||
}
|
||
const DND_HINT_HTML = '<div class="dnd-hint"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M9 11V6a3 3 0 0 1 6 0v5"/><path d="M9 11h6v8a4 4 0 0 1-8 0z"/></svg> Перетащите карточку или нажмите её, затем — на нужный ящик.</div>';
|
||
|
||
/* ============================================================
|
||
GLOSSARY — term → definition map, hover-tooltip on .gloss-term
|
||
============================================================ */
|
||
const GLOSSARY = [
|
||
{ term:'квадратное уравнение', def:'Уравнение вида $ax^2+bx+c=0$ с $a \\neq 0$.', sec:'p7', aliases:['квадратное уравнение','квадратного уравнения','квадратные уравнения','квадратных уравнений'] },
|
||
{ term:'неполное квадратное уравнение', def:'Уравнение $ax^2+bx+c=0$, у которого $b=0$ или $c=0$ (или оба).', sec:'p7', aliases:['неполное квадратное','неполные квадратные','неполное уравнение','неполного уравнения','неполных уравнений'] },
|
||
{ term:'приведённое уравнение', def:'Квадратное уравнение со старшим коэффициентом 1: $x^2+px+q=0$.', sec:'p9', aliases:['приведённое','приведённого','приведённые','приведённых'] },
|
||
{ term:'дискриминант', def:'$D = b^2 - 4ac$. По знаку $D$ — число корней: $D>0$ — два, $D=0$ — один, $D<0$ — нет.', sec:'p8', aliases:['дискриминант','дискриминанта','дискриминантом','дискриминанте'] },
|
||
{ term:'теорема Виета', def:'Для $x^2+px+q=0$: $x_1+x_2=-p,\\ x_1 x_2 = q$. Для $ax^2+bx+c=0$: $-b/a,\\ c/a$.', sec:'p9', aliases:['теорема Виета','теоремы Виета','теореме Виета','Виета'] },
|
||
{ term:'корень уравнения', def:'Значение переменной, при котором уравнение становится верным равенством.', sec:'p7', aliases:['корень уравнения','корни уравнения','корня уравнения'] },
|
||
{ term:'квадратный трёхчлен', def:'Многочлен $ax^2+bx+c$ при $a \\neq 0$.', sec:'p10', aliases:['квадратный трёхчлен','квадратного трёхчлена','квадратные трёхчлены','квадратных трёхчленов'] },
|
||
{ term:'разложение на множители', def:'$ax^2+bx+c = a(x-x_1)(x-x_2)$ при $D \\geq 0$.', sec:'p10', aliases:['разложение на множители','разложения на множители','разложить на множители'] },
|
||
{ term:'биквадратное уравнение', def:'Уравнение $ax^4+bx^2+c=0$. Решается заменой $t=x^2,\\ t \\geq 0$.', sec:'p12', aliases:['биквадратное','биквадратных','биквадратные','биквадратного'] },
|
||
{ term:'ОДЗ', def:'Область допустимых значений. Для дробей — знаменатель $\\neq 0$.', sec:'p12', aliases:['ОДЗ','область допустимых значений'] },
|
||
{ term:'посторонний корень', def:'Корень, полученный при решении, но не удовлетворяющий ОДЗ исходного уравнения.', sec:'p12', aliases:['посторонний корень','посторонние корни','постороннего корня'] },
|
||
{ term:'свободный член', def:'Коэффициент $c$ в $ax^2+bx+c$. Член без переменной.', sec:'p7', aliases:['свободный член','свободного члена'] },
|
||
{ term:'старший коэффициент', def:'Коэффициент $a$ при $x^2$ в квадратном уравнении.', sec:'p7', aliases:['старший коэффициент','старшего коэффициента'] },
|
||
];
|
||
function wrapGlossary(root){
|
||
if(!root || root.__glossDone) return;
|
||
const allAliases = [];
|
||
GLOSSARY.forEach((g, i) => g.aliases.forEach(a => allAliases.push({ a, i })));
|
||
allAliases.sort((x, y) => y.a.length - x.a.length);
|
||
const re = new RegExp('(?<![\\w-])(' + allAliases.map(x => x.a.replace(/[.*+?^${}()|[\\]\\\\]/g,'\\$&')).join('|') + ')(?![\\w-])', 'iu');
|
||
const walker = document.createTreeWalker(root, NodeFilter.SHOW_TEXT, {
|
||
acceptNode(node){
|
||
const p = node.parentElement;
|
||
if(!p) return NodeFilter.FILTER_REJECT;
|
||
if(p.closest('.katex, .gloss-term, button, input, select, .wg-badge, .card-icon, .sec-num, .psel-num, .hdr, .ach-popup, script, style, .search-modal, .sidecard, .gloss-tip')) return NodeFilter.FILTER_REJECT;
|
||
if(!re.test(node.nodeValue)) return NodeFilter.FILTER_REJECT;
|
||
return NodeFilter.FILTER_ACCEPT;
|
||
}
|
||
});
|
||
const nodes = [];
|
||
let n; while((n = walker.nextNode())) nodes.push(n);
|
||
nodes.forEach(node => {
|
||
const text = node.nodeValue;
|
||
const out = document.createDocumentFragment();
|
||
let cursor = 0;
|
||
const global = new RegExp(re.source, 'giu');
|
||
let m;
|
||
while((m = global.exec(text)) !== null){
|
||
if(m.index > cursor) out.appendChild(document.createTextNode(text.slice(cursor, m.index)));
|
||
const found = m[0].toLowerCase();
|
||
const hit = allAliases.find(x => x.a.toLowerCase() === found);
|
||
const g = hit ? GLOSSARY[hit.i] : null;
|
||
const sp = document.createElement('span');
|
||
sp.className = 'gloss-term';
|
||
sp.dataset.gloss = g ? g.term : '';
|
||
sp.textContent = m[0];
|
||
out.appendChild(sp);
|
||
cursor = m.index + m[0].length;
|
||
}
|
||
if(cursor < text.length) out.appendChild(document.createTextNode(text.slice(cursor)));
|
||
node.parentNode.replaceChild(out, node);
|
||
});
|
||
root.__glossDone = true;
|
||
}
|
||
function initGlossaryTip(){
|
||
const tip = document.getElementById('gloss-tip');
|
||
if(!tip) return;
|
||
let lockOpen = null;
|
||
function show(el){
|
||
const g = GLOSSARY.find(x => x.term === el.dataset.gloss);
|
||
if(!g) return;
|
||
tip.innerHTML = '<b>' + g.term[0].toUpperCase() + g.term.slice(1) + '</b><div style="margin-top:4px">' + g.def + '</div><div style="margin-top:6px;font-size:.72rem;color:var(--muted);text-transform:uppercase;letter-spacing:.06em">См. § ' + g.sec.replace('p','') + '</div>';
|
||
if(window.renderMathInElement) renderMath(tip);
|
||
const r = el.getBoundingClientRect();
|
||
tip.classList.add('show');
|
||
const tw = tip.offsetWidth, th = tip.offsetHeight;
|
||
let left = r.left, top = r.bottom + 8;
|
||
if(left + tw > window.innerWidth - 12) left = window.innerWidth - tw - 12;
|
||
if(top + th > window.innerHeight - 12) top = r.top - th - 8;
|
||
tip.style.left = Math.max(8, left) + 'px';
|
||
tip.style.top = Math.max(8, top) + 'px';
|
||
}
|
||
function hide(){ tip.classList.remove('show'); }
|
||
document.addEventListener('mouseover', e => {
|
||
const el = e.target.closest && e.target.closest('.gloss-term');
|
||
if(el && !lockOpen) show(el);
|
||
});
|
||
document.addEventListener('mouseout', e => {
|
||
const el = e.target.closest && e.target.closest('.gloss-term');
|
||
if(el && !lockOpen) hide();
|
||
});
|
||
document.addEventListener('click', e => {
|
||
const el = e.target.closest && e.target.closest('.gloss-term');
|
||
if(el){
|
||
if(lockOpen === el){ lockOpen = null; hide(); }
|
||
else { lockOpen = el; show(el); }
|
||
} else if(lockOpen && !e.target.closest('.gloss-tip')){
|
||
lockOpen = null; hide();
|
||
}
|
||
});
|
||
}
|
||
|
||
/* ============================================================
|
||
SEARCH — Ctrl+K modal across glossary + sections + formulas
|
||
============================================================ */
|
||
const SEARCH_INDEX = (function(){
|
||
const arr = [];
|
||
PARAS.forEach(p => arr.push({ kind:'Параграф', title:p.num + ' ' + p.name, desc:p.sub || '', sec:p.id }));
|
||
GLOSSARY.forEach(g => arr.push({ kind:'Понятие', title:g.term, desc:g.def.replace(/\$/g,''), sec:g.sec, gloss:g.term }));
|
||
[
|
||
['Формула','D = b² − 4ac','§8 — дискриминант','p8'],
|
||
['Формула','x = (−b ± √D) / 2a','§8 — корни через дискриминант','p8'],
|
||
['Формула','x₁ + x₂ = −p, x₁·x₂ = q','§9 — теорема Виета','p9'],
|
||
['Формула','ax² + bx + c = a(x − x₁)(x − x₂)','§10 — разложение трёхчлена','p10'],
|
||
['Формула','t = x², t ≥ 0','§12 — замена для биквадратного','p12'],
|
||
].forEach(([k,t,d,s]) => arr.push({ kind:k, title:t, desc:d, sec:s }));
|
||
arr.push({ kind:'Финал', title:'Боссы главы', desc:'7 проверочных боссов', sec:'final2' });
|
||
return arr;
|
||
})();
|
||
function initSearch(){
|
||
const modal = document.getElementById('search-modal');
|
||
const inp = document.getElementById('search-input');
|
||
const out = document.getElementById('search-results');
|
||
const btn = document.getElementById('search-btn');
|
||
if(!modal || !inp || !out) return;
|
||
let cur = 0, rows = [];
|
||
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 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 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);
|
||
if(r.gloss){
|
||
setTimeout(()=>{
|
||
const sec = document.getElementById('sec-' + r.sec);
|
||
const el = sec && sec.querySelector('[data-gloss="' + r.gloss + '"]');
|
||
if(el){ el.scrollIntoView({ behavior:'smooth', block:'center' }); el.style.transition = 'background .3s'; el.style.background = 'var(--warn,#f59e0b)'; setTimeout(()=>{ el.style.background = ''; }, 1400); }
|
||
}, 400);
|
||
}
|
||
}
|
||
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();
|
||
}
|
||
});
|
||
}
|
||
|
||
/* INIT */
|
||
function initSidebarToggle(){
|
||
const side = document.getElementById('col-side');
|
||
const back = document.getElementById('col-side-backdrop');
|
||
const btn = document.getElementById('sidebar-btn');
|
||
if(!side || !btn) return;
|
||
function open(){ side.classList.add('open'); back.classList.add('show'); }
|
||
function close(){ side.classList.remove('open'); back.classList.remove('show'); }
|
||
btn.addEventListener('click', ()=>{
|
||
if(side.classList.contains('open')) close();
|
||
else open();
|
||
});
|
||
back.addEventListener('click', close);
|
||
document.addEventListener('keydown', e=>{ if(e.key === 'Escape') close(); });
|
||
}
|
||
function init(){
|
||
loadProgress();
|
||
initTheme();
|
||
initSidebarToggle();
|
||
initGlossaryTip();
|
||
initSearch();
|
||
buildParaSelector();
|
||
refreshProgressUI();
|
||
loadServerReadState();
|
||
goTo('p7');
|
||
setTimeout(()=>achievement('start','Начало главы 2!'), 600);
|
||
// Sync XP с сервером: если серверный XP выше — обновляем локальный прогресс
|
||
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);
|
||
|
||
/* STUBS for paragraphs not yet implemented */
|
||
function buildP9stub(){ buildP9(); }
|
||
function buildP9(){
|
||
const box = document.getElementById('p9-body');
|
||
let html = '';
|
||
|
||
html += makeCard('repeat','Повторение',null,`
|
||
<ul style="margin-left:18px;line-height:1.7">
|
||
<li><b>Приведённое</b> уравнение: $x^2 + px + q = 0$ — старший коэффициент равен $1$.</li>
|
||
<li>Полное: $ax^2 + bx + c = 0$ → делим на $a$, получим $x^2 + \\dfrac{b}{a}x + \\dfrac{c}{a} = 0$.</li>
|
||
<li>Формулы корней через дискриминант — из § 8.</li>
|
||
</ul>`);
|
||
|
||
html += makeCard('theory','Теорема Виета','9.1',`
|
||
<p>Пусть $x_1$ и $x_2$ — корни приведённого уравнения $x^2 + px + q = 0$. Тогда:</p>
|
||
<div style="background:var(--sec-acc-soft);border-radius:10px;padding:14px;margin:10px 0;text-align:center;font-size:1.15rem">$$x_1 + x_2 = -p,\\qquad x_1 \\cdot x_2 = q$$</div>
|
||
<p><b>Обратная теорема.</b> Если для чисел $x_1, x_2$ выполнено $x_1+x_2=-p$ и $x_1 x_2=q$, то они — корни $x^2+px+q=0$.</p>`);
|
||
|
||
html += makeCard('rule','Общий случай (a ≠ 1)','9.2',`
|
||
<p>Для уравнения $ax^2 + bx + c = 0$:</p>
|
||
<div style="background:var(--sec-acc-soft);border-radius:10px;padding:14px;margin:10px 0;text-align:center">$$x_1 + x_2 = -\\dfrac{b}{a},\\qquad x_1 \\cdot x_2 = \\dfrac{c}{a}$$</div>
|
||
<p><b>Анализ знаков</b> (если корни вещественные):</p>
|
||
<ul style="margin-left:18px;line-height:1.7">
|
||
<li>$q > 0$ → корни одного знака; если ещё $-p > 0$ — оба положительные.</li>
|
||
<li>$q < 0$ → корни разных знаков.</li>
|
||
<li>$q = 0$ → один из корней равен нулю.</li>
|
||
</ul>`);
|
||
|
||
html += makeCard('example','Пример',null,`
|
||
<p><b>Подбор:</b> $x^2 - 7x + 12 = 0$. Ищем $x_1, x_2$: сумма $7$, произведение $12$. Это $3$ и $4$. Ответ: $\\{3;\\ 4\\}$.</p>
|
||
<p style="margin-top:6px"><b>Составление:</b> найти уравнение с корнями $-2$ и $5$. Сумма $= 3$, произведение $= -10$. Уравнение: $x^2 - 3x - 10 = 0$.</p>`);
|
||
|
||
/* ===== INTERACTIVE 1: Подбор корней ===== */
|
||
html += widget('Тренажёр Виета: устный подбор','INTERACT 1','Дано приведённое уравнение. Подберите корни в уме и введите через точку с запятой.',`
|
||
<div class="score-display"><span>Задача <b id="p9v-i">1</b> / 10</span><span>Очки: <b id="p9v-score">0</b></span></div>
|
||
<div id="p9v-task" style="font-size:1.4rem;text-align:center;padding:18px;background:var(--sec-acc-soft);border-radius:10px;margin-bottom:10px"></div>
|
||
<div style="display:flex;gap:8px;justify-content:center;flex-wrap:wrap">
|
||
<input type="text" id="p9v-inp" placeholder="например: 3; 4" style="width:200px;padding:8px;border:1.5px solid var(--border);border-radius:8px">
|
||
<button class="btn primary" id="p9v-go">Ответ</button>
|
||
<button class="btn" id="p9v-skip">Пропустить</button>
|
||
</div>
|
||
<div class="feedback" id="p9v-fb" style="display:none;margin-top:10px"></div>
|
||
<button class="btn primary" id="p9v-start" style="margin-top:10px">Начать</button>`);
|
||
|
||
/* ===== INTERACTIVE 2: Составить уравнение ===== */
|
||
html += widget('Конструктор: корни → уравнение','INTERACT 2','Выберите корни — система соберёт приведённое уравнение по Виета.',`
|
||
<div style="display:flex;gap:10px;flex-wrap:wrap;align-items:center;justify-content:center;margin-bottom:10px">
|
||
<label>$x_1$ = <input type="number" id="p9c-r1" value="2" style="width:80px;padding:6px;border:1.5px solid var(--border);border-radius:6px"></label>
|
||
<label>$x_2$ = <input type="number" id="p9c-r2" value="-3" style="width:80px;padding:6px;border:1.5px solid var(--border);border-radius:6px"></label>
|
||
</div>
|
||
<div id="p9c-out" style="padding:14px;background:var(--sec-acc-soft);border-radius:10px;font-size:1.05rem;line-height:1.8"></div>`);
|
||
|
||
/* ===== INTERACTIVE 3: Знаки корней ===== */
|
||
html += widget('Знаки корней по Виета','INTERACT 3','По уравнению определите знаки корней (если они существуют).',`
|
||
<div class="score-display"><span>Раунд <b id="p9z-i">1</b> / 8</span><span>Правильно: <b id="p9z-score">0</b></span></div>
|
||
<div id="p9z-task" style="font-size:1.3rem;text-align:center;padding:16px;background:var(--sec-acc-soft);border-radius:10px;margin-bottom:10px"></div>
|
||
<div style="display:flex;gap:6px;flex-wrap:wrap;justify-content:center">
|
||
<button class="btn" data-z="pp">Оба «+»</button>
|
||
<button class="btn" data-z="nn">Оба «−»</button>
|
||
<button class="btn" data-z="pn">Разных знаков</button>
|
||
<button class="btn" data-z="none">Корней нет</button>
|
||
</div>
|
||
<div class="feedback" id="p9z-fb" style="display:none;margin-top:10px"></div>
|
||
<button class="btn primary" id="p9z-start" style="margin-top:10px">Начать</button>`);
|
||
|
||
/* ===== INTERACTIVE 4: Проверка корней по Виета ===== */
|
||
html += widget('Быстрая проверка корней','INTERACT 4','Подставьте найденные корни — проверьте через Виета мгновенно.',`
|
||
<div style="display:flex;gap:8px;flex-wrap:wrap;align-items:center;margin-bottom:10px">
|
||
<label>Уравнение: $x^2$ + <input type="number" id="p9p-p" value="-5" style="width:60px;padding:5px;border:1.5px solid var(--border);border-radius:6px">$x$ + <input type="number" id="p9p-q" value="6" style="width:60px;padding:5px;border:1.5px solid var(--border);border-radius:6px"> = 0</label>
|
||
</div>
|
||
<div style="display:flex;gap:8px;flex-wrap:wrap;align-items:center">
|
||
<label>Ваши $x_1, x_2$: <input type="number" id="p9p-r1" value="2" style="width:70px;padding:5px;border:1.5px solid var(--border);border-radius:6px">, <input type="number" id="p9p-r2" value="3" style="width:70px;padding:5px;border:1.5px solid var(--border);border-radius:6px"></label>
|
||
<button class="btn primary" id="p9p-check">Проверить</button>
|
||
</div>
|
||
<div id="p9p-out" style="margin-top:10px;padding:10px 14px;border-radius:8px"></div>`);
|
||
|
||
/* ===== INTERACTIVE 5: Виета для непривед. ===== */
|
||
html += widget('Виета для $a \\neq 1$','INTERACT 5','Уравнение не приведённое. Введите сумму и произведение корней.',`
|
||
<div id="p9n-task" style="font-size:1.2rem;text-align:center;padding:14px;background:var(--sec-acc-soft);border-radius:10px;margin-bottom:10px"></div>
|
||
<div style="display:flex;gap:10px;flex-wrap:wrap;align-items:center;justify-content:center">
|
||
<label>$x_1+x_2$ = <input type="text" id="p9n-s" style="width:80px;padding:6px;border:1.5px solid var(--border);border-radius:6px"></label>
|
||
<label>$x_1 x_2$ = <input type="text" id="p9n-p" style="width:80px;padding:6px;border:1.5px solid var(--border);border-radius:6px"></label>
|
||
<button class="btn primary" id="p9n-go">Проверить</button>
|
||
<button class="btn" id="p9n-next">Следующее</button>
|
||
</div>
|
||
<div class="feedback" id="p9n-fb" style="display:none;margin-top:10px"></div>`);
|
||
|
||
html += makeCard('oral','Устные вопросы',null,`
|
||
<ol style="margin-left:18px;line-height:1.8">
|
||
<li>Сформулируйте теорему Виета для приведённого уравнения.</li>
|
||
<li>Что говорит обратная теорема?</li>
|
||
<li>Корни уравнения $x^2 - 9x + 20 = 0$ — целые? Найдите устно.</li>
|
||
<li>Какого знака произведение корней уравнения $x^2 + 3x - 10 = 0$?</li>
|
||
</ol>`);
|
||
|
||
html += makeCard('class','Класс — решите по Виета',null,`
|
||
<ol style="margin-left:18px;line-height:1.8">
|
||
<li>$x^2 - 11x + 28 = 0$</li>
|
||
<li>$x^2 + x - 12 = 0$</li>
|
||
<li>$x^2 + 10x + 21 = 0$</li>
|
||
<li>Составьте приведённое уравнение с корнями $-4$ и $7$.</li>
|
||
</ol>`);
|
||
|
||
html += makeCard('home','Домашка',null,`
|
||
<ol style="margin-left:18px;line-height:1.8">
|
||
<li>$x^2 - 8x + 15 = 0$</li>
|
||
<li>$x^2 + 2x - 35 = 0$</li>
|
||
<li>Составьте уравнение с корнями $\\dfrac{1}{2}$ и $-3$.</li>
|
||
<li>Найдите $p$, если корни $x^2 + px - 6 = 0$ — это $2$ и $-3$.</li>
|
||
</ol>`);
|
||
|
||
html += secNav('p8','p10');
|
||
box.innerHTML = html;
|
||
if(window.renderMathInElement) setTimeout(()=>renderMath(box), 0);
|
||
|
||
/* INIT 1 — Тренажёр Виета */
|
||
(function(){
|
||
let cur = null, i = 1, score = 0;
|
||
function gen(){
|
||
const r1 = -7 + Math.floor(Math.random()*15);
|
||
const r2 = -7 + Math.floor(Math.random()*15);
|
||
if(r1 === r2) return gen();
|
||
const p = -(r1 + r2), q = r1*r2;
|
||
return { r1, r2, p, q };
|
||
}
|
||
function show(){
|
||
cur = gen();
|
||
document.getElementById('p9v-i').textContent = i;
|
||
const t = 'x^2 ' + (cur.p >= 0 ? '+ ' + cur.p : '- ' + Math.abs(cur.p)) + 'x ' + (cur.q >= 0 ? '+ ' + cur.q : '- ' + Math.abs(cur.q)) + ' = 0';
|
||
document.getElementById('p9v-task').innerHTML = '$' + t + '$'; renderMath(document.getElementById('p9v-task'));
|
||
document.getElementById('p9v-inp').value = '';
|
||
document.getElementById('p9v-fb').style.display = 'none';
|
||
}
|
||
function check(){
|
||
const fb = document.getElementById('p9v-fb');
|
||
fb.style.display = 'block';
|
||
const u = document.getElementById('p9v-inp').value.replace(/,/g,';').split(/[;\s]+/).filter(Boolean).map(Number).sort((a,b)=>a-b);
|
||
const a = [cur.r1, cur.r2].sort((a,b)=>a-b);
|
||
const ok = u.length === 2 && u[0] === a[0] && u[1] === a[1];
|
||
if(ok){ score++; feedback(fb, true, '✓ Верно: ' + a.join(' и ')); }
|
||
else feedback(fb, false, 'Не то. Правильно: ' + a.join(' и '));
|
||
document.getElementById('p9v-score').textContent = score;
|
||
if(i >= 10){ setTimeout(()=>{ feedback(fb, score >= 7, 'Итог: ' + score + '/10'); if(score >= 7){ achievement('p9_vieta'); bumpProgress('p9', 16); confetti(); } }, 600); }
|
||
else { i++; setTimeout(show, 800); }
|
||
}
|
||
document.getElementById('p9v-start').addEventListener('click', ()=>{ i=1; score=0; document.getElementById('p9v-score').textContent = 0; show(); });
|
||
document.getElementById('p9v-go').addEventListener('click', check);
|
||
document.getElementById('p9v-inp').addEventListener('keyup', e=>{ if(e.key === 'Enter') check(); });
|
||
document.getElementById('p9v-skip').addEventListener('click', ()=>{ if(i < 10){ i++; show(); } });
|
||
})();
|
||
|
||
/* INIT 2 — Конструктор уравнения */
|
||
(function(){
|
||
const r1E = document.getElementById('p9c-r1'), r2E = document.getElementById('p9c-r2');
|
||
const out = document.getElementById('p9c-out');
|
||
let done = false;
|
||
function refresh(){
|
||
const r1 = +r1E.value, r2 = +r2E.value;
|
||
const p = -(r1 + r2), q = r1 * r2;
|
||
let s = '<div><b>Сумма:</b> $x_1 + x_2 = ' + r1 + ' + (' + r2 + ') = ' + (r1+r2) + ' \\Rightarrow -p = ' + (r1+r2) + ' \\Rightarrow p = ' + p + '$</div>';
|
||
s += '<div><b>Произведение:</b> $x_1 \\cdot x_2 = ' + r1 + ' \\cdot (' + r2 + ') = ' + q + ' \\Rightarrow q = ' + q + '$</div>';
|
||
s += '<div style="margin-top:8px;font-size:1.15rem"><b>Уравнение:</b> $x^2 ' + (p >= 0 ? '+ ' + p : '- ' + Math.abs(p)) + 'x ' + (q >= 0 ? '+ ' + q : '- ' + Math.abs(q)) + ' = 0$</div>';
|
||
out.innerHTML = s; renderMath(out);
|
||
if(!done){ done = true; setTimeout(()=>{ achievement('p9_constr'); bumpProgress('p9', 14); }, 300); }
|
||
}
|
||
r1E.addEventListener('input', refresh); r2E.addEventListener('input', refresh);
|
||
refresh();
|
||
})();
|
||
|
||
/* INIT 3 — Знаки корней */
|
||
(function(){
|
||
let cur = null, i = 1, score = 0;
|
||
function gen(){
|
||
const t = Math.floor(Math.random()*4);
|
||
if(t === 0){
|
||
const r1 = 1 + Math.floor(Math.random()*5), r2 = 1 + Math.floor(Math.random()*5);
|
||
return { p: -(r1+r2), q: r1*r2, kind: 'pp' };
|
||
}
|
||
if(t === 1){
|
||
const r1 = -1 - Math.floor(Math.random()*5), r2 = -1 - Math.floor(Math.random()*5);
|
||
return { p: -(r1+r2), q: r1*r2, kind: 'nn' };
|
||
}
|
||
if(t === 2){
|
||
const r1 = 1 + Math.floor(Math.random()*5), r2 = -1 - Math.floor(Math.random()*5);
|
||
return { p: -(r1+r2), q: r1*r2, kind: 'pn' };
|
||
}
|
||
const p = -5 + Math.floor(Math.random()*11), q = 4 + Math.floor(Math.random()*8);
|
||
const D = p*p - 4*q;
|
||
if(D >= 0) return gen();
|
||
return { p, q, kind:'none' };
|
||
}
|
||
function show(){
|
||
cur = gen();
|
||
document.getElementById('p9z-i').textContent = i;
|
||
const t = 'x^2 ' + (cur.p >= 0 ? '+ ' + cur.p : '- ' + Math.abs(cur.p)) + 'x ' + (cur.q >= 0 ? '+ ' + cur.q : '- ' + Math.abs(cur.q)) + ' = 0';
|
||
document.getElementById('p9z-task').innerHTML = '$' + t + '$'; renderMath(document.getElementById('p9z-task'));
|
||
document.getElementById('p9z-fb').style.display = 'none';
|
||
}
|
||
function ans(k){
|
||
const fb = document.getElementById('p9z-fb');
|
||
fb.style.display = 'block';
|
||
if(k === cur.kind){ score++; feedback(fb, true, '✓ Верно'); }
|
||
else feedback(fb, false, 'Не то. Правильно: ' + ({pp:'оба «+»',nn:'оба «−»',pn:'разных знаков',none:'корней нет'})[cur.kind]);
|
||
document.getElementById('p9z-score').textContent = score;
|
||
if(i >= 8){ setTimeout(()=>{ feedback(fb, score >= 6, 'Итог: ' + score + '/8'); if(score >= 6){ achievement('p9_signs'); bumpProgress('p9', 14); confetti(); } }, 600); }
|
||
else { i++; setTimeout(show, 800); }
|
||
}
|
||
document.getElementById('p9z-start').addEventListener('click', ()=>{ i=1; score=0; document.getElementById('p9z-score').textContent = 0; show(); });
|
||
document.querySelectorAll('[data-z]').forEach(b => b.addEventListener('click', ()=>ans(b.dataset.z)));
|
||
})();
|
||
|
||
/* INIT 4 — Проверка корней */
|
||
(function(){
|
||
document.getElementById('p9p-check').addEventListener('click', ()=>{
|
||
const p = +document.getElementById('p9p-p').value, q = +document.getElementById('p9p-q').value;
|
||
const r1 = +document.getElementById('p9p-r1').value, r2 = +document.getElementById('p9p-r2').value;
|
||
const sum = r1 + r2, prod = r1 * r2;
|
||
const out = document.getElementById('p9p-out');
|
||
const okS = sum === -p, okP = prod === q;
|
||
let html = '<div>$x_1 + x_2 = ' + sum + '$ vs $-p = ' + (-p) + '$ — ' + (okS ? '<span style="color:var(--ok)">✓</span>' : '<span style="color:var(--bad)">✗</span>') + '</div>';
|
||
html += '<div>$x_1 \\cdot x_2 = ' + prod + '$ vs $q = ' + q + '$ — ' + (okP ? '<span style="color:var(--ok)">✓</span>' : '<span style="color:var(--bad)">✗</span>') + '</div>';
|
||
if(okS && okP){ html += '<div style="margin-top:8px;color:var(--ok);font-weight:700">Корни верные!</div>'; achievement('p9_check'); bumpProgress('p9', 12); }
|
||
else html += '<div style="margin-top:8px;color:var(--bad);font-weight:700">Корни ошибочны.</div>';
|
||
out.style.background = (okS && okP) ? 'var(--ok-bg)' : 'var(--fail-bg)';
|
||
out.innerHTML = html; renderMath(out);
|
||
});
|
||
})();
|
||
|
||
/* INIT 5 — Виета для непривед. */
|
||
(function(){
|
||
let cur = null;
|
||
function gen(){
|
||
const a = 2 + Math.floor(Math.random()*3);
|
||
const r1 = -3 + Math.floor(Math.random()*7), r2 = -3 + Math.floor(Math.random()*7);
|
||
if(r1 === r2) return gen();
|
||
const b = -a*(r1+r2), c = a*r1*r2;
|
||
return { a, b, c, sum:-b/a, prod:c/a };
|
||
}
|
||
function show(){
|
||
cur = gen();
|
||
document.getElementById('p9n-task').innerHTML = '$' + cur.a + 'x^2 ' + (cur.b >= 0 ? '+ ' + cur.b : '- ' + Math.abs(cur.b)) + 'x ' + (cur.c >= 0 ? '+ ' + cur.c : '- ' + Math.abs(cur.c)) + ' = 0$';
|
||
renderMath(document.getElementById('p9n-task'));
|
||
document.getElementById('p9n-s').value = '';
|
||
document.getElementById('p9n-p').value = '';
|
||
document.getElementById('p9n-fb').style.display = 'none';
|
||
}
|
||
function parse(s){ s = s.trim().replace(/,/g,'.'); if(s.includes('/')){ const [a,b] = s.split('/').map(Number); return a/b; } return +s; }
|
||
document.getElementById('p9n-go').addEventListener('click', ()=>{
|
||
const us = parse(document.getElementById('p9n-s').value), up = parse(document.getElementById('p9n-p').value);
|
||
const fb = document.getElementById('p9n-fb'); fb.style.display = 'block';
|
||
const okS = Math.abs(us - cur.sum) < 1e-6, okP = Math.abs(up - cur.prod) < 1e-6;
|
||
if(okS && okP){ feedback(fb, true, '✓ Верно! $\\dfrac{-b}{a}=' + fmt(cur.sum) + ',\\ \\dfrac{c}{a}=' + fmt(cur.prod) + '$'); renderMath(fb); achievement('p9_nonpriv'); bumpProgress('p9', 14); }
|
||
else feedback(fb, false, 'Правильно: сумма = ' + fmt(cur.sum) + ', произведение = ' + fmt(cur.prod));
|
||
});
|
||
document.getElementById('p9n-next').addEventListener('click', show);
|
||
show();
|
||
})();
|
||
}
|
||
function buildP10stub(){ buildP10(); }
|
||
function buildP10(){
|
||
const box = document.getElementById('p10-body');
|
||
let html = '';
|
||
|
||
html += makeCard('repeat','Повторение',null,`
|
||
<ul style="margin-left:18px;line-height:1.7">
|
||
<li>Дискриминант $D = b^2 - 4ac$. Корни через формулу или Виета.</li>
|
||
<li><b>Формулы сокр. умножения:</b> $(a-b)(a+b) = a^2 - b^2$, $a^2 \\pm 2ab + b^2 = (a \\pm b)^2$.</li>
|
||
<li>Сокращение дробей: сокращаются равные множители числителя и знаменателя.</li>
|
||
</ul>`);
|
||
|
||
html += makeCard('theory','Что такое квадратный трёхчлен','10.1',`
|
||
<p><b>Квадратный трёхчлен</b> — это многочлен $ax^2 + bx + c$, где $a \\neq 0$. Числа $x_1, x_2$, при которых он равен нулю, называются <b>корнями</b> трёхчлена.</p>
|
||
<p style="margin-top:6px">Если $D \\geq 0$, трёхчлен раскладывается:</p>
|
||
<div style="background:var(--sec-acc-soft);border-radius:10px;padding:14px;margin:10px 0;text-align:center;font-size:1.15rem">$$ax^2 + bx + c = a(x - x_1)(x - x_2)$$</div>
|
||
<p>Если $D < 0$ — на множители первой степени с действительными коэффициентами не раскладывается.</p>`);
|
||
|
||
html += makeCard('algo','Алгоритм разложения',null,`
|
||
<ol style="margin-left:18px;line-height:1.8">
|
||
<li>Найти $D$ и корни $x_1, x_2$.</li>
|
||
<li>Если $D \\geq 0$, записать $a(x - x_1)(x - x_2)$.</li>
|
||
<li>Раскрыть скобки и проверить.</li>
|
||
</ol>`);
|
||
|
||
html += makeCard('example','Примеры',null,`
|
||
<p><b>1)</b> $x^2 - 5x + 6 = (x-2)(x-3)$.</p>
|
||
<p><b>2)</b> $2x^2 - 7x + 3$: $D = 49 - 24 = 25$, $x_1 = 3$, $x_2 = 0{,}5$. Разложение: $2(x-3)(x-0{,}5) = (x-3)(2x-1)$.</p>
|
||
<p><b>3)</b> $x^2 + 1$: $D = -4 < 0$ → не раскладывается.</p>
|
||
<p><b>4)</b> Сокращение: $\\dfrac{x^2 - 9}{x^2 - 5x + 6} = \\dfrac{(x-3)(x+3)}{(x-3)(x-2)} = \\dfrac{x+3}{x-2}$.</p>`);
|
||
|
||
/* INT 1 — Конструктор разложения */
|
||
html += widget('Конструктор разложения','INTERACT 1','Введите $a$, $x_1$, $x_2$ — получите трёхчлен и его разложение.',`
|
||
<div style="display:flex;gap:10px;flex-wrap:wrap;align-items:center;justify-content:center;margin-bottom:10px">
|
||
<label>$a$ = <input type="number" id="p10c-a" value="1" style="width:70px;padding:6px;border:1.5px solid var(--border);border-radius:6px"></label>
|
||
<label>$x_1$ = <input type="number" id="p10c-x1" value="2" style="width:70px;padding:6px;border:1.5px solid var(--border);border-radius:6px"></label>
|
||
<label>$x_2$ = <input type="number" id="p10c-x2" value="-3" style="width:70px;padding:6px;border:1.5px solid var(--border);border-radius:6px"></label>
|
||
</div>
|
||
<div id="p10c-out" style="padding:14px;background:var(--sec-acc-soft);border-radius:10px;font-size:1.05rem;line-height:1.8"></div>`);
|
||
|
||
/* INT 2 — Шаговый разлагатель */
|
||
html += widget('Пошаговый разлагатель','INTERACT 2','Введите $a$, $b$, $c$ и нажимайте «Дальше» — каждый шаг отдельно.',`
|
||
<div style="display:flex;gap:10px;flex-wrap:wrap;align-items:center;margin-bottom:10px">
|
||
<label>$a$ = <input type="number" id="p10s-a" value="1" style="width:70px;padding:6px;border:1.5px solid var(--border);border-radius:6px"></label>
|
||
<label>$b$ = <input type="number" id="p10s-b" value="-7" style="width:70px;padding:6px;border:1.5px solid var(--border);border-radius:6px"></label>
|
||
<label>$c$ = <input type="number" id="p10s-c" value="12" style="width:70px;padding:6px;border:1.5px solid var(--border);border-radius:6px"></label>
|
||
<button class="btn primary" id="p10s-go">Старт</button>
|
||
<button class="btn" id="p10s-next" style="display:none">Дальше</button>
|
||
<button class="btn" id="p10s-reset" style="display:none">Сначала</button>
|
||
</div>
|
||
<div id="p10s-stage" style="padding:14px;background:var(--card-soft);border-radius:10px;min-height:80px"></div>`);
|
||
|
||
/* INT 3 — Тренажёр разложения */
|
||
html += widget('Тренажёр разложения','INTERACT 3','Введите разложение в формате $(x-?)(x-?)$ — корни через точку с запятой, со знаками.',`
|
||
<div class="score-display"><span>Задача <b id="p10t-i">1</b> / 8</span><span>Очки: <b id="p10t-score">0</b></span></div>
|
||
<div id="p10t-task" style="font-size:1.3rem;text-align:center;padding:16px;background:var(--sec-acc-soft);border-radius:10px;margin-bottom:10px"></div>
|
||
<div style="display:flex;gap:8px;justify-content:center;flex-wrap:wrap">
|
||
<input type="text" id="p10t-inp" placeholder="корни: например 2; -3" style="width:200px;padding:8px;border:1.5px solid var(--border);border-radius:8px">
|
||
<button class="btn primary" id="p10t-go">Ответ</button>
|
||
</div>
|
||
<div class="feedback" id="p10t-fb" style="display:none;margin-top:10px"></div>
|
||
<button class="btn primary" id="p10t-start" style="margin-top:10px">Начать</button>`);
|
||
|
||
/* INT 4 — Сокращение дробей */
|
||
html += widget('Сокращение дробей','INTERACT 4','Дробь содержит квадратные трёхчлены. Разложите устно и введите сокращённую форму.',`
|
||
<div class="score-display"><span>Задача <b id="p10f-i">1</b> / 5</span><span>Очки: <b id="p10f-score">0</b></span></div>
|
||
<div id="p10f-task" style="font-size:1.2rem;text-align:center;padding:16px;background:var(--sec-acc-soft);border-radius:10px;margin-bottom:10px"></div>
|
||
<div id="p10f-opts" style="display:flex;flex-direction:column;gap:6px"></div>
|
||
<div class="feedback" id="p10f-fb" style="display:none;margin-top:10px"></div>
|
||
<button class="btn primary" id="p10f-start" style="margin-top:10px">Начать</button>`);
|
||
|
||
/* INT 5 — Drag: разложимо или нет */
|
||
html += widget('Разложимо или нет?','INTERACT 5','По знаку дискриминанта разнесите трёхчлены в нужные ящики.',`
|
||
${DND_HINT_HTML}
|
||
<div id="p10z-pool"></div>
|
||
<div class="drop-row" style="display:grid;grid-template-columns:1fr 1fr;gap:10px">
|
||
<div class="drop-box"><h5>Раскладывается ($D \\geq 0$)</h5><div class="drop-items" data-cat="yes"></div></div>
|
||
<div class="drop-box"><h5>Не раскладывается ($D < 0$)</h5><div class="drop-items" data-cat="no"></div></div>
|
||
</div>
|
||
<div class="actions"><button class="btn primary" id="p10z-check">Проверить</button><button class="btn" id="p10z-reset">Сначала</button></div>
|
||
<div class="feedback" id="p10z-fb" style="display:none"></div>`);
|
||
|
||
html += makeCard('oral','Устно',null,`
|
||
<ol style="margin-left:18px;line-height:1.8">
|
||
<li>При каком условии трёхчлен раскладывается?</li>
|
||
<li>Запишите разложение $x^2 - 4x + 3$.</li>
|
||
<li>Разложим ли $x^2 + 9$?</li>
|
||
</ol>`);
|
||
|
||
html += makeCard('class','Класс — разложите',null,`
|
||
<ol style="margin-left:18px;line-height:1.8">
|
||
<li>$x^2 - 8x + 15$</li>
|
||
<li>$2x^2 + 5x - 3$</li>
|
||
<li>$x^2 + 6x + 9$</li>
|
||
<li>Сократите: $\\dfrac{x^2 - 4}{x^2 + x - 6}$.</li>
|
||
</ol>`);
|
||
|
||
html += makeCard('home','Домашка',null,`
|
||
<ol style="margin-left:18px;line-height:1.8">
|
||
<li>$x^2 - 9x + 20$</li>
|
||
<li>$3x^2 - 8x + 4$</li>
|
||
<li>$x^2 + 4x + 7$ — раскладывается ли?</li>
|
||
<li>Сократите: $\\dfrac{x^2 - 25}{x^2 - 7x + 10}$.</li>
|
||
</ol>`);
|
||
|
||
html += secNav('p9','p11');
|
||
box.innerHTML = html;
|
||
if(window.renderMathInElement) setTimeout(()=>renderMath(box), 0);
|
||
|
||
/* INIT 1 */
|
||
(function(){
|
||
const aE = document.getElementById('p10c-a'), x1E = document.getElementById('p10c-x1'), x2E = document.getElementById('p10c-x2');
|
||
const out = document.getElementById('p10c-out');
|
||
let done = false;
|
||
function refresh(){
|
||
const a = +aE.value, x1 = +x1E.value, x2 = +x2E.value;
|
||
if(!a){ out.innerHTML = 'Введите $a \\neq 0$'; renderMath(out); return; }
|
||
const b = -a*(x1+x2), c = a*x1*x2;
|
||
const eq = (a === 1 ? '' : (a === -1 ? '-' : a)) + 'x^2 ' + (b >= 0 ? '+ ' + b : '- ' + Math.abs(b)) + 'x ' + (c >= 0 ? '+ ' + c : '- ' + Math.abs(c));
|
||
const fact = (a === 1 ? '' : (a === -1 ? '-' : a)) + '(x ' + (x1 >= 0 ? '- ' + x1 : '+ ' + Math.abs(x1)) + ')(x ' + (x2 >= 0 ? '- ' + x2 : '+ ' + Math.abs(x2)) + ')';
|
||
out.innerHTML = '<div><b>Трёхчлен:</b> $' + eq + '$</div><div><b>Разложение:</b> $' + fact + '$</div>';
|
||
renderMath(out);
|
||
if(!done){ done = true; setTimeout(()=>{ achievement('p10_constr'); bumpProgress('p10', 14); }, 300); }
|
||
}
|
||
[aE,x1E,x2E].forEach(e => e.addEventListener('input', refresh));
|
||
refresh();
|
||
})();
|
||
|
||
/* INIT 2 — пошаговый */
|
||
(function(){
|
||
const stage = document.getElementById('p10s-stage');
|
||
const goBtn = document.getElementById('p10s-go');
|
||
const nextBtn = document.getElementById('p10s-next');
|
||
const resetBtn = document.getElementById('p10s-reset');
|
||
let steps = [], idx = 0, awarded = false;
|
||
function build(a, b, c){
|
||
const D = b*b - 4*a*c;
|
||
const arr = [];
|
||
arr.push('<b>Шаг 1.</b> Исходный трёхчлен: $' + a + 'x^2 ' + (b >= 0 ? '+ ' + b : '- ' + Math.abs(b)) + 'x ' + (c >= 0 ? '+ ' + c : '- ' + Math.abs(c)) + '$');
|
||
arr.push('<b>Шаг 2.</b> $D = b^2 - 4ac = ' + (b*b) + ' - ' + (4*a*c) + ' = ' + D + '$');
|
||
if(D < 0){
|
||
arr.push('<b>Шаг 3.</b> $D < 0$ — на множители первой степени с действительными коэффициентами не раскладывается.');
|
||
} else {
|
||
const x1 = (-b - Math.sqrt(D))/(2*a), x2 = (-b + Math.sqrt(D))/(2*a);
|
||
arr.push('<b>Шаг 3.</b> Корни: $x_1 = ' + fmt(x1) + ',\\ x_2 = ' + fmt(x2) + '$');
|
||
arr.push('<b>Шаг 4.</b> Подставляем в формулу $a(x-x_1)(x-x_2)$:');
|
||
arr.push('<b>Ответ:</b> $' + (a === 1 ? '' : a) + '(x ' + (x1 >= 0 ? '- ' + fmt(x1) : '+ ' + fmt(-x1)) + ')(x ' + (x2 >= 0 ? '- ' + fmt(x2) : '+ ' + fmt(-x2)) + ')$');
|
||
}
|
||
return arr;
|
||
}
|
||
function render(){
|
||
stage.innerHTML = steps.slice(0, idx + 1)
|
||
.map(s => `<div style="margin:8px 0;padding:10px 12px;background:var(--card);border-left:3px solid var(--sec-acc);border-radius:7px;animation:fadeIn .3s ease">${s}</div>`)
|
||
.join('');
|
||
renderMath(stage);
|
||
if(idx >= steps.length - 1){
|
||
nextBtn.disabled = true; nextBtn.textContent = 'Готово';
|
||
if(!awarded){ awarded = true; achievement('p10_steps'); bumpProgress('p10', 14); confetti(); }
|
||
} else {
|
||
nextBtn.disabled = false; nextBtn.textContent = 'Дальше (' + (idx + 1) + '/' + steps.length + ')';
|
||
}
|
||
}
|
||
goBtn.addEventListener('click', ()=>{
|
||
const a = +document.getElementById('p10s-a').value, b = +document.getElementById('p10s-b').value, c = +document.getElementById('p10s-c').value;
|
||
if(!a){ stage.innerHTML = '<p>$a$ не может быть нулём.</p>'; renderMath(stage); return; }
|
||
steps = build(a, b, c); idx = 0; awarded = false;
|
||
goBtn.style.display = 'none'; nextBtn.style.display = ''; resetBtn.style.display = '';
|
||
render();
|
||
});
|
||
nextBtn.addEventListener('click', ()=>{ if(idx < steps.length - 1){ idx++; render(); } });
|
||
resetBtn.addEventListener('click', ()=>{
|
||
idx = 0; steps = []; awarded = false; stage.innerHTML = '';
|
||
goBtn.style.display = ''; nextBtn.style.display = 'none'; resetBtn.style.display = 'none';
|
||
});
|
||
})();
|
||
|
||
/* INIT 3 */
|
||
(function(){
|
||
let cur = null, i = 1, score = 0;
|
||
function gen(){
|
||
const r1 = -5 + Math.floor(Math.random()*11), r2 = -5 + Math.floor(Math.random()*11);
|
||
if(r1 === r2 || r1 === 0 && r2 === 0) return gen();
|
||
const b = -(r1+r2), c = r1*r2;
|
||
return { r1, r2, eq: 'x^2 ' + (b >= 0 ? '+ ' + b : '- ' + Math.abs(b)) + 'x ' + (c >= 0 ? '+ ' + c : '- ' + Math.abs(c)) };
|
||
}
|
||
function show(){
|
||
cur = gen();
|
||
document.getElementById('p10t-i').textContent = i;
|
||
document.getElementById('p10t-task').innerHTML = '$' + cur.eq + '$';
|
||
renderMath(document.getElementById('p10t-task'));
|
||
document.getElementById('p10t-inp').value = '';
|
||
document.getElementById('p10t-fb').style.display = 'none';
|
||
}
|
||
function check(){
|
||
const fb = document.getElementById('p10t-fb');
|
||
fb.style.display = 'block';
|
||
const u = document.getElementById('p10t-inp').value.replace(/,/g,';').split(/[;\s]+/).filter(Boolean).map(Number).sort((a,b)=>a-b);
|
||
const a = [cur.r1, cur.r2].sort((a,b)=>a-b);
|
||
const ok = u.length === 2 && u[0] === a[0] && u[1] === a[1];
|
||
if(ok){ score++; feedback(fb, true, '✓ $(x ' + (cur.r1 >= 0 ? '- ' + cur.r1 : '+ ' + Math.abs(cur.r1)) + ')(x ' + (cur.r2 >= 0 ? '- ' + cur.r2 : '+ ' + Math.abs(cur.r2)) + ')$'); renderMath(fb); }
|
||
else feedback(fb, false, 'Правильно: ' + a.join(' и '));
|
||
document.getElementById('p10t-score').textContent = score;
|
||
if(i >= 8){ setTimeout(()=>{ feedback(fb, score >= 6, 'Итог: ' + score + '/8'); if(score >= 6){ achievement('p10_train'); bumpProgress('p10', 16); confetti(); } }, 600); }
|
||
else { i++; setTimeout(show, 800); }
|
||
}
|
||
document.getElementById('p10t-start').addEventListener('click', ()=>{ i=1; score=0; document.getElementById('p10t-score').textContent = 0; show(); });
|
||
document.getElementById('p10t-go').addEventListener('click', check);
|
||
document.getElementById('p10t-inp').addEventListener('keyup', e=>{ if(e.key === 'Enter') check(); });
|
||
})();
|
||
|
||
/* INIT 4 — Сокращение */
|
||
(function(){
|
||
const tasks = [
|
||
{ n:'x^2 - 9', d:'x^2 - 5x + 6', ans:'\\dfrac{x+3}{x-2}', opts:['\\dfrac{x+3}{x-2}','\\dfrac{x-3}{x+2}','\\dfrac{x+3}{x+2}','\\dfrac{1}{x-2}'] },
|
||
{ n:'x^2 - 4', d:'x^2 + x - 6', ans:'\\dfrac{x-2}{x+3}', opts:['\\dfrac{x+2}{x+3}','\\dfrac{x-2}{x+3}','\\dfrac{x-2}{x-3}','\\dfrac{x+2}{x-3}'] },
|
||
{ n:'x^2 - 25', d:'x^2 - 7x + 10', ans:'\\dfrac{x+5}{x-2}', opts:['\\dfrac{x+5}{x-2}','\\dfrac{x-5}{x-2}','\\dfrac{x-5}{x+2}','\\dfrac{x+5}{x+2}'] },
|
||
{ n:'x^2 - 5x + 6', d:'x^2 - 4', ans:'\\dfrac{x-3}{x+2}', opts:['\\dfrac{x-3}{x+2}','\\dfrac{x+3}{x+2}','\\dfrac{x-3}{x-2}','\\dfrac{x+2}{x-3}'] },
|
||
{ n:'x^2 - 1', d:'x^2 + 3x - 4', ans:'\\dfrac{x+1}{x+4}', opts:['\\dfrac{x+1}{x+4}','\\dfrac{x-1}{x+4}','\\dfrac{x-1}{x-4}','\\dfrac{x+1}{x-4}'] },
|
||
];
|
||
let cur = null, i = 1, score = 0, shuffled = [];
|
||
function show(){
|
||
cur = shuffled[i-1];
|
||
document.getElementById('p10f-i').textContent = i;
|
||
document.getElementById('p10f-task').innerHTML = '$\\dfrac{' + cur.n + '}{' + cur.d + '} = \\ ?$';
|
||
renderMath(document.getElementById('p10f-task'));
|
||
const opts = document.getElementById('p10f-opts');
|
||
opts.innerHTML = '';
|
||
[...cur.opts].sort(()=>Math.random()-0.5).forEach(o=>{
|
||
const b = document.createElement('button');
|
||
b.className = 'btn';
|
||
b.innerHTML = '$' + o + '$';
|
||
b.style.cssText = 'text-align:left;padding:10px 14px';
|
||
b.addEventListener('click', ()=>{
|
||
const fb = document.getElementById('p10f-fb'); fb.style.display = 'block';
|
||
if(o === cur.ans){ score++; b.classList.add('ok'); feedback(fb, true, '✓'); }
|
||
else { b.classList.add('fail'); feedback(fb, false, 'Правильно: $' + cur.ans + '$'); renderMath(fb); }
|
||
document.getElementById('p10f-score').textContent = score;
|
||
if(i >= tasks.length){ setTimeout(()=>{ feedback(fb, score >= 4, 'Итог: ' + score + '/' + tasks.length); if(score >= 4){ achievement('p10_fraction'); bumpProgress('p10', 14); confetti(); } }, 600); }
|
||
else { i++; setTimeout(show, 900); }
|
||
});
|
||
opts.appendChild(b);
|
||
});
|
||
renderMath(opts);
|
||
document.getElementById('p10f-fb').style.display = 'none';
|
||
}
|
||
document.getElementById('p10f-start').addEventListener('click', ()=>{ i=1; score=0; document.getElementById('p10f-score').textContent = 0; shuffled = [...tasks].sort(()=>Math.random()-0.5); show(); });
|
||
})();
|
||
|
||
/* INIT 5 — Drag */
|
||
(function(){
|
||
const items = [
|
||
{ id:1, html:'$x^2 - 5x + 6$', cat:'yes' },
|
||
{ id:2, html:'$x^2 + 1$', cat:'no' },
|
||
{ id:3, html:'$2x^2 + 5x - 3$', cat:'yes' },
|
||
{ id:4, html:'$x^2 - 2x + 5$', cat:'no' },
|
||
{ id:5, html:'$x^2 - 9$', cat:'yes' },
|
||
{ id:6, html:'$3x^2 + x + 1$', cat:'no' },
|
||
{ id:7, html:'$x^2 + 6x + 9$', cat:'yes' },
|
||
{ id:8, html:'$x^2 + 4$', cat:'no' },
|
||
];
|
||
const sorter = setupSorter({ poolId:'p10z-pool', cats:['yes','no'], items, scopeSelector:'#p10-body' });
|
||
document.getElementById('p10z-check').addEventListener('click', ()=>{
|
||
const fb = document.getElementById('p10z-fb');
|
||
fb.style.display = 'block';
|
||
const placedCount = Object.keys(sorter.placed).length;
|
||
if(placedCount < items.length){ feedback(fb, false, '⚠ Разложите все ' + items.length + ' трёхчленов.'); return; }
|
||
let ok = 0; items.forEach(it=>{ if(sorter.placed[it.id] === it.cat) ok++; });
|
||
if(ok === items.length){ feedback(fb, true, '✓ Все ' + items.length + ' верно!'); achievement('p10_sort'); bumpProgress('p10', 14); confetti(); }
|
||
else feedback(fb, false, 'Верно ' + ok + ' из ' + items.length);
|
||
});
|
||
document.getElementById('p10z-reset').addEventListener('click', ()=>{ sorter.reset(); document.getElementById('p10z-fb').style.display='none'; });
|
||
})();
|
||
}
|
||
function buildP11stub(){ buildP11(); }
|
||
function buildP11(){
|
||
const box = document.getElementById('p11-body');
|
||
let html = '';
|
||
|
||
html += makeCard('repeat','Повторение',null,`
|
||
<ul style="margin-left:18px;line-height:1.7">
|
||
<li>Дискриминант, формулы корней, теорема Виета — из § 8, § 9.</li>
|
||
<li><b>Движение:</b> $s = v \\cdot t$; средняя скорость на двух участках через общее $s$ и общее $t$.</li>
|
||
<li><b>Работа:</b> $A = p \\cdot t$, где $p$ — производительность.</li>
|
||
<li><b>Двузначное число</b> с цифрами $a$ (десятки) и $b$ (единицы): $\\overline{ab} = 10a + b$.</li>
|
||
</ul>`);
|
||
|
||
html += makeCard('theory','4 шага решения','11.1',`
|
||
<ol style="margin-left:18px;line-height:1.9">
|
||
<li><b>Анализ.</b> Прочесть условие, выделить величины, связи, искомое.</li>
|
||
<li><b>Модель.</b> Обозначить неизвестное буквой; выразить остальные величины через неё; составить уравнение.</li>
|
||
<li><b>Решение.</b> Решить уравнение — обычно квадратное.</li>
|
||
<li><b>Анализ ответа.</b> Проверить смысл (положительные ли значения, целые ли — если требуется), записать ответ.</li>
|
||
</ol>`);
|
||
|
||
html += makeCard('example','Образец',null,`
|
||
<p><b>Задача.</b> Произведение двух последовательных натуральных чисел равно 132. Найдите эти числа.</p>
|
||
<p><b>Решение.</b> Пусть меньшее $= x$. Тогда $x(x+1) = 132 \\Rightarrow x^2 + x - 132 = 0$. $D = 1 + 528 = 529$, $\\sqrt{D}=23$. $x = (-1+23)/2 = 11$ или $x = -12$. Натуральное только $11$.</p>
|
||
<p><b>Ответ:</b> $11$ и $12$.</p>`);
|
||
|
||
/* INT 1 — Шаблон «4 шага» */
|
||
html += widget('Шаблон «4 шага»','INTERACT 1','Решим задачу пошагово. На каждом шаге выберите правильный вариант.',`
|
||
<p style="margin-bottom:10px"><b>Задача.</b> Площадь прямоугольника равна 60 см², а одна сторона на 7 см больше другой. Найдите стороны.</p>
|
||
<div id="p11s-step" data-st="1" style="padding:14px;background:var(--sec-acc-soft);border-radius:10px;font-size:1.05rem"></div>
|
||
<div id="p11s-opts" style="display:flex;flex-direction:column;gap:6px;margin-top:10px"></div>
|
||
<div class="feedback" id="p11s-fb" style="display:none;margin-top:10px"></div>`);
|
||
|
||
/* INT 2 — Тренажёр задач */
|
||
html += widget('Тренажёр текстовых задач','INTERACT 2','Решите задачу и введите ответ. После 5 задач — итог.',`
|
||
<div class="score-display"><span>Задача <b id="p11t-i">1</b> / 5</span><span>Очки: <b id="p11t-score">0</b></span></div>
|
||
<div id="p11t-task" style="padding:14px;background:var(--sec-acc-soft);border-radius:10px;font-size:.98rem;line-height:1.6;margin-bottom:10px"></div>
|
||
<div style="display:flex;gap:8px;justify-content:center;flex-wrap:wrap">
|
||
<input type="text" id="p11t-inp" placeholder="ответ (число или два через ;)" style="width:240px;padding:8px;border:1.5px solid var(--border);border-radius:8px">
|
||
<button class="btn primary" id="p11t-go">Ответ</button>
|
||
<button class="btn" id="p11t-hint">Подсказка</button>
|
||
</div>
|
||
<div class="feedback" id="p11t-fb" style="display:none;margin-top:10px"></div>
|
||
<button class="btn primary" id="p11t-start" style="margin-top:10px">Начать</button>`);
|
||
|
||
/* INT 3 — Конструктор задачи на движение */
|
||
html += widget('Движение по реке','INTERACT 3','Лодка идёт по реке и обратно. Введите $v_л$ и $v_р$ — узнайте, как изменится время.',`
|
||
<div style="display:flex;gap:10px;flex-wrap:wrap;align-items:center;justify-content:center;margin-bottom:10px">
|
||
<label>Скорость лодки $v_л$ (км/ч) = <input type="number" id="p11m-vl" value="10" style="width:70px;padding:6px;border:1.5px solid var(--border);border-radius:6px"></label>
|
||
<label>Скорость реки $v_р$ (км/ч) = <input type="number" id="p11m-vr" value="2" style="width:70px;padding:6px;border:1.5px solid var(--border);border-radius:6px"></label>
|
||
<label>Расстояние $S$ (км) = <input type="number" id="p11m-s" value="24" style="width:70px;padding:6px;border:1.5px solid var(--border);border-radius:6px"></label>
|
||
</div>
|
||
<div id="p11m-out" style="padding:14px;background:var(--sec-acc-soft);border-radius:10px;line-height:1.7"></div>`);
|
||
|
||
/* INT 4 — Задача про двузначное число */
|
||
html += widget('Задача про двузначное число','INTERACT 4','Сумма квадратов цифр двузначного числа равна 41, а само число равно сумме его цифр, умноженной на 6. Найдите число.',`
|
||
<p style="margin-bottom:10px">Пусть $a$ — десятки, $b$ — единицы. Тогда число $= 10a + b$, $a^2 + b^2 = 41$ и $10a + b = 6(a+b)$.</p>
|
||
<div style="display:flex;gap:8px;align-items:center;flex-wrap:wrap">
|
||
<label>Ваш ответ: <input type="number" id="p11d-inp" placeholder="двузначное число" style="width:130px;padding:6px;border:1.5px solid var(--border);border-radius:6px"></label>
|
||
<button class="btn primary" id="p11d-check">Проверить</button>
|
||
<button class="btn" id="p11d-show">Показать решение</button>
|
||
</div>
|
||
<div class="feedback" id="p11d-fb" style="display:none;margin-top:10px"></div>
|
||
<div id="p11d-sol" style="display:none;margin-top:10px;padding:12px;background:var(--card);border-radius:8px;border:1px solid var(--border)"></div>`);
|
||
|
||
/* INT 5 — Drag: типы задач */
|
||
html += widget('Классифицируем тип задачи','INTERACT 5','Прочитайте задачу и перетащите её в нужный ящик.',`
|
||
${DND_HINT_HTML}
|
||
<div id="p11c-pool"></div>
|
||
<div class="drop-row" style="display:grid;grid-template-columns:repeat(auto-fit,minmax(150px,1fr));gap:10px">
|
||
<div class="drop-box"><h5>Движение</h5><div class="drop-items" data-cat="dv"></div></div>
|
||
<div class="drop-box"><h5>Работа</h5><div class="drop-items" data-cat="wk"></div></div>
|
||
<div class="drop-box"><h5>Числа</h5><div class="drop-items" data-cat="nm"></div></div>
|
||
<div class="drop-box"><h5>Геометрия</h5><div class="drop-items" data-cat="gm"></div></div>
|
||
</div>
|
||
<div class="actions"><button class="btn primary" id="p11c-check">Проверить</button><button class="btn" id="p11c-reset">Сначала</button></div>
|
||
<div class="feedback" id="p11c-fb" style="display:none"></div>`);
|
||
|
||
html += makeCard('class','Класс — решите',null,`
|
||
<ol style="margin-left:18px;line-height:1.8">
|
||
<li>Произведение двух последовательных нечётных чисел равно 195. Найдите их.</li>
|
||
<li>Длина сада на 5 м больше ширины, а площадь — 84 м². Найдите стороны.</li>
|
||
<li>Двое рабочих вместе закончили работу за 6 ч. Первый один сделал бы её на 5 ч быстрее, чем второй. За сколько часов сделал бы её каждый?</li>
|
||
</ol>`);
|
||
|
||
html += makeCard('home','Домашка',null,`
|
||
<ol style="margin-left:18px;line-height:1.8">
|
||
<li>Сумма квадратов двух последовательных натуральных чисел равна 113. Найдите их.</li>
|
||
<li>Лодка прошла 30 км по течению и 30 км против за 4 ч. Скорость течения 1 км/ч. Найдите скорость лодки.</li>
|
||
<li>Найдите два числа, сумма которых 14, а произведение 48.</li>
|
||
</ol>`);
|
||
|
||
html += secNav('p10','p12');
|
||
box.innerHTML = html;
|
||
if(window.renderMathInElement) setTimeout(()=>renderMath(box), 0);
|
||
|
||
/* INIT 1 — Шаблон 4 шага */
|
||
(function(){
|
||
const steps = [
|
||
{ q:'Шаг 1. Что обозначим за $x$?', opts:['меньшую сторону','произведение','периметр','диагональ'], ok:0 },
|
||
{ q:'Шаг 2. Тогда большая сторона равна:', opts:['$x - 7$','$x + 7$','$60/x$','$7x$'], ok:1 },
|
||
{ q:'Шаг 3. Уравнение из условия $S = a \\cdot b = 60$:', opts:['$x(x+7) = 60$','$x^2 - 7 = 60$','$2x + 7 = 60$','$x/7 = 60$'], ok:0 },
|
||
{ q:'Шаг 4. После раскрытия скобок и переноса:', opts:['$x^2 + 7x - 60 = 0$','$x^2 - 7x + 60 = 0$','$x^2 + 7 = 60$','$x^2 = 67$'], ok:0 },
|
||
{ q:'Шаг 5. $D = 49 + 240 = 289 \\Rightarrow \\sqrt{D} = 17$. Корни:', opts:['$x = 5$ и $x = -12$','$x = 6$ и $x = -10$','$x = 4$ и $x = -15$','$x = 8$ и $x = -7$'], ok:0 },
|
||
{ q:'Шаг 6. Ответ:', opts:['5 см и 12 см','−12 см и 5 см','оба корня подходят','7 см и 15 см'], ok:0 },
|
||
];
|
||
let i = 0;
|
||
function show(){
|
||
const s = steps[i];
|
||
document.getElementById('p11s-step').innerHTML = '<b>' + (i+1) + ' / ' + steps.length + '.</b> ' + s.q;
|
||
renderMath(document.getElementById('p11s-step'));
|
||
const opts = document.getElementById('p11s-opts'); opts.innerHTML = '';
|
||
s.opts.forEach((o,k)=>{
|
||
const b = document.createElement('button');
|
||
b.className = 'btn'; b.innerHTML = o; b.style.cssText = 'text-align:left';
|
||
b.addEventListener('click', ()=>{
|
||
const fb = document.getElementById('p11s-fb'); fb.style.display = 'block';
|
||
if(k === s.ok){
|
||
b.classList.add('ok');
|
||
feedback(fb, true, '✓');
|
||
if(i >= steps.length - 1){ feedback(fb, true, '✓ Задача решена!'); achievement('p11_steps'); bumpProgress('p11', 18); confetti(); }
|
||
else setTimeout(()=>{ i++; show(); }, 700);
|
||
} else { b.classList.add('fail'); feedback(fb, false, 'Не то, подумайте ещё.'); }
|
||
});
|
||
opts.appendChild(b);
|
||
});
|
||
renderMath(opts);
|
||
document.getElementById('p11s-fb').style.display = 'none';
|
||
}
|
||
show();
|
||
})();
|
||
|
||
/* INIT 2 — Тренажёр */
|
||
(function(){
|
||
const tasks = [
|
||
{ q:'Произведение двух последовательных натуральных чисел равно 56. Найдите большее.', hint:'$x(x+1) = 56$', ans:[8] },
|
||
{ q:'Площадь прямоугольника 48 см², одна сторона на 2 см больше другой. Найдите меньшую.', hint:'$x(x+2)=48$', ans:[6] },
|
||
{ q:'Сумма числа и его квадрата равна 90. Найдите положительное число.', hint:'$x + x^2 = 90$', ans:[9] },
|
||
{ q:'Произведение двух чисел равно 35, а их сумма равна 12. Найдите большее.', hint:'Виета: $x_1+x_2=12,\\ x_1 x_2=35$', ans:[7] },
|
||
{ q:'Лодка прошла 12 км по течению (скорость = $v+1$) и обратно ($v-1$) за 5 ч. Найдите скорость в стоячей воде.', hint:'$\\dfrac{12}{v+1} + \\dfrac{12}{v-1} = 5$', ans:[5] },
|
||
];
|
||
let cur = null, i = 1, score = 0;
|
||
function show(){
|
||
cur = tasks[i-1];
|
||
document.getElementById('p11t-i').textContent = i;
|
||
document.getElementById('p11t-task').innerHTML = '<b>Задача ' + i + '.</b> ' + cur.q;
|
||
renderMath(document.getElementById('p11t-task'));
|
||
document.getElementById('p11t-inp').value = '';
|
||
document.getElementById('p11t-fb').style.display = 'none';
|
||
}
|
||
document.getElementById('p11t-start').addEventListener('click', ()=>{ i=1; score=0; document.getElementById('p11t-score').textContent = 0; show(); });
|
||
document.getElementById('p11t-go').addEventListener('click', ()=>{
|
||
const fb = document.getElementById('p11t-fb'); fb.style.display = 'block';
|
||
const u = +document.getElementById('p11t-inp').value;
|
||
const ok = cur.ans.some(a => Math.abs(u - a) < 1e-6);
|
||
if(ok){ score++; feedback(fb, true, '✓'); }
|
||
else feedback(fb, false, 'Не то. Ответ: ' + cur.ans.join(' или '));
|
||
document.getElementById('p11t-score').textContent = score;
|
||
if(i >= tasks.length){ setTimeout(()=>{ feedback(fb, score >= 3, 'Итог: ' + score + '/' + tasks.length); if(score >= 3){ achievement('p11_train'); bumpProgress('p11', 16); confetti(); } }, 600); }
|
||
else { i++; setTimeout(show, 900); }
|
||
});
|
||
document.getElementById('p11t-hint').addEventListener('click', ()=>{
|
||
const fb = document.getElementById('p11t-fb'); fb.style.display = 'block';
|
||
feedback(fb, true, 'Подсказка: ' + cur.hint); renderMath(fb);
|
||
});
|
||
})();
|
||
|
||
/* INIT 3 — Движение */
|
||
(function(){
|
||
const vlE = document.getElementById('p11m-vl'), vrE = document.getElementById('p11m-vr'), sE = document.getElementById('p11m-s');
|
||
const out = document.getElementById('p11m-out');
|
||
let done = false;
|
||
function refresh(){
|
||
const vl = +vlE.value, vr = +vrE.value, s = +sE.value;
|
||
if(vl <= vr || vr < 0 || s <= 0){ out.innerHTML = '<span style="color:var(--bad)">Условие: $v_л > v_р \\geq 0,\\ S > 0$.</span>'; renderMath(out); return; }
|
||
const t1 = s/(vl+vr), t2 = s/(vl-vr), tT = t1 + t2;
|
||
let html = '<div><b>По течению:</b> $v = ' + (vl+vr) + '$ км/ч → $t = ' + s + '/' + (vl+vr) + ' = ' + fmt(t1) + '$ ч</div>';
|
||
html += '<div><b>Против течения:</b> $v = ' + (vl-vr) + '$ км/ч → $t = ' + fmt(t2) + '$ ч</div>';
|
||
html += '<div><b>Итого время:</b> $' + fmt(tT) + '$ ч</div>';
|
||
const vAvg = 2*s/tT;
|
||
html += '<div><b>Средняя скорость:</b> $\\dfrac{2S}{t_1+t_2} = ' + fmt(vAvg) + '$ км/ч</div>';
|
||
out.innerHTML = html; renderMath(out);
|
||
if(!done){ done = true; setTimeout(()=>{ achievement('p11_move'); bumpProgress('p11', 14); }, 300); }
|
||
}
|
||
[vlE,vrE,sE].forEach(e => e.addEventListener('input', refresh));
|
||
refresh();
|
||
})();
|
||
|
||
/* INIT 4 — Задача про число */
|
||
(function(){
|
||
document.getElementById('p11d-check').addEventListener('click', ()=>{
|
||
const v = +document.getElementById('p11d-inp').value;
|
||
const fb = document.getElementById('p11d-fb'); fb.style.display = 'block';
|
||
if(v === 54){ feedback(fb, true, '✓ Верно! Число = 54.'); achievement('p11_digit'); bumpProgress('p11', 14); confetti(); }
|
||
else feedback(fb, false, 'Не то. Подсказка: $a^2 + b^2 = 41,\\ 10a+b = 6(a+b) \\Rightarrow 4a = 5b$.');
|
||
renderMath(fb);
|
||
});
|
||
document.getElementById('p11d-show').addEventListener('click', ()=>{
|
||
const s = document.getElementById('p11d-sol');
|
||
s.style.display = 'block';
|
||
s.innerHTML = '<p><b>Шаг 1.</b> Из $10a+b = 6(a+b)$ получаем $4a = 5b$, то есть $b = \\dfrac{4a}{5}$. Чтобы $b$ было цифрой (0..9), $a$ должно делиться на 5.</p><p><b>Шаг 2.</b> $a = 5$ → $b = 4$. Проверяем: $a^2+b^2 = 25+16 = 41$. ✓</p><p><b>Ответ:</b> число $= 54$.</p>';
|
||
renderMath(s);
|
||
});
|
||
})();
|
||
|
||
/* INIT 5 — Drag типы */
|
||
(function(){
|
||
const items = [
|
||
{ id:1, html:'Лодка прошла 24 км по течению и обратно.', cat:'dv' },
|
||
{ id:2, html:'Двое рабочих вместе закончили работу за 6 ч.', cat:'wk' },
|
||
{ id:3, html:'Произведение последовательных чисел равно 90.', cat:'nm' },
|
||
{ id:4, html:'Сторона квадрата увеличена на 3 см, площадь увеличилась на 33 см².', cat:'gm' },
|
||
{ id:5, html:'Автомобиль и автобус выехали навстречу из A и B.', cat:'dv' },
|
||
{ id:6, html:'Сумма квадратов цифр двузначного числа = 25.', cat:'nm' },
|
||
{ id:7, html:'Бассейн наполняется одной трубой на 2 ч быстрее, чем другой.', cat:'wk' },
|
||
{ id:8, html:'Площадь прямоугольника 48 см², а периметр 28 см.', cat:'gm' },
|
||
];
|
||
const sorter = setupSorter({ poolId:'p11c-pool', cats:['dv','wk','nm','gm'], items, scopeSelector:'#p11-body', columnLayout:true });
|
||
document.getElementById('p11c-check').addEventListener('click', ()=>{
|
||
const fb = document.getElementById('p11c-fb'); fb.style.display = 'block';
|
||
if(Object.keys(sorter.placed).length < items.length){ feedback(fb, false, '⚠ Разложите все ' + items.length + ' задач.'); return; }
|
||
let ok = 0; items.forEach(it=>{ if(sorter.placed[it.id] === it.cat) ok++; });
|
||
if(ok === items.length){ feedback(fb, true, '✓ Все верно!'); achievement('p11_class'); bumpProgress('p11', 14); confetti(); }
|
||
else feedback(fb, false, 'Верно ' + ok + ' из ' + items.length);
|
||
});
|
||
document.getElementById('p11c-reset').addEventListener('click', ()=>{ sorter.reset(); document.getElementById('p11c-fb').style.display='none'; });
|
||
})();
|
||
}
|
||
function buildP12stub(){ buildP12(); }
|
||
function buildP12(){
|
||
const box = document.getElementById('p12-body');
|
||
let html = '';
|
||
|
||
html += makeCard('repeat','Повторение',null,`
|
||
<ul style="margin-left:18px;line-height:1.7">
|
||
<li>Квадратное уравнение, дискриминант, Виета — §§ 7–9.</li>
|
||
<li>Разложение трёхчлена $ax^2+bx+c$ — § 10.</li>
|
||
<li><b>ОДЗ</b> дробного выражения: знаменатель $\\neq 0$.</li>
|
||
</ul>`);
|
||
|
||
html += makeCard('theory','Биквадратное уравнение','12.1',`
|
||
<p><b>Биквадратным</b> называют уравнение вида $ax^4 + bx^2 + c = 0$, $a \\neq 0$.</p>
|
||
<p style="margin-top:6px"><b>Метод.</b> Замена $t = x^2$, $t \\geq 0$. Получаем квадратное $at^2 + bt + c = 0$. Решаем его, потом для каждого $t \\geq 0$ находим $x = \\pm\\sqrt{t}$.</p>
|
||
<div style="background:var(--sec-acc-soft);border-radius:10px;padding:12px;margin:10px 0;text-align:center;font-size:1.05rem">$$t = x^2 \\geq 0,\\quad x = \\pm\\sqrt{t}$$</div>`);
|
||
|
||
html += makeCard('rule','Дробные → квадратные','12.2',`
|
||
<ol style="margin-left:18px;line-height:1.8">
|
||
<li>Найти ОДЗ (знаменатели $\\neq 0$).</li>
|
||
<li>Привести к общему знаменателю и умножить уравнение на него.</li>
|
||
<li>Решить полученное многочленное уравнение.</li>
|
||
<li>Отбросить посторонние корни (вне ОДЗ).</li>
|
||
</ol>`);
|
||
|
||
html += makeCard('example','Примеры',null,`
|
||
<p><b>1)</b> $x^4 - 5x^2 + 4 = 0$. Замена $t = x^2$: $t^2 - 5t + 4 = 0 \\Rightarrow t = 1$ или $t = 4$. Тогда $x = \\pm 1$ или $x = \\pm 2$. Ответ: $\\{-2;-1;1;2\\}$.</p>
|
||
<p style="margin-top:6px"><b>2)</b> $x^4 + 3x^2 - 4 = 0$. $t^2 + 3t - 4 = 0 \\Rightarrow t = 1$ или $t = -4$. Только $t = 1$ годится: $x = \\pm 1$.</p>
|
||
<p style="margin-top:6px"><b>3)</b> $\\dfrac{x}{x-2} - \\dfrac{2}{x+2} = 1$. ОДЗ: $x \\neq \\pm 2$. Умножим на $(x-2)(x+2)$: $x(x+2) - 2(x-2) = (x-2)(x+2) \\Rightarrow x^2 + 2x - 2x + 4 = x^2 - 4 \\Rightarrow 4 = -4$ — противоречие, корней нет.</p>`);
|
||
|
||
/* INT 1 — Биквадратное пошагово */
|
||
html += widget('Решатель биквадратного','INTERACT 1','Введите $a$, $b$, $c$ и нажимайте «Дальше» — шаги замены и решения.',`
|
||
<div style="display:flex;gap:10px;flex-wrap:wrap;align-items:center;margin-bottom:10px">
|
||
<label>$a$ = <input type="number" id="p12b-a" value="1" style="width:70px;padding:6px;border:1.5px solid var(--border);border-radius:6px"></label>
|
||
<label>$b$ = <input type="number" id="p12b-b" value="-5" style="width:70px;padding:6px;border:1.5px solid var(--border);border-radius:6px"></label>
|
||
<label>$c$ = <input type="number" id="p12b-c" value="4" style="width:70px;padding:6px;border:1.5px solid var(--border);border-radius:6px"></label>
|
||
<button class="btn primary" id="p12b-go">Старт</button>
|
||
<button class="btn" id="p12b-next" style="display:none">Дальше</button>
|
||
<button class="btn" id="p12b-reset" style="display:none">Сначала</button>
|
||
</div>
|
||
<div id="p12b-out" style="padding:14px;background:var(--sec-acc-soft);border-radius:10px;line-height:1.7;min-height:60px"></div>`);
|
||
|
||
/* INT 2 — Тренажёр биквадратных */
|
||
html += widget('Тренажёр биквадратных','INTERACT 2','Решите устно. Вводите все корни через точку с запятой (или «нет»).',`
|
||
<div class="score-display"><span>Задача <b id="p12t-i">1</b> / 6</span><span>Очки: <b id="p12t-score">0</b></span></div>
|
||
<div id="p12t-task" style="font-size:1.3rem;text-align:center;padding:16px;background:var(--sec-acc-soft);border-radius:10px;margin-bottom:10px"></div>
|
||
<div style="display:flex;gap:8px;justify-content:center;flex-wrap:wrap">
|
||
<input type="text" id="p12t-inp" placeholder="например: -2; -1; 1; 2" style="width:240px;padding:8px;border:1.5px solid var(--border);border-radius:8px">
|
||
<button class="btn primary" id="p12t-go">Ответ</button>
|
||
</div>
|
||
<div class="feedback" id="p12t-fb" style="display:none;margin-top:10px"></div>
|
||
<button class="btn primary" id="p12t-start" style="margin-top:10px">Начать</button>`);
|
||
|
||
/* INT 3 — Дробное уравнение */
|
||
html += widget('Дробное → квадратное','INTERACT 3','По шагам решаем уравнение с дробями. Внимание к ОДЗ!',`
|
||
<p style="margin-bottom:10px">Решим: $\\dfrac{x+3}{x-1} - \\dfrac{2}{x+1} = 2$</p>
|
||
<div id="p12f-step" style="padding:14px;background:var(--sec-acc-soft);border-radius:10px;font-size:1.02rem;line-height:1.7"></div>
|
||
<div id="p12f-opts" style="display:flex;flex-direction:column;gap:6px;margin-top:10px"></div>
|
||
<div class="feedback" id="p12f-fb" style="display:none;margin-top:10px"></div>`);
|
||
|
||
/* INT 4 — Какая замена */
|
||
html += widget('Выберите подходящую замену','INTERACT 4','Для каждого уравнения подберите замену переменной.',`
|
||
<div id="p12s-pool"></div>
|
||
<div class="feedback" id="p12s-fb" style="display:none;margin-top:10px"></div>`);
|
||
|
||
/* INT 5 — ОДЗ проверка */
|
||
html += widget('Найди посторонний корень','INTERACT 5','Дано дробное уравнение с готовыми кандидатами на корни. Найдите корень, который НЕ удовлетворяет ОДЗ.',`
|
||
<div id="p12o-task" style="padding:14px;background:var(--sec-acc-soft);border-radius:10px;font-size:1.02rem;line-height:1.7;margin-bottom:10px"></div>
|
||
<div id="p12o-opts" style="display:flex;gap:8px;justify-content:center;flex-wrap:wrap"></div>
|
||
<div class="feedback" id="p12o-fb" style="display:none;margin-top:10px"></div>
|
||
<button class="btn primary" id="p12o-start" style="margin-top:10px">Начать</button>`);
|
||
|
||
html += makeCard('class','Класс — решите',null,`
|
||
<ol style="margin-left:18px;line-height:1.8">
|
||
<li>$x^4 - 13x^2 + 36 = 0$</li>
|
||
<li>$x^4 - 5x^2 - 36 = 0$</li>
|
||
<li>$\\dfrac{2}{x-1} + \\dfrac{3}{x+1} = 1$</li>
|
||
</ol>`);
|
||
|
||
html += makeCard('home','Домашка',null,`
|
||
<ol style="margin-left:18px;line-height:1.8">
|
||
<li>$x^4 - 10x^2 + 9 = 0$</li>
|
||
<li>$x^4 + 7x^2 - 8 = 0$</li>
|
||
<li>$\\dfrac{x}{x+1} - \\dfrac{1}{x-1} = \\dfrac{2}{x^2-1}$</li>
|
||
<li>$(x^2 - 2)^2 - 3(x^2 - 2) - 4 = 0$ (замена!)</li>
|
||
</ol>`);
|
||
|
||
html += secNav('p11','final2');
|
||
box.innerHTML = html;
|
||
if(window.renderMathInElement) setTimeout(()=>renderMath(box), 0);
|
||
|
||
/* INIT 1 — Биквадратное пошагово */
|
||
(function(){
|
||
const out = document.getElementById('p12b-out');
|
||
const goBtn = document.getElementById('p12b-go');
|
||
const nextBtn = document.getElementById('p12b-next');
|
||
const resetBtn = document.getElementById('p12b-reset');
|
||
let steps = [], idx = 0, awarded = false;
|
||
function build(a, b, c){
|
||
const arr = [];
|
||
arr.push('<b>Дано:</b> $' + a + 'x^4 ' + (b >= 0 ? '+ ' + b : '- ' + Math.abs(b)) + 'x^2 ' + (c >= 0 ? '+ ' + c : '- ' + Math.abs(c)) + ' = 0$');
|
||
arr.push('<b>Замена.</b> Пусть $t = x^2,\\ t \\geq 0$. Получаем $' + a + 't^2 ' + (b >= 0 ? '+ ' + b : '- ' + Math.abs(b)) + 't ' + (c >= 0 ? '+ ' + c : '- ' + Math.abs(c)) + ' = 0$');
|
||
const D = b*b - 4*a*c;
|
||
arr.push('<b>Дискриминант.</b> $D = ' + (b*b) + ' - ' + (4*a*c) + ' = ' + D + '$');
|
||
if(D < 0){
|
||
arr.push('<b>Анализ.</b> $D < 0 \\Rightarrow$ нет $t$, значит нет $x$.');
|
||
arr.push('<b>Ответ:</b> корней нет.');
|
||
} else {
|
||
const t1 = (-b - Math.sqrt(D))/(2*a), t2 = (-b + Math.sqrt(D))/(2*a);
|
||
arr.push('<b>Корни вспомогательного.</b> $t_1 = ' + fmt(t1) + ',\\ t_2 = ' + fmt(t2) + '$');
|
||
const roots = [];
|
||
[t1, t2].forEach((t, k)=>{
|
||
if(t > 0){ const r = Math.sqrt(t); roots.push(r, -r); arr.push('<b>Возврат.</b> $t_' + (k+1) + ' = ' + fmt(t) + ' > 0 \\Rightarrow x = \\pm\\sqrt{' + fmt(t) + '} = \\pm' + fmt(r) + '$'); }
|
||
else if(t === 0){ roots.push(0); arr.push('<b>Возврат.</b> $t_' + (k+1) + ' = 0 \\Rightarrow x = 0$'); }
|
||
else arr.push('<b>Возврат.</b> $t_' + (k+1) + ' = ' + fmt(t) + ' < 0$ — не подходит, ведь $t \\geq 0$.');
|
||
});
|
||
const u = [...new Set(roots)].sort((a,b)=>a-b);
|
||
arr.push('<b>Ответ:</b> ' + (u.length ? '$\\{' + u.map(fmt).join(';\\ ') + '\\}$' : 'корней нет'));
|
||
}
|
||
return arr;
|
||
}
|
||
function render(){
|
||
out.innerHTML = steps.slice(0, idx + 1)
|
||
.map(s => `<div style="margin:6px 0;padding:9px 12px;background:var(--card);border-left:3px solid var(--sec-acc);border-radius:7px;animation:fadeIn .3s ease">${s}</div>`)
|
||
.join('');
|
||
renderMath(out);
|
||
if(idx >= steps.length - 1){
|
||
nextBtn.disabled = true; nextBtn.textContent = 'Готово';
|
||
if(!awarded){ awarded = true; achievement('p12_bi'); bumpProgress('p12', 14); confetti(); }
|
||
} else {
|
||
nextBtn.disabled = false; nextBtn.textContent = 'Дальше (' + (idx + 1) + '/' + steps.length + ')';
|
||
}
|
||
}
|
||
goBtn.addEventListener('click', ()=>{
|
||
const a = +document.getElementById('p12b-a').value, b = +document.getElementById('p12b-b').value, c = +document.getElementById('p12b-c').value;
|
||
if(!a){ out.innerHTML = '$a \\neq 0$'; renderMath(out); return; }
|
||
steps = build(a, b, c); idx = 0; awarded = false;
|
||
goBtn.style.display = 'none'; nextBtn.style.display = ''; resetBtn.style.display = '';
|
||
render();
|
||
});
|
||
nextBtn.addEventListener('click', ()=>{ if(idx < steps.length - 1){ idx++; render(); } });
|
||
resetBtn.addEventListener('click', ()=>{
|
||
idx = 0; steps = []; awarded = false; out.innerHTML = '';
|
||
goBtn.style.display = ''; nextBtn.style.display = 'none'; resetBtn.style.display = 'none';
|
||
});
|
||
})();
|
||
|
||
/* INIT 2 — Тренажёр биквадр */
|
||
(function(){
|
||
const tasks = [
|
||
{ eq:'x^4 - 5x^2 + 4 = 0', ans:[-2,-1,1,2] },
|
||
{ eq:'x^4 - 10x^2 + 9 = 0', ans:[-3,-1,1,3] },
|
||
{ eq:'x^4 - 13x^2 + 36 = 0', ans:[-3,-2,2,3] },
|
||
{ eq:'x^4 + 3x^2 - 4 = 0', ans:[-1,1] },
|
||
{ eq:'x^4 + 2x^2 + 5 = 0', ans:null },
|
||
{ eq:'x^4 - 16 = 0', ans:[-2,2] },
|
||
];
|
||
let cur = null, i = 1, score = 0, shuffled = [];
|
||
function show(){
|
||
cur = shuffled[i-1];
|
||
document.getElementById('p12t-i').textContent = i;
|
||
document.getElementById('p12t-task').innerHTML = '$' + cur.eq + '$';
|
||
renderMath(document.getElementById('p12t-task'));
|
||
document.getElementById('p12t-inp').value = '';
|
||
document.getElementById('p12t-fb').style.display = 'none';
|
||
}
|
||
function parse(s){
|
||
s = s.trim().toLowerCase();
|
||
if(/нет|none/.test(s)) return null;
|
||
return s.replace(/,/g,';').split(/[;\s]+/).filter(Boolean).map(Number).sort((a,b)=>a-b);
|
||
}
|
||
function check(){
|
||
const fb = document.getElementById('p12t-fb'); fb.style.display = 'block';
|
||
const u = parse(document.getElementById('p12t-inp').value);
|
||
let ok = false;
|
||
if(cur.ans === null) ok = u === null;
|
||
else if(u !== null){ const a = [...cur.ans].sort((x,y)=>x-y); ok = a.length === u.length && a.every((v,k)=>v === u[k]); }
|
||
if(ok){ score++; feedback(fb, true, '✓'); }
|
||
else feedback(fb, false, 'Правильно: ' + (cur.ans === null ? 'нет' : cur.ans.join('; ')));
|
||
document.getElementById('p12t-score').textContent = score;
|
||
if(i >= shuffled.length){ setTimeout(()=>{ feedback(fb, score >= 4, 'Итог: ' + score + '/' + shuffled.length); if(score >= 4){ achievement('p12_train'); bumpProgress('p12', 16); confetti(); } }, 600); }
|
||
else { i++; setTimeout(show, 900); }
|
||
}
|
||
document.getElementById('p12t-start').addEventListener('click', ()=>{ i=1; score=0; document.getElementById('p12t-score').textContent = 0; shuffled = [...tasks].sort(()=>Math.random()-0.5); show(); });
|
||
document.getElementById('p12t-go').addEventListener('click', check);
|
||
document.getElementById('p12t-inp').addEventListener('keyup', e=>{ if(e.key === 'Enter') check(); });
|
||
})();
|
||
|
||
/* INIT 3 — Дробное */
|
||
(function(){
|
||
const steps = [
|
||
{ q:'Шаг 1. ОДЗ:', opts:['$x \\neq 1$ и $x \\neq -1$','$x \\neq 0$','$x > 0$','любое $x$'], ok:0 },
|
||
{ q:'Шаг 2. Общий знаменатель:', opts:['$(x-1)(x+1)$','$x-1$','$x+1$','$x^2$'], ok:0 },
|
||
{ q:'Шаг 3. После умножения на ОЗ:', opts:['$(x+3)(x+1) - 2(x-1) = 2(x-1)(x+1)$','$x+3 - 2 = 2$','$(x+3) - 2 = 2(x-1)$','$x+3-2(x+1) = 2$'], ok:0 },
|
||
{ q:'Шаг 4. Раскрытие и приведение:', opts:['$x^2 + 4x + 5 = 2x^2 - 2 \\Rightarrow x^2 - 4x - 7 = 0$','$x^2 = 7$','$x^2 + 4x = 0$','$x = 5$'], ok:0 },
|
||
{ q:'Шаг 5. Корни:', opts:['$x = 2 \\pm \\sqrt{11}$','$x = 1, 7$','$x = -1, 7$','корней нет'], ok:0 },
|
||
{ q:'Шаг 6. Проверка ОДЗ ($x \\neq \\pm 1$):', opts:['оба корня подходят','$x = 2 - \\sqrt{11}$ лишний','$x = 2 + \\sqrt{11}$ лишний','оба лишние'], ok:0 },
|
||
];
|
||
let i = 0;
|
||
function show(){
|
||
const s = steps[i];
|
||
document.getElementById('p12f-step').innerHTML = '<b>' + (i+1) + ' / ' + steps.length + '.</b> ' + s.q;
|
||
renderMath(document.getElementById('p12f-step'));
|
||
const opts = document.getElementById('p12f-opts'); opts.innerHTML = '';
|
||
s.opts.forEach((o,k)=>{
|
||
const b = document.createElement('button');
|
||
b.className = 'btn'; b.innerHTML = o; b.style.cssText = 'text-align:left';
|
||
b.addEventListener('click', ()=>{
|
||
const fb = document.getElementById('p12f-fb'); fb.style.display = 'block';
|
||
if(k === s.ok){
|
||
b.classList.add('ok');
|
||
feedback(fb, true, '✓');
|
||
if(i >= steps.length - 1){ feedback(fb, true, '✓ Уравнение решено!'); achievement('p12_frac'); bumpProgress('p12', 16); confetti(); }
|
||
else setTimeout(()=>{ i++; show(); }, 700);
|
||
} else { b.classList.add('fail'); feedback(fb, false, 'Не то, подумайте.'); }
|
||
});
|
||
opts.appendChild(b);
|
||
});
|
||
renderMath(opts);
|
||
document.getElementById('p12f-fb').style.display = 'none';
|
||
}
|
||
show();
|
||
})();
|
||
|
||
/* INIT 4 — Замены */
|
||
(function(){
|
||
const items = [
|
||
{ eq:'x^4 + 3x^2 - 4 = 0', ans:'t=x^2' },
|
||
{ eq:'(x^2-3)^2 - 5(x^2-3) + 6 = 0', ans:'t=x^2-3' },
|
||
{ eq:'x^2 + \\dfrac{1}{x^2} - 4 = 0', ans:'t=x^2' },
|
||
{ eq:'(x+\\dfrac{1}{x})^2 - 3(x+\\dfrac{1}{x}) + 2 = 0', ans:'t=x+1/x' },
|
||
];
|
||
const opts = ['t=x^2', 't=x^2-3', 't=x+1/x', 't=x-1'];
|
||
const pool = document.getElementById('p12s-pool');
|
||
let answered = 0, score = 0;
|
||
items.forEach((it, idx)=>{
|
||
const row = document.createElement('div');
|
||
row.style.cssText = 'display:flex;gap:8px;align-items:center;padding:8px;border-bottom:1px solid var(--border);flex-wrap:wrap';
|
||
row.innerHTML = '<div style="flex:1;min-width:160px">$' + it.eq + '$</div>';
|
||
const sel = document.createElement('select');
|
||
sel.style.cssText = 'padding:6px;border:1.5px solid var(--border);border-radius:6px';
|
||
sel.innerHTML = '<option value="">— выбрать —</option>' + opts.map(o => '<option value="' + o + '">$' + o + '$</option>').join('').replace(/\$/g,'');
|
||
const mark = document.createElement('span'); mark.style.cssText = 'font-weight:700;font-size:1.2rem;width:24px;text-align:center';
|
||
const check = document.createElement('button'); check.className = 'btn small'; check.textContent = 'Проверить';
|
||
check.addEventListener('click', ()=>{
|
||
if(!sel.value){ mark.textContent = '?'; mark.style.color = 'var(--muted)'; return; }
|
||
if(sel.value === it.ans){ mark.textContent = '✓'; mark.style.color = 'var(--ok)'; if(!row.dataset.scored){ row.dataset.scored='1'; score++; answered++; } }
|
||
else { mark.textContent = '✗'; mark.style.color = 'var(--bad)'; if(!row.dataset.answered){ row.dataset.answered='1'; answered++; } }
|
||
if(answered >= items.length){
|
||
const fb = document.getElementById('p12s-fb'); fb.style.display = 'block';
|
||
feedback(fb, score >= 3, 'Итог: ' + score + '/' + items.length);
|
||
if(score >= 3){ achievement('p12_subst'); bumpProgress('p12', 14); confetti(); }
|
||
}
|
||
});
|
||
row.appendChild(sel); row.appendChild(check); row.appendChild(mark);
|
||
pool.appendChild(row);
|
||
});
|
||
if(window.renderMathInElement) renderMath(pool);
|
||
})();
|
||
|
||
/* INIT 5 — Посторонний корень */
|
||
(function(){
|
||
const tasks = [
|
||
{ eq:'\\dfrac{1}{x-2} = \\dfrac{x-2}{4}', roots:[0, 4, -2, 2], wrong:[2], odz:'x \\neq 2' },
|
||
{ eq:'\\dfrac{x}{x-3} = \\dfrac{9}{x-3}', roots:[3, 9, -9, 0], wrong:[3], odz:'x \\neq 3' },
|
||
{ eq:'\\dfrac{1}{x+1} + \\dfrac{1}{x-1} = 0', roots:[1, -1, 0, 2], wrong:[1,-1], odz:'x \\neq \\pm 1' },
|
||
];
|
||
let cur = null, i = 1;
|
||
function show(){
|
||
cur = tasks[i-1];
|
||
document.getElementById('p12o-task').innerHTML = '<b>Уравнение ' + i + ' / ' + tasks.length + ':</b> $' + cur.eq + '$<br><span style="color:var(--muted);font-size:.9rem">ОДЗ: $' + cur.odz + '$</span><br>Кандидаты на корни:';
|
||
renderMath(document.getElementById('p12o-task'));
|
||
const opts = document.getElementById('p12o-opts'); opts.innerHTML = '';
|
||
cur.roots.forEach(r=>{
|
||
const b = document.createElement('button'); b.className = 'btn'; b.textContent = 'x = ' + r;
|
||
b.addEventListener('click', ()=>{
|
||
const fb = document.getElementById('p12o-fb'); fb.style.display = 'block';
|
||
if(cur.wrong.includes(r)){ b.classList.add('ok'); feedback(fb, true, '✓ Это посторонний корень (не в ОДЗ).'); }
|
||
else { b.classList.add('fail'); feedback(fb, false, 'Не то — этот корень допустим в ОДЗ. Ищите тот, что нарушает.'); return; }
|
||
if(i >= tasks.length){ setTimeout(()=>{ feedback(fb, true, 'Все 3 пройдено!'); achievement('p12_odz'); bumpProgress('p12', 14); confetti(); }, 500); }
|
||
else { i++; setTimeout(show, 800); }
|
||
});
|
||
opts.appendChild(b);
|
||
});
|
||
document.getElementById('p12o-fb').style.display = 'none';
|
||
}
|
||
document.getElementById('p12o-start').addEventListener('click', ()=>{ i=1; show(); });
|
||
})();
|
||
}
|
||
function buildFinal2stub(){ buildFinal2(); }
|
||
function buildFinal2(){
|
||
const box = document.getElementById('final2-body');
|
||
let html = '';
|
||
|
||
html += `<div class="card"><div class="card-header"><div class="card-icon theory">${ICONS.theory}</div><div class="card-title">Поздравляем!</div></div><div class="card-body">
|
||
<p>Вы прошли все 6 параграфов главы «Квадратные уравнения». В финале вас ждут <b>7 боссов</b> — по одному на каждый параграф и один общий. Победите всех — получите титул «Магистр квадратных уравнений».</p>
|
||
</div></div>`;
|
||
|
||
/* BOSS ARENA */
|
||
html += widget('Боссы главы 2','BOSS ARENA','Каждый босс — 5 заданий. Победите всех 7, чтобы открыть финальный титул.',`
|
||
<div id="boss-grid" style="display:grid;grid-template-columns:repeat(auto-fit,minmax(150px,1fr));gap:10px"></div>
|
||
<div id="boss-arena" style="margin-top:14px;display:none">
|
||
<div id="boss-head" style="display:flex;align-items:center;gap:14px;padding:14px;background:linear-gradient(135deg,#1f2937,#0f172a);border-radius:10px;color:#fff;margin-bottom:10px">
|
||
<div id="boss-emoji" style="font-size:2.4rem;line-height:1">★</div>
|
||
<div style="flex:1">
|
||
<div id="boss-name" style="font-family:'Unbounded',sans-serif;font-weight:800;font-size:1.05rem"></div>
|
||
<div id="boss-subname" style="font-size:.85rem;opacity:.75"></div>
|
||
<div class="hp-bar" style="margin-top:6px;background:rgba(255,255,255,.15);height:8px;border-radius:4px;overflow:hidden"><div id="boss-hp" style="height:100%;background:linear-gradient(90deg,#10b981,#fbbf24,#ef4444);width:100%;transition:width .5s"></div></div>
|
||
</div>
|
||
<button class="btn" id="boss-quit" style="background:rgba(255,255,255,.15);color:#fff;border-color:transparent">Выйти</button>
|
||
</div>
|
||
<div id="boss-task" style="padding:16px;background:var(--card);border:1.5px solid var(--sec-acc);border-radius:10px;min-height:120px"></div>
|
||
<div id="boss-opts" style="display:flex;flex-direction:column;gap:6px;margin-top:10px"></div>
|
||
<div id="boss-fb" class="feedback" style="display:none;margin-top:10px"></div>
|
||
</div>`);
|
||
|
||
/* FUN MATH */
|
||
html += widget('Увлекательная математика','BONUS','3 факта и парадокса про квадратные уравнения.',`
|
||
<details class="spoiler" style="margin-bottom:8px"><summary>Кто открыл формулу корней?</summary>
|
||
<div class="spoiler-body">Первые методы — в Древнем Вавилоне (2000 до н. э.). Полную формулу для произвольных коэффициентов записал Мухаммед аль-Хорезми в IX веке в своём трактате «Краткая книга об исчислении ал-джабра и ал-мукабалы». От слова «ал-джабр» произошло слово «алгебра».</div>
|
||
</details>
|
||
<details class="spoiler" style="margin-bottom:8px"><summary>Почему $a \\neq 0$?</summary>
|
||
<div class="spoiler-body">Если $a = 0$, то уравнение $bx + c = 0$ — линейное (степени 1), а не квадратное. Степень уравнения — это степень его старшего члена, поэтому требование $a \\neq 0$ нужно, чтобы старший член был именно $x^2$.</div>
|
||
</details>
|
||
<details class="spoiler"><summary>Парадокс «потерянного корня»</summary>
|
||
<div class="spoiler-body">Если уравнение $\\dfrac{x^2-4}{x-2} = 0$ умножить на $x-2$, получим $x^2 - 4 = 0 \\Rightarrow x = \\pm 2$. Но $x = 2$ обнуляет знаменатель! Этот корень — посторонний. ОДЗ всегда выручает.</div>
|
||
</details>`);
|
||
|
||
/* PRACTICE GENERATOR */
|
||
html += widget('Финальная практика','PRACTICE','Случайные задачи из всей главы. Решайте, сколько хотите.',`
|
||
<div class="score-display"><span>Решено: <b id="prac-i">0</b></span><span>Правильно: <b id="prac-score">0</b></span><span>Серия: <b id="prac-streak">0</b></span></div>
|
||
<div id="prac-task" style="padding:14px;background:var(--sec-acc-soft);border-radius:10px;font-size:1.05rem;line-height:1.6;margin-bottom:10px"></div>
|
||
<div style="display:flex;gap:8px;flex-wrap:wrap;align-items:center;justify-content:center">
|
||
<input type="text" id="prac-inp" placeholder="ответ" style="width:200px;padding:8px;border:1.5px solid var(--border);border-radius:8px">
|
||
<button class="btn primary" id="prac-go">Ответ</button>
|
||
<button class="btn" id="prac-next">Следующая</button>
|
||
</div>
|
||
<div class="feedback" id="prac-fb" style="display:none;margin-top:10px"></div>`);
|
||
|
||
/* CERTIFICATE */
|
||
html += `<div class="card"><div class="card-header"><div class="card-icon home">${ICONS.home}</div><div class="card-title">Сертификат прохождения</div></div><div class="card-body">
|
||
<p>Как вы оцените своё знание квадратных уравнений?</p>
|
||
<div id="cert-state" style="margin-top:10px;font-size:.9rem;color:var(--muted)"></div>
|
||
</div></div>`;
|
||
|
||
html += secNav('p12', null);
|
||
box.innerHTML = html;
|
||
if(window.renderMathInElement) setTimeout(()=>renderMath(box), 0);
|
||
|
||
/* ===== BOSSES ===== */
|
||
const BOSSES = [
|
||
{ id:'b1', name:'Хранитель неполных', sub:'§ 7 · ax²+bx=0 и ax²+c=0', icon:'⚔', hp:5, tasks:[
|
||
{ t:'select', q:'Сколько корней у уравнения $5x^2 - 45 = 0$?', opts:['Два','Один','Корней нет'], ok:0 },
|
||
{ t:'input', q:'Решите $3x^2 - 12x = 0$. Введите больший корень.', ans:[4] },
|
||
{ t:'yesno', q:'Уравнение $2x^2 + 8 = 0$ имеет корни.', ok:false },
|
||
{ t:'input', q:'Корни $x^2 - 16 = 0$ — это $\\pm a$. Найдите $a$.', ans:[4] },
|
||
{ t:'select', q:'$ax^2 + c = 0$ имеет два корня, если…', opts:['$-c/a > 0$','$c > 0$','$ac > 0$','любое'], ok:0 },
|
||
]},
|
||
{ id:'b2', name:'Дискриминатор', sub:'§ 8 · D = b² − 4ac', icon:'D', hp:5, tasks:[
|
||
{ t:'input', q:'Найдите $D$ для $x^2 - 7x + 12 = 0$.', ans:[1] },
|
||
{ t:'select', q:'$D < 0$ означает:', opts:['два различных корня','один корень','корней нет'], ok:2 },
|
||
{ t:'input', q:'Сумма корней $2x^2 - 10x + 8 = 0$ (через $-b/a$):', ans:[5] },
|
||
{ t:'yesno', q:'У $x^2 + x + 1 = 0$ есть действительные корни.', ok:false },
|
||
{ t:'input', q:'При каком $m$ уравнение $x^2 - 4x + m = 0$ имеет один корень? $m = ?$', ans:[4] },
|
||
]},
|
||
{ id:'b3', name:'Дух Виета', sub:'§ 9 · сумма и произведение', icon:'∑', hp:5, tasks:[
|
||
{ t:'input', q:'Корни $x^2 - 9x + 20 = 0$. Введите больший.', ans:[5] },
|
||
{ t:'select', q:'Если $x_1 + x_2 = -p,\\ x_1 x_2 = q$, то корни $\\{2; 7\\}$ дают уравнение:', opts:['$x^2 - 9x + 14 = 0$','$x^2 + 9x + 14 = 0$','$x^2 - 7x + 9 = 0$'], ok:0 },
|
||
{ t:'yesno', q:'У уравнения $x^2 + 3x + 10 = 0$ корни одного знака.', ok:true },
|
||
{ t:'input', q:'Произведение корней $x^2 + 4x - 21 = 0$.', ans:[-21] },
|
||
{ t:'select', q:'Знаки корней $x^2 - 5x + 6 = 0$:', opts:['оба «+»','оба «−»','разные'], ok:0 },
|
||
]},
|
||
{ id:'b4', name:'Разложитель', sub:'§ 10 · a(x−x₁)(x−x₂)', icon:'( )', hp:5, tasks:[
|
||
{ t:'select', q:'$x^2 - 4 = ?$', opts:['$(x-2)(x+2)$','$(x-4)(x+1)$','$(x-2)^2$','не раскладывается'], ok:0 },
|
||
{ t:'yesno', q:'Трёхчлен $x^2 + 9$ раскладывается на множители первой степени с действительными коэффициентами.', ok:false },
|
||
{ t:'input', q:'Корни трёхчлена $x^2 - 7x + 12$. Меньший:', ans:[3] },
|
||
{ t:'select', q:'$\\dfrac{x^2-1}{x^2+x-2}$ сокращается до:', opts:['$\\dfrac{x+1}{x+2}$','$\\dfrac{x-1}{x+2}$','$\\dfrac{1}{x+2}$'], ok:0 },
|
||
{ t:'input', q:'Найдите $x_1 \\cdot x_2$ для $2x^2 - 7x + 3$ (по Виета $c/a$).', ans:[1.5] },
|
||
]},
|
||
{ id:'b5', name:'Архивариус задач', sub:'§ 11 · текстовые модели', icon:'?', hp:5, tasks:[
|
||
{ t:'input', q:'Произведение двух последовательных натуральных чисел = 72. Большее:', ans:[9] },
|
||
{ t:'input', q:'Площадь прямоугольника 35 см², сторона на 2 см больше другой. Меньшая сторона:', ans:[5] },
|
||
{ t:'select', q:'Двое за 6 ч; первый один на 5 ч быстрее. Уравнение для времени второго $t$:', opts:['$\\dfrac{1}{t-5} + \\dfrac{1}{t} = \\dfrac{1}{6}$','$t + (t-5) = 6$','$6 = t(t-5)$'], ok:0 },
|
||
{ t:'yesno', q:'В задаче на площадь оба корня уравнения подходят, если оба положительные.', ok:true },
|
||
{ t:'input', q:'Сумма квадратов двух последовательных натуральных = 41. Меньшее:', ans:[4] },
|
||
]},
|
||
{ id:'b6', name:'Мастер замены', sub:'§ 12 · t = x², ОДЗ', icon:'t', hp:5, tasks:[
|
||
{ t:'input', q:'Сколько корней у $x^4 - 13x^2 + 36 = 0$?', ans:[4] },
|
||
{ t:'yesno', q:'У уравнения $x^4 + 5x^2 + 4 = 0$ есть действительные корни.', ok:false },
|
||
{ t:'select', q:'Подходящая замена для $(x^2-1)^2 - 5(x^2-1) + 4 = 0$:', opts:['$t = x^2 - 1$','$t = x^2$','$t = x - 1$'], ok:0 },
|
||
{ t:'input', q:'Биквадратное $x^4 - 5x^2 + 4$. Больший положительный корень:', ans:[2] },
|
||
{ t:'select', q:'В уравнении $\\dfrac{1}{x-2} = \\dfrac{x-2}{4}$ посторонний корень — это $x = ?$', opts:['$x = 2$','$x = 4$','$x = 0$'], ok:0 },
|
||
]},
|
||
{ id:'b7', name:'Магистр алгебры', sub:'Финал · вся глава', icon:'★', hp:7, tasks:[
|
||
{ t:'input', q:'$x^2 + 5x + 6 = 0$. Меньший корень:', ans:[-3] },
|
||
{ t:'select', q:'$2x^2 - 8 = 0 \\Rightarrow x = ?$', opts:['$\\pm 2$','$\\pm 4$','$\\pm\\sqrt{2}$','$\\pm 8$'], ok:0 },
|
||
{ t:'input', q:'$D$ для $3x^2 + 5x - 2 = 0$:', ans:[49] },
|
||
{ t:'input', q:'По Виета: $x_1 + x_2$ для $x^2 - 11x + 28$:', ans:[11] },
|
||
{ t:'select', q:'$x^2 - 9$ разложится в:', opts:['$(x-3)(x+3)$','$(x-9)(x+1)$','$(x-3)^2$'], ok:0 },
|
||
{ t:'input', q:'$x^4 - 10x^2 + 9 = 0$. Сумма всех корней:', ans:[0] },
|
||
{ t:'yesno', q:'Уравнение $x^2 = -4$ имеет корни в множестве действительных чисел.', ok:false },
|
||
]},
|
||
];
|
||
|
||
const BOSS_STATE = (function(){
|
||
try { return JSON.parse(localStorage.getItem('algebra8_ch2_bosses') || '{}'); } catch(e){ return {}; }
|
||
})();
|
||
function saveBosses(){ try{ localStorage.setItem('algebra8_ch2_bosses', JSON.stringify(BOSS_STATE)); }catch(e){} }
|
||
|
||
function renderBossGrid(){
|
||
const g = document.getElementById('boss-grid');
|
||
g.innerHTML = '';
|
||
BOSSES.forEach(b=>{
|
||
const won = BOSS_STATE[b.id];
|
||
const c = document.createElement('div');
|
||
c.style.cssText = 'background:' + (won ? 'linear-gradient(135deg,var(--ok-bg),#d1fae5)' : 'var(--card)') + ';border:1.5px solid ' + (won ? 'var(--ok)' : 'var(--border)') + ';border-radius:10px;padding:10px 12px;cursor:pointer;text-align:center;transition:transform .15s,box-shadow .15s';
|
||
c.innerHTML = '<div style="font-size:1.6rem;line-height:1;margin-bottom:4px">' + b.icon + '</div><div style="font-family:Unbounded,sans-serif;font-size:.78rem;font-weight:800;color:var(--sec-acc-d)">' + b.name + '</div><div style="font-size:.7rem;color:var(--muted);margin-top:2px">' + b.sub + '</div>' + (won ? '<div style="color:var(--ok);font-weight:700;font-size:.8rem;margin-top:4px">✓ Побеждён</div>' : '<div style="margin-top:4px"><button class="btn small" style="font-size:.72rem">В бой</button></div>');
|
||
c.addEventListener('click', ()=>startBoss(b.id));
|
||
c.addEventListener('mouseover', ()=>{ c.style.transform='translateY(-2px)'; c.style.boxShadow='var(--sh2)'; });
|
||
c.addEventListener('mouseout', ()=>{ c.style.transform=''; c.style.boxShadow=''; });
|
||
g.appendChild(c);
|
||
});
|
||
if(window.renderMathInElement) renderMath(g);
|
||
}
|
||
|
||
let currentBoss = null, taskIdx = 0, hpLeft = 0;
|
||
|
||
function startBoss(id){
|
||
const b = BOSSES.find(x => x.id === id);
|
||
if(!b) return;
|
||
currentBoss = b; taskIdx = 0; hpLeft = b.hp;
|
||
document.getElementById('boss-arena').style.display = 'block';
|
||
document.getElementById('boss-name').textContent = b.name;
|
||
document.getElementById('boss-subname').textContent = b.sub;
|
||
document.getElementById('boss-emoji').textContent = b.icon;
|
||
showTask();
|
||
}
|
||
function showTask(){
|
||
const t = currentBoss.tasks[taskIdx];
|
||
if(!t){ winBoss(); return; }
|
||
document.getElementById('boss-hp').style.width = (hpLeft / currentBoss.hp * 100) + '%';
|
||
const tEl = document.getElementById('boss-task');
|
||
tEl.innerHTML = '<div style="font-size:.75rem;color:var(--muted);margin-bottom:6px">Задание ' + (taskIdx + 1) + ' / ' + currentBoss.tasks.length + '</div><div style="font-size:1.05rem">' + t.q + '</div>';
|
||
renderMath(tEl);
|
||
const opts = document.getElementById('boss-opts'); opts.innerHTML = '';
|
||
document.getElementById('boss-fb').style.display = 'none';
|
||
if(t.t === 'select'){
|
||
t.opts.forEach((o,k)=>{
|
||
const b = document.createElement('button');
|
||
b.className = 'btn'; b.innerHTML = o; b.style.cssText = 'text-align:left';
|
||
b.addEventListener('click', ()=>checkAnswer(k === t.ok, b));
|
||
opts.appendChild(b);
|
||
});
|
||
renderMath(opts);
|
||
} else if(t.t === 'yesno'){
|
||
['Да','Нет'].forEach((lab, k)=>{
|
||
const b = document.createElement('button');
|
||
b.className = 'btn'; b.textContent = lab;
|
||
b.addEventListener('click', ()=>checkAnswer((k === 0) === t.ok, b));
|
||
opts.appendChild(b);
|
||
});
|
||
} else if(t.t === 'input'){
|
||
const wrap = document.createElement('div');
|
||
wrap.style.cssText = 'display:flex;gap:8px;align-items:center';
|
||
const inp = document.createElement('input');
|
||
inp.type = 'text'; inp.placeholder = 'ваш ответ';
|
||
inp.style.cssText = 'flex:1;padding:8px;border:1.5px solid var(--border);border-radius:8px';
|
||
const go = document.createElement('button'); go.className = 'btn primary'; go.textContent = 'Ответ';
|
||
go.addEventListener('click', ()=>{
|
||
const u = parseFloat(inp.value.replace(',','.'));
|
||
const ok = t.ans.some(a => Math.abs(u - a) < 1e-6);
|
||
checkAnswer(ok, go);
|
||
});
|
||
inp.addEventListener('keyup', e=>{ if(e.key === 'Enter') go.click(); });
|
||
wrap.appendChild(inp); wrap.appendChild(go); opts.appendChild(wrap);
|
||
}
|
||
}
|
||
function checkAnswer(ok, btn){
|
||
const fb = document.getElementById('boss-fb'); fb.style.display = 'block';
|
||
if(ok){
|
||
if(btn) btn.classList.add('ok');
|
||
feedback(fb, true, '✓ Точное попадание!');
|
||
taskIdx++;
|
||
setTimeout(showTask, 700);
|
||
} else {
|
||
if(btn) btn.classList.add('fail');
|
||
hpLeft = Math.max(0, hpLeft - 0);
|
||
feedback(fb, false, '✗ Промах! Попробуйте ещё.');
|
||
}
|
||
}
|
||
function winBoss(){
|
||
BOSS_STATE[currentBoss.id] = 1; saveBosses();
|
||
achievement('boss_' + currentBoss.id, 'Победил: ' + currentBoss.name);
|
||
bumpProgress('final2', 14); confetti();
|
||
document.getElementById('boss-task').innerHTML = '<div style="text-align:center;padding:20px"><div style="font-size:3rem">' + currentBoss.icon + '</div><h3 style="color:var(--ok);margin:10px 0">Победа!</h3><p>Босс <b>«' + currentBoss.name + '»</b> повержен.</p></div>';
|
||
document.getElementById('boss-opts').innerHTML = '<button class="btn primary" id="boss-back">К списку боссов</button>';
|
||
document.getElementById('boss-fb').style.display = 'none';
|
||
document.getElementById('boss-back').addEventListener('click', closeBoss);
|
||
renderBossGrid();
|
||
if(BOSSES.every(b => BOSS_STATE[b.id])){
|
||
setTimeout(()=>{ achievement('all_bosses', 'Магистр квадратных уравнений!'); confetti(); refreshCert(); }, 800);
|
||
}
|
||
}
|
||
function closeBoss(){ document.getElementById('boss-arena').style.display = 'none'; currentBoss = null; }
|
||
document.getElementById('boss-quit').addEventListener('click', closeBoss);
|
||
renderBossGrid();
|
||
|
||
/* ===== PRACTICE ===== */
|
||
(function(){
|
||
let cur = null, total = 0, score = 0, streak = 0;
|
||
function gen(){
|
||
const t = Math.floor(Math.random()*5);
|
||
if(t === 0){
|
||
const r1 = -5 + Math.floor(Math.random()*11), r2 = -5 + Math.floor(Math.random()*11);
|
||
if(r1 === r2) return gen();
|
||
const b = -(r1+r2), c = r1*r2;
|
||
return { q:'Решите $x^2 ' + (b >= 0 ? '+ ' + b : '- ' + Math.abs(b)) + 'x ' + (c >= 0 ? '+ ' + c : '- ' + Math.abs(c)) + ' = 0$. Введите больший корень.', ans:[Math.max(r1, r2)] };
|
||
}
|
||
if(t === 1){
|
||
const r = 1 + Math.floor(Math.random()*7);
|
||
return { q:'$x^2 - ' + (r*r) + ' = 0 \\Rightarrow x = \\pm ?$', ans:[r] };
|
||
}
|
||
if(t === 2){
|
||
const r1 = 1 + Math.floor(Math.random()*7), r2 = -(1 + Math.floor(Math.random()*7));
|
||
const b = -(r1+r2), c = r1*r2;
|
||
return { q:'Сумма корней $x^2 ' + (b >= 0 ? '+ ' + b : '- ' + Math.abs(b)) + 'x ' + (c >= 0 ? '+ ' + c : '- ' + Math.abs(c)) + ' = 0$?', ans:[r1+r2] };
|
||
}
|
||
if(t === 3){
|
||
const a = 1 + Math.floor(Math.random()*3), b = -(2 + Math.floor(Math.random()*8)), c = 1 + Math.floor(Math.random()*6);
|
||
return { q:'Найдите $D$ для $' + a + 'x^2 ' + (b >= 0 ? '+ ' + b : '- ' + Math.abs(b)) + 'x ' + (c >= 0 ? '+ ' + c : '- ' + Math.abs(c)) + ' = 0$', ans:[b*b - 4*a*c] };
|
||
}
|
||
const r = 1 + Math.floor(Math.random()*4);
|
||
return { q:'Биквадратное $x^4 - ' + (r*r + 1) + 'x^2 + ' + (r*r) + ' = 0$. Сколько корней?', ans:[r === 1 ? 2 : 4] };
|
||
}
|
||
function show(){
|
||
cur = gen();
|
||
document.getElementById('prac-task').innerHTML = cur.q;
|
||
renderMath(document.getElementById('prac-task'));
|
||
document.getElementById('prac-inp').value = '';
|
||
document.getElementById('prac-fb').style.display = 'none';
|
||
}
|
||
document.getElementById('prac-go').addEventListener('click', ()=>{
|
||
const fb = document.getElementById('prac-fb'); fb.style.display = 'block';
|
||
const u = parseFloat(document.getElementById('prac-inp').value.replace(',','.'));
|
||
const ok = cur.ans.some(a => Math.abs(u - a) < 1e-6);
|
||
total++; if(ok){ score++; streak++; feedback(fb, true, '✓'); if(streak === 5){ achievement('prac_streak', 'Серия из 5!'); confetti(); } }
|
||
else { streak = 0; feedback(fb, false, 'Правильно: ' + cur.ans.join(', ')); }
|
||
document.getElementById('prac-i').textContent = total;
|
||
document.getElementById('prac-score').textContent = score;
|
||
document.getElementById('prac-streak').textContent = streak;
|
||
if(total >= 5) { bumpProgress('final2', 4); }
|
||
});
|
||
document.getElementById('prac-next').addEventListener('click', show);
|
||
document.getElementById('prac-inp').addEventListener('keyup', e=>{ if(e.key === 'Enter') document.getElementById('prac-go').click(); });
|
||
show();
|
||
})();
|
||
|
||
/* CERTIFICATE */
|
||
function refreshCert(){
|
||
const won = BOSSES.filter(b => BOSS_STATE[b.id]).length;
|
||
const cs = document.getElementById('cert-state');
|
||
if(!cs) return;
|
||
if(won === BOSSES.length){
|
||
cs.innerHTML = '<div style="padding:18px;background:linear-gradient(135deg,#fef3c7,#fde68a);border:2px solid #d97706;border-radius:12px;text-align:center"><div style="font-family:Unbounded,sans-serif;font-size:1.2rem;font-weight:900;color:#92400e">МАГИСТР КВАДРАТНЫХ УРАВНЕНИЙ</div><div style="font-size:.9rem;color:#78350f;margin-top:4px">Все 7 боссов главы 2 повержены — вы освоили §§ 7–12.</div></div>';
|
||
} else {
|
||
cs.innerHTML = 'Побеждено боссов: <b>' + won + ' / ' + BOSSES.length + '</b>. Победите всех, чтобы получить титул.';
|
||
}
|
||
}
|
||
refreshCert();
|
||
}
|
||
</script>
|
||
|
||
<script>
|
||
/* ============================================================
|
||
§ 7 — КВАДРАТНЫЕ УРАВНЕНИЯ. НЕПОЛНЫЕ
|
||
============================================================ */
|
||
function buildP7(){
|
||
const box = document.getElementById('p7-body');
|
||
let html = '';
|
||
|
||
/* Card: повторение */
|
||
html += makeCard('repeat','Повторение',null, `
|
||
<ul style="margin-left:18px;line-height:1.7">
|
||
<li><b>Линейное уравнение</b> $ax+b=0$: при $a \\neq 0$ один корень $x=-b/a$.</li>
|
||
<li><b>Раскрытие скобок:</b> $(a+b)^2 = a^2+2ab+b^2$, $(a-b)^2=a^2-2ab+b^2$.</li>
|
||
<li><b>Свойство нуля:</b> произведение равно нулю $\\Leftrightarrow$ хотя бы один множитель равен нулю.</li>
|
||
<li><b>Квадратный корень:</b> $\\sqrt{a}$ определён только для $a \\geq 0$.</li>
|
||
</ul>`);
|
||
|
||
/* Card: теория */
|
||
html += makeCard('theory','Что такое квадратное уравнение','7.1',`
|
||
<p style="margin-bottom:8px"><b>Определение.</b> Уравнение вида $ax^2 + bx + c = 0$, где $a \\neq 0$, называется <b>квадратным</b>. Числа $a$, $b$, $c$ — <b>коэффициенты</b>:</p>
|
||
<ul style="margin-left:18px;line-height:1.7">
|
||
<li>$a$ — старший (первый) коэффициент;</li>
|
||
<li>$b$ — второй коэффициент;</li>
|
||
<li>$c$ — свободный член.</li>
|
||
</ul>
|
||
<p style="margin-top:8px">Если $a=1$, уравнение называют <b>приведённым</b>. Если хотя бы один из коэффициентов $b$ или $c$ равен нулю — <b>неполным</b>.</p>`);
|
||
|
||
/* Card: правило */
|
||
html += makeCard('rule','Три типа неполных уравнений','7.2',`
|
||
<table class="tbl" style="width:100%;border-collapse:collapse;font-size:.95rem">
|
||
<thead><tr style="background:var(--sec-acc-soft,var(--pri-soft))"><th style="padding:8px;text-align:left">Вид</th><th style="padding:8px;text-align:left">Метод</th><th style="padding:8px;text-align:left">Корни</th></tr></thead>
|
||
<tbody>
|
||
<tr><td style="padding:8px;border-bottom:1px solid var(--border)">$ax^2 + bx = 0$</td><td style="padding:8px;border-bottom:1px solid var(--border)">$x(ax+b)=0$</td><td style="padding:8px;border-bottom:1px solid var(--border)">$x_1=0,\\ x_2=-b/a$</td></tr>
|
||
<tr><td style="padding:8px;border-bottom:1px solid var(--border)">$ax^2 + c = 0$</td><td style="padding:8px;border-bottom:1px solid var(--border)">$x^2 = -c/a$</td><td style="padding:8px;border-bottom:1px solid var(--border)">$x = \\pm\\sqrt{-c/a}$ или нет</td></tr>
|
||
<tr><td style="padding:8px">$ax^2 = 0$</td><td style="padding:8px">очевидно</td><td style="padding:8px">$x = 0$</td></tr>
|
||
</tbody>
|
||
</table>`);
|
||
|
||
/* Card: алгоритм */
|
||
html += makeCard('algo','Универсальная стратегия',null,`
|
||
<ol style="margin-left:18px;line-height:1.8">
|
||
<li>Привести уравнение к виду $ax^2+bx+c=0$.</li>
|
||
<li>Определить, какие коэффициенты равны нулю.</li>
|
||
<li>Выбрать метод по таблице выше.</li>
|
||
<li>Проверить ответ подстановкой.</li>
|
||
</ol>`);
|
||
|
||
/* Card: пример */
|
||
html += makeCard('example','Решим вместе','7.3',`
|
||
<p><b>Пример 1.</b> $3x^2 - 12x = 0$. Выносим $x$ за скобки: $x(3x - 12) = 0$. Откуда $x_1 = 0$ или $3x-12 = 0 \\Rightarrow x_2 = 4$. Ответ: $\\{0;\\ 4\\}$.</p>
|
||
<p style="margin-top:6px"><b>Пример 2.</b> $2x^2 - 18 = 0$. Получаем $x^2 = 9 \\Rightarrow x = \\pm 3$.</p>
|
||
<p style="margin-top:6px"><b>Пример 3.</b> $5x^2 + 20 = 0 \\Rightarrow x^2 = -4$. Корней нет.</p>`);
|
||
|
||
/* ===== INTERACTIVE 1: Конструктор уравнения ===== */
|
||
html += widget('Конструктор квадратного уравнения','INTERACT 1','Перетягивайте слайдеры $a$, $b$, $c$. Смотрите, как уравнение оживает и меняет тип.', `
|
||
<div class="sliders">
|
||
<label>$a$ = <b id="p7c-a-val">1</b><input type="range" min="-5" max="5" step="1" value="1" id="p7c-a"></label>
|
||
<label>$b$ = <b id="p7c-b-val">0</b><input type="range" min="-9" max="9" step="1" value="0" id="p7c-b"></label>
|
||
<label>$c$ = <b id="p7c-c-val">-9</b><input type="range" min="-12" max="12" step="1" value="-9" id="p7c-c"></label>
|
||
</div>
|
||
<div class="eq-show" id="p7c-eq" style="text-align:center;font-size:1.4rem;margin:10px 0;padding:14px;background:var(--sec-acc-soft);border-radius:10px"></div>
|
||
<div id="p7c-type" style="text-align:center;font-weight:600;color:var(--sec-acc-d);margin-bottom:6px"></div>
|
||
<div id="p7c-roots" style="text-align:center;margin-bottom:6px"></div>`);
|
||
|
||
/* ===== INTERACTIVE 2: Drag-сортировка ===== */
|
||
html += widget('Сортировка уравнений по типу','INTERACT 2','Перетащите каждое уравнение в нужный ящик. На тач-экранах: тап по карточке, затем тап по ящику.',`
|
||
${DND_HINT_HTML}
|
||
<div id="p7s-pool"></div>
|
||
<div class="drop-row" style="display:grid;grid-template-columns:repeat(auto-fit,minmax(180px,1fr));gap:10px">
|
||
<div class="drop-box"><h5>Полное</h5><div class="drop-items" data-cat="full"></div></div>
|
||
<div class="drop-box"><h5>Неполное</h5><div class="drop-items" data-cat="incomplete"></div></div>
|
||
<div class="drop-box"><h5>Не квадратное</h5><div class="drop-items" data-cat="notquad"></div></div>
|
||
</div>
|
||
<div class="actions"><button class="btn primary" id="p7s-check">Проверить</button><button class="btn" id="p7s-reset">Сначала</button></div>
|
||
<div class="feedback" id="p7s-fb" style="display:none"></div>`);
|
||
|
||
/* ===== INTERACTIVE 3: Пошаговый решатель ===== */
|
||
html += widget('Пошаговый решатель неполных','INTERACT 3','Выберите тип, нажимайте «Дальше» и наблюдайте за решением пошагово.',`
|
||
<div class="pipe-tabs" style="display:flex;gap:6px;margin-bottom:10px">
|
||
<button class="btn small p7p-tab active" data-tp="bc0">$ax^2+bx=0$</button>
|
||
<button class="btn small p7p-tab" data-tp="ac0">$ax^2+c=0$</button>
|
||
</div>
|
||
<div id="p7p-stage" style="padding:14px;background:var(--card-soft);border-radius:10px;min-height:160px;font-size:1.05rem"></div>
|
||
<div class="actions" style="margin-top:10px"><button class="btn primary" id="p7p-next">Дальше</button><button class="btn" id="p7p-reset">Сначала</button></div>`);
|
||
|
||
/* ===== INTERACTIVE 4: Задача про страницу книги ===== */
|
||
html += widget('Задача про страницу книги','INTERACT 4','Длина страницы на 4 см больше ширины, а площадь — 165 см². Найдите ширину.',`
|
||
<p style="margin-bottom:10px">Пусть ширина $= x$, тогда длина $= x+4$. Уравнение: $x(x+4) = 165 \\Rightarrow x^2 + 4x - 165 = 0$.</p>
|
||
<div style="display:flex;gap:8px;align-items:center;flex-wrap:wrap">
|
||
<label>Ваш ответ (ширина, см): <input type="number" id="p7b-inp" style="width:80px;padding:6px;border:1.5px solid var(--border);border-radius:6px"></label>
|
||
<button class="btn primary" id="p7b-check">Проверить</button>
|
||
</div>
|
||
<div class="feedback" id="p7b-fb" style="display:none;margin-top:10px"></div>
|
||
<svg id="p7b-svg" viewBox="0 0 320 200" style="width:100%;max-width:320px;margin-top:12px;display:block">
|
||
<rect x="40" y="40" width="240" height="120" fill="var(--sec-acc-soft)" stroke="var(--sec-acc)" stroke-width="2"/>
|
||
<text x="160" y="32" text-anchor="middle" font-size="14" fill="var(--sec-acc-d)">длина = x + 4</text>
|
||
<text x="22" y="105" font-size="14" fill="var(--sec-acc-d)" transform="rotate(-90 22 105)">x</text>
|
||
<text x="160" y="108" text-anchor="middle" font-size="20" fill="var(--ink)" id="p7b-area">S = 165</text>
|
||
</svg>`);
|
||
|
||
/* ===== INTERACTIVE 5: Тренажёр 10 неполных ===== */
|
||
html += widget('Тренажёр: 10 неполных','INTERACT 5','Решайте устно. Ответ — большее значение корня (через запятую: если корней два — оба, по возрастанию).',`
|
||
<div class="score-display"><span>Задача <b id="p7t-i">1</b> / 10</span><span>Очки: <b id="p7t-score">0</b></span><span>Время: <b id="p7t-time">0</b> c</span></div>
|
||
<div id="p7t-task" style="font-size:1.4rem;text-align:center;padding:18px;background:var(--sec-acc-soft);border-radius:10px;margin-bottom:10px"></div>
|
||
<div style="display:flex;gap:8px;align-items:center;justify-content:center;flex-wrap:wrap">
|
||
<input type="text" id="p7t-inp" placeholder="например 0; 4 или -3; 3 или нет" style="width:240px;padding:8px;border:1.5px solid var(--border);border-radius:8px">
|
||
<button class="btn primary" id="p7t-go">Ответ</button>
|
||
<button class="btn" id="p7t-skip">Пропустить</button>
|
||
</div>
|
||
<div class="feedback" id="p7t-fb" style="display:none;margin-top:10px"></div>
|
||
<button class="btn primary" id="p7t-start" style="margin-top:10px">Начать</button>`);
|
||
|
||
/* ===== INTERACTIVE 6: Есть ли корни у ax²+c=0 ===== */
|
||
html += widget('«Имеет ли корни?» — $ax^2 + c = 0$','INTERACT 6','Быстрый тренажёр на знаки. Нужно сообразить, имеет ли уравнение корни.',`
|
||
<div class="score-display"><span>Раунд <b id="p7r-i">1</b> / 8</span><span>Правильно: <b id="p7r-score">0</b></span></div>
|
||
<div id="p7r-task" style="font-size:1.5rem;text-align:center;padding:18px;background:var(--sec-acc-soft);border-radius:10px;margin-bottom:10px"></div>
|
||
<div style="display:flex;gap:8px;justify-content:center">
|
||
<button class="btn primary" id="p7r-yes">Имеет корни</button>
|
||
<button class="btn" id="p7r-no">Корней нет</button>
|
||
</div>
|
||
<div class="feedback" id="p7r-fb" style="display:none;margin-top:10px"></div>
|
||
<button class="btn primary" id="p7r-start" style="margin-top:10px">Начать</button>`);
|
||
|
||
/* ===== Устно / Класс / Дом ===== */
|
||
html += makeCard('oral','Устные вопросы',null,`
|
||
<ol style="margin-left:18px;line-height:1.8">
|
||
<li>Какое уравнение называется квадратным? Какое — неполным?</li>
|
||
<li>Может ли коэффициент $a$ быть равен нулю? Почему?</li>
|
||
<li>Сколько корней может быть у уравнения $ax^2+c=0$?</li>
|
||
<li>Назовите коэффициенты уравнения $5x^2 - 2x + 7 = 0$.</li>
|
||
</ol>`);
|
||
|
||
html += makeCard('class','Класс — решите в тетради',null,`
|
||
<ol style="margin-left:18px;line-height:1.8">
|
||
<li>$4x^2 - 9 = 0$</li>
|
||
<li>$3x^2 + 6x = 0$</li>
|
||
<li>$x^2 + 25 = 0$</li>
|
||
<li>$2x^2 = 50$</li>
|
||
<li>$x(x-7) = 0$</li>
|
||
</ol>`);
|
||
|
||
html += makeCard('home','Домашка',null,`
|
||
<ol style="margin-left:18px;line-height:1.8">
|
||
<li>$9x^2 - 16 = 0$</li>
|
||
<li>$5x^2 + 10x = 0$</li>
|
||
<li>$x^2 + 4 = 0$</li>
|
||
<li>Длина прямоугольника на 3 см больше ширины, а площадь равна 54 см². Найдите стороны.</li>
|
||
</ol>`);
|
||
|
||
html += secNav(null,'p8');
|
||
|
||
box.innerHTML = html;
|
||
if(window.renderMathInElement) setTimeout(()=>renderMath(box), 0);
|
||
|
||
/* ===== INIT INTERACTIVE 1 ===== */
|
||
(function initConstructor(){
|
||
const aE = document.getElementById('p7c-a'), bE = document.getElementById('p7c-b'), cE = document.getElementById('p7c-c');
|
||
const eq = document.getElementById('p7c-eq'), tp = document.getElementById('p7c-type'), rt = document.getElementById('p7c-roots');
|
||
let done = false;
|
||
function termA(a){
|
||
if(a === 0) return '';
|
||
if(a === 1) return 'x^2';
|
||
if(a === -1) return '-x^2';
|
||
return a + 'x^2';
|
||
}
|
||
function termB(b){
|
||
if(b === 0) return '';
|
||
const s = b > 0 ? ' + ' : ' - ';
|
||
const v = Math.abs(b);
|
||
return s + (v === 1 ? '' : v) + 'x';
|
||
}
|
||
function termC(c){
|
||
if(c === 0) return '';
|
||
const s = c > 0 ? ' + ' : ' - ';
|
||
return s + Math.abs(c);
|
||
}
|
||
function refresh(){
|
||
const a = +aE.value, b = +bE.value, c = +cE.value;
|
||
document.getElementById('p7c-a-val').textContent = a;
|
||
document.getElementById('p7c-b-val').textContent = b;
|
||
document.getElementById('p7c-c-val').textContent = c;
|
||
if(a === 0){ eq.innerHTML = '$' + (termB(b) || '0') + termC(c) + ' = 0$ <span style="color:var(--bad);font-size:.85rem">не квадратное!</span>'; tp.textContent = 'Не квадратное (a=0)'; rt.textContent = ''; renderMath(eq); return; }
|
||
let s = termA(a) + termB(b) + termC(c) + ' = 0';
|
||
eq.innerHTML = '$' + s + '$';
|
||
let label = '';
|
||
if(b === 0 && c === 0) label = 'Неполное: $ax^2=0$ · корень $x=0$';
|
||
else if(b === 0) label = 'Неполное: $ax^2+c=0$';
|
||
else if(c === 0) label = 'Неполное: $ax^2+bx=0$ · корни $x_1=0,\\ x_2=' + fmt(-b/a) + '$';
|
||
else label = 'Полное квадратное · решаем через дискриминант';
|
||
tp.innerHTML = label;
|
||
let rs = '';
|
||
if(b === 0 && c === 0) rs = '$x=0$';
|
||
else if(c === 0) rs = '$x_1=0,\\ x_2=' + fmt(-b/a) + '$';
|
||
else if(b === 0){
|
||
const v = -c/a;
|
||
if(v > 0){ const r = Math.sqrt(v); rs = '$x = \\pm' + fmt(r) + '$'; }
|
||
else if(v === 0) rs = '$x=0$';
|
||
else rs = 'корней нет';
|
||
} else {
|
||
const D = b*b - 4*a*c;
|
||
if(D > 0){ const r1 = (-b - Math.sqrt(D))/(2*a), r2 = (-b + Math.sqrt(D))/(2*a); rs = '$x_1=' + fmt(r1) + ',\\ x_2=' + fmt(r2) + '$'; }
|
||
else if(D === 0) rs = '$x=' + fmt(-b/(2*a)) + '$';
|
||
else rs = 'корней нет';
|
||
}
|
||
rt.innerHTML = rs;
|
||
renderMath(eq); renderMath(tp); renderMath(rt);
|
||
if(!done){ done = true; setTimeout(()=>{ achievement('p7_constr'); bumpProgress('p7', 12); }, 300); }
|
||
}
|
||
aE.addEventListener('input', refresh); bE.addEventListener('input', refresh); cE.addEventListener('input', refresh);
|
||
refresh();
|
||
})();
|
||
|
||
/* ===== INIT INTERACTIVE 2 ===== */
|
||
(function initSort(){
|
||
const items = [
|
||
{ id:1, html:'$2x^2 + 3x - 5 = 0$', cat:'full' },
|
||
{ id:2, html:'$x^2 - 16 = 0$', cat:'incomplete' },
|
||
{ id:3, html:'$3x^2 + 6x = 0$', cat:'incomplete' },
|
||
{ id:4, html:'$x + 5 = 0$', cat:'notquad' },
|
||
{ id:5, html:'$5x^2 - 2x + 1 = 0$', cat:'full' },
|
||
{ id:6, html:'$7x^2 = 0$', cat:'incomplete' },
|
||
{ id:7, html:'$x^3 - x = 0$', cat:'notquad' },
|
||
{ id:8, html:'$x^2 + x + 1 = 0$', cat:'full' },
|
||
];
|
||
const sorter = setupSorter({ poolId:'p7s-pool', cats:['full','incomplete','notquad'], items, scopeSelector:'#p7-body' });
|
||
document.getElementById('p7s-check').addEventListener('click', ()=>{
|
||
const total = items.length;
|
||
const placedCount = Object.keys(sorter.placed).length;
|
||
const fb = document.getElementById('p7s-fb');
|
||
fb.style.display = 'block';
|
||
if(placedCount < total){ feedback(fb, false, '⚠ Разложите ВСЕ уравнения по ящикам.'); return; }
|
||
let ok = 0; items.forEach(it=>{ if(sorter.placed[it.id] === it.cat) ok++; });
|
||
if(ok === total){ feedback(fb, true, '✓ Идеально! Все ' + total + ' верно.'); achievement('p7_sort'); bumpProgress('p7', 14); confetti(); }
|
||
else feedback(fb, false, 'Верно ' + ok + ' из ' + total + '. Попробуйте перепроверить.');
|
||
});
|
||
document.getElementById('p7s-reset').addEventListener('click', ()=>{ sorter.reset(); document.getElementById('p7s-fb').style.display='none'; });
|
||
})();
|
||
|
||
/* ===== INIT INTERACTIVE 3 ===== */
|
||
(function initPipeline(){
|
||
const stages = {
|
||
bc0: [
|
||
'<b>Дано:</b> $2x^2 - 6x = 0$',
|
||
'<b>Шаг 1:</b> Вынесем $x$ за скобки: $x(2x - 6) = 0$',
|
||
'<b>Шаг 2:</b> Произведение равно нулю $\\Leftrightarrow$ один из множителей равен нулю.',
|
||
'<b>Шаг 3:</b> $x = 0$ или $2x - 6 = 0$',
|
||
'<b>Шаг 4:</b> $x_1 = 0,\\ x_2 = 3$',
|
||
'<b>Ответ:</b> $\\{0;\\ 3\\}$',
|
||
],
|
||
ac0: [
|
||
'<b>Дано:</b> $3x^2 - 27 = 0$',
|
||
'<b>Шаг 1:</b> Перенесём $-27$ вправо: $3x^2 = 27$',
|
||
'<b>Шаг 2:</b> Разделим на $3$: $x^2 = 9$',
|
||
'<b>Шаг 3:</b> Возьмём квадратный корень: $x = \\pm\\sqrt{9}$',
|
||
'<b>Шаг 4:</b> $x = \\pm 3$',
|
||
'<b>Ответ:</b> $\\{-3;\\ 3\\}$',
|
||
],
|
||
};
|
||
let mode = 'bc0', idx = 0;
|
||
const stage = document.getElementById('p7p-stage');
|
||
let solved = false;
|
||
function show(){
|
||
const arr = stages[mode];
|
||
stage.innerHTML = arr.slice(0, idx + 1).map(s => '<p style="margin:6px 0">' + s + '</p>').join('');
|
||
renderMath(stage);
|
||
if(idx >= arr.length - 1 && !solved){ solved = true; achievement('p7_pipeline'); bumpProgress('p7', 12); }
|
||
}
|
||
document.querySelectorAll('.p7p-tab').forEach(t=>{
|
||
t.addEventListener('click', ()=>{ document.querySelectorAll('.p7p-tab').forEach(x=>x.classList.remove('active')); t.classList.add('active'); mode = t.dataset.tp; idx = 0; show(); });
|
||
});
|
||
document.getElementById('p7p-next').addEventListener('click', ()=>{ if(idx < stages[mode].length - 1) idx++; show(); });
|
||
document.getElementById('p7p-reset').addEventListener('click', ()=>{ idx = 0; show(); });
|
||
show();
|
||
})();
|
||
|
||
/* ===== INIT INTERACTIVE 4 ===== */
|
||
(function initBook(){
|
||
document.getElementById('p7b-check').addEventListener('click', ()=>{
|
||
const v = +document.getElementById('p7b-inp').value;
|
||
const fb = document.getElementById('p7b-fb');
|
||
fb.style.display = 'block';
|
||
if(Math.abs(v - 11) < 1e-9){ feedback(fb, true, '✓ Верно! Ширина = 11 см, длина = 15 см. $11 \\cdot 15 = 165$.'); renderMath(fb); achievement('p7_book'); bumpProgress('p7', 14); confetti(); }
|
||
else feedback(fb, false, 'Не то. $x^2+4x-165=0$, корни $-15$ и $11$. Ширина не может быть отрицательной.');
|
||
});
|
||
})();
|
||
|
||
/* ===== INIT INTERACTIVE 5 ===== */
|
||
(function initTrain(){
|
||
function gen(){
|
||
const t = Math.floor(Math.random()*3);
|
||
if(t === 0){
|
||
const a = 1 + Math.floor(Math.random()*4);
|
||
const r2 = 1 + Math.floor(Math.random()*8);
|
||
return { eq: a + 'x^2 - ' + (a*r2) + 'x = 0', ans: [0, r2] };
|
||
}
|
||
if(t === 1){
|
||
const a = 1 + Math.floor(Math.random()*3);
|
||
const r = 1 + Math.floor(Math.random()*7);
|
||
return { eq: a + 'x^2 - ' + (a*r*r) + ' = 0', ans: [-r, r] };
|
||
}
|
||
const a = 1 + Math.floor(Math.random()*3);
|
||
const c = 1 + Math.floor(Math.random()*15);
|
||
return { eq: a + 'x^2 + ' + c + ' = 0', ans: null };
|
||
}
|
||
let cur = null, i = 1, score = 0, t0 = 0, timer = null;
|
||
function newOne(){
|
||
cur = gen();
|
||
document.getElementById('p7t-task').innerHTML = '$' + cur.eq + ' = 0$'; renderMath(document.getElementById('p7t-task'));
|
||
document.getElementById('p7t-i').textContent = i;
|
||
document.getElementById('p7t-inp').value = '';
|
||
document.getElementById('p7t-fb').style.display = 'none';
|
||
}
|
||
function parse(s){
|
||
s = s.trim().toLowerCase();
|
||
if(/нет|none/.test(s)) return null;
|
||
return s.replace(/,/g,';').split(/[;\s]+/).filter(Boolean).map(Number).sort((a,b)=>a-b);
|
||
}
|
||
function check(){
|
||
const u = parse(document.getElementById('p7t-inp').value);
|
||
const fb = document.getElementById('p7t-fb');
|
||
fb.style.display = 'block';
|
||
let ok = false;
|
||
if(cur.ans === null){ ok = u === null; }
|
||
else if(u !== null){ const a = [...cur.ans].sort((a,b)=>a-b); ok = a.length === u.length && a.every((x,k)=>x === u[k]); }
|
||
if(ok){ score++; feedback(fb, true, '✓'); } else feedback(fb, false, 'Правильно: ' + (cur.ans === null ? 'нет' : cur.ans.join('; ')));
|
||
document.getElementById('p7t-score').textContent = score;
|
||
if(i >= 10){
|
||
setTimeout(()=>{ feedback(fb, score >= 7, 'Итог: ' + score + '/10 за ' + (((Date.now() - t0)/1000)|0) + ' c'); if(score >= 7){ achievement('p7_train'); bumpProgress('p7', 16); confetti(); } clearInterval(timer); }, 500);
|
||
} else { i++; setTimeout(newOne, 700); }
|
||
}
|
||
document.getElementById('p7t-start').addEventListener('click', ()=>{ i = 1; score = 0; document.getElementById('p7t-score').textContent = 0; t0 = Date.now(); if(timer) clearInterval(timer); timer = setInterval(()=>{ document.getElementById('p7t-time').textContent = ((Date.now() - t0)/1000)|0; }, 200); newOne(); });
|
||
document.getElementById('p7t-go').addEventListener('click', check);
|
||
document.getElementById('p7t-inp').addEventListener('keyup', e=>{ if(e.key === 'Enter') check(); });
|
||
document.getElementById('p7t-skip').addEventListener('click', ()=>{ if(i < 10){ i++; newOne(); } });
|
||
})();
|
||
|
||
/* ===== INIT INTERACTIVE 6 ===== */
|
||
(function initRoots(){
|
||
let i = 1, score = 0, cur = null;
|
||
function gen(){
|
||
const a = (Math.random() < 0.5 ? 1 : -1) * (1 + Math.floor(Math.random()*5));
|
||
const c = (Math.random() < 0.5 ? 1 : -1) * (1 + Math.floor(Math.random()*16));
|
||
const v = -c/a;
|
||
return { a, c, ok: v > 0, eq: (a === 1 ? '' : (a === -1 ? '-' : a)) + 'x^2 ' + (c >= 0 ? '+ ' + c : '- ' + Math.abs(c)) + ' = 0' };
|
||
}
|
||
function newOne(){
|
||
cur = gen();
|
||
document.getElementById('p7r-i').textContent = i;
|
||
document.getElementById('p7r-task').innerHTML = '$' + cur.eq + '$'; renderMath(document.getElementById('p7r-task'));
|
||
document.getElementById('p7r-fb').style.display = 'none';
|
||
}
|
||
function answer(yes){
|
||
const fb = document.getElementById('p7r-fb');
|
||
fb.style.display = 'block';
|
||
const ok = (cur.ok === yes);
|
||
if(ok){ score++; feedback(fb, true, '✓ Точно. $x^2 = ' + fmt(-cur.c/cur.a) + '$'); }
|
||
else feedback(fb, false, 'Нет. $x^2 = ' + fmt(-cur.c/cur.a) + '$ — ' + (cur.ok ? 'корни есть' : 'корней нет'));
|
||
renderMath(fb);
|
||
document.getElementById('p7r-score').textContent = score;
|
||
if(i >= 8){ setTimeout(()=>{ feedback(fb, score >= 6, 'Итог: ' + score + '/8'); if(score >= 6){ achievement('p7_roots'); bumpProgress('p7', 14); confetti(); } }, 600); }
|
||
else { i++; setTimeout(newOne, 800); }
|
||
}
|
||
document.getElementById('p7r-start').addEventListener('click', ()=>{ i = 1; score = 0; document.getElementById('p7r-score').textContent = 0; newOne(); });
|
||
document.getElementById('p7r-yes').addEventListener('click', ()=>answer(true));
|
||
document.getElementById('p7r-no').addEventListener('click', ()=>answer(false));
|
||
})();
|
||
}
|
||
</script>
|
||
<script>
|
||
/* ============================================================
|
||
§ 8 — ФОРМУЛЫ КОРНЕЙ. ДИСКРИМИНАНТ
|
||
============================================================ */
|
||
function buildP8(){
|
||
const box = document.getElementById('p8-body');
|
||
let html = '';
|
||
|
||
html += makeCard('repeat','Повторение из § 7',null,`
|
||
<ul style="margin-left:18px;line-height:1.7">
|
||
<li>Квадратное уравнение: $ax^2+bx+c=0,\\ a \\neq 0$.</li>
|
||
<li>Неполные уравнения решали без дискриминанта — через вынесение или прямой корень.</li>
|
||
<li>Свойство квадратного корня: $\\sqrt{D}$ существует только при $D \\geq 0$.</li>
|
||
</ul>`);
|
||
|
||
html += makeCard('theory','Универсальная формула','8.1',`
|
||
<p>Для уравнения $ax^2 + bx + c = 0$, $a \\neq 0$, выделим полный квадрат:</p>
|
||
<div style="background:var(--sec-acc-soft);border-radius:10px;padding:12px;margin:8px 0;text-align:center">$$a\\left(x + \\dfrac{b}{2a}\\right)^2 = \\dfrac{b^2 - 4ac}{4a}$$</div>
|
||
<p>Выражение $\\boxed{D = b^2 - 4ac}$ называется <b>дискриминантом</b>. От его знака зависит, есть ли корни.</p>`);
|
||
|
||
html += makeCard('rule','Главные формулы','8.2',`
|
||
<div style="background:var(--card-soft);border:1.5px solid var(--sec-acc);border-radius:10px;padding:12px;margin:6px 0;text-align:center;font-size:1.1rem">$$D = b^2 - 4ac \\qquad x_{1,2} = \\dfrac{-b \\pm \\sqrt{D}}{2a}$$</div>
|
||
<table class="tbl" style="width:100%;border-collapse:collapse;margin-top:8px">
|
||
<thead><tr style="background:var(--sec-acc-soft)"><th style="padding:8px;text-align:left">Знак $D$</th><th style="padding:8px;text-align:left">Сколько корней</th><th style="padding:8px;text-align:left">Формула</th></tr></thead>
|
||
<tbody>
|
||
<tr><td style="padding:8px;border-bottom:1px solid var(--border)">$D > 0$</td><td style="padding:8px;border-bottom:1px solid var(--border)">Два различных</td><td style="padding:8px;border-bottom:1px solid var(--border)">$x_{1,2} = \\dfrac{-b \\pm \\sqrt{D}}{2a}$</td></tr>
|
||
<tr><td style="padding:8px;border-bottom:1px solid var(--border)">$D = 0$</td><td style="padding:8px;border-bottom:1px solid var(--border)">Один (кратный)</td><td style="padding:8px;border-bottom:1px solid var(--border)">$x = \\dfrac{-b}{2a}$</td></tr>
|
||
<tr><td style="padding:8px">$D < 0$</td><td style="padding:8px">Корней нет</td><td style="padding:8px">—</td></tr>
|
||
</tbody>
|
||
</table>`);
|
||
|
||
html += makeCard('algo','Алгоритм решения','8.3',`
|
||
<ol style="margin-left:18px;line-height:1.8">
|
||
<li>Запишите уравнение в виде $ax^2+bx+c=0$, $a \\neq 0$.</li>
|
||
<li>Выпишите коэффициенты $a$, $b$, $c$ со знаками.</li>
|
||
<li>Вычислите $D = b^2 - 4ac$.</li>
|
||
<li>Сравните $D$ с нулём — определите число корней.</li>
|
||
<li>Если $D \\geq 0$, по формуле найдите $x_1$, $x_2$.</li>
|
||
</ol>`);
|
||
|
||
html += makeCard('example','Пример решения','8.4',`
|
||
<p><b>Решим:</b> $2x^2 - 5x + 2 = 0$. Здесь $a=2$, $b=-5$, $c=2$.</p>
|
||
<p>$D = (-5)^2 - 4 \\cdot 2 \\cdot 2 = 25 - 16 = 9$.</p>
|
||
<p>$\\sqrt{D} = 3$. $x_{1,2} = \\dfrac{5 \\pm 3}{4}$, поэтому $x_1 = \\dfrac{1}{2},\\ x_2 = 2$.</p>
|
||
<p><b>Проверка:</b> $2 \\cdot 4 - 10 + 2 = 0$. ✓</p>`);
|
||
|
||
/* INTERACTIVE 1: калькулятор дискриминанта */
|
||
html += widget('Калькулятор дискриминанта','INTERACT 1','Введите $a$, $b$, $c$ — мгновенно увидите $D$ и корни. Идёт пошаговое вычисление.',`
|
||
<div style="display:flex;gap:10px;flex-wrap:wrap;align-items:center;margin-bottom:10px">
|
||
<label>$a$ = <input type="number" id="p8d-a" value="1" style="width:70px;padding:6px;border:1.5px solid var(--border);border-radius:6px"></label>
|
||
<label>$b$ = <input type="number" id="p8d-b" value="-5" style="width:70px;padding:6px;border:1.5px solid var(--border);border-radius:6px"></label>
|
||
<label>$c$ = <input type="number" id="p8d-c" value="6" style="width:70px;padding:6px;border:1.5px solid var(--border);border-radius:6px"></label>
|
||
</div>
|
||
<div id="p8d-out" style="padding:14px;background:var(--sec-acc-soft);border-radius:10px;line-height:1.8"></div>`);
|
||
|
||
/* INTERACTIVE 2: парабола */
|
||
html += widget('Парабола: $y = ax^2+bx+c$','INTERACT 2','Двигайте слайдеры — смотрите, как меняется график и сколько раз он пересекает ось OX.',`
|
||
<div class="sliders">
|
||
<label>$a$ = <b id="p8g-a-val">1</b><input type="range" min="-3" max="3" step="0.1" value="1" id="p8g-a"></label>
|
||
<label>$b$ = <b id="p8g-b-val">0</b><input type="range" min="-6" max="6" step="0.2" value="0" id="p8g-b"></label>
|
||
<label>$c$ = <b id="p8g-c-val">-4</b><input type="range" min="-8" max="8" step="0.2" value="-4" id="p8g-c"></label>
|
||
</div>
|
||
<svg id="p8g-svg" viewBox="-10 -10 220 160" style="width:100%;max-width:480px;display:block;margin:10px auto;background:#fafafa;border:1px solid var(--border);border-radius:8px"></svg>
|
||
<div id="p8g-info" style="text-align:center;font-weight:600;color:var(--sec-acc-d)"></div>`);
|
||
|
||
/* INTERACTIVE 3: 3 случая */
|
||
html += widget('Три случая дискриминанта','INTERACT 3','Кликните по карточке — увидите пример и график для каждого случая.',`
|
||
<div style="display:grid;grid-template-columns:repeat(auto-fit,minmax(160px,1fr));gap:10px;margin-bottom:12px">
|
||
<div class="case-card" data-case="pos"><h5>$D > 0$</h5><div>Два корня</div></div>
|
||
<div class="case-card" data-case="zero"><h5>$D = 0$</h5><div>Один корень</div></div>
|
||
<div class="case-card" data-case="neg"><h5>$D < 0$</h5><div>Корней нет</div></div>
|
||
</div>
|
||
<div id="p8c-detail" style="padding:14px;background:var(--card-soft);border-radius:10px;min-height:140px;display:flex;gap:14px;flex-wrap:wrap;align-items:center"></div>`);
|
||
|
||
/* INTERACTIVE 4: тренажёр D */
|
||
html += widget('Тренажёр: «Сколько корней?»','INTERACT 4','По данному уравнению вычислите $D$ устно и ответьте: сколько корней?',`
|
||
<div class="score-display"><span>Задача <b id="p8t-i">1</b> / 10</span><span>Очки: <b id="p8t-score">0</b></span></div>
|
||
<div id="p8t-task" style="font-size:1.3rem;text-align:center;padding:16px;background:var(--sec-acc-soft);border-radius:10px;margin-bottom:10px"></div>
|
||
<div id="p8t-d-hint" style="text-align:center;color:var(--muted);margin-bottom:8px;font-size:.85rem"></div>
|
||
<div style="display:flex;gap:8px;justify-content:center;flex-wrap:wrap">
|
||
<button class="btn" data-n="2" id="p8t-2">Два</button>
|
||
<button class="btn" data-n="1" id="p8t-1">Один</button>
|
||
<button class="btn" data-n="0" id="p8t-0">Корней нет</button>
|
||
</div>
|
||
<div class="feedback" id="p8t-fb" style="display:none;margin-top:10px"></div>
|
||
<button class="btn primary" id="p8t-start" style="margin-top:10px">Начать</button>`);
|
||
|
||
/* INTERACTIVE 5: пошаговый */
|
||
html += widget('Пошаговый решатель','INTERACT 5','Введите $a$, $b$, $c$ и нажимайте «Дальше» — каждый шаг появляется отдельно.',`
|
||
<div style="display:flex;gap:10px;flex-wrap:wrap;align-items:center;margin-bottom:10px">
|
||
<label>$a$ = <input type="number" id="p8s-a" value="1" style="width:70px;padding:6px;border:1.5px solid var(--border);border-radius:6px"></label>
|
||
<label>$b$ = <input type="number" id="p8s-b" value="-7" style="width:70px;padding:6px;border:1.5px solid var(--border);border-radius:6px"></label>
|
||
<label>$c$ = <input type="number" id="p8s-c" value="12" style="width:70px;padding:6px;border:1.5px solid var(--border);border-radius:6px"></label>
|
||
<button class="btn primary" id="p8s-go">Старт</button>
|
||
<button class="btn" id="p8s-next" style="display:none">Дальше</button>
|
||
<button class="btn" id="p8s-reset" style="display:none">Сначала</button>
|
||
</div>
|
||
<div id="p8s-stage" style="padding:14px;background:var(--card-soft);border-radius:10px;min-height:100px"></div>`);
|
||
|
||
/* INTERACTIVE 6: угадай знак D по графику */
|
||
html += widget('«Угадай знак D» — только по графику','INTERACT 6','По параболе определите знак дискриминанта.',`
|
||
<div class="score-display"><span>Раунд <b id="p8r-i">1</b> / 8</span><span>Правильно: <b id="p8r-score">0</b></span></div>
|
||
<svg id="p8r-svg" viewBox="-10 -10 220 160" style="width:100%;max-width:380px;display:block;margin:10px auto;background:#fafafa;border:1px solid var(--border);border-radius:8px"></svg>
|
||
<div style="display:flex;gap:8px;justify-content:center;flex-wrap:wrap">
|
||
<button class="btn" data-d="pos" id="p8r-pos">$D > 0$</button>
|
||
<button class="btn" data-d="zero" id="p8r-zero">$D = 0$</button>
|
||
<button class="btn" data-d="neg" id="p8r-neg">$D < 0$</button>
|
||
</div>
|
||
<div class="feedback" id="p8r-fb" style="display:none;margin-top:10px"></div>
|
||
<button class="btn primary" id="p8r-start" style="margin-top:10px">Начать</button>`);
|
||
|
||
/* Устно/класс/дом */
|
||
html += makeCard('oral','Устные вопросы',null,`
|
||
<ol style="margin-left:18px;line-height:1.8">
|
||
<li>Что такое дискриминант? Какова его формула?</li>
|
||
<li>При каком знаке $D$ уравнение имеет два корня? Один? Ни одного?</li>
|
||
<li>Как изменится $D$, если уравнение умножить на $-1$?</li>
|
||
<li>Найдите $D$ для $x^2 + 2x + 1 = 0$.</li>
|
||
</ol>`);
|
||
|
||
html += makeCard('class','Класс — решите в тетради',null,`
|
||
<ol style="margin-left:18px;line-height:1.8">
|
||
<li>$x^2 - 7x + 12 = 0$</li>
|
||
<li>$2x^2 + 3x - 2 = 0$</li>
|
||
<li>$x^2 - 6x + 9 = 0$</li>
|
||
<li>$3x^2 + x + 1 = 0$</li>
|
||
<li>$5x^2 - 4x - 1 = 0$</li>
|
||
</ol>`);
|
||
|
||
html += makeCard('home','Домашка',null,`
|
||
<ol style="margin-left:18px;line-height:1.8">
|
||
<li>$x^2 + 5x + 6 = 0$</li>
|
||
<li>$2x^2 - 7x + 3 = 0$</li>
|
||
<li>$4x^2 - 4x + 1 = 0$</li>
|
||
<li>$x^2 + x + 2 = 0$</li>
|
||
<li>При каких $m$ уравнение $x^2 - 4x + m = 0$ имеет один корень?</li>
|
||
</ol>`);
|
||
|
||
html += secNav('p7','p9');
|
||
box.innerHTML = html;
|
||
if(window.renderMathInElement) setTimeout(()=>renderMath(box), 0);
|
||
|
||
/* INIT INTERACTIVE 1 */
|
||
(function initDisc(){
|
||
const aE = document.getElementById('p8d-a'), bE = document.getElementById('p8d-b'), cE = document.getElementById('p8d-c');
|
||
const out = document.getElementById('p8d-out');
|
||
let done = false;
|
||
function refresh(){
|
||
const a = +aE.value, b = +bE.value, c = +cE.value;
|
||
if(!isFinite(a) || a === 0){ out.innerHTML = 'Введите $a \\neq 0$ — иначе уравнение не квадратное.'; renderMath(out); return; }
|
||
const D = b*b - 4*a*c;
|
||
let s = '<div><b>Уравнение:</b> $' + (a === 1 ? '' : (a === -1 ? '-' : a)) + 'x^2 ' + (b >= 0 ? '+' + b : b) + 'x ' + (c >= 0 ? '+' + c : c) + ' = 0$</div>';
|
||
s += '<div><b>Шаг 1.</b> $D = b^2 - 4ac = (' + b + ')^2 - 4 \\cdot (' + a + ') \\cdot (' + c + ') = ' + (b*b) + ' - ' + (4*a*c) + ' = ' + D + '$</div>';
|
||
if(D > 0){ const r1 = (-b - Math.sqrt(D))/(2*a), r2 = (-b + Math.sqrt(D))/(2*a); s += '<div><b>Шаг 2.</b> $D > 0 \\Rightarrow$ два корня:</div><div>$x_{1,2} = \\dfrac{-(' + b + ') \\pm \\sqrt{' + D + '}}{2 \\cdot ' + a + '}$</div><div><b>Ответ:</b> $x_1 = ' + fmt(r1) + ',\\ x_2 = ' + fmt(r2) + '$</div>'; }
|
||
else if(D === 0){ s += '<div><b>Шаг 2.</b> $D = 0 \\Rightarrow$ один корень: $x = ' + fmt(-b/(2*a)) + '$</div>'; }
|
||
else s += '<div><b>Шаг 2.</b> $D < 0 \\Rightarrow$ корней нет.</div>';
|
||
out.innerHTML = s; renderMath(out);
|
||
if(!done){ done = true; setTimeout(()=>{ achievement('p8_disc'); bumpProgress('p8', 12); }, 300); }
|
||
}
|
||
[aE,bE,cE].forEach(e => e.addEventListener('input', refresh));
|
||
refresh();
|
||
})();
|
||
|
||
/* INIT INTERACTIVE 2 */
|
||
(function initParabola(){
|
||
const aE = document.getElementById('p8g-a'), bE = document.getElementById('p8g-b'), cE = document.getElementById('p8g-c');
|
||
const svg = document.getElementById('p8g-svg'), info = document.getElementById('p8g-info');
|
||
let done = false;
|
||
const W = 200, H = 140, x0 = W/2, y0 = H/2, sx = 14, sy = 14;
|
||
function refresh(){
|
||
const a = +aE.value, b = +bE.value, c = +cE.value;
|
||
document.getElementById('p8g-a-val').textContent = a.toFixed(1);
|
||
document.getElementById('p8g-b-val').textContent = b.toFixed(1);
|
||
document.getElementById('p8g-c-val').textContent = c.toFixed(1);
|
||
let path = '';
|
||
for(let i = 0; i <= 200; i++){
|
||
const x = -7 + i * 14 / 200;
|
||
const y = a*x*x + b*x + c;
|
||
const cx = x0 + x*sx, cy = y0 - y*sy;
|
||
path += (i === 0 ? 'M' : 'L') + cx.toFixed(2) + ',' + cy.toFixed(2) + ' ';
|
||
}
|
||
let svgC = '<line x1="0" y1="' + y0 + '" x2="' + W + '" y2="' + y0 + '" stroke="#999" stroke-width="0.5"/>';
|
||
svgC += '<line x1="' + x0 + '" y1="0" x2="' + x0 + '" y2="' + H + '" stroke="#999" stroke-width="0.5"/>';
|
||
svgC += '<path d="' + path + '" fill="none" stroke="var(--sec-acc)" stroke-width="1.8"/>';
|
||
if(a !== 0){
|
||
const D = b*b - 4*a*c;
|
||
if(D >= 0){
|
||
const r1 = (-b - Math.sqrt(D))/(2*a), r2 = (-b + Math.sqrt(D))/(2*a);
|
||
[r1, r2].forEach(r=>{ svgC += '<circle cx="' + (x0 + r*sx).toFixed(2) + '" cy="' + y0 + '" r="3" fill="#e91e63"/>'; });
|
||
info.innerHTML = 'D = ' + D.toFixed(2) + ' · ' + (D > 0 ? 'два корня: ' + fmt(r1) + ', ' + fmt(r2) : 'один корень: ' + fmt(r1));
|
||
} else info.innerHTML = 'D = ' + D.toFixed(2) + ' · корней нет (парабола не пересекает OX)';
|
||
}
|
||
svg.innerHTML = svgC;
|
||
if(!done){ done = true; setTimeout(()=>{ achievement('p8_parab'); bumpProgress('p8', 12); }, 400); }
|
||
}
|
||
[aE,bE,cE].forEach(e => e.addEventListener('input', refresh));
|
||
refresh();
|
||
})();
|
||
|
||
/* INIT INTERACTIVE 3 */
|
||
(function initCases(){
|
||
const data = {
|
||
pos: { eq: 'x^2 - 5x + 6 = 0', D: 1, roots: 'x_1 = 2,\\ x_2 = 3', svg: parab(1, -5, 6) },
|
||
zero:{ eq: 'x^2 - 4x + 4 = 0', D: 0, roots: 'x = 2', svg: parab(1, -4, 4) },
|
||
neg: { eq: 'x^2 + x + 1 = 0', D:-3, roots: 'корней нет', svg: parab(1, 1, 1) },
|
||
};
|
||
function parab(a, b, c){
|
||
const W = 180, H = 130, x0 = W/2, y0 = H/2, sx = 11, sy = 11;
|
||
let path = '';
|
||
for(let i = 0; i <= 200; i++){
|
||
const x = -8 + i * 16 / 200;
|
||
const y = a*x*x + b*x + c;
|
||
const cx = x0 + x*sx, cy = y0 - y*sy;
|
||
if(cy >= -20 && cy <= H + 20) path += (path ? 'L' : 'M') + cx.toFixed(2) + ',' + cy.toFixed(2) + ' ';
|
||
}
|
||
let s = '<svg viewBox="0 0 ' + W + ' ' + H + '" style="width:180px;height:130px;background:#fff;border:1px solid #ddd;border-radius:6px">';
|
||
s += '<line x1="0" y1="' + y0 + '" x2="' + W + '" y2="' + y0 + '" stroke="#999" stroke-width="0.5"/>';
|
||
s += '<line x1="' + x0 + '" y1="0" x2="' + x0 + '" y2="' + H + '" stroke="#999" stroke-width="0.5"/>';
|
||
s += '<path d="' + path + '" fill="none" stroke="#e91e63" stroke-width="1.5"/></svg>';
|
||
return s;
|
||
}
|
||
const detail = document.getElementById('p8c-detail');
|
||
let seen = new Set();
|
||
document.querySelectorAll('.case-card').forEach(c=>{
|
||
c.addEventListener('click', ()=>{
|
||
document.querySelectorAll('.case-card').forEach(x=>x.classList.remove('active'));
|
||
c.classList.add('active');
|
||
const d = data[c.dataset.case];
|
||
detail.innerHTML = '<div style="flex:1;min-width:180px"><div><b>Пример:</b> $' + d.eq + '$</div><div><b>$D$ =</b> ' + d.D + '</div><div><b>Корни:</b> $' + d.roots + '$</div></div><div>' + d.svg + '</div>';
|
||
renderMath(detail);
|
||
seen.add(c.dataset.case);
|
||
if(seen.size === 3){ achievement('p8_cases'); bumpProgress('p8', 12); }
|
||
});
|
||
});
|
||
document.querySelector('.case-card').click();
|
||
})();
|
||
|
||
/* INIT INTERACTIVE 4 */
|
||
(function initTrain(){
|
||
let cur = null, i = 1, score = 0;
|
||
function gen(){
|
||
const t = Math.floor(Math.random()*3);
|
||
if(t === 0){
|
||
const r1 = -3 + Math.floor(Math.random()*7), r2 = -3 + Math.floor(Math.random()*7);
|
||
if(r1 === r2) return gen();
|
||
return { a:1, b:-(r1+r2), c:r1*r2, n:2 };
|
||
}
|
||
if(t === 1){
|
||
const r = -4 + Math.floor(Math.random()*9);
|
||
return { a:1, b:-2*r, c:r*r, n:1 };
|
||
}
|
||
const b = -3 + Math.floor(Math.random()*7), c = 3 + Math.floor(Math.random()*8);
|
||
return { a:1, b, c, n:0 };
|
||
}
|
||
function newOne(){
|
||
cur = gen();
|
||
const a = cur.a, b = cur.b, c = cur.c;
|
||
const txt = 'x^2 ' + (b >= 0 ? '+ ' + b : '- ' + Math.abs(b)) + 'x ' + (c >= 0 ? '+ ' + c : '- ' + Math.abs(c)) + ' = 0';
|
||
document.getElementById('p8t-task').innerHTML = '$' + txt + '$';
|
||
document.getElementById('p8t-i').textContent = i;
|
||
document.getElementById('p8t-d-hint').innerHTML = 'Подскажу: $D = b^2 - 4ac$';
|
||
renderMath(document.getElementById('p8t-task'));
|
||
renderMath(document.getElementById('p8t-d-hint'));
|
||
document.getElementById('p8t-fb').style.display = 'none';
|
||
}
|
||
function answer(n){
|
||
const D = cur.b*cur.b - 4*cur.a*cur.c;
|
||
const fb = document.getElementById('p8t-fb');
|
||
fb.style.display = 'block';
|
||
if(n === cur.n){ score++; feedback(fb, true, '✓ Верно! D = ' + D); }
|
||
else feedback(fb, false, 'Нет. D = ' + D + ' → ' + (cur.n === 2 ? '2 корня' : cur.n === 1 ? '1 корень' : 'корней нет'));
|
||
document.getElementById('p8t-score').textContent = score;
|
||
if(i >= 10){ setTimeout(()=>{ feedback(fb, score >= 7, 'Итог: ' + score + '/10'); if(score >= 7){ achievement('p8_disc_train'); bumpProgress('p8', 16); confetti(); } }, 600); }
|
||
else { i++; setTimeout(newOne, 800); }
|
||
}
|
||
document.getElementById('p8t-start').addEventListener('click', ()=>{ i=1; score=0; document.getElementById('p8t-score').textContent = 0; newOne(); });
|
||
document.getElementById('p8t-2').addEventListener('click', ()=>answer(2));
|
||
document.getElementById('p8t-1').addEventListener('click', ()=>answer(1));
|
||
document.getElementById('p8t-0').addEventListener('click', ()=>answer(0));
|
||
})();
|
||
|
||
/* INIT INTERACTIVE 5 — пошаговый по одному шагу */
|
||
(function initSteps(){
|
||
const stage = document.getElementById('p8s-stage');
|
||
const goBtn = document.getElementById('p8s-go');
|
||
const nextBtn = document.getElementById('p8s-next');
|
||
const resetBtn = document.getElementById('p8s-reset');
|
||
let steps = [], idx = 0, awarded = false;
|
||
function buildSteps(a, b, c){
|
||
const D = b*b - 4*a*c;
|
||
const arr = [];
|
||
arr.push('<b>Шаг 1.</b> Выпишем коэффициенты: $a = ' + a + ',\\ b = ' + b + ',\\ c = ' + c + '$');
|
||
arr.push('<b>Шаг 2.</b> $D = b^2 - 4ac = (' + b + ')^2 - 4 \\cdot (' + a + ') \\cdot (' + c + ') = ' + (b*b) + ' - ' + (4*a*c) + ' = ' + D + '$');
|
||
arr.push('<b>Шаг 3.</b> ' + (D > 0 ? '$D > 0 \\Rightarrow$ два корня' : D === 0 ? '$D = 0 \\Rightarrow$ один корень' : '$D < 0 \\Rightarrow$ корней нет'));
|
||
if(D >= 0){
|
||
arr.push('<b>Шаг 4.</b> $\\sqrt{D} = \\sqrt{' + D + '} = ' + fmt(Math.sqrt(D)) + '$');
|
||
if(D > 0){
|
||
const r1 = (-b - Math.sqrt(D))/(2*a), r2 = (-b + Math.sqrt(D))/(2*a);
|
||
arr.push('<b>Шаг 5.</b> $x_{1,2} = \\dfrac{-b \\pm \\sqrt{D}}{2a} = \\dfrac{' + (-b) + ' \\pm ' + fmt(Math.sqrt(D)) + '}{' + (2*a) + '}$');
|
||
arr.push('<b>Шаг 6.</b> $x_1 = ' + fmt(r1) + ',\\ x_2 = ' + fmt(r2) + '$');
|
||
} else {
|
||
arr.push('<b>Шаг 5.</b> $x = \\dfrac{-b}{2a} = \\dfrac{' + (-b) + '}{' + (2*a) + '} = ' + fmt(-b/(2*a)) + '$');
|
||
}
|
||
}
|
||
arr.push('<b>Ответ:</b> ' + (D > 0 ? 'два корня' : D === 0 ? 'один корень' : 'корней нет'));
|
||
return arr;
|
||
}
|
||
function render(){
|
||
const html = steps.slice(0, idx + 1)
|
||
.map((s, k)=>`<div class="step-line" style="margin:8px 0;padding:10px 12px;background:var(--card);border-left:3px solid var(--sec-acc);border-radius:7px;animation:fadeIn .3s ease">${s}</div>`)
|
||
.join('');
|
||
stage.innerHTML = html;
|
||
renderMath(stage);
|
||
if(idx >= steps.length - 1){
|
||
nextBtn.disabled = true;
|
||
nextBtn.textContent = 'Готово';
|
||
if(!awarded){ awarded = true; achievement('p8_steps'); bumpProgress('p8', 14); confetti(); }
|
||
} else {
|
||
nextBtn.disabled = false;
|
||
nextBtn.textContent = 'Дальше (' + (idx + 1) + '/' + steps.length + ')';
|
||
}
|
||
}
|
||
goBtn.addEventListener('click', ()=>{
|
||
const a = +document.getElementById('p8s-a').value;
|
||
const b = +document.getElementById('p8s-b').value;
|
||
const c = +document.getElementById('p8s-c').value;
|
||
if(!a){ stage.innerHTML = '<p>$a$ не может быть нулём.</p>'; renderMath(stage); return; }
|
||
steps = buildSteps(a, b, c);
|
||
idx = 0; awarded = false;
|
||
goBtn.style.display = 'none';
|
||
nextBtn.style.display = ''; resetBtn.style.display = '';
|
||
nextBtn.disabled = false;
|
||
render();
|
||
});
|
||
nextBtn.addEventListener('click', ()=>{
|
||
if(idx < steps.length - 1){ idx++; render(); }
|
||
});
|
||
resetBtn.addEventListener('click', ()=>{
|
||
idx = 0; steps = []; awarded = false;
|
||
stage.innerHTML = '';
|
||
goBtn.style.display = ''; nextBtn.style.display = 'none'; resetBtn.style.display = 'none';
|
||
});
|
||
})();
|
||
|
||
/* INIT INTERACTIVE 6 */
|
||
(function initGraph(){
|
||
let cur = null, i = 1, score = 0;
|
||
function gen(){
|
||
const t = Math.floor(Math.random()*3);
|
||
const a = Math.random() < 0.5 ? 1 : -1;
|
||
if(t === 0){
|
||
const r1 = -3 + Math.floor(Math.random()*7), r2 = -3 + Math.floor(Math.random()*7);
|
||
if(r1 === r2) return gen();
|
||
return { a, b:-a*(r1+r2), c:a*r1*r2, kind:'pos' };
|
||
}
|
||
if(t === 1){
|
||
const r = -3 + Math.floor(Math.random()*7);
|
||
return { a, b:-2*a*r, c:a*r*r, kind:'zero' };
|
||
}
|
||
const v = 1 + Math.floor(Math.random()*4);
|
||
return { a:1, b:0, c:v, kind:'neg' };
|
||
}
|
||
function draw(){
|
||
const W = 200, H = 140, x0 = W/2, y0 = H/2, sx = 14, sy = 14;
|
||
let path = '';
|
||
for(let k = 0; k <= 200; k++){
|
||
const x = -7 + k * 14 / 200;
|
||
const y = cur.a*x*x + cur.b*x + cur.c;
|
||
const cx = x0 + x*sx, cy = y0 - y*sy;
|
||
if(cy >= -40 && cy <= H + 40) path += (path ? 'L' : 'M') + cx.toFixed(2) + ',' + cy.toFixed(2) + ' ';
|
||
}
|
||
let s = '<line x1="0" y1="' + y0 + '" x2="' + W + '" y2="' + y0 + '" stroke="#999" stroke-width="0.5"/>';
|
||
s += '<line x1="' + x0 + '" y1="0" x2="' + x0 + '" y2="' + H + '" stroke="#999" stroke-width="0.5"/>';
|
||
s += '<path d="' + path + '" fill="none" stroke="#e91e63" stroke-width="1.6"/>';
|
||
document.getElementById('p8r-svg').innerHTML = s;
|
||
}
|
||
function newOne(){
|
||
cur = gen();
|
||
document.getElementById('p8r-i').textContent = i;
|
||
document.getElementById('p8r-fb').style.display = 'none';
|
||
draw();
|
||
}
|
||
function answer(k){
|
||
const fb = document.getElementById('p8r-fb');
|
||
fb.style.display = 'block';
|
||
if(k === cur.kind){ score++; feedback(fb, true, '✓'); }
|
||
else feedback(fb, false, 'Нет. Правильно: ' + (cur.kind === 'pos' ? 'D > 0' : cur.kind === 'zero' ? 'D = 0' : 'D < 0'));
|
||
document.getElementById('p8r-score').textContent = score;
|
||
if(i >= 8){ setTimeout(()=>{ feedback(fb, score >= 6, 'Итог: ' + score + '/8'); if(score >= 6){ achievement('p8_graph'); bumpProgress('p8', 14); confetti(); } }, 600); }
|
||
else { i++; setTimeout(newOne, 800); }
|
||
}
|
||
document.getElementById('p8r-start').addEventListener('click', ()=>{ i=1; score=0; document.getElementById('p8r-score').textContent = 0; newOne(); });
|
||
document.getElementById('p8r-pos').addEventListener('click', ()=>answer('pos'));
|
||
document.getElementById('p8r-zero').addEventListener('click', ()=>answer('zero'));
|
||
document.getElementById('p8r-neg').addEventListener('click', ()=>answer('neg'));
|
||
})();
|
||
}
|
||
</script>
|
||
|
||
</body>
|
||
</html>
|