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 →» в шапке.
1775 lines
111 KiB
HTML
1775 lines
111 KiB
HTML
<!doctype html>
|
||
<html lang="ru">
|
||
<head>
|
||
<meta charset="UTF-8">
|
||
<meta http-equiv="Cache-Control" content="no-cache, no-store, must-revalidate">
|
||
<meta http-equiv="Pragma" content="no-cache">
|
||
<meta http-equiv="Expires" content="0">
|
||
<title>Алгебра 8 · Глава 3 · Неравенства</title>
|
||
<meta name="viewport" content="width=device-width,initial-scale=1">
|
||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/katex@0.16.9/dist/katex.min.css">
|
||
<script defer src="https://cdn.jsdelivr.net/npm/katex@0.16.9/dist/katex.min.js"></script>
|
||
<script defer src="https://cdn.jsdelivr.net/npm/katex@0.16.9/dist/contrib/auto-render.min.js"
|
||
onload="renderMathInElement(document.body,{delimiters:[{left:'$$',right:'$$',display:true},{left:'$',right:'$',display:false},{left:'\\[',right:'\\]',display:true},{left:'\\(',right:'\\)',display:false}],throwOnError:false})"></script>
|
||
<script src="/js/api.js" defer></script>
|
||
<script src="/js/xp.js" defer></script>
|
||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800&family=Manrope:wght@600;700;800;900&family=Unbounded:wght@700;800;900&family=JetBrains+Mono:wght@500;700&display=swap" rel="stylesheet">
|
||
<style>
|
||
:root{
|
||
--bg:#fafafa; --card:#fff; --card-soft:#f8fafc; --text:#0f172a; --ink:#0f172a; --muted:#64748b;
|
||
--border:#e2e8f0; --sh:0 1px 3px rgba(0,0,0,.06); --sh2:0 4px 14px rgba(0,0,0,.08);
|
||
--pri:#6366f1; --pri2:#4338ca; --pri-soft:#e0e7ff;
|
||
--acc:#06b6d4; --acc2:#0e7490; --acc-soft:#cffafe;
|
||
--ok:#10b981; --ok-bg:#d1fae5; --warn:#f59e0b; --warn-bg:#fef3c7;
|
||
--bad:#ef4444; --fail:#dc2626; --fail-bg:#fee2e2;
|
||
}
|
||
.dark{--bg:#0a0e1a; --card:#111827; --card-soft:#0f1729; --text:#e2e8f0; --ink:#e2e8f0; --muted:#94a3b8; --border:#1e293b}
|
||
*{margin:0;padding:0;box-sizing:border-box;-webkit-tap-highlight-color:transparent}
|
||
html,body{font-family:'Inter',system-ui,sans-serif;background:var(--bg);color:var(--text);line-height:1.55;font-size:15px}
|
||
button,input,select,textarea{font-family:inherit;font-size:inherit}
|
||
button{cursor:pointer;border:0;background:transparent;color:inherit}
|
||
a{color:inherit;text-decoration:none}
|
||
.ic{width:16px;height:16px;display:inline-block;flex-shrink:0;stroke:currentColor;fill:none;stroke-width:2;stroke-linecap:round;stroke-linejoin:round;vertical-align:middle}
|
||
|
||
/* HEADER */
|
||
.hdr{position:relative;background:linear-gradient(110deg,#4338ca 0%,#6366f1 50%,#8b5cf6 100%);color:#fff;padding:46px 22px 30px;overflow:hidden;border-bottom:2px solid rgba(199,180,255,.2);min-height:130px}
|
||
.hdr::before{content:'ГЛАВА 3';position:absolute;right:-12px;top:50%;transform:translateY(-50%);font-family:'Unbounded',sans-serif;font-size:clamp(5rem,15vw,11rem);font-weight:900;letter-spacing:-.04em;color:transparent;-webkit-text-stroke:1.5px rgba(220,210,255,.1);line-height:1;pointer-events:none;user-select:none;z-index:0}
|
||
.hdr-row{position:relative;z-index:1;display:flex;align-items:center;gap:14px;flex-wrap:wrap}
|
||
.hdr h1{font-family:'Unbounded',sans-serif;font-size:1.5rem;font-weight:900;letter-spacing:-.01em;line-height:1.3;padding-top:4px}
|
||
.hdr-sub{font-size:.85rem;opacity:.88;margin-top:6px;font-weight:500;line-height:1.4}
|
||
.hdr-side{margin-left:auto;display:flex;gap:8px;align-items:center;flex-wrap:wrap}
|
||
.hdr-btn{padding:7px 12px;border-radius:9px;background:rgba(255,255,255,.14);color:#fff;font-weight:600;font-size:.82rem;display:inline-flex;align-items:center;gap:6px;transition:background .15s;text-decoration:none}
|
||
.hdr-btn:hover{background:rgba(255,255,255,.24)}
|
||
|
||
/* MAIN GRID */
|
||
.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:'x > a';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:.10;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(99,102,241,.32)}
|
||
.hero-progress{flex:1;min-width:200px;max-width:280px}
|
||
.hp-label{font-size:.74rem;font-weight:700;color:var(--muted);text-transform:uppercase;letter-spacing:.06em;display:block;margin-bottom:5px}
|
||
.hp-bar{height:8px;background:rgba(99,102,241,.18);border-radius:5px;overflow:hidden}
|
||
.hp-fill{height:100%;background:linear-gradient(90deg,var(--pri),var(--acc));border-radius:5px;width:0%;transition:width .6s cubic-bezier(.16,1,.3,1)}
|
||
.hp-text{font-size:.78rem;color:var(--muted);font-weight:700;margin-top:4px;display:block}
|
||
.hero-xp-badge{display:inline-flex;align-items:center;gap:6px;padding:6px 12px;background:linear-gradient(135deg,var(--warn,#f59e0b),var(--pri));color:#fff;border-radius:99px;font-size:.82rem;font-weight:800;letter-spacing:.02em;box-shadow:0 4px 12px rgba(99,102,241,.22);font-family:'Unbounded',sans-serif}
|
||
.hero-xp-badge svg{flex-shrink:0}
|
||
|
||
/* 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(99,102,241,.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,#e0e7ff)}
|
||
.psel-card.final .psel-num{color:var(--warn)}
|
||
|
||
/* SECTION COLORS */
|
||
.sec[id="sec-p13"] { --sec-acc:#6366f1; --sec-acc-d:#4338ca; --sec-acc-soft:#e0e7ff; }
|
||
.sec[id="sec-p14"] { --sec-acc:#0ea5e9; --sec-acc-d:#0369a1; --sec-acc-soft:#e0f2fe; }
|
||
.sec[id="sec-p15"] { --sec-acc:#10b981; --sec-acc-d:#047857; --sec-acc-soft:#d1fae5; }
|
||
.sec[id="sec-p16"] { --sec-acc:#f59e0b; --sec-acc-d:#b45309; --sec-acc-soft:#fef3c7; }
|
||
.sec[id="sec-p17"] { --sec-acc:#8b5cf6; --sec-acc-d:#6d28d9; --sec-acc-soft:#ede9fe; }
|
||
.sec[id="sec-p18"] { --sec-acc:#e11d48; --sec-acc-d:#9f1239; --sec-acc-soft:#ffe4e6; }
|
||
.sec[id="sec-final3"] { --sec-acc:#d97706; --sec-acc-d:#b45309; --sec-acc-soft:#fef3c7; }
|
||
|
||
.sec{display:none;position:relative;animation:fadeIn .35s ease}
|
||
.sec.active{display:block}
|
||
@keyframes fadeIn{from{opacity:0;transform:translateY(8px)}to{opacity:1;transform:none}}
|
||
.sec::before{content:attr(data-watermark);position:absolute;right:-20px;top:10%;font-family:'Unbounded',sans-serif;font-size:clamp(6rem,18vw,14rem);font-weight:900;color:transparent;-webkit-text-stroke:1.5px var(--sec-acc-soft,var(--pri-soft));line-height:1;pointer-events:none;user-select:none;z-index:0;opacity:.35}
|
||
.sec-header{margin-bottom:22px;padding-bottom:14px;border-bottom:2px solid var(--sec-acc-soft,var(--pri-soft));position:relative;z-index:1}
|
||
.sec-num{display:inline-block;padding:4px 10px;background:linear-gradient(135deg,var(--sec-acc,var(--pri)),var(--sec-acc-d,var(--pri2)));color:#fff;border-radius:7px;font-family:'Unbounded',sans-serif;font-size:.78rem;font-weight:800;letter-spacing:.04em;margin-bottom:8px}
|
||
.sec-h{font-family:'Unbounded',sans-serif;font-size:1.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(99,102,241,.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(99,102,241,.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 .ic{width:18px;height:18px}
|
||
.card-title{font-family:'Unbounded',sans-serif;font-size:.82rem;font-weight:800;text-transform:uppercase;letter-spacing:.06em;color:var(--muted);flex:1}
|
||
.card-num{font-size:.74rem;font-weight:700;color:var(--muted);background:var(--sec-acc-soft,var(--pri-soft));padding:3px 7px;border-radius:5px}
|
||
.card-body{font-size:.94rem;line-height:1.65}
|
||
.card-body p{margin-bottom:8px}
|
||
.card-body p:last-child{margin-bottom:0}
|
||
|
||
/* WIDGET */
|
||
.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(99,102,241,.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(99,102,241,.10))}
|
||
|
||
/* BUTTONS */
|
||
.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}
|
||
|
||
/* INPUTS */
|
||
.tinp{padding:8px 12px;border:1.5px solid var(--border);border-radius:8px;background:var(--card);color:var(--text);transition:border-color .15s,box-shadow .15s}
|
||
.tinp:focus{outline:0;border-color:var(--sec-acc,var(--pri));box-shadow:0 0 0 3px var(--sec-acc-soft,var(--pri-soft))}
|
||
|
||
/* CHIPS / FEEDBACK */
|
||
.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))}
|
||
.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)}
|
||
|
||
/* 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}
|
||
.sidecard-row:last-child{margin-bottom:0}
|
||
@media(max-width:980px){.col-side{position:static;max-height:none}}
|
||
|
||
/* XP card */
|
||
.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}
|
||
|
||
/* SPOILER */
|
||
.spoiler{border:1px solid var(--border);border-radius:10px;background:var(--card);margin:10px 0;overflow:hidden}
|
||
.spoiler summary{padding:8px 14px;background:var(--sec-acc-soft,var(--pri-soft));font-weight:700;cursor:pointer;font-size:.88rem;color:var(--sec-acc-d,var(--pri2));list-style:none;display:flex;align-items:center;gap:8px}
|
||
.spoiler summary::-webkit-details-marker{display:none}
|
||
.spoiler summary::before{content:'+';font-size:1.2rem;font-weight:900;color:var(--sec-acc,var(--pri));width:18px}
|
||
.spoiler[open] summary::before{content:'−'}
|
||
.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}
|
||
|
||
/* TABLES */
|
||
.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}
|
||
|
||
/* ACH popup */
|
||
.ach-popup{position:fixed;top:80px;right:18px;background:linear-gradient(135deg,#10b981,#06b6d4);color:#fff;padding:12px 18px;border-radius:11px;font-weight:700;font-size:.9rem;box-shadow:0 8px 28px rgba(16,185,129,.45);z-index:1002;display:none;align-items:center;gap:8px;animation:achIn .45s cubic-bezier(.34,1.56,.64,1) forwards;max-width:340px}
|
||
.ach-popup.show{display:flex}
|
||
@keyframes achIn{from{opacity:0;transform:translateX(40px)}to{opacity:1;transform:none}}
|
||
|
||
/* SCORE */
|
||
.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}
|
||
.actions{display:flex;gap:8px;flex-wrap:wrap;margin-top:10px}
|
||
|
||
/* 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(99,102,241,.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}
|
||
}
|
||
|
||
/* NUMBER LINE — спец-виджет для §15-§17 */
|
||
.numline{background:var(--card);border:1px solid var(--border);border-radius:10px;padding:14px 6px;margin:10px 0;overflow-x:auto}
|
||
.numline svg{width:100%;min-width:520px;height:88px;display:block}
|
||
|
||
/* 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}
|
||
</style>
|
||
</head>
|
||
<body>
|
||
|
||
<header class="hdr">
|
||
<div class="hdr-row">
|
||
<div>
|
||
<h1>Алгебра 8 · Глава 3</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-ch2" class="hdr-btn" title="К Главе 2">
|
||
<svg class="ic" viewBox="0 0 24 24"><polyline points="15 18 9 12 15 6"/></svg>
|
||
Глава 2
|
||
</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>Равенства — это точечный ответ: $x = 5$. Неравенства — это <b>множество</b> ответов: $x > 3$ означает «3, 4, 5, и так далее, бесконечно». Эта глава научит сравнивать числа, решать неравенства всех типов и понимать <b>метод интервалов</b> — универсальный инструмент.</p>
|
||
<div class="hero-row">
|
||
<button class="btn-primary" onclick="goTo('p13')">
|
||
<svg class="ic" viewBox="0 0 24 24"><polygon points="6 4 20 12 6 20 6 4" fill="currentColor" stroke="none"/></svg>
|
||
Начать § 13
|
||
</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-p13" class="sec" data-watermark="<>">
|
||
<div class="sec-header"><span class="sec-num">§ 13</span><h2 class="sec-h">Числовые неравенства и их свойства</h2></div>
|
||
<div id="p13-body"></div>
|
||
</section>
|
||
|
||
<section id="sec-p14" class="sec" data-watermark="±">
|
||
<div class="sec-header"><span class="sec-num">§ 14</span><h2 class="sec-h">Сложение и умножение числовых неравенств. Оценка значений</h2></div>
|
||
<div id="p14-body"></div>
|
||
</section>
|
||
|
||
<section id="sec-p15" class="sec" data-watermark="[;]">
|
||
<div class="sec-header"><span class="sec-num">§ 15</span><h2 class="sec-h">Числовые промежутки. Линейные неравенства</h2></div>
|
||
<div id="p15-body"></div>
|
||
</section>
|
||
|
||
<section id="sec-p16" class="sec" data-watermark="∩">
|
||
<div class="sec-header"><span class="sec-num">§ 16</span><h2 class="sec-h">Системы и совокупности линейных неравенств</h2></div>
|
||
<div id="p16-body"></div>
|
||
</section>
|
||
|
||
<section id="sec-p17" class="sec" data-watermark="x²">
|
||
<div class="sec-header"><span class="sec-num">§ 17</span><h2 class="sec-h">Квадратные неравенства. Метод интервалов</h2></div>
|
||
<div id="p17-body"></div>
|
||
</section>
|
||
|
||
<section id="sec-p18" class="sec" data-watermark="1/x">
|
||
<div class="sec-header"><span class="sec-num">§ 18</span><h2 class="sec-h">Дробно-рациональные неравенства</h2></div>
|
||
<div id="p18-body"></div>
|
||
</section>
|
||
|
||
<section id="sec-final3" class="sec" data-watermark="★">
|
||
<div class="sec-header"><span class="sec-num" style="background:linear-gradient(135deg,#f59e0b,#8b5cf6)">Финал главы</span><h2 class="sec-h">Итоги. Практическая и увлекательная математика</h2></div>
|
||
<div id="final3-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» · Глава 3 · 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: 'p13',
|
||
progress: { p13:0, p14:0, p15:0, p16:0, p17:0, p18:0, final3:0 },
|
||
achievements: new Map(),
|
||
xp: 0,
|
||
level: 1,
|
||
};
|
||
|
||
const XP_LEVELS = null;
|
||
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: 'Начало главы 3!',
|
||
p13_compare: 'Сравнил числа',
|
||
p13_props: 'Свойства неравенств',
|
||
p13_sign: 'Знак не меняется',
|
||
p13_flip: 'Знак меняется',
|
||
p13_chain: 'Цепочка свойств',
|
||
p14_estimate: 'Оценил выражение',
|
||
p14_add: 'Сложил неравенства',
|
||
p14_mul: 'Перемножил неравенства',
|
||
p14_drag: 'Сортировка операций',
|
||
p14_train: 'Тренажёр оценки',
|
||
p15_line: 'Промежутки на прямой',
|
||
p15_convert: 'Конвертация записи',
|
||
p15_solver: 'Линейное решено',
|
||
p15_train: 'Тренажёр линейных',
|
||
p15_drag: 'Промежуток к неравенству',
|
||
p16_intersect: 'Пересечение промежутков',
|
||
p16_solver: 'Система решена',
|
||
p16_union: 'Совокупность',
|
||
p16_train: 'Тренажёр систем',
|
||
p17_parab: 'Парабола и знак',
|
||
p17_intervals: 'Метод интервалов',
|
||
p17_solver: 'Квадратное неравенство',
|
||
p17_train: 'Тренажёр квадратных',
|
||
p17_drag: 'График к решению',
|
||
p18_intervals: 'Интервалы для дробей',
|
||
p18_solver: 'Дробно-рациональное',
|
||
p18_odz: 'ОДЗ учтена',
|
||
boss_b1: 'Босс §13 повержен',
|
||
boss_b2: 'Босс §14 повержен',
|
||
boss_b3: 'Босс §15 повержен',
|
||
boss_b4: 'Босс §16 повержен',
|
||
boss_b5: 'Босс §17 повержен',
|
||
boss_b6: 'Босс §18 повержен',
|
||
boss_b7: 'Чемпион неравенств',
|
||
all_bosses: 'Все 7 боссов побеждены!',
|
||
prac_streak: 'Серия из 5 верных',
|
||
};
|
||
|
||
function loadProgress(){
|
||
try{
|
||
const s = localStorage.getItem('algebra8_ch3_progress');
|
||
if(s) Object.assign(STATE.progress, JSON.parse(s));
|
||
const a = localStorage.getItem('algebra8_ch3_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 для всех глав
|
||
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_ch3_progress', JSON.stringify(STATE.progress));
|
||
localStorage.setItem('algebra8_ch3_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 прогресса */
|
||
const _TB_SLUG = 'algebra-8-ch3';
|
||
const _markedRead = new Set();
|
||
let _pendingProgressBody = null, _progressTimer = null;
|
||
function _flushProgress(){
|
||
const body = _pendingProgressBody;
|
||
_pendingProgressBody = null;
|
||
if(!body) return;
|
||
const tok = (window.LS && LS.getToken) ? LS.getToken() : '';
|
||
if(!tok) return;
|
||
fetch('/api/textbooks/' + _TB_SLUG + '/progress', {
|
||
method:'POST',
|
||
headers:{ 'Content-Type':'application/json', 'Authorization':'Bearer ' + tok },
|
||
body: JSON.stringify(body),
|
||
keepalive: true,
|
||
}).catch(()=>{});
|
||
}
|
||
function _queueProgress(patch){
|
||
_pendingProgressBody = Object.assign(_pendingProgressBody || {}, patch);
|
||
if(_progressTimer) clearTimeout(_progressTimer);
|
||
_progressTimer = setTimeout(_flushProgress, 600);
|
||
}
|
||
function markLastPara(id){ _queueProgress({ last_para: id }); }
|
||
function markParaRead(id){
|
||
if(_markedRead.has(id)) return;
|
||
_markedRead.add(id);
|
||
_queueProgress({ mark_read: id });
|
||
}
|
||
window.addEventListener('beforeunload', _flushProgress);
|
||
function loadServerReadState(){
|
||
const tok = (window.LS && LS.getToken) ? LS.getToken() : '';
|
||
if(!tok) return;
|
||
fetch('/api/textbooks/' + _TB_SLUG, { headers:{ 'Authorization':'Bearer ' + tok } })
|
||
.then(r => r.ok ? r.json() : null)
|
||
.then(d => {
|
||
if(!d || !d.progress) return;
|
||
(d.progress.read || []).forEach(k => {
|
||
_markedRead.add(k);
|
||
if((STATE.progress[k] || 0) < 50) STATE.progress[k] = 100;
|
||
});
|
||
saveProgress(); refreshProgressUI();
|
||
})
|
||
.catch(()=>{});
|
||
}
|
||
|
||
function addXp(n, src){
|
||
if(!n) return;
|
||
const prev = STATE.level;
|
||
STATE.xp = Math.max(0, (STATE.xp || 0) + n);
|
||
STATE.level = calcLevel(STATE.xp);
|
||
saveProgress();
|
||
refreshProgressUI();
|
||
if(window.LS && window.LS.xp) window.LS.xp.add(n, 'algebra8-ch3-' + (src || 'misc'));
|
||
if(STATE.level > prev){
|
||
const pop = document.getElementById('ach-popup');
|
||
if(pop){
|
||
document.getElementById('ach-text').textContent = 'Уровень ' + STATE.level + '!';
|
||
pop.classList.add('show');
|
||
setTimeout(()=>pop.classList.remove('show'), 2600);
|
||
}
|
||
if(window.confetti) try { confetti(); } catch(e){}
|
||
}
|
||
}
|
||
function refreshProgressUI(){
|
||
const total = Math.round(Object.values(STATE.progress).reduce((a,b)=>a+b,0) / 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';
|
||
}
|
||
if(STATE.current && document.getElementById('sidebar-content')){
|
||
try { buildSidebar(STATE.current); } catch(e){}
|
||
}
|
||
}
|
||
function achievement(id, text){
|
||
if(STATE.achievements.has(id)) return;
|
||
STATE.achievements.set(id, text || ACH_LABELS[id] || id);
|
||
saveProgress();
|
||
const pop = document.getElementById('ach-popup');
|
||
if(pop){
|
||
document.getElementById('ach-text').textContent = text || ACH_LABELS[id] || id;
|
||
pop.classList.add('show');
|
||
setTimeout(()=>pop.classList.remove('show'), 3300);
|
||
}
|
||
addXp(20, 'ach-' + id);
|
||
}
|
||
|
||
/* PARAGRAPH LIST */
|
||
const PARAS = [
|
||
{ id:'p13', num:'§ 13', name:'Числовые неравенства', sub:'Свойства' },
|
||
{ id:'p14', num:'§ 14', name:'Сложение и умножение', sub:'Оценка значений' },
|
||
{ id:'p15', num:'§ 15', name:'Промежутки и линейные', sub:'Графика и решение' },
|
||
{ id:'p16', num:'§ 16', name:'Системы неравенств', sub:'Пересечение и объединение' },
|
||
{ id:'p17', num:'§ 17', name:'Квадратные неравенства', sub:'Метод интервалов' },
|
||
{ id:'p18', num:'§ 18', name:'Дробно-рациональные', sub:'Интервалы и ОДЗ' },
|
||
{ id:'final3', 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 = {
|
||
p13:()=>buildP13(), p14:()=>buildP14(),
|
||
p15:()=>buildP15stub(), p16:()=>buildP16stub(),
|
||
p17:()=>buildP17stub(), p18:()=>buildP18stub(),
|
||
final3:()=>buildFinal3stub(),
|
||
};
|
||
function ensureBuilt(id){
|
||
if(BUILT.has(id)) return;
|
||
const fn = BUILDERS[id];
|
||
if(fn){ fn(); BUILT.add(id); }
|
||
}
|
||
function goTo(id){
|
||
STATE.current = id;
|
||
ensureBuilt(id);
|
||
document.querySelectorAll('.sec').forEach(s=>s.classList.remove('active'));
|
||
const el = document.getElementById('sec-' + id);
|
||
if(el) el.classList.add('active');
|
||
document.querySelectorAll('.psel-card').forEach(c=>c.classList.toggle('active', c.dataset.id === id));
|
||
buildSidebar(id);
|
||
window.scrollTo({top:0, behavior:'smooth'});
|
||
if((STATE.progress[id]||0) < 10) bumpProgress(id, 10);
|
||
if(window.renderMathInElement) setTimeout(()=>renderMath(el), 0);
|
||
setTimeout(()=>{ try { wrapGlossary(el); } catch(e){} }, 60);
|
||
markLastPara(id);
|
||
}
|
||
|
||
/* SIDEBAR */
|
||
const SIDEBARS = {
|
||
p13: { title:'Шпаргалка § 13', rows:[
|
||
['$a > b$','значит $a - b > 0$'],
|
||
['Транзитивность','$a > b,\\ b > c \\Rightarrow a > c$'],
|
||
['$+c$ к обеим','знак не меняется'],
|
||
['$\\times k > 0$','знак не меняется'],
|
||
['$\\times k < 0$','знак МЕНЯЕТСЯ'],
|
||
]},
|
||
p14: { title:'Шпаргалка § 14', rows:[
|
||
['Сложение','$a < b,\\ c < d \\Rightarrow a + c < b + d$'],
|
||
['Умножение (+)','$0<a<b,\\ 0<c<d \\Rightarrow ac<bd$'],
|
||
['Оценка','если $a \\leq x \\leq b$ и $c \\leq y \\leq d$, то $a+c \\leq x+y \\leq b+d$'],
|
||
['Разность','$x - y$: $a - d \\leq x-y \\leq b - c$'],
|
||
]},
|
||
p15: { title:'Шпаргалка § 15', rows:[
|
||
['$[a;b]$','отрезок: $a \\leq x \\leq b$'],
|
||
['$(a;b)$','интервал: $a < x < b$'],
|
||
['$[a;b)$','полуинтервал'],
|
||
['$(-\\infty;a)$','луч $x < a$'],
|
||
['Линейное','$ax + b > 0 \\Rightarrow$ изоляция $x$'],
|
||
]},
|
||
p16: { title:'Шпаргалка § 16', rows:[
|
||
['Система','И · пересечение решений'],
|
||
['Совокупность','ИЛИ · объединение решений'],
|
||
['Открытая точка','знак $<$ или $>$ без равенства'],
|
||
]},
|
||
p17: { title:'Шпаргалка § 17', rows:[
|
||
['Метод интервалов','найти корни → отметить → знаки'],
|
||
['$a > 0$','парабола вверх'],
|
||
['$a < 0$','парабола вниз'],
|
||
['$D < 0$','знак не меняется'],
|
||
]},
|
||
p18: { title:'Шпаргалка § 18', rows:[
|
||
['$\\dfrac{f(x)}{g(x)} \\geq 0$','знаки числителя и знаменателя'],
|
||
['ОДЗ','знаменатель $\\neq 0$'],
|
||
['Полюс','выколотая точка на прямой'],
|
||
]},
|
||
final3: { title:'Финал главы', rows:[
|
||
['7 боссов','один на каждый § + общий'],
|
||
['Награда','«Чемпион неравенств»'],
|
||
['Практика','случайные задачи всей главы'],
|
||
]},
|
||
};
|
||
|
||
const TIPS = [
|
||
{ sec:'p13', html:'При умножении на $-1$ <b>знак неравенства меняется</b>. Это самое частое место ошибок.' },
|
||
{ sec:'p14', html:'Складывать можно <b>одного знака</b>: $a<b$ и $c<d$, но НЕ $a<b$ и $c>d$.' },
|
||
{ sec:'p15', html:'Квадратная скобка $[$ — точка <b>входит</b> в множество. Круглая $($ — <b>выколота</b>.' },
|
||
{ sec:'p16', html:'Система — это «И», пересечение. Совокупность — «ИЛИ», объединение. Не путайте.' },
|
||
{ sec:'p17', html:'Знак между корнями определяется коэффициентом $a$: если $a>0$, между корнями знак минус.' },
|
||
{ sec:'p18', html:'Точки, где знаменатель $=0$, всегда <b>выколотые</b>, даже при нестрогом неравенстве.' },
|
||
{ sec:'final3', html:'Метод интервалов — универсальный. Освойте его на §17, и §18 пойдёт легко.' },
|
||
];
|
||
|
||
function buildSidebar(id){
|
||
const box = document.getElementById('sidebar-content');
|
||
const sb = SIDEBARS[id] || SIDEBARS.p13;
|
||
let html = '';
|
||
|
||
// XP card
|
||
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>${xpNext} XP</span></div>
|
||
</div>`;
|
||
|
||
// Шпаргалка
|
||
html += `<div class="sidecard"><h4>${sb.title}</h4>`;
|
||
sb.rows.forEach(([k,v])=>{
|
||
html += `<div class="sidecard-row"><b>${k}</b> ${v ? '— ' + v : ''}</div>`;
|
||
});
|
||
html += '</div>';
|
||
|
||
// Совет дня
|
||
const tip = TIPS.find(t => t.sec === id) || TIPS[0];
|
||
html += `<div class="sidecard" style="background:linear-gradient(135deg,var(--warn-bg,#fef3c7),var(--pri-soft));border-color:var(--warn,#f59e0b)">
|
||
<h4 style="color:#92400e;display:flex;align-items:center;gap:6px">
|
||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" style="width:14px;height:14px"><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_ch3_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_ch3_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 = {p13:'§13',p14:'§14',p15:'§15',p16:'§16',p17:'§17',p18:'§18',final3:'Финал'};
|
||
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 */
|
||
let _confettiCanvas = null, _confettiParticles = [], _confettiRaf = null;
|
||
function confetti(){
|
||
if(!_confettiCanvas){
|
||
_confettiCanvas = document.createElement('canvas');
|
||
_confettiCanvas.style.cssText = 'position:fixed;top:0;left:0;width:100%;height:100%;pointer-events:none;z-index:9999';
|
||
document.body.appendChild(_confettiCanvas);
|
||
}
|
||
const c = _confettiCanvas;
|
||
c.width = window.innerWidth; c.height = window.innerHeight;
|
||
const ctx = c.getContext('2d');
|
||
const colors = ['#6366f1','#8b5cf6','#06b6d4','#10b981','#f59e0b'];
|
||
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();
|
||
}
|
||
|
||
/* DnD shared sorter */
|
||
function setupSorter(cfg){
|
||
const placed = {};
|
||
const pool = document.getElementById(cfg.poolId);
|
||
const scope = document.querySelector(cfg.scopeSelector);
|
||
if(!pool || !scope) return { placed, render: ()=>{}, reset: ()=>{} };
|
||
pool.classList.add('dnd-pool');
|
||
if(cfg.columnLayout) pool.classList.add('col');
|
||
let armed = null;
|
||
function buildChip(it, isPlaced){
|
||
const e = document.createElement('div');
|
||
e.className = 'dnd-chip' + (isPlaced ? ' placed' : '');
|
||
e.dataset.id = it.id;
|
||
e.innerHTML = '<span class="dnd-txt">' + it.html + '</span><span class="dnd-x" title="Убрать">×</span>';
|
||
attach(e, it.id);
|
||
return e;
|
||
}
|
||
function attach(elm, itId){
|
||
let ghost = null, dragging = false, sx = 0, sy = 0;
|
||
elm.addEventListener('pointerdown', ev => {
|
||
if(ev.button !== undefined && ev.button !== 0) return;
|
||
if(ev.target.classList && ev.target.classList.contains('dnd-x')){
|
||
ev.stopPropagation();
|
||
if(placed[itId]){ delete placed[itId]; render(); }
|
||
else if(armed === itId){ armed = null; render(); }
|
||
return;
|
||
}
|
||
sx = ev.clientX; sy = ev.clientY;
|
||
const r = elm.getBoundingClientRect();
|
||
const ox = ev.clientX - r.left, oy = ev.clientY - r.top;
|
||
try { elm.setPointerCapture(ev.pointerId); } catch(e){}
|
||
function onMove(e){
|
||
const dx = e.clientX - sx, dy = e.clientY - sy;
|
||
if(!dragging && Math.hypot(dx, dy) > 8){
|
||
dragging = true;
|
||
ghost = elm.cloneNode(true);
|
||
ghost.classList.remove('armed');
|
||
ghost.style.cssText = 'position:fixed;z-index:9999;pointer-events:none;opacity:.9;transform:rotate(-2.5deg);box-shadow:0 14px 36px rgba(0,0,0,.32);width:' + r.width + 'px;left:' + (e.clientX - ox) + 'px;top:' + (e.clientY - oy) + 'px';
|
||
document.body.appendChild(ghost);
|
||
elm.classList.add('dragging');
|
||
}
|
||
if(dragging && ghost){
|
||
ghost.style.left = (e.clientX - ox) + 'px';
|
||
ghost.style.top = (e.clientY - oy) + 'px';
|
||
const under = document.elementsFromPoint(e.clientX, e.clientY);
|
||
scope.querySelectorAll('.drop-box.over, .dnd-pool.over').forEach(n => n.classList.remove('over'));
|
||
const tgt = under.find(n => n.classList && (n.classList.contains('drop-box') || n.classList.contains('dnd-pool')));
|
||
if(tgt) tgt.classList.add('over');
|
||
}
|
||
}
|
||
function onUp(e){
|
||
elm.removeEventListener('pointermove', onMove);
|
||
elm.removeEventListener('pointerup', onUp);
|
||
elm.removeEventListener('pointercancel', onUp);
|
||
elm.classList.remove('dragging');
|
||
if(ghost){ ghost.remove(); ghost = null; }
|
||
scope.querySelectorAll('.drop-box.over, .dnd-pool.over').forEach(n => n.classList.remove('over'));
|
||
if(dragging){
|
||
const under = document.elementsFromPoint(e.clientX, e.clientY);
|
||
const box = under.find(n => n.classList && n.classList.contains('drop-box'));
|
||
const pl = under.find(n => n.classList && n.classList.contains('dnd-pool'));
|
||
if(box){
|
||
const di = box.querySelector('[data-cat]');
|
||
if(di){ placed[itId] = di.dataset.cat; armed = null; render(); return; }
|
||
} else if(pl){ delete placed[itId]; armed = null; render(); return; }
|
||
} else {
|
||
if(placed[itId]){ delete placed[itId]; armed = null; render(); }
|
||
else { armed = (armed === itId) ? null : itId; render(); }
|
||
}
|
||
dragging = false;
|
||
}
|
||
elm.addEventListener('pointermove', onMove);
|
||
elm.addEventListener('pointerup', onUp);
|
||
elm.addEventListener('pointercancel', onUp);
|
||
});
|
||
}
|
||
function attachBoxTaps(){
|
||
scope.querySelectorAll('.drop-box').forEach(box => {
|
||
box.addEventListener('click', ev => {
|
||
if(!armed) return;
|
||
if(ev.target.closest('.dnd-chip')) return;
|
||
const di = box.querySelector('[data-cat]');
|
||
if(di){ placed[armed] = di.dataset.cat; armed = null; render(); }
|
||
});
|
||
});
|
||
}
|
||
function render(){
|
||
pool.innerHTML = '';
|
||
cfg.items.forEach(it => { if(placed[it.id]) return; const c = buildChip(it, false); if(armed === it.id) c.classList.add('armed'); pool.appendChild(c); });
|
||
cfg.cats.forEach(cat => {
|
||
const box = scope.querySelector('.drop-items[data-cat="' + cat + '"]');
|
||
if(!box) return;
|
||
box.innerHTML = '';
|
||
cfg.items.forEach(it => { if(placed[it.id] !== cat) return; box.appendChild(buildChip(it, true)); });
|
||
});
|
||
if(window.renderMathInElement) try { renderMath(scope); } catch(_){}
|
||
}
|
||
attachBoxTaps();
|
||
render();
|
||
return { placed, render, reset(){ for(const k in placed) delete placed[k]; armed = null; render(); } };
|
||
}
|
||
const 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 */
|
||
const GLOSSARY = [
|
||
{ term:'числовое неравенство', def:'Запись вида $a > b$ или $a < b$, где $a, b$ — числа.', sec:'p13', aliases:['числовое неравенство','числовых неравенств','числового неравенства'] },
|
||
{ term:'свойства неравенств', def:'Транзитивность, прибавление, умножение на положительное/отрицательное (смена знака).', sec:'p13', aliases:['свойства неравенств','свойств неравенств'] },
|
||
{ term:'оценка значения', def:'Если $a \\leq x \\leq b$, найти границы для $x+y$, $xy$, $x/y$, и т.п.', sec:'p14', aliases:['оценка значения','оценка значений'] },
|
||
{ term:'числовой промежуток', def:'Отрезок, интервал, полуинтервал или луч на числовой прямой.', sec:'p15', aliases:['числовой промежуток','числового промежутка','числовые промежутки','числовых промежутков','промежуток','промежутка','промежутки','промежутков'] },
|
||
{ term:'линейное неравенство', def:'Неравенство вида $ax + b > 0$ (или с другим знаком), $a \\neq 0$.', sec:'p15', aliases:['линейное неравенство','линейных неравенств','линейные неравенства','линейного неравенства'] },
|
||
{ term:'система неравенств', def:'Несколько неравенств, выполненных одновременно (И). Решение — пересечение.', sec:'p16', aliases:['система неравенств','системы неравенств','систем неравенств','системе неравенств'] },
|
||
{ term:'совокупность неравенств', def:'Несколько неравенств, из которых выполняется хотя бы одно (ИЛИ). Решение — объединение.', sec:'p16', aliases:['совокупность неравенств','совокупности неравенств','совокупностей неравенств'] },
|
||
{ term:'квадратное неравенство', def:'Неравенство $ax^2 + bx + c \\gtrless 0$, $a \\neq 0$.', sec:'p17', aliases:['квадратное неравенство','квадратных неравенств','квадратные неравенства','квадратного неравенства'] },
|
||
{ term:'метод интервалов', def:'Найти корни → отметить на прямой → определить знаки на интервалах.', sec:'p17', aliases:['метод интервалов','методом интервалов','методу интервалов'] },
|
||
{ term:'дробно-рациональное неравенство', def:'Неравенство, содержащее дробь $\\dfrac{f(x)}{g(x)}$. Решается методом интервалов с учётом ОДЗ.', sec:'p18', aliases:['дробно-рациональное','дробно-рациональных','дробно-рациональные','дробно-рациональным'] },
|
||
{ term:'ОДЗ', def:'Область допустимых значений. Для дробей — знаменатель $\\neq 0$.', sec:'p18', aliases:['ОДЗ','область допустимых значений'] },
|
||
{ term:'выколотая точка', def:'Точка на прямой, не входящая в множество (например, при строгом $<$ или знаменатель $=0$).', sec:'p15', 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(elm){
|
||
const g = GLOSSARY.find(x => x.term === elm.dataset.gloss);
|
||
if(!g) return;
|
||
tip.innerHTML = '<b>' + g.term[0].toUpperCase() + g.term.slice(1) + '</b><div style="margin-top:4px">' + g.def + '</div><div style="margin-top:6px;font-size:.72rem;color:var(--muted);text-transform:uppercase;letter-spacing:.06em">См. § ' + g.sec.replace('p','') + '</div>';
|
||
if(window.renderMathInElement) renderMath(tip);
|
||
const r = elm.getBoundingClientRect();
|
||
tip.classList.add('show');
|
||
const tw = tip.offsetWidth, th = tip.offsetHeight;
|
||
let left = r.left, top = r.bottom + 8;
|
||
if(left + tw > window.innerWidth - 12) left = window.innerWidth - tw - 12;
|
||
if(top + th > window.innerHeight - 12) top = r.top - th - 8;
|
||
tip.style.left = Math.max(8, left) + 'px';
|
||
tip.style.top = Math.max(8, top) + 'px';
|
||
}
|
||
function hide(){ tip.classList.remove('show'); }
|
||
document.addEventListener('mouseover', e => {
|
||
const elm = e.target.closest && e.target.closest('.gloss-term');
|
||
if(elm && !lockOpen) show(elm);
|
||
});
|
||
document.addEventListener('mouseout', e => {
|
||
const elm = e.target.closest && e.target.closest('.gloss-term');
|
||
if(elm && !lockOpen) hide();
|
||
});
|
||
document.addEventListener('click', e => {
|
||
const elm = e.target.closest && e.target.closest('.gloss-term');
|
||
if(elm){
|
||
if(lockOpen === elm){ lockOpen = null; hide(); }
|
||
else { lockOpen = elm; show(elm); }
|
||
} else if(lockOpen && !e.target.closest('.gloss-tip')){
|
||
lockOpen = null; hide();
|
||
}
|
||
});
|
||
}
|
||
|
||
/* SEARCH */
|
||
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 }));
|
||
[
|
||
['Формула','a > b ⟺ a − b > 0','§13 — определение','p13'],
|
||
['Формула','×k<0 ⟹ знак МЕНЯЕТСЯ','§13 — главное правило','p13'],
|
||
['Формула','a<b, c<d ⟹ a+c<b+d','§14 — сложение неравенств','p14'],
|
||
['Формула','[a;b], (a;b), [a;b)','§15 — обозначения промежутков','p15'],
|
||
['Формула','Метод интервалов: корни → знаки','§17 — алгоритм','p17'],
|
||
].forEach(([k,t,d,s]) => arr.push({ kind:k, title:t, desc:d, sec:s }));
|
||
arr.push({ kind:'Финал', title:'Боссы главы', desc:'7 проверочных боссов', sec:'final3' });
|
||
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 score(q, it){
|
||
const t = (it.title + ' ' + it.desc).toLowerCase();
|
||
if(t.includes(q)) return 100 + (it.title.toLowerCase().startsWith(q) ? 50 : 0);
|
||
let s = 0; q.split(/\s+/).forEach(w => { if(w && t.includes(w)) s += 10; });
|
||
return s;
|
||
}
|
||
function rank(q){
|
||
q = q.trim().toLowerCase();
|
||
if(!q) return SEARCH_INDEX.slice(0, 12);
|
||
return SEARCH_INDEX.map(it => ({ it, s: score(q, it) })).filter(x => x.s > 0).sort((a,b) => b.s - a.s).slice(0, 20).map(x => x.it);
|
||
}
|
||
function render(){
|
||
cur = 0;
|
||
if(!rows.length){ out.innerHTML = '<div class="search-empty">Ничего не найдено</div>'; return; }
|
||
out.innerHTML = rows.map((r, i) => `<button class="search-row${i === 0 ? ' active' : ''}" data-i="${i}"><div class="sr-kind">${r.kind}</div><div class="sr-title">${r.title}</div>${r.desc ? `<div class="sr-desc">${r.desc.length > 90 ? r.desc.slice(0, 90) + '…' : r.desc}</div>` : ''}</button>`).join('');
|
||
out.querySelectorAll('.search-row').forEach(b => b.addEventListener('click', ()=>{ cur = +b.dataset.i; pick(); }));
|
||
}
|
||
function pick(){
|
||
const r = rows[cur]; if(!r) return;
|
||
close();
|
||
goTo(r.sec);
|
||
if(r.gloss){
|
||
setTimeout(()=>{
|
||
const sec = document.getElementById('sec-' + r.sec);
|
||
const elm = sec && sec.querySelector('[data-gloss="' + r.gloss + '"]');
|
||
if(elm){ elm.scrollIntoView({ behavior:'smooth', block:'center' }); elm.style.transition = 'background .3s'; elm.style.background = 'var(--warn,#f59e0b)'; setTimeout(()=>{ elm.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();
|
||
}
|
||
});
|
||
}
|
||
|
||
/* SIDEBAR TOGGLE */
|
||
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(); });
|
||
}
|
||
|
||
/* INIT */
|
||
function init(){
|
||
loadProgress();
|
||
initTheme();
|
||
initSidebarToggle();
|
||
initGlossaryTip();
|
||
initSearch();
|
||
buildParaSelector();
|
||
refreshProgressUI();
|
||
loadServerReadState();
|
||
goTo('p13');
|
||
setTimeout(()=>achievement('start','Начало главы 3!'), 600);
|
||
if(window.LS && window.LS.xp){
|
||
window.LS.xp.load().then(function(s){
|
||
if(s && s.xp > STATE.xp){
|
||
STATE.xp = s.xp;
|
||
STATE.level = calcLevel(STATE.xp);
|
||
saveProgress();
|
||
refreshProgressUI();
|
||
if(STATE.current) buildSidebar(STATE.current);
|
||
}
|
||
});
|
||
}
|
||
}
|
||
document.addEventListener('DOMContentLoaded', init);
|
||
|
||
/* STUBS */
|
||
function buildP15stub(){ document.getElementById('p15-body').innerHTML = `<div class="card"><div class="card-body"><p style="text-align:center;padding:20px"><b>§ 15 — Числовые промежутки. Линейные неравенства</b><br><br>Будет в Wave 2.</p></div></div>${secNav('p14','p16')}`; }
|
||
function buildP16stub(){ document.getElementById('p16-body').innerHTML = `<div class="card"><div class="card-body"><p style="text-align:center;padding:20px"><b>§ 16 — Системы неравенств</b><br><br>Будет в Wave 2.</p></div></div>${secNav('p15','p17')}`; }
|
||
function buildP17stub(){ document.getElementById('p17-body').innerHTML = `<div class="card"><div class="card-body"><p style="text-align:center;padding:20px"><b>§ 17 — Квадратные неравенства. Метод интервалов</b><br><br>Будет в Wave 3.</p></div></div>${secNav('p16','p18')}`; }
|
||
function buildP18stub(){ document.getElementById('p18-body').innerHTML = `<div class="card"><div class="card-body"><p style="text-align:center;padding:20px"><b>§ 18 — Дробно-рациональные неравенства</b><br><br>Будет в Wave 3.</p></div></div>${secNav('p17','final3')}`; }
|
||
function buildFinal3stub(){ document.getElementById('final3-body').innerHTML = `<div class="card"><div class="card-body"><p style="text-align:center;padding:20px"><b>Финал главы</b><br><br>Будет в Wave 4 — 7 боссов, увлекательная математика, практика.</p></div></div>${secNav('p18',null)}`; }
|
||
</script>
|
||
|
||
<script>
|
||
/* ============================================================
|
||
§ 13 — ЧИСЛОВЫЕ НЕРАВЕНСТВА И ИХ СВОЙСТВА
|
||
============================================================ */
|
||
function buildP13(){
|
||
const box = document.getElementById('p13-body');
|
||
let html = '';
|
||
|
||
html += makeCard('repeat','Повторение',null,`
|
||
<ul style="margin-left:18px;line-height:1.7">
|
||
<li>Действительные числа на числовой прямой: левее — меньше, правее — больше.</li>
|
||
<li>Знаки: $<$ (меньше), $>$ (больше), $\\leq$, $\\geq$, $\\neq$.</li>
|
||
<li>$|a|$ — модуль: расстояние от точки $a$ до нуля.</li>
|
||
</ul>`);
|
||
|
||
html += makeCard('theory','Что значит «$a > b$»','13.1',`
|
||
<p><b>Определение.</b> Число $a$ больше числа $b$, если разность $a - b$ — положительное число.</p>
|
||
<div style="background:var(--sec-acc-soft);border-radius:10px;padding:14px;margin:10px 0;text-align:center;font-size:1.15rem">$$a > b \\iff a - b > 0,\\qquad a < b \\iff a - b < 0$$</div>
|
||
<p>Это удобно, потому что любое сравнение сводится к проверке знака разности.</p>`);
|
||
|
||
html += makeCard('rule','5 главных свойств','13.2',`
|
||
<ol style="margin-left:18px;line-height:1.9">
|
||
<li><b>Транзитивность:</b> $a > b$ и $b > c \\Rightarrow a > c$.</li>
|
||
<li><b>Прибавление:</b> $a > b \\Rightarrow a + c > b + c$ (знак не меняется).</li>
|
||
<li><b>Умножение/деление на положительное:</b> $a > b,\\ k > 0 \\Rightarrow ak > bk$.</li>
|
||
<li><b>Умножение/деление на отрицательное:</b> $a > b,\\ k < 0 \\Rightarrow ak < bk$ — <span style="color:var(--bad);font-weight:700">знак меняется!</span></li>
|
||
<li><b>Возведение в квадрат для положительных:</b> $a > b > 0 \\Rightarrow a^2 > b^2$.</li>
|
||
</ol>`);
|
||
|
||
html += makeCard('example','Примеры',null,`
|
||
<p><b>1)</b> Сравним $\\sqrt{5}$ и $2{,}2$. Возведём в квадрат: $5$ и $4{,}84$. Поскольку $5 > 4{,}84$ и обе величины положительные, $\\sqrt{5} > 2{,}2$.</p>
|
||
<p><b>2)</b> Из $a > b$ умножим на $-3$: получим $-3a < -3b$. Знак ПОМЕНЯЛСЯ.</p>
|
||
<p><b>3)</b> Дано $a > 0$ и $b < 0$. Тогда $a - b > 0$, то есть $a > b$ (естественно: положительное больше отрицательного).</p>`);
|
||
|
||
/* INT 1 — Сравни числа (drag-сортировка) */
|
||
html += widget('Расставь по возрастанию','INTERACT 1','Перетащи числа в правильном порядке — слева направо. Проверь нажатием.',`
|
||
${DND_HINT_HTML}
|
||
<div id="p13a-pool"></div>
|
||
<div class="drop-row" style="display:grid;grid-template-columns:repeat(5, 1fr);gap:6px">
|
||
<div class="drop-box" style="min-height:60px"><h5>1-й</h5><div class="drop-items" data-cat="1"></div></div>
|
||
<div class="drop-box" style="min-height:60px"><h5>2-й</h5><div class="drop-items" data-cat="2"></div></div>
|
||
<div class="drop-box" style="min-height:60px"><h5>3-й</h5><div class="drop-items" data-cat="3"></div></div>
|
||
<div class="drop-box" style="min-height:60px"><h5>4-й</h5><div class="drop-items" data-cat="4"></div></div>
|
||
<div class="drop-box" style="min-height:60px"><h5>5-й</h5><div class="drop-items" data-cat="5"></div></div>
|
||
</div>
|
||
<div class="actions"><button class="btn primary" id="p13a-check">Проверить</button><button class="btn" id="p13a-new">Новая партия</button></div>
|
||
<div class="feedback" id="p13a-fb" style="display:none"></div>`);
|
||
|
||
/* INT 2 — Свойства: знак меняется или нет */
|
||
html += widget('Знак меняется или нет?','INTERACT 2','Дано $a > b$. Что произойдёт со знаком после операции?',`
|
||
<div class="score-display"><span>Раунд <b id="p13s-i">1</b> / 8</span><span>Очки: <b id="p13s-score">0</b></span></div>
|
||
<div id="p13s-task" style="font-size:1.2rem;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">
|
||
<button class="btn" id="p13s-keep">Не меняется</button>
|
||
<button class="btn" id="p13s-flip">Меняется</button>
|
||
</div>
|
||
<div class="feedback" id="p13s-fb" style="display:none;margin-top:10px"></div>
|
||
<button class="btn primary" id="p13s-start" style="margin-top:10px">Начать</button>`);
|
||
|
||
/* INT 3 — Конструктор: a > b + операция */
|
||
html += widget('Конструктор операций','INTERACT 3','Выбери $a$, $b$, операцию и число. Смотри, как меняется неравенство.',`
|
||
<div class="sliders">
|
||
<label>$a$ = <b id="p13c-a-val">8</b><input type="range" min="-10" max="10" step="1" value="8" id="p13c-a"></label>
|
||
<label>$b$ = <b id="p13c-b-val">3</b><input type="range" min="-10" max="10" step="1" value="3" id="p13c-b"></label>
|
||
<label>$k$ = <b id="p13c-k-val">2</b><input type="range" min="-5" max="5" step="1" value="2" id="p13c-k"></label>
|
||
</div>
|
||
<div style="display:flex;gap:6px;flex-wrap:wrap;justify-content:center;margin:10px 0">
|
||
<button class="btn small p13c-op active" data-op="add">$+ k$</button>
|
||
<button class="btn small p13c-op" data-op="sub">$- k$</button>
|
||
<button class="btn small p13c-op" data-op="mul">$\\times k$</button>
|
||
<button class="btn small p13c-op" data-op="div">$\\div k$</button>
|
||
</div>
|
||
<div id="p13c-out" style="padding:14px;background:var(--sec-acc-soft);border-radius:10px;font-size:1.05rem;line-height:1.8;text-align:center"></div>`);
|
||
|
||
/* INT 4 — Цепочка свойств: пошаговое доказательство */
|
||
html += widget('Цепочка свойств','INTERACT 4','Дано: $a > 5$. Подбери правильный шаг для каждого следующего вывода.',`
|
||
<div id="p13ch-stage" style="padding:14px;background:var(--sec-acc-soft);border-radius:10px;line-height:1.8;min-height:60px"></div>
|
||
<div id="p13ch-opts" style="display:flex;flex-direction:column;gap:6px;margin-top:10px"></div>
|
||
<div class="feedback" id="p13ch-fb" style="display:none;margin-top:10px"></div>
|
||
<button class="btn primary" id="p13ch-start" style="margin-top:10px">Начать</button>`);
|
||
|
||
/* INT 5 — Drag классификация: какое свойство */
|
||
html += widget('Какое свойство применили?','INTERACT 5','Отнеси каждый переход к нужному свойству.',`
|
||
${DND_HINT_HTML}
|
||
<div id="p13d-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="add"></div></div>
|
||
<div class="drop-box"><h5>×/÷ на +</h5><div class="drop-items" data-cat="mulpos"></div></div>
|
||
<div class="drop-box"><h5>×/÷ на −</h5><div class="drop-items" data-cat="mulneg"></div></div>
|
||
<div class="drop-box"><h5>Транзитивность</h5><div class="drop-items" data-cat="trans"></div></div>
|
||
</div>
|
||
<div class="actions"><button class="btn primary" id="p13d-check">Проверить</button><button class="btn" id="p13d-reset">Сначала</button></div>
|
||
<div class="feedback" id="p13d-fb" style="display:none"></div>`);
|
||
|
||
/* INT 6 — Тренажёр «Что больше?» */
|
||
html += widget('Что больше?','INTERACT 6','Сравни два выражения за 30 секунд каждое.',`
|
||
<div class="score-display"><span>Задача <b id="p13t-i">1</b> / 10</span><span>Очки: <b id="p13t-score">0</b></span></div>
|
||
<div id="p13t-task" style="display:flex;align-items:center;gap:14px;justify-content:center;padding:18px;background:var(--sec-acc-soft);border-radius:10px;margin-bottom:10px">
|
||
<span id="p13t-left" style="font-size:1.5rem;font-weight:700"></span>
|
||
<span style="font-size:1.3rem;color:var(--muted)">vs</span>
|
||
<span id="p13t-right" style="font-size:1.5rem;font-weight:700"></span>
|
||
</div>
|
||
<div style="display:flex;gap:6px;justify-content:center;flex-wrap:wrap">
|
||
<button class="btn" id="p13t-lt">$<$</button>
|
||
<button class="btn" id="p13t-eq">$=$</button>
|
||
<button class="btn" id="p13t-gt">$>$</button>
|
||
</div>
|
||
<div class="feedback" id="p13t-fb" style="display:none;margin-top:10px"></div>
|
||
<button class="btn primary" id="p13t-start" style="margin-top:10px">Начать</button>`);
|
||
|
||
html += makeCard('oral','Устные вопросы',null,`
|
||
<ol style="margin-left:18px;line-height:1.8">
|
||
<li>Что означает запись $a > b$ через разность?</li>
|
||
<li>Можно ли прибавить $-7$ к обеим частям $a > b$?</li>
|
||
<li>Что произойдёт со знаком при умножении на $-2$?</li>
|
||
<li>Сравните $-5$ и $-3$. Какое больше?</li>
|
||
</ol>`);
|
||
|
||
html += makeCard('class','Класс — выполните',null,`
|
||
<ol style="margin-left:18px;line-height:1.8">
|
||
<li>Сравните $\\sqrt{7}$ и $2{,}6$.</li>
|
||
<li>Дано $a > b$. Сравните: а) $a + 5$ и $b + 5$; б) $-a$ и $-b$; в) $a/3$ и $b/3$.</li>
|
||
<li>Докажите: если $a > b > 0$, то $\\dfrac{1}{a} < \\dfrac{1}{b}$.</li>
|
||
</ol>`);
|
||
|
||
html += makeCard('home','Домашка',null,`
|
||
<ol style="margin-left:18px;line-height:1.8">
|
||
<li>Сравните $\\sqrt{10}$ и $3{,}2$.</li>
|
||
<li>Дано $x < y$. Что больше: $7 - 3x$ или $7 - 3y$?</li>
|
||
<li>Известно, что $-2 < a < 3$. Найдите границы для $5a + 1$.</li>
|
||
<li>Докажите: $(a-b)^2 + (b-c)^2 + (a-c)^2 \\geq 0$ для любых $a, b, c$.</li>
|
||
</ol>`);
|
||
|
||
html += secNav(null, 'p14');
|
||
box.innerHTML = html;
|
||
if(window.renderMathInElement) setTimeout(()=>renderMath(box), 0);
|
||
|
||
/* INIT 1 — Сортировка по возрастанию */
|
||
(function(){
|
||
const sets = [
|
||
[{v:-3},{v:0},{v:2},{v:5},{v:7}],
|
||
[{v:-5,html:'$-5$'},{v:-2,html:'$-2$'},{v:0,html:'$0$'},{v:3,html:'$3$'},{v:6,html:'$6$'}],
|
||
[{v:Math.sqrt(2),html:'$\\sqrt{2}$'},{v:1.5,html:'$1{,}5$'},{v:Math.sqrt(3),html:'$\\sqrt{3}$'},{v:2,html:'$2$'},{v:Math.sqrt(5),html:'$\\sqrt{5}$'}],
|
||
[{v:-0.5,html:'$-\\dfrac{1}{2}$'},{v:0,html:'$0$'},{v:0.3,html:'$0{,}3$'},{v:0.5,html:'$\\dfrac{1}{2}$'},{v:1,html:'$1$'}],
|
||
[{v:-3.14,html:'$-\\pi$'},{v:-3,html:'$-3$'},{v:0,html:'$0$'},{v:Math.sqrt(2),html:'$\\sqrt{2}$'},{v:Math.PI,html:'$\\pi$'}],
|
||
];
|
||
let setIdx = 0, items = [], sorter;
|
||
function build(){
|
||
const cur = sets[setIdx];
|
||
items = cur.map((c, i) => ({ id: i + 1, html: c.html || ('$' + c.v + '$'), v: c.v }));
|
||
const shuffled = [...items].sort(()=>Math.random()-0.5);
|
||
sorter = setupSorter({ poolId:'p13a-pool', cats:['1','2','3','4','5'], items: shuffled, scopeSelector:'#p13-body' });
|
||
document.getElementById('p13a-fb').style.display = 'none';
|
||
}
|
||
document.getElementById('p13a-check').addEventListener('click', ()=>{
|
||
const fb = document.getElementById('p13a-fb'); fb.style.display = 'block';
|
||
if(Object.keys(sorter.placed).length < items.length){ feedback(fb, false, '⚠ Расставьте все 5 чисел.'); return; }
|
||
const sorted = [...items].sort((a,b)=>a.v-b.v);
|
||
let ok = true;
|
||
for(let i = 0; i < sorted.length; i++){
|
||
if(sorter.placed[sorted[i].id] !== String(i + 1)){ ok = false; break; }
|
||
}
|
||
if(ok){ feedback(fb, true, '✓ Правильно!'); achievement('p13_compare'); bumpProgress('p13', 14); confetti(); }
|
||
else feedback(fb, false, 'Не совсем. Проверьте знаки и сравнение корней.');
|
||
});
|
||
document.getElementById('p13a-new').addEventListener('click', ()=>{ setIdx = (setIdx + 1) % sets.length; build(); });
|
||
build();
|
||
})();
|
||
|
||
/* INIT 2 — Знак меняется или нет */
|
||
(function(){
|
||
const tasks = [
|
||
{ txt:'Прибавим $+5$ к обеим частям', flip:false },
|
||
{ txt:'Умножим обе части на $-3$', flip:true },
|
||
{ txt:'Разделим обе части на $2$', flip:false },
|
||
{ txt:'Прибавим $-7$', flip:false },
|
||
{ txt:'Умножим на $-1$', flip:true },
|
||
{ txt:'Разделим на $-4$', flip:true },
|
||
{ txt:'Возведём положительные части в квадрат', flip:false },
|
||
{ txt:'Умножим на $\\dfrac{1}{2}$ (положительное)', flip:false },
|
||
];
|
||
let cur = null, i = 1, score = 0, shuffled = [];
|
||
function show(){
|
||
cur = shuffled[i-1];
|
||
document.getElementById('p13s-i').textContent = i;
|
||
document.getElementById('p13s-task').innerHTML = '<b>Дано:</b> $a > b$. ' + cur.txt + '.';
|
||
renderMath(document.getElementById('p13s-task'));
|
||
document.getElementById('p13s-fb').style.display = 'none';
|
||
}
|
||
function answer(flip){
|
||
const fb = document.getElementById('p13s-fb'); fb.style.display = 'block';
|
||
if(flip === cur.flip){ score++; feedback(fb, true, '✓'); }
|
||
else feedback(fb, false, 'Не то. Правильно: ' + (cur.flip ? 'знак МЕНЯЕТСЯ' : 'знак не меняется'));
|
||
document.getElementById('p13s-score').textContent = score;
|
||
if(i >= 8){ setTimeout(()=>{ feedback(fb, score >= 6, 'Итог: ' + score + '/8'); if(score >= 6){ achievement(score === 8 ? 'p13_flip' : 'p13_sign'); bumpProgress('p13', 14); confetti(); } }, 600); }
|
||
else { i++; setTimeout(show, 800); }
|
||
}
|
||
document.getElementById('p13s-start').addEventListener('click', ()=>{ i=1; score=0; document.getElementById('p13s-score').textContent = 0; shuffled = [...tasks].sort(()=>Math.random()-0.5); show(); });
|
||
document.getElementById('p13s-keep').addEventListener('click', ()=>answer(false));
|
||
document.getElementById('p13s-flip').addEventListener('click', ()=>answer(true));
|
||
})();
|
||
|
||
/* INIT 3 — Конструктор */
|
||
(function(){
|
||
const aE = document.getElementById('p13c-a'), bE = document.getElementById('p13c-b'), kE = document.getElementById('p13c-k');
|
||
const out = document.getElementById('p13c-out');
|
||
let op = 'add', done = false;
|
||
document.querySelectorAll('.p13c-op').forEach(btn => btn.addEventListener('click', ()=>{
|
||
document.querySelectorAll('.p13c-op').forEach(x=>x.classList.remove('active'));
|
||
btn.classList.add('active'); op = btn.dataset.op; refresh();
|
||
}));
|
||
function refresh(){
|
||
const a = +aE.value, b = +bE.value, k = +kE.value;
|
||
document.getElementById('p13c-a-val').textContent = a;
|
||
document.getElementById('p13c-b-val').textContent = b;
|
||
document.getElementById('p13c-k-val').textContent = k;
|
||
const sign = a > b ? '>' : a < b ? '<' : '=';
|
||
let na, nb, sym;
|
||
const flip = (op === 'mul' || op === 'div') && k < 0;
|
||
if(op === 'add'){ na = a + k; nb = b + k; sym = sign; }
|
||
else if(op === 'sub'){ na = a - k; nb = b - k; sym = sign; }
|
||
else if(op === 'mul'){ if(k === 0){ na = 0; nb = 0; sym = '='; } else { na = a * k; nb = b * k; sym = flip ? (sign === '>' ? '<' : sign === '<' ? '>' : '=') : sign; } }
|
||
else { if(k === 0){ out.innerHTML = 'Деление на $0$ не определено.'; renderMath(out); return; } na = a / k; nb = b / k; sym = flip ? (sign === '>' ? '<' : sign === '<' ? '>' : '=') : sign; }
|
||
let s = '<div>Исходно: $' + a + ' ' + sign + ' ' + b + '$</div>';
|
||
s += '<div>После операции: $' + fmt(na) + ' ' + sym + ' ' + fmt(nb) + '$</div>';
|
||
if(flip && sign !== '=') s += '<div style="color:var(--bad);font-weight:700;margin-top:4px">Знак изменился!</div>';
|
||
out.innerHTML = s; renderMath(out);
|
||
if(!done){ done = true; setTimeout(()=>{ achievement('p13_props'); bumpProgress('p13', 12); }, 300); }
|
||
}
|
||
[aE,bE,kE].forEach(e => e.addEventListener('input', refresh));
|
||
refresh();
|
||
})();
|
||
|
||
/* INIT 4 — Цепочка */
|
||
(function(){
|
||
const steps = [
|
||
{ q:'Дано $a > 5$. Прибавим $3$ к обеим частям. Что получим?', opts:['$a + 3 > 8$','$a + 3 > 5$','$a + 3 < 8$','$3a > 15$'], ok:0 },
|
||
{ q:'Из $a + 3 > 8$ умножим на $2$. Что получим?', opts:['$2a + 6 > 16$','$2a + 3 > 8$','$2a + 6 < 16$','$a + 6 > 16$'], ok:0 },
|
||
{ q:'Из $2a + 6 > 16$ вычтем $6$. Что получим?', opts:['$2a > 10$','$2a > 22$','$2a < 10$','$a > 10$'], ok:0 },
|
||
{ q:'Из $2a > 10$ разделим на $-2$. Что получим?', opts:['$-a < -5$','$-a > -5$','$-a < 5$','$a < 5$'], ok:0 },
|
||
{ q:'Какой исходный шаг мы могли бы сделать одной операцией: «из $a > 5$ к $-a < -5$»?', opts:['Умножить на $-1$','Прибавить $-5$','Возвести в квадрат','Прибавить $5$'], ok:0 },
|
||
];
|
||
let i = 0;
|
||
function show(){
|
||
const s = steps[i];
|
||
document.getElementById('p13ch-stage').innerHTML = '<b>Шаг ' + (i + 1) + ' / ' + steps.length + '.</b> ' + s.q;
|
||
renderMath(document.getElementById('p13ch-stage'));
|
||
const opts = document.getElementById('p13ch-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('p13ch-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('p13_chain'); bumpProgress('p13', 14); confetti(); } else setTimeout(()=>{ i++; show(); }, 700); }
|
||
else { b.classList.add('fail'); feedback(fb, false, 'Не то. Подумайте.'); }
|
||
});
|
||
opts.appendChild(b);
|
||
});
|
||
renderMath(opts);
|
||
document.getElementById('p13ch-fb').style.display = 'none';
|
||
}
|
||
document.getElementById('p13ch-start').addEventListener('click', ()=>{ i = 0; show(); });
|
||
})();
|
||
|
||
/* INIT 5 — Drag классификация */
|
||
(function(){
|
||
const items = [
|
||
{ id:1, html:'$a > b \\Rightarrow a + 7 > b + 7$', cat:'add' },
|
||
{ id:2, html:'$a > b \\Rightarrow 5a > 5b$', cat:'mulpos' },
|
||
{ id:3, html:'$a > b \\Rightarrow -2a < -2b$', cat:'mulneg' },
|
||
{ id:4, html:'$a > b,\\ b > c \\Rightarrow a > c$', cat:'trans' },
|
||
{ id:5, html:'$a > b \\Rightarrow a - 4 > b - 4$', cat:'add' },
|
||
{ id:6, html:'$a > b \\Rightarrow \\dfrac{a}{3} > \\dfrac{b}{3}$', cat:'mulpos' },
|
||
{ id:7, html:'$a > b \\Rightarrow \\dfrac{a}{-2} < \\dfrac{b}{-2}$', cat:'mulneg' },
|
||
{ id:8, html:'$x > 3,\\ 3 > y \\Rightarrow x > y$', cat:'trans' },
|
||
];
|
||
const sorter = setupSorter({ poolId:'p13d-pool', cats:['add','mulpos','mulneg','trans'], items, scopeSelector:'#p13-body' });
|
||
document.getElementById('p13d-check').addEventListener('click', ()=>{
|
||
const fb = document.getElementById('p13d-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, '✓ Идеально!'); bumpProgress('p13', 12); confetti(); }
|
||
else feedback(fb, false, 'Верно ' + ok + ' из ' + items.length);
|
||
});
|
||
document.getElementById('p13d-reset').addEventListener('click', ()=>{ sorter.reset(); document.getElementById('p13d-fb').style.display='none'; });
|
||
})();
|
||
|
||
/* INIT 6 — Что больше? */
|
||
(function(){
|
||
function gen(){
|
||
const t = Math.floor(Math.random()*4);
|
||
if(t === 0){
|
||
const a = -5 + Math.floor(Math.random()*11);
|
||
const b = -5 + Math.floor(Math.random()*11);
|
||
return { L: '$' + a + '$', R: '$' + b + '$', cmp: a < b ? '<' : a > b ? '>' : '=' };
|
||
}
|
||
if(t === 1){
|
||
const a = 1 + Math.floor(Math.random()*8);
|
||
const b = (2 + Math.random() * 4).toFixed(1);
|
||
return { L: '$\\sqrt{' + a + '}$', R: '$' + b + '$', cmp: a < +b * +b ? '<' : a > +b * +b ? '>' : '=' };
|
||
}
|
||
if(t === 2){
|
||
const a = 1 + Math.floor(Math.random()*5), b = 1 + Math.floor(Math.random()*5);
|
||
const c = 1 + Math.floor(Math.random()*5), d = 1 + Math.floor(Math.random()*5);
|
||
const L = a/b, R = c/d;
|
||
return { L: '$\\dfrac{' + a + '}{' + b + '}$', R: '$\\dfrac{' + c + '}{' + d + '}$', cmp: L < R ? '<' : L > R ? '>' : '=' };
|
||
}
|
||
const sign = Math.random() < 0.5 ? -1 : 1;
|
||
const a = 1 + Math.floor(Math.random()*5);
|
||
const b = 1 + Math.floor(Math.random()*5);
|
||
return { L: '$' + (sign * a) + '$', R: '$' + (sign * b) + '$', cmp: sign*a < sign*b ? '<' : sign*a > sign*b ? '>' : '=' };
|
||
}
|
||
let cur = null, i = 1, score = 0;
|
||
function show(){
|
||
cur = gen();
|
||
document.getElementById('p13t-i').textContent = i;
|
||
document.getElementById('p13t-left').innerHTML = cur.L;
|
||
document.getElementById('p13t-right').innerHTML = cur.R;
|
||
renderMath(document.getElementById('p13t-task'));
|
||
document.getElementById('p13t-fb').style.display = 'none';
|
||
}
|
||
function answer(sym){
|
||
const fb = document.getElementById('p13t-fb'); fb.style.display = 'block';
|
||
if(sym === cur.cmp){ score++; feedback(fb, true, '✓'); }
|
||
else feedback(fb, false, 'Не то. Правильно: ' + cur.cmp);
|
||
document.getElementById('p13t-score').textContent = score;
|
||
if(i >= 10){ setTimeout(()=>{ feedback(fb, score >= 7, 'Итог: ' + score + '/10'); if(score >= 7){ achievement('p13_compare'); bumpProgress('p13', 16); confetti(); } }, 600); }
|
||
else { i++; setTimeout(show, 700); }
|
||
}
|
||
document.getElementById('p13t-start').addEventListener('click', ()=>{ i=1; score=0; document.getElementById('p13t-score').textContent = 0; show(); });
|
||
document.getElementById('p13t-lt').addEventListener('click', ()=>answer('<'));
|
||
document.getElementById('p13t-eq').addEventListener('click', ()=>answer('='));
|
||
document.getElementById('p13t-gt').addEventListener('click', ()=>answer('>'));
|
||
})();
|
||
}
|
||
</script>
|
||
<script>
|
||
/* ============================================================
|
||
§ 14 — СЛОЖЕНИЕ И УМНОЖЕНИЕ НЕРАВЕНСТВ. ОЦЕНКА
|
||
============================================================ */
|
||
function buildP14(){
|
||
const box = document.getElementById('p14-body');
|
||
let html = '';
|
||
|
||
html += makeCard('repeat','Повторение § 13',null,`
|
||
<ul style="margin-left:18px;line-height:1.7">
|
||
<li>Свойства неравенств: прибавление, умножение/деление на положительное (знак сохраняется), на отрицательное (меняется).</li>
|
||
<li>$a > b \\iff a - b > 0$.</li>
|
||
</ul>`);
|
||
|
||
html += makeCard('theory','Сложение неравенств','14.1',`
|
||
<p><b>Правило.</b> Если $a < b$ и $c < d$, то $a + c < b + d$.</p>
|
||
<div style="background:var(--sec-acc-soft);border-radius:10px;padding:12px;margin:8px 0;text-align:center;font-size:1.1rem">$$\\begin{aligned} a &< b \\\\ c &< d \\\\ \\hline a + c &< b + d \\end{aligned}$$</div>
|
||
<p><b>Важно:</b> неравенства должны быть одного направления. Складывать $a < b$ и $c > d$ напрямую нельзя.</p>`);
|
||
|
||
html += makeCard('rule','Умножение неравенств','14.2',`
|
||
<p>Для <b>положительных</b> чисел: если $0 < a < b$ и $0 < c < d$, то $ac < bd$.</p>
|
||
<div style="background:var(--sec-acc-soft);border-radius:10px;padding:12px;margin:8px 0;text-align:center;font-size:1.1rem">$$0 < a < b,\\quad 0 < c < d \\Rightarrow ac < bd$$</div>
|
||
<p><b>Внимание:</b> требование «положительные» обязательно. Например, $-3 < 2$ и $-3 < 2$, но $(-3)(-3) = 9 > 2 \\cdot 2 = 4$ — формула не работает для отрицательных.</p>`);
|
||
|
||
html += makeCard('algo','Оценка значений','14.3',`
|
||
<p>Если $a \\leq x \\leq b$ и $c \\leq y \\leq d$, то:</p>
|
||
<table class="tbl" style="margin:8px 0">
|
||
<thead><tr><th>Выражение</th><th>Нижняя граница</th><th>Верхняя граница</th></tr></thead>
|
||
<tbody>
|
||
<tr><td>$x + y$</td><td>$a + c$</td><td>$b + d$</td></tr>
|
||
<tr><td>$x - y$</td><td>$a - d$</td><td>$b - c$</td></tr>
|
||
<tr><td>$xy$ (все $\\geq 0$)</td><td>$ac$</td><td>$bd$</td></tr>
|
||
<tr><td>$\\dfrac{x}{y}$ (все $> 0$)</td><td>$\\dfrac{a}{d}$</td><td>$\\dfrac{b}{c}$</td></tr>
|
||
</tbody>
|
||
</table>
|
||
<p style="font-size:.88rem;color:var(--muted)">Запомни: для разности и деления — у второй переменной границы «крест-накрест».</p>`);
|
||
|
||
html += makeCard('example','Пример',null,`
|
||
<p><b>Дано:</b> $2 \\leq x \\leq 5$, $1 \\leq y \\leq 3$. <b>Найдите границы для $x + y$, $x - y$, $xy$.</b></p>
|
||
<p><b>$x + y$:</b> $2 + 1 \\leq x + y \\leq 5 + 3 \\Rightarrow 3 \\leq x+y \\leq 8$.</p>
|
||
<p><b>$x - y$:</b> $2 - 3 \\leq x - y \\leq 5 - 1 \\Rightarrow -1 \\leq x-y \\leq 4$.</p>
|
||
<p><b>$xy$ (все $> 0$):</b> $2 \\cdot 1 \\leq xy \\leq 5 \\cdot 3 \\Rightarrow 2 \\leq xy \\leq 15$.</p>`);
|
||
|
||
/* INT 1 — Калькулятор оценки */
|
||
html += widget('Калькулятор оценок','INTERACT 1','Введите границы для $x$ и $y$ — увидите границы для всех 4 выражений сразу.',`
|
||
<div style="display:grid;grid-template-columns:repeat(auto-fit,minmax(170px,1fr));gap:10px;margin-bottom:10px">
|
||
<label class="tinp" style="display:flex;align-items:center;gap:6px;background:var(--card)">$x \\geq$ <input type="number" id="p14e-a" value="2" class="tinp" style="width:60px;padding:4px;border:0"></label>
|
||
<label class="tinp" style="display:flex;align-items:center;gap:6px;background:var(--card)">$x \\leq$ <input type="number" id="p14e-b" value="5" class="tinp" style="width:60px;padding:4px;border:0"></label>
|
||
<label class="tinp" style="display:flex;align-items:center;gap:6px;background:var(--card)">$y \\geq$ <input type="number" id="p14e-c" value="1" class="tinp" style="width:60px;padding:4px;border:0"></label>
|
||
<label class="tinp" style="display:flex;align-items:center;gap:6px;background:var(--card)">$y \\leq$ <input type="number" id="p14e-d" value="3" class="tinp" style="width:60px;padding:4px;border:0"></label>
|
||
</div>
|
||
<div id="p14e-out" style="padding:14px;background:var(--sec-acc-soft);border-radius:10px;line-height:1.8;font-size:1rem"></div>`);
|
||
|
||
/* INT 2 — Тренажёр оценки */
|
||
html += widget('Тренажёр: найдите границу','INTERACT 2','По заданным границам $x$ и $y$ найдите верхнюю или нижнюю границу выражения.',`
|
||
<div class="score-display"><span>Задача <b id="p14t-i">1</b> / 8</span><span>Очки: <b id="p14t-score">0</b></span></div>
|
||
<div id="p14t-task" style="padding:14px;background:var(--sec-acc-soft);border-radius:10px;font-size:1.04rem;line-height:1.7;margin-bottom:10px"></div>
|
||
<div style="display:flex;gap:8px;justify-content:center;flex-wrap:wrap">
|
||
<input type="number" id="p14t-inp" placeholder="ваш ответ" class="tinp" style="width:140px">
|
||
<button class="btn primary" id="p14t-go">Ответ</button>
|
||
</div>
|
||
<div class="feedback" id="p14t-fb" style="display:none;margin-top:10px"></div>
|
||
<button class="btn primary" id="p14t-start" style="margin-top:10px">Начать</button>`);
|
||
|
||
/* INT 3 — Drag: можно/нельзя складывать */
|
||
html += widget('Можно ли применить правило?','INTERACT 3','Отнеси каждую пару неравенств к одной из категорий.',`
|
||
${DND_HINT_HTML}
|
||
<div id="p14d-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="add"></div></div>
|
||
<div class="drop-box"><h5>Можно перемножить</h5><div class="drop-items" data-cat="mul"></div></div>
|
||
<div class="drop-box"><h5>Ни того, ни другого</h5><div class="drop-items" data-cat="none"></div></div>
|
||
</div>
|
||
<div class="actions"><button class="btn primary" id="p14d-check">Проверить</button><button class="btn" id="p14d-reset">Сначала</button></div>
|
||
<div class="feedback" id="p14d-fb" style="display:none"></div>`);
|
||
|
||
/* INT 4 — Пошаговое сложение */
|
||
html += widget('Пошаговое сложение и оценка','INTERACT 4','По заданным $x, y$ — пройди 4 шага: сложение, разность, произведение, итог.',`
|
||
<p style="margin-bottom:10px"><b>Дано:</b> $1 \\leq x \\leq 4$, $2 \\leq y \\leq 6$.</p>
|
||
<div id="p14p-stage" style="padding:14px;background:var(--sec-acc-soft);border-radius:10px;line-height:1.8;min-height:80px"></div>
|
||
<div class="actions" style="margin-top:10px"><button class="btn primary" id="p14p-go">Старт</button><button class="btn" id="p14p-next" style="display:none">Дальше</button><button class="btn" id="p14p-reset" style="display:none">Сначала</button></div>`);
|
||
|
||
/* INT 5 — Складываем неравенства (упражнение) */
|
||
html += widget('Сложи неравенства','INTERACT 5','Дано: $a < b$ и $c < d$. Подбери, что получится после сложения.',`
|
||
<div class="score-display"><span>Раунд <b id="p14a-i">1</b> / 6</span><span>Очки: <b id="p14a-score">0</b></span></div>
|
||
<div id="p14a-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 id="p14a-opts" style="display:flex;flex-direction:column;gap:6px"></div>
|
||
<div class="feedback" id="p14a-fb" style="display:none;margin-top:10px"></div>
|
||
<button class="btn primary" id="p14a-start" style="margin-top:10px">Начать</button>`);
|
||
|
||
html += makeCard('oral','Устные вопросы',null,`
|
||
<ol style="margin-left:18px;line-height:1.8">
|
||
<li>Можно ли складывать $a > b$ и $c < d$? Почему?</li>
|
||
<li>Какое условие нужно для перемножения неравенств?</li>
|
||
<li>Если $1 \\leq x \\leq 3$, какие границы у $-x$?</li>
|
||
</ol>`);
|
||
|
||
html += makeCard('class','Класс — выполните',null,`
|
||
<ol style="margin-left:18px;line-height:1.8">
|
||
<li>$3 \\leq x \\leq 7$, $2 \\leq y \\leq 5$. Найдите границы для $x + y$, $x - y$, $xy$.</li>
|
||
<li>Сложите неравенства $a < b$ и $-c < -d$ (где $d < c$).</li>
|
||
<li>$0{,}5 \\leq a \\leq 2$, $1 \\leq b \\leq 4$. Найдите границы $\\dfrac{a}{b}$.</li>
|
||
</ol>`);
|
||
|
||
html += makeCard('home','Домашка',null,`
|
||
<ol style="margin-left:18px;line-height:1.8">
|
||
<li>$-2 \\leq x \\leq 1$, $0 \\leq y \\leq 4$. Границы для $x + y$ и $x - y$.</li>
|
||
<li>$1 \\leq a \\leq 5$, $2 \\leq b \\leq 6$. Границы $ab$ и $\\dfrac{a}{b}$.</li>
|
||
<li>Известно: периметр треугольника $P$ удовлетворяет $12 \\leq P \\leq 18$. Найти границы половины периметра $\\dfrac{P}{2}$.</li>
|
||
</ol>`);
|
||
|
||
html += secNav('p13', 'p15');
|
||
box.innerHTML = html;
|
||
if(window.renderMathInElement) setTimeout(()=>renderMath(box), 0);
|
||
|
||
/* INIT 1 — Калькулятор оценки */
|
||
(function(){
|
||
const ids = ['p14e-a','p14e-b','p14e-c','p14e-d'];
|
||
const out = document.getElementById('p14e-out');
|
||
let done = false;
|
||
function refresh(){
|
||
const a = +document.getElementById('p14e-a').value;
|
||
const b = +document.getElementById('p14e-b').value;
|
||
const c = +document.getElementById('p14e-c').value;
|
||
const d = +document.getElementById('p14e-d').value;
|
||
if(b < a || d < c){ out.innerHTML = '<span style="color:var(--bad)">Неверные границы: нижняя должна быть $\\leq$ верхней.</span>'; renderMath(out); return; }
|
||
const sumLo = a + c, sumHi = b + d;
|
||
const subLo = a - d, subHi = b - c;
|
||
const prods = [a*c, a*d, b*c, b*d];
|
||
const prdLo = Math.min(...prods), prdHi = Math.max(...prods);
|
||
const positives = a > 0 && c > 0;
|
||
let s = '<div><b>$x + y$:</b> $' + fmt(sumLo) + ' \\leq x+y \\leq ' + fmt(sumHi) + '$</div>';
|
||
s += '<div><b>$x - y$:</b> $' + fmt(subLo) + ' \\leq x-y \\leq ' + fmt(subHi) + '$</div>';
|
||
s += '<div><b>$xy$:</b> $' + fmt(prdLo) + ' \\leq xy \\leq ' + fmt(prdHi) + '$</div>';
|
||
if(positives){
|
||
s += '<div><b>$\\dfrac{x}{y}$:</b> $' + fmt(a/d) + ' \\leq \\dfrac{x}{y} \\leq ' + fmt(b/c) + '$ (все положительные)</div>';
|
||
} else {
|
||
s += '<div style="color:var(--muted)">Для $\\dfrac{x}{y}$ нужны все положительные — иначе формула сложнее.</div>';
|
||
}
|
||
out.innerHTML = s; renderMath(out);
|
||
if(!done){ done = true; setTimeout(()=>{ achievement('p14_estimate'); bumpProgress('p14', 14); }, 300); }
|
||
}
|
||
ids.forEach(id => document.getElementById(id).addEventListener('input', refresh));
|
||
refresh();
|
||
})();
|
||
|
||
/* INIT 2 — Тренажёр оценки */
|
||
(function(){
|
||
function gen(){
|
||
const a = 1 + Math.floor(Math.random()*4);
|
||
const b = a + 1 + Math.floor(Math.random()*4);
|
||
const c = 1 + Math.floor(Math.random()*4);
|
||
const d = c + 1 + Math.floor(Math.random()*4);
|
||
const ops = [
|
||
{ txt:'верхняя граница $x + y$', ans: b + d },
|
||
{ txt:'нижняя граница $x + y$', ans: a + c },
|
||
{ txt:'верхняя граница $x - y$', ans: b - c },
|
||
{ txt:'нижняя граница $x - y$', ans: a - d },
|
||
{ txt:'верхняя граница $xy$', ans: b * d },
|
||
{ txt:'нижняя граница $xy$', ans: a * c },
|
||
];
|
||
const op = ops[Math.floor(Math.random()*ops.length)];
|
||
return { a, b, c, d, op };
|
||
}
|
||
let cur = null, i = 1, score = 0;
|
||
function show(){
|
||
cur = gen();
|
||
document.getElementById('p14t-i').textContent = i;
|
||
document.getElementById('p14t-task').innerHTML = '<b>Дано:</b> $' + cur.a + ' \\leq x \\leq ' + cur.b + '$, $' + cur.c + ' \\leq y \\leq ' + cur.d + '$.<br><b>Найдите:</b> ' + cur.op.txt + '.';
|
||
renderMath(document.getElementById('p14t-task'));
|
||
document.getElementById('p14t-inp').value = '';
|
||
document.getElementById('p14t-fb').style.display = 'none';
|
||
}
|
||
function check(){
|
||
const fb = document.getElementById('p14t-fb'); fb.style.display = 'block';
|
||
const u = +document.getElementById('p14t-inp').value;
|
||
if(u === cur.op.ans){ score++; feedback(fb, true, '✓ ' + cur.op.ans); }
|
||
else feedback(fb, false, 'Не то. Правильно: ' + cur.op.ans);
|
||
document.getElementById('p14t-score').textContent = score;
|
||
if(i >= 8){ setTimeout(()=>{ feedback(fb, score >= 6, 'Итог: ' + score + '/8'); if(score >= 6){ achievement('p14_train'); bumpProgress('p14', 16); confetti(); } }, 600); }
|
||
else { i++; setTimeout(show, 800); }
|
||
}
|
||
document.getElementById('p14t-start').addEventListener('click', ()=>{ i=1; score=0; document.getElementById('p14t-score').textContent = 0; show(); });
|
||
document.getElementById('p14t-go').addEventListener('click', check);
|
||
document.getElementById('p14t-inp').addEventListener('keyup', e=>{ if(e.key === 'Enter') check(); });
|
||
})();
|
||
|
||
/* INIT 3 — Drag складывать/перемножать */
|
||
(function(){
|
||
const items = [
|
||
{ id:1, html:'$2 < 5$ и $3 < 7$', cat:'mul' },
|
||
{ id:2, html:'$a < b$ и $c < d$', cat:'add' },
|
||
{ id:3, html:'$a > b$ и $c < d$', cat:'none' },
|
||
{ id:4, html:'$-3 < 1$ и $-2 < 4$', cat:'add' },
|
||
{ id:5, html:'$5 > 2$ и $4 > 1$', cat:'mul' },
|
||
{ id:6, html:'$a < b$ и $c > d$', cat:'none' },
|
||
{ id:7, html:'$0 < x < 3$ и $1 < y < 5$', cat:'mul' },
|
||
{ id:8, html:'$-2 < a < 1$ и $-1 < b < 4$', cat:'add' },
|
||
];
|
||
const sorter = setupSorter({ poolId:'p14d-pool', cats:['add','mul','none'], items, scopeSelector:'#p14-body' });
|
||
document.getElementById('p14d-check').addEventListener('click', ()=>{
|
||
const fb = document.getElementById('p14d-fb'); fb.style.display = 'block';
|
||
if(Object.keys(sorter.placed).length < items.length){ feedback(fb, false, '⚠ Разложите все.'); 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('p14_drag'); bumpProgress('p14', 14); confetti(); }
|
||
else feedback(fb, false, 'Верно ' + ok + ' из ' + items.length);
|
||
});
|
||
document.getElementById('p14d-reset').addEventListener('click', ()=>{ sorter.reset(); document.getElementById('p14d-fb').style.display='none'; });
|
||
})();
|
||
|
||
/* INIT 4 — Пошаговое */
|
||
(function(){
|
||
const stage = document.getElementById('p14p-stage');
|
||
const goBtn = document.getElementById('p14p-go');
|
||
const nextBtn = document.getElementById('p14p-next');
|
||
const resetBtn = document.getElementById('p14p-reset');
|
||
const steps = [
|
||
'<b>Шаг 1.</b> $x + y$: складываем границы соответствующих частей. $1 + 2 = 3$ — низ, $4 + 6 = 10$ — верх. Итог: $3 \\leq x+y \\leq 10$.',
|
||
'<b>Шаг 2.</b> $x - y$: внимание на «крест-накрест». Низ: $1 - 6 = -5$. Верх: $4 - 2 = 2$. Итог: $-5 \\leq x-y \\leq 2$.',
|
||
'<b>Шаг 3.</b> $xy$ (все положительные): низ — произведение нижних, $1 \\cdot 2 = 2$. Верх — произведение верхних, $4 \\cdot 6 = 24$. Итог: $2 \\leq xy \\leq 24$.',
|
||
'<b>Шаг 4.</b> $\\dfrac{x}{y}$ (все $> 0$): низ — $\\dfrac{\\text{мин}\\ x}{\\text{макс}\\ y} = \\dfrac{1}{6}$. Верх — $\\dfrac{\\text{макс}\\ x}{\\text{мин}\\ y} = \\dfrac{4}{2} = 2$. Итог: $\\dfrac{1}{6} \\leq \\dfrac{x}{y} \\leq 2$.',
|
||
'<b>Ответ:</b> все 4 границы найдены. Заметьте — для разности и деления у $y$ границы поменялись местами.',
|
||
];
|
||
let idx = 0, awarded = false;
|
||
function render(){
|
||
stage.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(stage);
|
||
if(idx >= steps.length - 1){
|
||
nextBtn.disabled = true; nextBtn.textContent = 'Готово';
|
||
if(!awarded){ awarded = true; achievement('p14_add'); bumpProgress('p14', 14); confetti(); }
|
||
} else { nextBtn.disabled = false; nextBtn.textContent = 'Дальше (' + (idx + 1) + '/' + steps.length + ')'; }
|
||
}
|
||
goBtn.addEventListener('click', ()=>{ 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; stage.innerHTML = ''; goBtn.style.display = ''; nextBtn.style.display = 'none'; resetBtn.style.display = 'none'; });
|
||
})();
|
||
|
||
/* INIT 5 — Сложи неравенства */
|
||
(function(){
|
||
const tasks = [
|
||
{ q:'$3 < 5$ и $2 < 4$. Результат сложения:', opts:['$5 < 9$','$3 < 9$','$5 < 4$','$1 < 1$'], ok:0 },
|
||
{ q:'$-1 < 4$ и $0 < 7$. Результат сложения:', opts:['$-1 < 11$','$0 < 11$','$-1 < 7$','$0 < 4$'], ok:0 },
|
||
{ q:'$a < b$ и $c < d$, всегда верно:', opts:['$a + c < b + d$','$a + c > b + d$','$ac < bd$','ничего из перечисленного'], ok:0 },
|
||
{ q:'$2 < 6$ и $3 < 4$. Перемножение (все положительные):', opts:['$6 < 24$','$5 < 10$','$2 < 24$','$6 < 4$'], ok:0 },
|
||
{ q:'$-3 < 2$ и $-1 < 5$. Можно сложить?', opts:['Да: $-4 < 7$','Нет, неравенства разных знаков','Только если они положительные','Нет, потому что $-1 > -3$'], ok:0 },
|
||
{ q:'$a > b$ и $c > d$, тогда $a + c$ ? $b + d$:', opts:['$>$','$<$','$=$','непредсказуемо'], ok:0 },
|
||
];
|
||
let cur = null, i = 1, score = 0, shuffled = [];
|
||
function show(){
|
||
cur = shuffled[i-1];
|
||
document.getElementById('p14a-i').textContent = i;
|
||
document.getElementById('p14a-task').innerHTML = '<b>Задача ' + i + '.</b> ' + cur.q;
|
||
renderMath(document.getElementById('p14a-task'));
|
||
const opts = document.getElementById('p14a-opts'); opts.innerHTML = '';
|
||
cur.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('p14a-fb'); fb.style.display = 'block';
|
||
if(k === cur.ok){ score++; b.classList.add('ok'); feedback(fb, true, '✓'); }
|
||
else { b.classList.add('fail'); feedback(fb, false, 'Не то.'); }
|
||
document.getElementById('p14a-score').textContent = score;
|
||
if(i >= shuffled.length){ setTimeout(()=>{ feedback(fb, score >= 4, 'Итог: ' + score + '/' + shuffled.length); if(score >= 4){ achievement('p14_mul'); bumpProgress('p14', 14); confetti(); } }, 700); }
|
||
else { i++; setTimeout(show, 800); }
|
||
});
|
||
opts.appendChild(b);
|
||
});
|
||
renderMath(opts);
|
||
document.getElementById('p14a-fb').style.display = 'none';
|
||
}
|
||
document.getElementById('p14a-start').addEventListener('click', ()=>{ i=1; score=0; document.getElementById('p14a-score').textContent = 0; shuffled = [...tasks].sort(()=>Math.random()-0.5); show(); });
|
||
})();
|
||
}
|
||
</script>
|
||
|
||
</body>
|
||
</html>
|