790c2e9445
Сделано:
1. /css/alg7-fx.css — универсальные эффекты:
- shake (тряска) при неправильном ответе
- pulse (зелёное свечение) при правильном
- combo-badge (огненный шильдик ×3, ×5, ×10) при сериях
- streak-индикатор в углу с пульсацией
- sparkles (искры) при успехе
- стили для двух новых визуализаторов
2. /js/alg7-fx.js — система комбо + визуализаторы:
- MutationObserver автоматически отслеживает .feedback по всем
четырём главам без правки feedback() в каждой
- комбо-милестоны: 3 → +5 XP, 5 → +15, 10 → +50, 15 → +75, 20 → +100
- бонус автоматически уходит через window.addXp(), который
уже есть на window благодаря top-level function declarations
- ALG7.buildQuadSumViz() — большой квадрат (a+b)² с 4 цветными
областями (a², ab, ab, b²); слайдеры a, b; режим (a+b)/(a-b);
клик по области → подсветка в формуле; живые числа
- ALG7.buildDiffSquaresViz() — 3-этапная анимация a²-b²=(a-b)(a+b):
1) большой квадрат с вырезанной угловой b²
2) пунктирная линия разреза в L-форме
3) перестроенный прямоугольник со сторонами (a-b)×(a+b)
3. Подключено во всех 4 главах одной строкой <link>/<script>.
4. Ch2 §12: добавлен 4-й интерактив — геометрическая визуализация
квадрата суммы/разности. Школьник видит ПОЧЕМУ (a+b)²=a²+2ab+b².
5. Ch2 §13: добавлен 3-й интерактив — анимированное геометрическое
доказательство разности квадратов. Жмёшь «Шаг» → L-форма
расклеивается и собирается в прямоугольник.
Эффекты работают везде где есть .feedback — все боссы, все
тренажёры, все викторины. Не требует правки логики каждой главы.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1600 lines
125 KiB
HTML
1600 lines
125 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>Алгебра 7 · Глава 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="stylesheet" href="/css/alg7-fx.css">
|
||
<script src="/js/alg7-fx.js" defer></script>
|
||
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800&family=Manrope:wght@600;700;800;900&family=Unbounded:wght@700;800;900&family=JetBrains+Mono:wght@500;700&display=swap" rel="stylesheet">
|
||
<style>
|
||
:root{
|
||
--bg:#fafafa; --card:#fff; --card-soft:#f8fafc; --text:#0f172a; --ink:#0f172a; --muted:#64748b;
|
||
--border:#e2e8f0; --sh:0 1px 3px rgba(0,0,0,.06); --sh2:0 4px 14px rgba(0,0,0,.08);
|
||
--pri:#7c3aed; --pri2:#6d28d9; --pri-soft:#ede9fe;
|
||
--acc:#a78bfa; --acc2:#7c3aed; --acc-soft:#f5f3ff;
|
||
--ok:#10b981; --ok-bg:#d1fae5; --warn:#f59e0b; --warn-bg:#fef3c7;
|
||
--bad:#ef4444; --fail:#dc2626; --fail-bg:#fee2e2;
|
||
}
|
||
.dark{--bg:#0a0a14; --card:#13102a; --card-soft:#1a1430; --text:#f0e6ff; --ink:#f0e6ff; --muted:#9080b0; --border:#2a1f48}
|
||
*{margin:0;padding:0;box-sizing:border-box;-webkit-tap-highlight-color:transparent}
|
||
html,body{font-family:'Inter',system-ui,sans-serif;background:var(--bg);color:var(--text);line-height:1.55;font-size:15px}
|
||
button,input,select,textarea{font-family:inherit;font-size:inherit}
|
||
button{cursor:pointer;border:0;background:transparent;color:inherit}
|
||
a{color:inherit;text-decoration:none}
|
||
.ic{width:16px;height:16px;display:inline-block;flex-shrink:0;stroke:currentColor;fill:none;stroke-width:2;stroke-linecap:round;stroke-linejoin:round;vertical-align:middle}
|
||
|
||
.hdr{position:relative;background:linear-gradient(110deg,#3b0764 0%,#7c3aed 55%,#a78bfa 100%);color:#fff;padding:46px 22px 30px;overflow:hidden;border-bottom:2px solid rgba(196,181,253,.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(232,219,255,.12);line-height:1;pointer-events:none;user-select:none;z-index:0}
|
||
.hdr-row{position:relative;z-index:1;display:flex;align-items:center;gap:14px;flex-wrap:wrap}
|
||
.hdr h1{font-family:'Unbounded',sans-serif;font-size:1.5rem;font-weight:900;letter-spacing:-.01em;line-height:1.3;padding-top:4px}
|
||
.hdr-sub{font-size:.85rem;opacity:.88;margin-top:6px;font-weight:500;line-height:1.4}
|
||
.hdr-side{margin-left:auto;display:flex;gap:8px;align-items:center;flex-wrap:wrap}
|
||
.hdr-btn{padding:7px 12px;border-radius:9px;background:rgba(255,255,255,.14);color:#fff;font-weight:600;font-size:.82rem;display:inline-flex;align-items:center;gap:6px;transition:background .15s;text-decoration:none}
|
||
.hdr-btn:hover{background:rgba(255,255,255,.24)}
|
||
|
||
.main{max-width:1240px;margin:0 auto;padding:22px;width:100%;display:grid;grid-template-columns:1fr 280px;gap:24px}
|
||
@media(max-width:980px){.main{grid-template-columns:1fr;padding:14px}}
|
||
.col-main{min-width:0}
|
||
|
||
.hero{background:linear-gradient(135deg,var(--pri-soft) 0%,var(--acc-soft) 50%,var(--pri-soft) 100%);background-size:200% 200%;animation:heroShift 12s ease-in-out infinite;border:1px solid var(--border);border-radius:18px;padding:24px 22px;margin-bottom:24px;position:relative;overflow:hidden}
|
||
@keyframes heroShift{0%,100%{background-position:0% 50%}50%{background-position:100% 50%}}
|
||
.hero::before{content:'y=kx+b';position:absolute;right:-10px;top:-20px;font-size:clamp(2rem,8vw,5.5rem);font-weight:900;color:var(--pri);opacity:.10;line-height:1;pointer-events:none;font-family:'Unbounded',sans-serif}
|
||
.hero h2{font-family:'Unbounded',sans-serif;font-size:1.55rem;font-weight:800;color:var(--pri2);margin-bottom:10px;letter-spacing:-.01em}
|
||
.hero p{font-size:.95rem;color:var(--text);opacity:.88;margin-bottom:14px;max-width:640px}
|
||
.hero-row{display:flex;gap:14px;flex-wrap:wrap;align-items:center}
|
||
.btn-primary{padding:11px 22px;background:linear-gradient(135deg,var(--pri),var(--pri2));color:#fff;border-radius:11px;font-weight:700;font-size:.92rem;display:inline-flex;align-items:center;gap:8px;box-shadow:var(--sh2);transition:transform .15s,box-shadow .15s}
|
||
.btn-primary:hover{transform:translateY(-1px);box-shadow:0 8px 28px rgba(124,58,237,.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(124,58,237,.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(124,58,237,.22);font-family:'Unbounded',sans-serif}
|
||
|
||
.psel{margin-bottom:24px}
|
||
.psel-title{font-size:.72rem;font-weight:800;color:var(--muted);text-transform:uppercase;letter-spacing:.08em;margin-bottom:10px}
|
||
.psel-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(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:.86rem;font-weight:700;color:var(--text);line-height:1.3;margin-bottom:8px}
|
||
.psel-prog{height:4px;background:rgba(124,58,237,.10);border-radius:3px;overflow:hidden}
|
||
.psel-prog-fill{height:100%;background:var(--pri);width:0%;transition:width .4s}
|
||
.psel-card.final{background:linear-gradient(135deg,#fff5e1,#fef3c7)}
|
||
.psel-card.final .psel-num{color:var(--warn)}
|
||
|
||
.sec[id="sec-p15"]{ --sec-acc:#7c3aed; --sec-acc-d:#6d28d9; --sec-acc-soft:#ede9fe; }
|
||
.sec[id="sec-p16"]{ --sec-acc:#9333ea; --sec-acc-d:#7e22ce; --sec-acc-soft:#f3e8ff; }
|
||
.sec[id="sec-p17"]{ --sec-acc:#c026d3; --sec-acc-d:#a21caf; --sec-acc-soft:#fae8ff; }
|
||
.sec[id="sec-p18"]{ --sec-acc:#db2777; --sec-acc-d:#9d174d; --sec-acc-soft:#fce7f3; }
|
||
.sec[id="sec-p19"]{ --sec-acc:#2563eb; --sec-acc-d:#1d4ed8; --sec-acc-soft:#dbeafe; }
|
||
.sec[id="sec-p20"]{ --sec-acc:#0891b2; --sec-acc-d:#0e7490; --sec-acc-soft:#cffafe; }
|
||
.sec[id="sec-final3"]{ --sec-acc:#7c3aed; --sec-acc-d:#6d28d9; --sec-acc-soft:#ede9fe; }
|
||
|
||
.sec{display:none;position:relative;animation:fadeIn .35s ease}
|
||
.sec.active{display:block}
|
||
@keyframes fadeIn{from{opacity:0;transform:translateY(8px)}to{opacity:1;transform:none}}
|
||
.sec::before{content:attr(data-watermark);position:absolute;right:-20px;top:10%;font-family:'Unbounded',sans-serif;font-size:clamp(6rem,18vw,14rem);font-weight:900;color:transparent;-webkit-text-stroke:1.5px var(--sec-acc-soft,var(--pri-soft));line-height:1;pointer-events:none;user-select:none;z-index:0;opacity:.35}
|
||
.sec-header{margin-bottom:22px;padding-bottom:14px;border-bottom:2px solid var(--sec-acc-soft,var(--pri-soft));position:relative;z-index:1}
|
||
.sec-num{display:inline-block;padding:4px 10px;background:linear-gradient(135deg,var(--sec-acc,var(--pri)),var(--sec-acc-d,var(--pri2)));color:#fff;border-radius:7px;font-family:'Unbounded',sans-serif;font-size:.78rem;font-weight:800;letter-spacing:.04em;margin-bottom:8px}
|
||
.sec-h{font-family:'Unbounded',sans-serif;font-size:1.6rem;font-weight:800;color:var(--sec-acc-d,var(--pri2));letter-spacing:-.01em;line-height:1.25}
|
||
|
||
.card{background:var(--card);border:1px solid var(--border);border-radius:14px;padding:18px 20px;margin-bottom:16px;box-shadow:0 1px 3px rgba(0,0,0,.04),0 8px 24px rgba(124,58,237,.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(124,58,237,.12)}
|
||
.card-header{display:flex;align-items:center;gap:10px;margin-bottom:12px;padding-bottom:10px;border-bottom:1px dashed var(--border)}
|
||
.card-icon{width:32px;height:32px;border-radius:9px;display:flex;align-items:center;justify-content:center;flex-shrink:0;color:#fff}
|
||
.card-icon.repeat{background:#0ea5e9}.card-icon.theory{background:#8b5cf6}.card-icon.algo{background:#f59e0b}.card-icon.rule{background:#ec4899}.card-icon.example{background:#10b981}.card-icon.oral{background:#06b6d4}
|
||
.card-icon .ic{width:18px;height:18px}
|
||
.card-title{font-family:'Unbounded',sans-serif;font-size:.82rem;font-weight:800;text-transform:uppercase;letter-spacing:.06em;color:var(--muted);flex:1}
|
||
.card-num{font-size:.74rem;font-weight:700;color:var(--muted);background:var(--sec-acc-soft,var(--pri-soft));padding:3px 7px;border-radius:5px}
|
||
.card-body{font-size:.94rem;line-height:1.65}
|
||
.card-body p{margin-bottom:8px}
|
||
.card-body p:last-child{margin-bottom:0}
|
||
|
||
.wg{background:linear-gradient(135deg,var(--card),var(--sec-acc-soft,var(--pri-soft)));border:1.5px solid var(--sec-acc,var(--pri));border-radius:14px;padding:18px 20px;margin-bottom:18px;box-shadow:var(--sh2);position:relative;z-index:1}
|
||
.wg-header{display:flex;align-items:center;gap:8px;margin-bottom:14px}
|
||
.wg-badge{padding:4px 9px;background:var(--sec-acc,var(--pri));color:#fff;border-radius:6px;font-family:'Unbounded',sans-serif;font-size:.68rem;font-weight:800;text-transform:uppercase;letter-spacing:.06em}
|
||
.wg-title{font-family:'Unbounded',sans-serif;font-size:1.05rem;font-weight:800;color:var(--sec-acc-d,var(--pri2));flex:1}
|
||
.wg-help{font-size:.88rem;color:var(--text);margin-bottom:12px;line-height:1.55;background:linear-gradient(135deg,var(--warn-bg,#fef3c7),var(--sec-acc-soft,var(--pri-soft)));border-left:4px solid var(--warn,#f59e0b);padding:9px 14px;border-radius:9px}
|
||
|
||
.btn{padding:8px 16px;border-radius:8px;background:var(--card);color:var(--text);border:1.5px solid var(--border);font-weight:600;font-size:.88rem;transition:background .15s,border-color .15s,transform .1s}
|
||
.btn:hover{background:var(--sec-acc-soft,var(--pri-soft));border-color:var(--sec-acc,var(--pri))}
|
||
.btn:active{transform:scale(.96)}
|
||
.btn.primary{background:var(--sec-acc,var(--pri));color:#fff;border-color:var(--sec-acc,var(--pri))}
|
||
.btn.primary:hover{background:var(--sec-acc-d,var(--pri2));border-color:var(--sec-acc-d,var(--pri2))}
|
||
.tinp{padding:8px 12px;border:1.5px solid var(--border);border-radius:8px;background:var(--card);color:var(--text);transition:border-color .15s;font-family:'JetBrains Mono',monospace}
|
||
.tinp:focus{outline:0;border-color:var(--sec-acc,var(--pri));box-shadow:0 0 0 3px var(--sec-acc-soft,var(--pri-soft))}
|
||
|
||
.feedback{padding:10px 14px;border-radius:9px;font-weight:600;font-size:.88rem;margin-top:8px;display:none}
|
||
.feedback.ok{display:block;background:var(--ok-bg);color:#065f46;border-left:4px solid var(--ok)}
|
||
.feedback.fail{display:block;background:var(--fail-bg);color:#7f1d1d;border-left:4px solid var(--fail)}
|
||
|
||
.col-side{position:sticky;top:14px;align-self:start;height:fit-content;max-height:calc(100vh - 28px);overflow-y:auto}
|
||
.sidecard{background:var(--card);border:1px solid var(--border);border-radius:14px;padding:16px;margin-bottom:14px;box-shadow:var(--sh)}
|
||
.sidecard h4{font-family:'Unbounded',sans-serif;font-size:.74rem;font-weight:800;color:var(--pri2);text-transform:uppercase;letter-spacing:.07em;margin-bottom:10px;padding-bottom:8px;border-bottom:1px solid var(--border)}
|
||
.sidecard-row{margin-bottom:8px;font-size:.86rem;line-height:1.6}
|
||
.sidecard-row b{color:var(--pri);font-weight:700}
|
||
.sidecard-row:last-child{margin-bottom:0}
|
||
@media(max-width:980px){.col-side{position:static;max-height:none}}
|
||
|
||
.xp-card{background:linear-gradient(135deg,var(--acc-soft),var(--pri-soft));border:1.5px solid var(--acc);border-radius:12px;padding:14px;margin-bottom:14px}
|
||
.xp-card-title{font-size:.68rem;font-weight:800;color:var(--acc2);text-transform:uppercase;letter-spacing:.07em;margin-bottom:8px;display:flex;align-items:center;justify-content:space-between}
|
||
.xp-level{font-size:1.1rem;font-weight:900;color:var(--acc2);font-family:'Unbounded',sans-serif}
|
||
.xp-bar{height:9px;background:rgba(167,139,250,.22);border-radius:6px;overflow:hidden;margin:7px 0}
|
||
.xp-fill{height:100%;background:linear-gradient(90deg,var(--acc),var(--pri));border-radius:6px;transition:width .5s cubic-bezier(.4,0,.2,1)}
|
||
.xp-nums{font-size:.74rem;color:var(--muted);display:flex;justify-content:space-between}
|
||
|
||
.spoiler{border:1px solid var(--border);border-radius:10px;background:var(--card);margin:10px 0;overflow:hidden}
|
||
.spoiler summary{padding:8px 14px;background:var(--sec-acc-soft,var(--pri-soft));font-weight:700;cursor:pointer;font-size:.88rem;color:var(--sec-acc-d,var(--pri2));list-style:none;display:flex;align-items:center;gap:8px}
|
||
.spoiler summary::-webkit-details-marker{display:none}
|
||
.spoiler summary::before{content:'+';font-size:1.2rem;font-weight:900;color:var(--sec-acc,var(--pri));width:18px}
|
||
.spoiler[open] summary::before{content:'\2212'}
|
||
.spoiler-body{padding:10px 14px;font-size:.92rem;line-height:1.6}
|
||
|
||
.sec-nav{display:flex;gap:10px;margin-top:24px;padding-top:20px;border-top:1px solid var(--border);justify-content:space-between;flex-wrap:wrap}
|
||
.foot{text-align:center;padding:30px 16px;color:var(--muted);font-size:.78rem;border-top:1px solid var(--border);margin-top:30px}
|
||
|
||
.ach-popup{position:fixed;top:80px;right:18px;background:linear-gradient(135deg,#7c3aed,#a78bfa);color:#fff;padding:12px 18px;border-radius:11px;font-weight:700;font-size:.9rem;box-shadow:0 8px 28px rgba(124,58,237,.45);z-index:1002;display:none;align-items:center;gap:8px;max-width:340px}
|
||
.ach-popup.show{display:flex}
|
||
|
||
.dnd-pool{display:flex;flex-wrap:wrap;gap:8px;margin-bottom:14px;padding:10px;border:1.5px dashed var(--border);border-radius:10px;min-height:54px;transition:border-color .18s,background .18s}
|
||
.dnd-pool.over{border-color:var(--sec-acc,var(--pri));background:var(--sec-acc-soft,var(--pri-soft));border-style:solid}
|
||
.dnd-pool.col{flex-direction:column;align-items:stretch}
|
||
.dnd-chip{display:inline-flex;align-items:center;gap:6px;padding:6px 12px;background:var(--card);border:1.5px solid var(--border);border-radius:10px;cursor:grab;user-select:none;font-size:.92rem;line-height:1.4;transition:transform .12s,box-shadow .12s,border-color .12s;touch-action:none;max-width:100%}
|
||
.dnd-chip:hover{transform:translateY(-1px);border-color:var(--sec-acc,var(--pri));box-shadow:var(--sh)}
|
||
.dnd-chip.armed{border-color:var(--sec-acc,var(--pri));background:var(--sec-acc-soft,var(--pri-soft));box-shadow:0 0 0 3px rgba(124,58,237,.22)}
|
||
.dnd-chip.dragging{opacity:.28}
|
||
.dnd-chip.placed{background:var(--sec-acc-soft,var(--pri-soft));border-color:var(--sec-acc,var(--pri))}
|
||
.dnd-chip .dnd-x{padding:0 5px;color:var(--muted);font-weight:700;font-size:1.05rem;border-radius:4px;cursor:pointer}
|
||
.dnd-chip .dnd-x:hover{color:var(--bad,var(--fail));background:var(--fail-bg)}
|
||
.drop-box{background:var(--card);border:1.5px dashed var(--border);border-radius:10px;padding:10px;min-height:90px}
|
||
.drop-box:hover{border-color:var(--sec-acc,var(--pri));background:var(--sec-acc-soft,var(--pri-soft))}
|
||
.drop-box h5{font-family:'Unbounded',sans-serif;font-size:.78rem;color:var(--sec-acc-d,var(--pri2));margin-bottom:8px;text-transform:uppercase;letter-spacing:.05em}
|
||
.drop-box.over{border-color:var(--sec-acc,var(--pri));background:var(--sec-acc-soft,var(--pri-soft));border-style:solid;transform:scale(1.015)}
|
||
.drop-items{display:flex;flex-wrap:wrap;gap:6px;min-height:32px}
|
||
.actions{display:flex;gap:8px;flex-wrap:wrap;margin-top:10px}
|
||
.sliders{display:grid;grid-template-columns:repeat(auto-fit,minmax(180px,1fr));gap:10px;margin-bottom:10px}
|
||
.sliders label{display:block;font-size:.92rem;color:var(--muted);background:var(--card);padding:8px 12px;border-radius:8px;border:1px solid var(--border);line-height:1.5}
|
||
.sliders label b{font-family:'JetBrains Mono',monospace;font-size:1.05rem;color:var(--sec-acc-d,var(--pri2));margin-left:4px}
|
||
.sliders label input[type="range"]{display:block;width:100%;margin-top:6px;accent-color:var(--sec-acc,var(--pri))}
|
||
.score-display{display:flex;gap:14px;flex-wrap:wrap;align-items:center;padding:10px 14px;background:var(--sec-acc-soft,var(--pri-soft));border-radius:10px;margin-bottom:12px}
|
||
.score-display b{color:var(--sec-acc-d,var(--pri2));font-size:1.15rem}
|
||
|
||
.hp-boss{height:14px;background:rgba(220,38,38,.12);border-radius:9px;overflow:hidden;border:1px solid #fecaca;margin:8px 0}
|
||
.hp-boss-fill{height:100%;background:linear-gradient(90deg,#dc2626,#f59e0b);border-radius:9px;transition:width .5s cubic-bezier(.4,0,.2,1)}
|
||
|
||
.col-side-backdrop{position:fixed;inset:0;background:rgba(0,0,0,.42);z-index:9990;display:none}
|
||
.col-side-backdrop.show{display:block}
|
||
@media(max-width:980px){
|
||
.col-side{position:fixed;top:0;right:0;height:100vh;width:300px;max-width:88vw;background:var(--bg);box-shadow:-12px 0 24px rgba(0,0,0,.18);padding:18px 16px;overflow-y:auto;transform:translateX(100%);transition:transform .25s ease;z-index:9991;max-height:none}
|
||
.col-side.open{transform:none}
|
||
}
|
||
|
||
.gloss-term{border-bottom:1.5px dotted var(--sec-acc,var(--pri));cursor:help;color:var(--sec-acc-d,var(--pri2));font-weight:600;padding:0 1px}
|
||
.gloss-term:hover{background:var(--sec-acc-soft,var(--pri-soft));border-radius:3px}
|
||
.gloss-tip{position:fixed;max-width:320px;padding:11px 14px;background:var(--card);border:1.5px solid var(--sec-acc,var(--pri));border-radius:11px;font-size:.84rem;line-height:1.55;box-shadow:0 12px 32px rgba(0,0,0,.18);z-index:9994;display:none;pointer-events:none;color:var(--text)}
|
||
.gloss-tip.show{display:block}
|
||
.gloss-tip b{color:var(--sec-acc-d,var(--pri2));font-size:.92rem}
|
||
|
||
.search-modal{position:fixed;inset:0;background:rgba(15,23,42,.55);backdrop-filter:blur(4px);z-index:9993;display:none;align-items:flex-start;justify-content:center;padding-top:14vh}
|
||
.search-modal.show{display:flex}
|
||
.search-box{background:var(--bg);border:1px solid var(--border);border-radius:14px;width:560px;max-width:92vw;max-height:70vh;display:flex;flex-direction:column;overflow:hidden;box-shadow:0 24px 64px rgba(0,0,0,.4)}
|
||
.search-input{padding:14px 16px;font-size:1rem;border:0;border-bottom:1px solid var(--border);background:transparent;color:var(--text);outline:none}
|
||
.search-results{flex:1;overflow-y:auto;padding:6px 0}
|
||
.search-row{display:block;padding:8px 16px;cursor:pointer;border-bottom:1px solid var(--border);text-align:left;background:transparent;border:0;width:100%;color:var(--text)}
|
||
.search-row:hover,.search-row.active{background:var(--sec-acc-soft,var(--pri-soft))}
|
||
.search-row .sr-kind{font-size:.7rem;font-weight:800;color:var(--muted);text-transform:uppercase;letter-spacing:.06em;margin-bottom:2px}
|
||
.search-row .sr-title{font-weight:700;font-size:.92rem;color:var(--text)}
|
||
.search-row .sr-desc{font-size:.8rem;color:var(--muted);margin-top:2px}
|
||
.search-empty{padding:20px;text-align:center;color:var(--muted);font-size:.88rem}
|
||
.search-foot{padding:8px 14px;border-top:1px solid var(--border);font-size:.74rem;color:var(--muted);display:flex;gap:14px}
|
||
.search-foot kbd{padding:2px 6px;background:var(--card);border:1px solid var(--border);border-radius:4px;font-family:'JetBrains Mono',monospace;font-size:.72rem}
|
||
|
||
.boss-card{padding:16px;background:var(--card);border-radius:12px;border:2px solid var(--bad,#dc2626);margin-bottom:14px}
|
||
.boss-head{display:flex;align-items:center;gap:10px;margin-bottom:10px}
|
||
.boss-title{font-family:'Unbounded',sans-serif;font-weight:800;color:#7f1d1d;font-size:1.04rem;flex:1}
|
||
.boss-stage{font-size:.85rem;color:var(--muted)}
|
||
.boss-q{font-size:1rem;line-height:1.55;padding:11px 13px;background:var(--card-soft);border-radius:8px;margin-bottom:9px;border-left:3px solid var(--bad,#dc2626)}
|
||
|
||
.tbl{width:100%;border-collapse:collapse;margin:12px 0;font-size:.88rem}
|
||
.tbl th,.tbl td{padding:7px 10px;border:1px solid var(--border);text-align:center}
|
||
.tbl th{background:var(--sec-acc-soft,var(--pri-soft));color:var(--sec-acc-d,var(--pri2));font-weight:700}
|
||
|
||
.coord{background:#fafafa;border:1px solid var(--border);border-radius:10px;display:block;max-width:100%;margin:0 auto}
|
||
.dark .coord{background:#0f172a}
|
||
</style>
|
||
</head>
|
||
<body>
|
||
|
||
<header class="hdr">
|
||
<div class="hdr-row">
|
||
<div>
|
||
<h1>Алгебра 7 · Глава 3</h1>
|
||
<div class="hdr-sub">Линейные уравнения · Неравенства · Линейная функция</div>
|
||
</div>
|
||
<div class="hdr-side">
|
||
<a href="/textbook/algebra-7" class="hdr-btn"><svg class="ic" viewBox="0 0 24 24"><polyline points="15 18 9 12 15 6"/></svg> К алгебре 7</a>
|
||
<button id="search-btn" class="hdr-btn"><svg class="ic" viewBox="0 0 24 24"><circle cx="11" cy="11" r="7"/><path d="m21 21-4-4"/></svg> Поиск</button>
|
||
<button id="sidebar-btn" class="hdr-btn"><svg class="ic" viewBox="0 0 24 24"><line x1="4" y1="6" x2="20" y2="6"/><line x1="4" y1="12" x2="20" y2="12"/><line x1="4" y1="18" x2="14" y2="18"/></svg> Шпаргалка</button>
|
||
<button id="theme-btn" class="hdr-btn"><svg class="ic" viewBox="0 0 24 24"><path d="M21 12.8A9 9 0 1 1 11.2 3a7 7 0 0 0 9.8 9.8z"/></svg><span id="theme-lab">Тёмная</span></button>
|
||
</div>
|
||
</div>
|
||
</header>
|
||
|
||
<main class="main">
|
||
<div class="col-main">
|
||
|
||
<section class="hero">
|
||
<h2>Уравнения, неравенства и функции</h2>
|
||
<p>В этой главе мы изучаем <b>линейные уравнения с одной переменной</b> и применяем их к текстовым задачам. Знакомимся со <b>свойствами числовых неравенств</b> и решаем <b>линейные неравенства</b>. Узнаём, что такое <b>функция</b> и как устроена <b>линейная функция</b> $y = kx + b$ — фундамент всей школьной алгебры.</p>
|
||
<div class="hero-row">
|
||
<button class="btn-primary" onclick="goTo('p15')"><svg class="ic" viewBox="0 0 24 24"><polygon points="6 4 20 12 6 20 6 4" fill="currentColor" stroke="none"/></svg> Начать § 15</button>
|
||
<div class="hero-progress">
|
||
<span class="hp-label">Прогресс по главе</span>
|
||
<div class="hp-bar"><div id="hero-hp-fill" class="hp-fill"></div></div>
|
||
<span id="hero-hp-text" class="hp-text">0%</span>
|
||
</div>
|
||
<div id="hero-xp-badge" class="hero-xp-badge"></div>
|
||
</div>
|
||
</section>
|
||
|
||
<section class="psel">
|
||
<div class="psel-title">Параграфы главы</div>
|
||
<div id="psel-grid" class="psel-grid"></div>
|
||
</section>
|
||
|
||
<section id="sec-p15" class="sec" data-watermark="ax=b"><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="><"><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="ax>b"><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-p19" class="sec" data-watermark="f(x)"><div class="sec-header"><span class="sec-num">§ 19</span><h2 class="sec-h">Функция</h2></div><div id="p19-body"></div></section>
|
||
<section id="sec-p20" class="sec" data-watermark="y=kx+b"><div class="sec-header"><span class="sec-num">§ 20</span><h2 class="sec-h">Линейная функция и её свойства</h2></div><div id="p20-body"></div></section>
|
||
<section id="sec-final3" class="sec" data-watermark="★"><div class="sec-header"><span class="sec-num" style="background:linear-gradient(135deg,#7c3aed,#a78bfa)">Финал главы</span><h2 class="sec-h">Итоги. 5 боссов главы 3</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">Интерактивный учебник «Алгебра 7» · Глава 3 · Уравнения, неравенства, функция · LearnSpace</footer>
|
||
|
||
<div id="ach-popup" class="ach-popup"><svg class="ic" viewBox="0 0 24 24" style="width:22px;height:22px"><polygon points="12,2 22,20 2,20"/></svg><span id="ach-text">Достижение!</span></div>
|
||
<div id="gloss-tip" class="gloss-tip"></div>
|
||
<div id="search-modal" class="search-modal" role="dialog">
|
||
<div class="search-box">
|
||
<input type="text" id="search-input" class="search-input" placeholder="Поиск: понятие, формула, параграф…" autocomplete="off">
|
||
<div id="search-results" class="search-results"></div>
|
||
<div class="search-foot"><span><kbd>↑↓</kbd> навигация</span><span><kbd>Enter</kbd> открыть</span><span><kbd>Esc</kbd> закрыть</span></div>
|
||
</div>
|
||
</div>
|
||
|
||
<script>
|
||
'use strict';
|
||
|
||
const STATE = { current:'p15', progress:{p15:0,p16:0,p17:0,p18:0,p19:0,p20:0,final3:0}, achievements:new Map(), xp:0, level:1 };
|
||
const TOTAL_PARAS = 7;
|
||
const _TB_SLUG = 'algebra-7-ch3';
|
||
|
||
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!',
|
||
p16_done:'Текстовые задачи покорены!',
|
||
p18_done:'Неравенства решаешь уверенно!',
|
||
p20_done:'Линейная функция — твой друг!',
|
||
ch3_done:'Глава 3 пройдена!',
|
||
};
|
||
|
||
function loadProgress(){
|
||
try{
|
||
const s=localStorage.getItem('algebra7_ch3_progress'); if(s) Object.assign(STATE.progress, JSON.parse(s));
|
||
const a=localStorage.getItem('algebra7_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)); } }
|
||
STATE.xp=+(localStorage.getItem('algebra7_xp')||0); STATE.level=calcLevel(STATE.xp);
|
||
}catch(e){}
|
||
}
|
||
function saveProgress(){
|
||
try{
|
||
localStorage.setItem('algebra7_ch3_progress', JSON.stringify(STATE.progress));
|
||
localStorage.setItem('algebra7_ch3_achievements', JSON.stringify(Object.fromEntries(STATE.achievements)));
|
||
localStorage.setItem('algebra7_xp', String(STATE.xp));
|
||
}catch(e){}
|
||
}
|
||
function bumpProgress(key, delta){
|
||
STATE.progress[key]=Math.max(0,Math.min(100,(STATE.progress[key]||0)+delta));
|
||
saveProgress(); refreshProgressUI();
|
||
if(STATE.progress[key]>=50) markParaRead(key);
|
||
if(STATE.progress[key]>=100){
|
||
if(key==='p16') achievement('p16_done');
|
||
else if(key==='p18') achievement('p18_done');
|
||
else if(key==='p20') achievement('p20_done');
|
||
else if(key==='final3') achievement('ch3_done');
|
||
}
|
||
}
|
||
|
||
const _markedRead=new Set();
|
||
let _pendingProgressBody=null, _progressTimer=null;
|
||
function _flushProgress(){
|
||
const body=_pendingProgressBody; _pendingProgressBody=null; if(!body) return;
|
||
const tok=(window.LS&&LS.getToken)?LS.getToken():''; if(!tok) return;
|
||
fetch('/api/textbooks/'+_TB_SLUG+'/progress',{method:'POST',headers:{'Content-Type':'application/json','Authorization':'Bearer '+tok},body:JSON.stringify(body),keepalive:true}).catch(()=>{});
|
||
}
|
||
function _queueProgress(patch){ _pendingProgressBody=Object.assign(_pendingProgressBody||{},patch); if(_progressTimer) clearTimeout(_progressTimer); _progressTimer=setTimeout(_flushProgress, 600); }
|
||
function markLastPara(id){ _queueProgress({last_para:id}); }
|
||
function markParaRead(id){ if(_markedRead.has(id)) return; _markedRead.add(id); _queueProgress({mark_read:id}); }
|
||
window.addEventListener('beforeunload', _flushProgress);
|
||
function loadServerReadState(){
|
||
const tok=(window.LS&&LS.getToken)?LS.getToken():''; if(!tok) return;
|
||
fetch('/api/textbooks/'+_TB_SLUG,{headers:{'Authorization':'Bearer '+tok}}).then(r=>r.ok?r.json():null).then(d=>{
|
||
if(!d||!d.progress) return;
|
||
(d.progress.read||[]).forEach(k=>{_markedRead.add(k); if((STATE.progress[k]||0)<50) STATE.progress[k]=100;});
|
||
saveProgress(); refreshProgressUI();
|
||
}).catch(()=>{});
|
||
}
|
||
|
||
function addXp(n,src){
|
||
if(!n) return;
|
||
const prev=STATE.level; STATE.xp=Math.max(0,(STATE.xp||0)+n); STATE.level=calcLevel(STATE.xp);
|
||
saveProgress(); refreshProgressUI();
|
||
if(window.LS&&window.LS.xp) window.LS.xp.add(n,'algebra7-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)/TOTAL_PARAS);
|
||
const f=document.getElementById('hero-hp-fill'); if(f) f.style.width=total+'%';
|
||
const t=document.getElementById('hero-hp-text'); if(t) t.textContent=total+'% пройдено';
|
||
document.querySelectorAll('[data-prog-card]').forEach(el=>{ const k=el.dataset.progCard; const fl=el.querySelector('.psel-prog-fill'); if(fl) fl.style.width=(STATE.progress[k]||0)+'%'; });
|
||
const xpBadge=document.getElementById('hero-xp-badge');
|
||
if(xpBadge){ xpBadge.innerHTML='<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" style="width:13px;height:13px"><polygon points="12 2 22 20 2 20"/></svg> Ур. '+STATE.level+' \xb7 '+(STATE.xp||0)+' XP'; }
|
||
if(STATE.current && document.getElementById('sidebar-content')){ try{ buildSidebar(STATE.current); }catch(e){} }
|
||
}
|
||
|
||
function achievement(id,text){
|
||
if(STATE.achievements.has(id)) return;
|
||
STATE.achievements.set(id, text||ACH_LABELS[id]||id); saveProgress();
|
||
const pop=document.getElementById('ach-popup');
|
||
if(pop){ document.getElementById('ach-text').textContent=text||ACH_LABELS[id]||id; pop.classList.add('show'); setTimeout(()=>pop.classList.remove('show'),3300); }
|
||
addXp(20,'ach-'+id);
|
||
}
|
||
|
||
const PARAS = [
|
||
{ id:'p15', num:'§ 15', name:'Линейные уравнения', sub:'$ax = b$ — три случая' },
|
||
{ id:'p16', num:'§ 16', name:'Текстовые задачи', sub:'Составление уравнений' },
|
||
{ id:'p17', num:'§ 17', name:'Числовые неравенства', sub:'3 свойства + сложение/умножение' },
|
||
{ id:'p18', num:'§ 18', name:'Линейные неравенства', sub:'$ax > b$ и алгоритм' },
|
||
{ id:'p19', num:'§ 19', name:'Функция', sub:'Аргумент, $f(x)$, нули' },
|
||
{ id:'p20', num:'§ 20', name:'Линейная функция', sub:'$y = kx + b$' },
|
||
{ id:'final3', num:'★', name:'Финал главы', sub:'Итоги \xB7 5 боссов', final:true },
|
||
];
|
||
|
||
function buildParaSelector(){
|
||
const g=document.getElementById('psel-grid'); g.innerHTML='';
|
||
PARAS.forEach(p=>{
|
||
const card=document.createElement('div');
|
||
card.className='psel-card'+(p.final?' final':'');
|
||
card.dataset.id=p.id; card.dataset.progCard=p.id;
|
||
card.innerHTML='<div class="psel-num">'+p.num+'</div><div class="psel-name">'+p.name+'</div><div class="psel-prog"><div class="psel-prog-fill"></div></div>';
|
||
card.addEventListener('click', ()=>goTo(p.id));
|
||
g.appendChild(card);
|
||
});
|
||
}
|
||
|
||
const BUILT=new Set();
|
||
const BUILDERS = { p15:()=>buildP15(), p16:()=>buildP16(), p17:()=>buildP17(), p18:()=>buildP18(), p19:()=>buildP19(), p20:()=>buildP20(), final3:()=>buildFinal3() };
|
||
function ensureBuilt(id){ if(BUILT.has(id)) return; const fn=BUILDERS[id]; if(fn){ fn(); BUILT.add(id); } }
|
||
function goTo(id){
|
||
STATE.current=id; ensureBuilt(id);
|
||
document.querySelectorAll('.sec').forEach(s=>s.classList.remove('active'));
|
||
const el=document.getElementById('sec-'+id); if(el) el.classList.add('active');
|
||
document.querySelectorAll('.psel-card').forEach(c=>c.classList.toggle('active', c.dataset.id===id));
|
||
buildSidebar(id);
|
||
window.scrollTo({top:0,behavior:'smooth'});
|
||
if((STATE.progress[id]||0)<10) bumpProgress(id, 10);
|
||
if(window.renderMathInElement) setTimeout(()=>renderMath(el), 0);
|
||
setTimeout(()=>{ try{ wrapGlossary(el); }catch(e){} }, 60);
|
||
markLastPara(id);
|
||
}
|
||
|
||
const SIDEBARS = {
|
||
p15:{title:'Шпаргалка \xA7 15',rows:[
|
||
['Уравнение','равенство с переменной'],
|
||
['Корень','значение переменной, при котором уравнение верно'],
|
||
['Линейное','вида $ax = b$'],
|
||
['$a \\ne 0$','единственный корень $x = b/a$'],
|
||
['$a = 0, b = 0$','любое число — корень'],
|
||
['$a = 0, b \\ne 0$','корней нет'],
|
||
]},
|
||
p16:{title:'Шпаргалка \xA7 16',rows:[
|
||
['Шаг 1','прочитай задачу, найди неизвестное'],
|
||
['Шаг 2','обозначь неизвестное буквой $x$'],
|
||
['Шаг 3','составь уравнение из условия'],
|
||
['Шаг 4','реши уравнение, проверь смысл'],
|
||
]},
|
||
p17:{title:'Шпаргалка \xA7 17',rows:[
|
||
['$a > b \\Leftrightarrow$','$a - b > 0$'],
|
||
['Свойство 1','если $a > b$ и $b > c$, то $a > c$'],
|
||
['Свойство 2','к обеим частям можно прибавить число'],
|
||
['Свойство 3','умножая на положительное — знак сохр., на отрицательное — меняется'],
|
||
['Сложение','$a > b$ и $c > d \\Rightarrow a + c > b + d$'],
|
||
]},
|
||
p18:{title:'Шпаргалка \xA7 18',rows:[
|
||
['Равносильные','имеют одинаковые решения'],
|
||
['Перенос','знак меняется'],
|
||
['Умножение на $>0$','знак неравенства сохраняется'],
|
||
['Умножение на $<0$','знак <b>меняется</b>'],
|
||
['$0 \\cdot x < 0$','корней нет'],
|
||
['$0 \\cdot x > -1$','любое число'],
|
||
]},
|
||
p19:{title:'Шпаргалка \xA7 19',rows:[
|
||
['Функция','каждому $x$ — единственное $y$'],
|
||
['Аргумент','независимая переменная $x$'],
|
||
['$D(f)$','область определения'],
|
||
['$E(f)$','множество значений'],
|
||
['Нуль функции','$x$, при котором $f(x) = 0$'],
|
||
['График','множество точек $(x; f(x))$'],
|
||
]},
|
||
p20:{title:'Шпаргалка \xA7 20',rows:[
|
||
['Линейная','$y = kx + b$'],
|
||
['$k$','угловой коэффициент'],
|
||
['$b$','ордината точки $(0; b)$'],
|
||
['$k > 0$','острый угол с осью $x$'],
|
||
['$k < 0$','тупой угол с осью $x$'],
|
||
['$k = 0$','прямая параллельна оси $x$'],
|
||
['$k_1 = k_2$','прямые параллельны'],
|
||
['$k_1 \\ne k_2$','прямые пересекаются'],
|
||
]},
|
||
final3:{title:'Финал главы',rows:[
|
||
['\xA715–\xA720','теория главы 3'],
|
||
['Боссов','5 итоговых задач'],
|
||
['Награда','+100 XP за полное прохождение'],
|
||
]},
|
||
};
|
||
const TIPS=[
|
||
{sec:'p15',html:'Уравнение $ax = b$ имеет: <b>один</b> корень ($a \\ne 0$), <b>бесконечно</b> много корней ($a = b = 0$) или <b>не имеет</b> корней ($a = 0, b \\ne 0$).'},
|
||
{sec:'p16',html:'Чтобы составить уравнение, обозначь неизвестное буквой и «переведи» условие задачи на язык математики.'},
|
||
{sec:'p17',html:'При умножении неравенства на <b>отрицательное число</b> знак неравенства <b>меняется</b>!'},
|
||
{sec:'p18',html:'Когда после преобразований получается $0 \\cdot x < $ что-то — посмотри: верно неравенство $0 < $ что-то? Если да — любое число; если нет — корней нет.'},
|
||
{sec:'p19',html:'График — это <b>множество точек</b>. Точка $(x_0; y_0)$ принадлежит графику $y = f(x)$, если $y_0 = f(x_0)$.'},
|
||
{sec:'p20',html:'Чтобы построить $y = kx + b$, найди две точки: подставь два значения $x$. Через две точки проведи прямую.'},
|
||
{sec:'final3',html:'5 боссов проверяют все темы главы 3.'},
|
||
];
|
||
|
||
function buildSidebar(id){
|
||
const box=document.getElementById('sidebar-content');
|
||
const sb=SIDEBARS[id]||SIDEBARS.p15;
|
||
let html='';
|
||
const xpForLv=_xpForLevel(STATE.level), xpNext=_xpForLevel(STATE.level+1);
|
||
const xpInLv=STATE.xp-xpForLv, xpRange=xpNext-xpForLv;
|
||
const xpPct=xpRange>0?Math.round(xpInLv/xpRange*100):100;
|
||
html+='<div class="xp-card"><div class="xp-card-title"><span>XP-прогресс</span><span class="xp-level">Ур. '+STATE.level+'</span></div><div class="xp-bar"><div class="xp-fill" style="width:'+xpPct+'%"></div></div><div class="xp-nums"><span>'+STATE.xp+' XP</span><span>'+xpNext+' XP</span></div></div>';
|
||
html+='<div class="sidecard"><h4>'+sb.title+'</h4>';
|
||
sb.rows.forEach(([k,v])=>{ html+='<div class="sidecard-row"><b>'+k+'</b>'+(v?' — '+v:'')+'</div>'; });
|
||
html+='</div>';
|
||
const tip=TIPS.find(t=>t.sec===id)||TIPS[0];
|
||
html+='<div class="sidecard" style="background:linear-gradient(135deg,var(--warn-bg,#fef3c7),var(--pri-soft));border-color:var(--warn,#f59e0b)"><h4 style="color:#92400e;display:flex;align-items:center;gap:6px"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" style="width:14px;height:14px"><polygon points="12,2 22,20 2,20"/></svg>Подсказка</h4><div class="sidecard-row" style="margin-bottom:0;font-size:.84rem;line-height:1.55">'+tip.html+'</div></div>';
|
||
if(STATE.achievements.size>0){
|
||
html+='<div class="sidecard"><h4>Достижения <span style="color:var(--warn);float:right">'+STATE.achievements.size+'</span></h4>';
|
||
[...STATE.achievements.values()].slice(-4).forEach(text=>{ html+='<div class="sidecard-row" style="font-size:.78rem;color:var(--ok)">✓ '+text+'</div>'; });
|
||
html+='</div>';
|
||
}
|
||
box.innerHTML=html;
|
||
if(window.renderMathInElement) try{ renderMath(box); }catch(e){}
|
||
}
|
||
|
||
function initTheme(){
|
||
const t=localStorage.getItem('algebra7_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('algebra7_ch3_theme', dark?'dark':'light');
|
||
document.getElementById('theme-lab').textContent=dark?'Светлая':'Тёмная';
|
||
});
|
||
}
|
||
|
||
function renderMath(root){ if(window.renderMathInElement){ try{ renderMathInElement(root, {delimiters:[{left:'$$',right:'$$',display:true},{left:'$',right:'$',display:false},{left:'\\[',right:'\\]',display:true},{left:'\\(',right:'\\)',display:false}],throwOnError:false}); }catch(e){} } }
|
||
function feedback(elm, ok, text){ if(!elm) return; elm.className='feedback '+(ok?'ok':'fail'); elm.innerHTML=text||(ok?'✓ Верно!':'✗ Неверно'); elm.style.display='block'; try{renderMath(elm);}catch(e){} }
|
||
function fmt(n){ if(!isFinite(n)) return '?'; if(Number.isInteger(n)) return String(n); return Math.abs(n-Math.round(n))<1e-9?String(Math.round(n)):(+n.toFixed(6)).toString(); }
|
||
|
||
const ICONS = {
|
||
repeat:'<svg class="ic" viewBox="0 0 24 24"><polyline points="9 11 12 14 22 4"/><path d="M21 12v7a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h11"/></svg>',
|
||
theory:'<svg class="ic" viewBox="0 0 24 24"><path d="M4 19.5A2.5 2.5 0 0 1 6.5 17H20"/><path d="M6.5 2H20v20H6.5A2.5 2.5 0 0 1 4 19.5v-15A2.5 2.5 0 0 1 6.5 2z"/></svg>',
|
||
algo:'<svg class="ic" viewBox="0 0 24 24"><polyline points="17 11 21 7 17 3"/><line x1="21" y1="7" x2="9" y2="7"/><polyline points="7 13 3 17 7 21"/><line x1="3" y1="17" x2="15" y2="17"/></svg>',
|
||
rule:'<svg class="ic" viewBox="0 0 24 24"><path d="M6 8a6 6 0 0 1 12 0c0 7 3 9 3 9H3s3-2 3-9"/><path d="M10.3 21a1.94 1.94 0 0 0 3.4 0"/></svg>',
|
||
example:'<svg class="ic" viewBox="0 0 24 24"><path d="M9 18h6"/><path d="M10 22h4"/><path d="M12 2a7 7 0 0 0-4 13c1 1 2 2 2 4h4c0-2 1-3 2-4a7 7 0 0 0-4-13z"/></svg>',
|
||
oral:'<svg class="ic" viewBox="0 0 24 24"><circle cx="12" cy="12" r="10"/><path d="M9.09 9a3 3 0 0 1 5.83 1c0 2-3 3-3 3"/><line x1="12" y1="17" x2="12.01" y2="17"/></svg>',
|
||
};
|
||
function makeCard(kind, title, num, body){
|
||
const labels={repeat:'Повторение',theory:'Теория',algo:'Алгоритм',rule:'Правило',example:'Пример',oral:'Устно'};
|
||
return '<div class="card"><div class="card-header"><div class="card-icon '+kind+'">'+ICONS[kind]+'</div><div class="card-title">'+(labels[kind]||'')+(title&&title!==labels[kind]?' \xb7 '+title:'')+'</div>'+(num?'<div class="card-num">'+num+'</div>':'')+'</div><div class="card-body">'+body+'</div></div>';
|
||
}
|
||
function secNav(prev, next){
|
||
const NAMES={p15:'\xA715',p16:'\xA716',p17:'\xA717',p18:'\xA718',p19:'\xA719',p20:'\xA720',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;
|
||
}
|
||
|
||
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=['#7c3aed','#a78bfa','#c084fc','#f59e0b','#10b981'];
|
||
for(let i=0;i<80;i++){ _confettiParticles.push({x:window.innerWidth/2+(Math.random()-.5)*200,y:window.innerHeight/2,vx:(Math.random()-.5)*14,vy:-10-Math.random()*10,g:.4,life:100,color:colors[i%colors.length],r:4+Math.random()*4,rot:0,vRot:(Math.random()-.5)*.3}); }
|
||
if(_confettiRaf) cancelAnimationFrame(_confettiRaf);
|
||
function frame(){ ctx.clearRect(0,0,c.width,c.height); _confettiParticles=_confettiParticles.filter(p=>{p.x+=p.vx;p.y+=p.vy;p.vy+=p.g;p.life--;p.rot+=p.vRot;ctx.save();ctx.translate(p.x,p.y);ctx.rotate(p.rot);ctx.fillStyle=p.color;ctx.fillRect(-p.r,-p.r/2,p.r*2,p.r);ctx.restore();return p.life>0&&p.y<c.height+50;}); if(_confettiParticles.length>0) _confettiRaf=requestAnimationFrame(frame); else{ ctx.clearRect(0,0,c.width,c.height); _confettiRaf=null; } }
|
||
frame();
|
||
}
|
||
|
||
function setupSorter(cfg){
|
||
const placed={}; const pool=document.getElementById(cfg.poolId); const scope=document.querySelector(cfg.scopeSelector);
|
||
if(!pool||!scope) return {placed,render:()=>{},reset:()=>{}};
|
||
pool.classList.add('dnd-pool'); if(cfg.columnLayout) pool.classList.add('col');
|
||
let armed=null;
|
||
function buildChip(it,isPlaced){ const e=document.createElement('div'); e.className='dnd-chip'+(isPlaced?' placed':''); e.dataset.id=it.id; e.innerHTML='<span class="dnd-txt">'+it.html+'</span><span class="dnd-x" title="Убрать">\xd7</span>'; attach(e,it.id); return e; }
|
||
function attach(elm,itId){ let ghost=null,dragging=false,sx=0,sy=0; elm.addEventListener('pointerdown',ev=>{ if(ev.button!==undefined&&ev.button!==0) return;
|
||
ev.preventDefault(); if(ev.target.classList&&ev.target.classList.contains('dnd-x')){ ev.stopPropagation(); if(placed[itId]){delete placed[itId];render();}else if(armed===itId){armed=null;render();} return; } sx=ev.clientX;sy=ev.clientY; const r=elm.getBoundingClientRect(); const ox=ev.clientX-r.left,oy=ev.clientY-r.top; try{elm.setPointerCapture(ev.pointerId);}catch(e){} function onMove(e){ const dx=e.clientX-sx,dy=e.clientY-sy; if(!dragging&&Math.hypot(dx,dy)>8){ dragging=true; ghost=elm.cloneNode(true); ghost.classList.remove('armed'); ghost.style.cssText='position:fixed;z-index:9999;pointer-events:none;opacity:.9;transform:rotate(-2.5deg);box-shadow:0 14px 36px rgba(0,0,0,.32);width:'+r.width+'px;left:'+(e.clientX-ox)+'px;top:'+(e.clientY-oy)+'px'; document.body.appendChild(ghost); elm.classList.add('dragging'); } if(dragging&&ghost){ ghost.style.left=(e.clientX-ox)+'px';ghost.style.top=(e.clientY-oy)+'px'; const under=document.elementsFromPoint(e.clientX,e.clientY); scope.querySelectorAll('.drop-box.over,.dnd-pool.over').forEach(n=>n.classList.remove('over')); const tgt=under.find(n=>n.classList&&(n.classList.contains('drop-box')||n.classList.contains('dnd-pool'))); if(tgt)tgt.classList.add('over'); } } function onUp(e){ elm.removeEventListener('pointermove',onMove);elm.removeEventListener('pointerup',onUp);elm.removeEventListener('pointercancel',onUp);elm.classList.remove('dragging'); if(ghost){ghost.remove();ghost=null;} scope.querySelectorAll('.drop-box.over,.dnd-pool.over').forEach(n=>n.classList.remove('over')); if(dragging){ const under=document.elementsFromPoint(e.clientX,e.clientY); const box=under.find(n=>n.classList&&n.classList.contains('drop-box')); const pl=under.find(n=>n.classList&&n.classList.contains('dnd-pool')); if(box){const di=box.querySelector('[data-cat]');if(di){placed[itId]=di.dataset.cat;armed=null;render();return;}}else if(pl){delete placed[itId];armed=null;render();return;} }else{ if(placed[itId]){delete placed[itId];armed=null;render();}else{armed=(armed===itId)?null:itId;render();} } dragging=false; } elm.addEventListener('pointermove',onMove);elm.addEventListener('pointerup',onUp);elm.addEventListener('pointercancel',onUp); }); }
|
||
function attachBoxTaps(){ scope.querySelectorAll('.drop-box').forEach(box=>{ box.addEventListener('click',ev=>{ if(!armed)return; if(ev.target.closest('.dnd-chip'))return; const di=box.querySelector('[data-cat]'); if(di){placed[armed]=di.dataset.cat;armed=null;render();} }); }); }
|
||
function render(){ pool.innerHTML=''; cfg.items.forEach(it=>{if(placed[it.id])return;const c=buildChip(it,false);if(armed===it.id)c.classList.add('armed');pool.appendChild(c);}); cfg.cats.forEach(cat=>{const box=scope.querySelector('.drop-items[data-cat="'+cat+'"]');if(!box)return;box.innerHTML='';cfg.items.forEach(it=>{if(placed[it.id]!==cat)return;box.appendChild(buildChip(it,true));});}); if(window.renderMathInElement)try{renderMath(scope);}catch(_){} }
|
||
attachBoxTaps(); render();
|
||
return {placed,render,reset(){ for(const k in placed) delete placed[k]; armed=null; render(); }};
|
||
}
|
||
|
||
const GLOSSARY = [
|
||
{ term:'уравнение', def:'Равенство с переменной (буквой), для которой ищут все значения, при которых равенство верно.', sec:'p15', aliases:['уравнение','уравнения','уравнении','уравнений','уравнениями','уравнением'] },
|
||
{ term:'корень уравнения', def:'Значение переменной, при котором уравнение превращается в верное равенство.', sec:'p15', aliases:['корень уравнения','корни уравнения','корнем уравнения','корней уравнения'] },
|
||
{ term:'линейное уравнение', def:'Уравнение вида $ax = b$ с одной переменной.', sec:'p15', aliases:['линейное уравнение','линейного уравнения','линейные уравнения','линейному уравнению','линейным уравнением','линейных уравнений'] },
|
||
{ term:'равносильные уравнения', def:'Уравнения, имеющие одинаковые корни (или оба не имеющие корней).', sec:'p15', aliases:['равносильные уравнения','равносильных уравнений','равносильные'] },
|
||
{ term:'числовое неравенство', def:'Сравнение двух чисел с помощью знаков $>, <, \\ge, \\le$.', sec:'p17', aliases:['числовое неравенство','числовые неравенства','числового неравенства','числовых неравенств'] },
|
||
{ term:'линейное неравенство', def:'Неравенство вида $ax > b$ (или $\\ge, <, \\le$) с одной переменной.', sec:'p18', aliases:['линейное неравенство','линейные неравенства','линейного неравенства','линейных неравенств','линейным неравенством'] },
|
||
{ term:'равносильные неравенства', def:'Неравенства, имеющие одно и то же множество решений.', sec:'p18', aliases:['равносильные неравенства','равносильных неравенств'] },
|
||
{ term:'функция', def:'Зависимость, при которой каждому значению переменной $x$ соответствует ровно одно значение $y$.', sec:'p19', aliases:['функция','функции','функцию','функцией','функций'] },
|
||
{ term:'аргумент', def:'Независимая переменная функции (обычно обозначается $x$).', sec:'p19', aliases:['аргумент','аргумента','аргументе','аргументы','аргументов','аргументом'] },
|
||
{ term:'область определения', def:'Множество всех значений аргумента, при которых функция определена.', sec:'p19', aliases:['область определения','области определения','областью определения'] },
|
||
{ term:'множество значений', def:'Все значения, которые принимает функция при $x$ из области определения.', sec:'p19', aliases:['множество значений','множества значений','множестве значений'] },
|
||
{ term:'нуль функции', def:'Значение аргумента $x$, при котором $f(x) = 0$.', sec:'p19', aliases:['нуль функции','нули функции','нулём функции','нулей функции'] },
|
||
{ term:'график функции', def:'Множество всех точек $(x; f(x))$ координатной плоскости.', sec:'p19', aliases:['график функции','графика функции','графику функции','графики функций','графики'] },
|
||
{ term:'линейная функция', def:'Функция вида $y = kx + b$, где $k$ и $b$ — числа.', sec:'p20', aliases:['линейная функция','линейные функции','линейной функции','линейную функцию','линейных функций'] },
|
||
{ term:'угловой коэффициент', def:'Число $k$ в формуле $y = kx + b$. Определяет наклон прямой.', sec:'p20', aliases:['угловой коэффициент','углового коэффициента','угловые коэффициенты','угловым коэффициентом'] },
|
||
];
|
||
|
||
function wrapGlossary(root){
|
||
if(!root||root.__glossDone) return;
|
||
const allAliases=[]; GLOSSARY.forEach((g,i)=>g.aliases.forEach(a=>allAliases.push({a,i})));
|
||
allAliases.sort((x,y)=>y.a.length-x.a.length);
|
||
const re=new RegExp('(?<![\\w\\u0400-\\u04ff-])('+allAliases.map(x=>x.a.replace(/[.*+?^${}()|[\]\\]/g,'\\$&')).join('|')+')(?![\\w\\u0400-\\u04ff-])', 'iu');
|
||
const walker=document.createTreeWalker(root, NodeFilter.SHOW_TEXT, { acceptNode(node){ const p=node.parentElement; if(!p) return NodeFilter.FILTER_REJECT; if(p.closest('.katex,.gloss-term,button,input,select,.wg-badge,.card-icon,.sec-num,.psel-num,.hdr,.ach-popup,script,style,.search-modal,.sidecard,.gloss-tip')) return NodeFilter.FILTER_REJECT; if(!re.test(node.nodeValue)) return NodeFilter.FILTER_REJECT; return NodeFilter.FILTER_ACCEPT; } });
|
||
const nodes=[]; let n; while((n=walker.nextNode())) nodes.push(n);
|
||
nodes.forEach(node=>{ const text=node.nodeValue; const out=document.createDocumentFragment(); let cursor=0; const global=new RegExp(re.source,'giu'); let m; while((m=global.exec(text))!==null){ if(m.index>cursor) out.appendChild(document.createTextNode(text.slice(cursor,m.index))); const found=m[0].toLowerCase(); const hit=allAliases.find(x=>x.a.toLowerCase()===found); const g=hit?GLOSSARY[hit.i]:null; const sp=document.createElement('span'); sp.className='gloss-term'; sp.dataset.gloss=g?g.term:''; sp.textContent=m[0]; out.appendChild(sp); cursor=m.index+m[0].length; } if(cursor<text.length) out.appendChild(document.createTextNode(text.slice(cursor))); node.parentNode.replaceChild(out,node); });
|
||
root.__glossDone=true;
|
||
}
|
||
function initGlossaryTip(){
|
||
const tip=document.getElementById('gloss-tip'); if(!tip) return;
|
||
let lockOpen=null;
|
||
function show(elm){ const g=GLOSSARY.find(x=>x.term===elm.dataset.gloss); if(!g) return; tip.innerHTML='<b>'+g.term[0].toUpperCase()+g.term.slice(1)+'</b><div style="margin-top:4px">'+g.def+'</div><div style="margin-top:6px;font-size:.72rem;color:var(--muted);text-transform:uppercase;letter-spacing:.06em">См. \xA7 '+g.sec.replace('p','')+'</div>'; if(window.renderMathInElement) renderMath(tip); const r=elm.getBoundingClientRect(); tip.classList.add('show'); const tw=tip.offsetWidth,th=tip.offsetHeight; let left=r.left,top=r.bottom+8; if(left+tw>window.innerWidth-12) left=window.innerWidth-tw-12; if(top+th>window.innerHeight-12) top=r.top-th-8; tip.style.left=Math.max(8,left)+'px'; tip.style.top=Math.max(8,top)+'px'; }
|
||
function hide(){ tip.classList.remove('show'); }
|
||
document.addEventListener('mouseover',e=>{ const elm=e.target.closest&&e.target.closest('.gloss-term'); if(elm&&!lockOpen) show(elm); });
|
||
document.addEventListener('mouseout',e=>{ const elm=e.target.closest&&e.target.closest('.gloss-term'); if(elm&&!lockOpen) hide(); });
|
||
document.addEventListener('click',e=>{ const elm=e.target.closest&&e.target.closest('.gloss-term'); if(elm){ if(lockOpen===elm){lockOpen=null;hide();}else{lockOpen=elm;show(elm);} }else if(lockOpen&&!e.target.closest('.gloss-tip')){lockOpen=null;hide();} });
|
||
}
|
||
|
||
const SEARCH_INDEX = (function(){
|
||
const arr=[];
|
||
PARAS.forEach(p=>arr.push({kind:'Параграф',title:p.num+' '+p.name,desc:p.sub||'',sec:p.id}));
|
||
GLOSSARY.forEach(g=>arr.push({kind:'Понятие',title:g.term,desc:g.def.replace(/\$/g,''),sec:g.sec}));
|
||
[
|
||
['Формула','y = kx + b — линейная функция','\xA720','p20'],
|
||
['Формула','ax = b — линейное уравнение','\xA715','p15'],
|
||
['Формула','k > 0 → острый угол; k < 0 → тупой угол','\xA720','p20'],
|
||
['Формула','k1 = k2 → прямые параллельны','\xA720','p20'],
|
||
].forEach(([k,t,d,s])=>arr.push({kind:k,title:t,desc:d,sec:s}));
|
||
return arr;
|
||
})();
|
||
function initSearch(){
|
||
const modal=document.getElementById('search-modal'),inp=document.getElementById('search-input'),out=document.getElementById('search-results'),btn=document.getElementById('search-btn');
|
||
if(!modal||!inp||!out) return;
|
||
let cur=0,rows=[];
|
||
function score(q,it){ const t=(it.title+' '+it.desc).toLowerCase(); if(t.includes(q)) return 100+(it.title.toLowerCase().startsWith(q)?50:0); let s=0; q.split(/\s+/).forEach(w=>{if(w&&t.includes(w))s+=10;}); return s; }
|
||
function rank(q){ q=q.trim().toLowerCase(); if(!q) return SEARCH_INDEX.slice(0,12); return SEARCH_INDEX.map(it=>({it,s:score(q,it)})).filter(x=>x.s>0).sort((a,b)=>b.s-a.s).slice(0,20).map(x=>x.it); }
|
||
function render(){ cur=0; if(!rows.length){out.innerHTML='<div class="search-empty">Ничего не найдено</div>';return;} out.innerHTML=rows.map((r,i)=>'<button class="search-row'+(i===0?' active':'')+'" data-i="'+i+'"><div class="sr-kind">'+r.kind+'</div><div class="sr-title">'+r.title+'</div>'+(r.desc?'<div class="sr-desc">'+(r.desc.length>90?r.desc.slice(0,90)+'…':r.desc)+'</div>':'')+'</button>').join(''); out.querySelectorAll('.search-row').forEach(b=>b.addEventListener('click',()=>{cur=+b.dataset.i;pick();})); }
|
||
function pick(){ const r=rows[cur]; if(!r) return; close(); goTo(r.sec); }
|
||
function move(d){ const items=out.querySelectorAll('.search-row'); if(!items.length) return; items[cur]&&items[cur].classList.remove('active'); cur=(cur+d+items.length)%items.length; items[cur].classList.add('active'); items[cur].scrollIntoView({block:'nearest'}); }
|
||
function open(){ modal.classList.add('show'); inp.value=''; rows=rank(''); render(); setTimeout(()=>inp.focus(),50); }
|
||
function close(){ modal.classList.remove('show'); }
|
||
btn&&btn.addEventListener('click',open);
|
||
modal.addEventListener('click',e=>{if(e.target===modal)close();});
|
||
inp.addEventListener('input',()=>{rows=rank(inp.value);render();});
|
||
inp.addEventListener('keydown',e=>{ if(e.key==='ArrowDown'){e.preventDefault();move(1);}else if(e.key==='ArrowUp'){e.preventDefault();move(-1);}else if(e.key==='Enter'){e.preventDefault();pick();}else if(e.key==='Escape'){e.preventDefault();close();} });
|
||
document.addEventListener('keydown',e=>{ if((e.ctrlKey||e.metaKey)&&(e.key==='k'||e.key==='K')){ e.preventDefault(); if(modal.classList.contains('show')) close(); else open(); } });
|
||
}
|
||
|
||
function initSidebarToggle(){
|
||
const side=document.getElementById('col-side'),back=document.getElementById('col-side-backdrop'),btn=document.getElementById('sidebar-btn');
|
||
if(!side||!btn) return;
|
||
function open(){ side.classList.add('open'); back.classList.add('show'); }
|
||
function close(){ side.classList.remove('open'); back.classList.remove('show'); }
|
||
btn.addEventListener('click',()=>{ if(side.classList.contains('open')) close(); else open(); });
|
||
back.addEventListener('click',close);
|
||
document.addEventListener('keydown',e=>{ if(e.key==='Escape') close(); });
|
||
}
|
||
|
||
function init(){
|
||
loadProgress(); initTheme(); initSidebarToggle(); initGlossaryTip(); initSearch();
|
||
buildParaSelector(); refreshProgressUI(); loadServerReadState(); goTo('p15');
|
||
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);
|
||
|
||
function makeTrainer(opts){
|
||
let i=0, score=0;
|
||
const Q=opts.questions;
|
||
const parser = opts.parser || (v => parseFloat(String(v).replace(',','.')));
|
||
function show(){
|
||
if(i >= Q.length){
|
||
document.getElementById(opts.idPrefix+'-q').innerHTML = '<b>Готово!</b> Результат: '+score+' / '+Q.length;
|
||
if(opts.onComplete) opts.onComplete(score, Q.length);
|
||
return;
|
||
}
|
||
document.getElementById(opts.idPrefix+'-i').textContent = (i+1);
|
||
document.getElementById(opts.idPrefix+'-s').textContent = score;
|
||
document.getElementById(opts.idPrefix+'-q').innerHTML = Q[i].q;
|
||
document.getElementById(opts.idPrefix+'-ans').value = '';
|
||
renderMath(document.getElementById(opts.idPrefix+'-q'));
|
||
document.getElementById(opts.idPrefix+'-fb').style.display = 'none';
|
||
}
|
||
function go(){
|
||
if(i >= Q.length) return;
|
||
const fb = document.getElementById(opts.idPrefix+'-fb');
|
||
const raw = document.getElementById(opts.idPrefix+'-ans').value.trim();
|
||
if(raw === ''){ feedback(fb, false, '✗ Введи ответ.'); return; }
|
||
const expected = Q[i].a;
|
||
let ok = false;
|
||
if(typeof expected === 'function') ok = expected(raw);
|
||
else { const got = parser(raw); ok = !isNaN(got) && Math.abs(got - expected) < 1e-6; }
|
||
if(ok){ score++; feedback(fb, true, '✓ Верно! Дальше ▶'); }
|
||
else feedback(fb, false, '✗ Неверно. Правильно: <b>'+(Q[i].show||expected)+'</b>. Дальше ▶');
|
||
document.getElementById(opts.idPrefix+'-s').textContent = score;
|
||
i++; setTimeout(show, 1100);
|
||
}
|
||
document.getElementById(opts.idPrefix+'-go').addEventListener('click', go);
|
||
document.getElementById(opts.idPrefix+'-ans').addEventListener('keydown', e=>{ if(e.key==='Enter') go(); });
|
||
const restart = document.getElementById(opts.idPrefix+'-start');
|
||
if(restart) restart.addEventListener('click', ()=>{ i=0; score=0; show(); });
|
||
show();
|
||
}
|
||
function trainerHTML(idPrefix, total, placeholder){
|
||
return '<div class="score-display"><span>Задача <b id="'+idPrefix+'-i">1</b> / '+total+'</span><span>Очки: <b id="'+idPrefix+'-s">0</b> / '+total+'</span></div>'
|
||
+'<div id="'+idPrefix+'-q" style="padding:14px;background:var(--sec-acc-soft);border-radius:10px;font-size:1.05rem;margin-bottom:10px;text-align:center"></div>'
|
||
+'<div style="display:flex;gap:10px;align-items:center;flex-wrap:wrap;justify-content:center">'
|
||
+'<input type="text" id="'+idPrefix+'-ans" class="tinp" placeholder="'+(placeholder||'Ответ')+'" style="width:140px;text-align:center">'
|
||
+'<button class="btn primary" id="'+idPrefix+'-go">Проверить</button>'
|
||
+'<button class="btn" id="'+idPrefix+'-start">Заново</button>'
|
||
+'</div><div class="feedback" id="'+idPrefix+'-fb"></div>';
|
||
}
|
||
function readButton(paraId){
|
||
return '<div style="margin-top:18px;display:flex;justify-content:center">'
|
||
+'<button class="btn primary" id="'+paraId+'-read-btn">'
|
||
+'<svg class="ic" viewBox="0 0 24 24"><path d="M4 19.5A2.5 2.5 0 0 1 6.5 17H20"/><path d="M6.5 2H20v20H6.5A2.5 2.5 0 0 1 4 19.5v-15A2.5 2.5 0 0 1 6.5 2z"/></svg>'
|
||
+' Я прочитал \xA7'+paraId.replace('p','')+' (+10 XP)'
|
||
+'</button></div>';
|
||
}
|
||
function wireReadBtn(paraId){
|
||
document.getElementById(paraId+'-read-btn').addEventListener('click', ()=>{
|
||
addXp(10, paraId+'-read'); bumpProgress(paraId, 30);
|
||
const b=document.getElementById(paraId+'-read-btn'); b.textContent='Прочитано! +10 XP'; b.disabled=true; b.style.opacity=.6;
|
||
});
|
||
}
|
||
|
||
/* Coordinate plane SVG helper for §19-§20 */
|
||
function coordSVG(opts){
|
||
/* opts: { width=300, height=260, xMin=-5, xMax=5, yMin=-5, yMax=5, lines=[{k,b,color}], points=[{x,y,label,color}] } */
|
||
const w=opts.width||300, h=opts.height||260;
|
||
const xMin=opts.xMin||-5, xMax=opts.xMax||5, yMin=opts.yMin||-5, yMax=opts.yMax||5;
|
||
const padL=24, padR=14, padT=14, padB=24;
|
||
const W=w-padL-padR, H=h-padT-padB;
|
||
const sx = x => padL + (x - xMin) / (xMax - xMin) * W;
|
||
const sy = y => padT + (yMax - y) / (yMax - yMin) * H;
|
||
let svg = '<svg class="coord" viewBox="0 0 '+w+' '+h+'">';
|
||
/* grid */
|
||
for(let i=xMin;i<=xMax;i++){
|
||
svg += '<line x1="'+sx(i)+'" y1="'+padT+'" x2="'+sx(i)+'" y2="'+(padT+H)+'" stroke="#e2e8f0" stroke-width="0.6"/>';
|
||
}
|
||
for(let i=yMin;i<=yMax;i++){
|
||
svg += '<line x1="'+padL+'" y1="'+sy(i)+'" x2="'+(padL+W)+'" y2="'+sy(i)+'" stroke="#e2e8f0" stroke-width="0.6"/>';
|
||
}
|
||
/* axes */
|
||
svg += '<line x1="'+padL+'" y1="'+sy(0)+'" x2="'+(padL+W)+'" y2="'+sy(0)+'" stroke="#64748b" stroke-width="1.5"/>';
|
||
svg += '<line x1="'+sx(0)+'" y1="'+padT+'" x2="'+sx(0)+'" y2="'+(padT+H)+'" stroke="#64748b" stroke-width="1.5"/>';
|
||
/* axis arrows */
|
||
svg += '<polyline points="'+(padL+W-6)+','+(sy(0)-4)+' '+(padL+W)+','+sy(0)+' '+(padL+W-6)+','+(sy(0)+4)+'" stroke="#64748b" stroke-width="1.5" fill="none"/>';
|
||
svg += '<polyline points="'+(sx(0)-4)+','+(padT+6)+' '+sx(0)+','+padT+' '+(sx(0)+4)+','+(padT+6)+'" stroke="#64748b" stroke-width="1.5" fill="none"/>';
|
||
/* labels */
|
||
svg += '<text x="'+(padL+W-4)+'" y="'+(sy(0)+14)+'" font-size="10" fill="#64748b" font-family="JetBrains Mono,monospace">x</text>';
|
||
svg += '<text x="'+(sx(0)+6)+'" y="'+(padT+8)+'" font-size="10" fill="#64748b" font-family="JetBrains Mono,monospace">y</text>';
|
||
/* tick numbers on x */
|
||
for(let i=xMin;i<=xMax;i++){ if(i===0) continue; svg += '<text x="'+sx(i)+'" y="'+(sy(0)+13)+'" font-size="8" fill="#64748b" font-family="JetBrains Mono,monospace" text-anchor="middle">'+i+'</text>'; }
|
||
for(let i=yMin;i<=yMax;i++){ if(i===0) continue; svg += '<text x="'+(sx(0)-5)+'" y="'+(sy(i)+3)+'" font-size="8" fill="#64748b" font-family="JetBrains Mono,monospace" text-anchor="end">'+i+'</text>'; }
|
||
/* O */
|
||
svg += '<text x="'+(sx(0)-5)+'" y="'+(sy(0)+13)+'" font-size="9" fill="#64748b" font-family="JetBrains Mono,monospace" text-anchor="end">O</text>';
|
||
/* lines */
|
||
(opts.lines||[]).forEach(L=>{
|
||
const x1=xMin, y1=L.k*x1+L.b, x2=xMax, y2=L.k*x2+L.b;
|
||
/* clip to box */
|
||
function clip(x1,y1,x2,y2){
|
||
const pts = [];
|
||
[yMax,yMin].forEach(yEdge=>{
|
||
if((y1-yEdge)*(y2-yEdge)<=0 && (y1!==y2)){
|
||
const t=(yEdge-y1)/(y2-y1);
|
||
const x=x1+t*(x2-x1);
|
||
if(x>=xMin-.01 && x<=xMax+.01) pts.push([x,yEdge]);
|
||
}
|
||
});
|
||
[xMax,xMin].forEach(xEdge=>{
|
||
if((x1-xEdge)*(x2-xEdge)<=0 && (x1!==x2)){
|
||
const t=(xEdge-x1)/(x2-x1);
|
||
const y=y1+t*(y2-y1);
|
||
if(y>=yMin-.01 && y<=yMax+.01) pts.push([xEdge,y]);
|
||
}
|
||
});
|
||
return pts.slice(0,2);
|
||
}
|
||
const pts=clip(x1,y1,x2,y2);
|
||
if(pts.length>=2){
|
||
svg += '<line x1="'+sx(pts[0][0])+'" y1="'+sy(pts[0][1])+'" x2="'+sx(pts[1][0])+'" y2="'+sy(pts[1][1])+'" stroke="'+(L.color||'#7c3aed')+'" stroke-width="2.4"/>';
|
||
}
|
||
/* y-intercept dot */
|
||
if(L.b>=yMin && L.b<=yMax){
|
||
svg += '<circle cx="'+sx(0)+'" cy="'+sy(L.b)+'" r="3.5" fill="'+(L.color||'#7c3aed')+'" stroke="#fff" stroke-width="1.5"/>';
|
||
}
|
||
});
|
||
/* points */
|
||
(opts.points||[]).forEach(p=>{
|
||
svg += '<circle cx="'+sx(p.x)+'" cy="'+sy(p.y)+'" r="4" fill="'+(p.color||'#ef4444')+'" stroke="#fff" stroke-width="1.5"/>';
|
||
if(p.label) svg += '<text x="'+(sx(p.x)+6)+'" y="'+(sy(p.y)-6)+'" font-size="9" fill="'+(p.color||'#ef4444')+'" font-family="JetBrains Mono,monospace" font-weight="700">'+p.label+'</text>';
|
||
});
|
||
svg += '</svg>';
|
||
return svg;
|
||
}
|
||
|
||
/* BUILDERS stubs filled below */
|
||
function buildP15(){
|
||
const box = document.getElementById('p15-body');
|
||
let html = '';
|
||
|
||
html += makeCard('theory', 'Уравнение. Корень уравнения', '15.1', `
|
||
<p><b>Уравнением</b> с одной переменной называется равенство, содержащее эту переменную.</p>
|
||
<p>Например: $-3x = 1$, $\\;2x - 7 = -13$, $\\;5(x - 2) + 1 = 4x$.</p>
|
||
<p><b>Корнем</b> уравнения называется значение переменной, при котором уравнение превращается в верное равенство.</p>
|
||
<p><b>Решить уравнение</b> — значит найти все его корни или доказать, что их нет.</p>
|
||
<details class="spoiler"><summary>Пример проверки</summary><div class="spoiler-body">
|
||
Является ли $-3$ корнем $2x - 7 = -13$? Подставим: $2 \\cdot (-3) - 7 = -13$, т. е. $-13 = -13$ — верно. Да, $-3$ — корень.
|
||
</div></details>`);
|
||
|
||
html += makeCard('rule', 'Равносильные уравнения', '15.2', `
|
||
<p>Два уравнения называются <b>равносильными</b>, если они имеют одно и то же множество корней (или оба не имеют корней).</p>
|
||
<p>Свойства равносильных преобразований:</p>
|
||
<ol style="padding-left:22px;line-height:1.85">
|
||
<li>к обеим частям можно прибавить (или вычесть) одно и то же число;</li>
|
||
<li>обе части можно умножить (или разделить) на одно и то же число, <b>не равное нулю</b>;</li>
|
||
<li>можно <b>переносить</b> члены из одной части в другую, меняя знак.</li>
|
||
</ol>`);
|
||
|
||
html += makeCard('theory', 'Линейное уравнение $ax = b$', '15.3', `
|
||
<p><b>Линейным уравнением</b> с одной переменной называется уравнение вида $ax = b$, где $a$ и $b$ — числа.</p>
|
||
<p>Возможны <b>три случая</b>:</p>
|
||
<ul style="padding-left:22px;line-height:1.85">
|
||
<li>$a \\ne 0$ — <b>единственный корень</b>: $x = \\dfrac{b}{a}$.</li>
|
||
<li>$a = 0$ и $b = 0$ — <b>любое число</b> является корнем (бесконечно много корней).</li>
|
||
<li>$a = 0$ и $b \\ne 0$ — <b>корней нет</b>.</li>
|
||
</ul>`);
|
||
|
||
html += makeCard('algo', 'Алгоритм решения линейного уравнения', '15.4', `
|
||
<ol style="padding-left:22px;line-height:1.9">
|
||
<li>раскрыть скобки;</li>
|
||
<li>перенести члены с переменной в левую часть, числа — в правую (с заменой знака);</li>
|
||
<li>привести подобные слагаемые;</li>
|
||
<li>получить $ax = b$;</li>
|
||
<li>разделить обе части на $a$ (если $a \\ne 0$).</li>
|
||
</ol>
|
||
<p><b>Пример:</b> $5(x - 2) + 1 = 4x \\;\\Rightarrow\\; 5x - 10 + 1 = 4x \\;\\Rightarrow\\; x = 9$.</p>`);
|
||
|
||
html += '<div class="wg" id="p15-iv1">'
|
||
+'<div class="wg-header"><span class="wg-badge">ИНТЕРАКТИВ 1</span><div class="wg-title">Сколько корней?</div></div>'
|
||
+'<div class="wg-help">Для каждого уравнения определи: один корень / нет корней / любое число.</div>'
|
||
+'<div class="score-display"><span>Задача <b id="p15-iv1-i">1</b> / 7</span><span>Очки: <b id="p15-iv1-s">0</b> / 7</span></div>'
|
||
+'<div id="p15-iv1-q" style="padding:14px;background:var(--sec-acc-soft);border-radius:10px;font-size:1.15rem;text-align:center;margin-bottom:10px"></div>'
|
||
+'<div style="display:flex;gap:8px;justify-content:center;flex-wrap:wrap">'
|
||
+'<button class="btn primary" id="p15-iv1-one" style="background:#10b981;border-color:#10b981">Один корень</button>'
|
||
+'<button class="btn primary" id="p15-iv1-none" style="background:#dc2626;border-color:#dc2626">Корней нет</button>'
|
||
+'<button class="btn primary" id="p15-iv1-any" style="background:#7c3aed;border-color:#7c3aed">Любое число</button>'
|
||
+'</div><div class="feedback" id="p15-iv1-fb"></div></div>';
|
||
|
||
html += '<div class="wg" id="p15-iv2">'
|
||
+'<div class="wg-header"><span class="wg-badge">ИНТЕРАКТИВ 2</span><div class="wg-title">Тренажёр: реши уравнение</div></div>'
|
||
+'<div class="wg-help">Реши линейное уравнение и введи корень (число — десятичная дробь через запятую или точку).</div>'
|
||
+trainerHTML('p15-iv2', 6, 'корень $x$')
|
||
+'</div>';
|
||
|
||
html += '<div class="wg" id="p15-iv3">'
|
||
+'<div class="wg-header"><span class="wg-badge">ИНТЕРАКТИВ 3</span><div class="wg-title">Уравнения со скобками</div></div>'
|
||
+'<div class="wg-help">Раскрой скобки, приведи подобные, реши.</div>'
|
||
+trainerHTML('p15-iv3', 5, 'корень $x$')
|
||
+'</div>';
|
||
|
||
html += secNav(null, 'p16') + readButton('p15');
|
||
box.innerHTML = html; renderMath(box);
|
||
|
||
(function(){
|
||
const Q=[
|
||
{ e:'$0 \\cdot x = 12$', ans:'none' },
|
||
{ e:'$3x = 0$', ans:'one' },
|
||
{ e:'$0 \\cdot x = 0$', ans:'any' },
|
||
{ e:'$-5x = 35$', ans:'one' },
|
||
{ e:'$0{,}1 \\cdot x = 9$', ans:'one' },
|
||
{ e:'$0 \\cdot x = \\dfrac{1}{3}$', ans:'none' },
|
||
{ e:'$5(x-2)+1 = 4x$', ans:'one' },
|
||
];
|
||
let i=0,score=0;
|
||
function show(){
|
||
if(i>=Q.length){ document.getElementById('p15-iv1-q').innerHTML='<b>Готово!</b> '+score+' / '+Q.length; if(score===Q.length){addXp(15,'p15-iv1');bumpProgress('p15',25);} else if(score>=5){addXp(7,'p15-iv1');bumpProgress('p15',15);} return; }
|
||
document.getElementById('p15-iv1-i').textContent=(i+1);
|
||
document.getElementById('p15-iv1-s').textContent=score;
|
||
document.getElementById('p15-iv1-q').innerHTML=Q[i].e;
|
||
renderMath(document.getElementById('p15-iv1-q'));
|
||
document.getElementById('p15-iv1-fb').style.display='none';
|
||
}
|
||
function ans(a){
|
||
if(i>=Q.length) return;
|
||
const fb=document.getElementById('p15-iv1-fb');
|
||
if(a===Q[i].ans){ score++; feedback(fb,true,'✓ Верно!'); }
|
||
else{ const lab={one:'один корень',none:'корней нет',any:'любое число'}; feedback(fb,false,'✗ Правильно: <b>'+lab[Q[i].ans]+'</b>.'); }
|
||
document.getElementById('p15-iv1-s').textContent=score;
|
||
i++; setTimeout(show,1100);
|
||
}
|
||
document.getElementById('p15-iv1-one').addEventListener('click',()=>ans('one'));
|
||
document.getElementById('p15-iv1-none').addEventListener('click',()=>ans('none'));
|
||
document.getElementById('p15-iv1-any').addEventListener('click',()=>ans('any'));
|
||
show();
|
||
})();
|
||
|
||
makeTrainer({
|
||
idPrefix:'p15-iv2',
|
||
questions:[
|
||
{ q:'$-5x = 45$', a:-9 },
|
||
{ q:'$24x = 8$', a:1/3, show:'$1/3 \\approx 0{,}333$' },
|
||
{ q:'$-x = 2{,}8$', a:-2.8 },
|
||
{ q:'$0{,}5x = -9$', a:-18 },
|
||
{ q:'$\\tfrac{x}{7} = 5$', a:35 },
|
||
{ q:'$3{,}5x = 7$', a:2 },
|
||
],
|
||
onComplete:(s,n)=>{ if(s===n){addXp(15,'p15-iv2');bumpProgress('p15',25);} else if(s>=4){addXp(8,'p15-iv2');bumpProgress('p15',15);} }
|
||
});
|
||
|
||
makeTrainer({
|
||
idPrefix:'p15-iv3',
|
||
questions:[
|
||
{ q:'$3x - (x - 14) = 5$', a:-4.5, show:'$-4{,}5$' },
|
||
{ q:'$18 - (6x + 5) = 4 - 7x$', a:-9 },
|
||
{ q:'$4x + 5 = 6 + 5(x - 3)$', a:14 },
|
||
{ q:'$2(x - 1) - 4 = 6(x + 2)$', a:-4.5, show:'$-4{,}5$' },
|
||
{ q:'$3(x - 2) - 5(x + 1) = -8x$', a:11/6, show:'$11/6$' },
|
||
],
|
||
onComplete:(s,n)=>{ if(s===n){addXp(18,'p15-iv3');bumpProgress('p15',30);} else if(s>=3){addXp(8,'p15-iv3');bumpProgress('p15',15);} }
|
||
});
|
||
|
||
wireReadBtn('p15');
|
||
}
|
||
function buildP16(){
|
||
const box = document.getElementById('p16-body');
|
||
let html = '';
|
||
|
||
html += makeCard('algo', 'Решение текстовой задачи', '16.1', `
|
||
<p>Чтобы решить задачу с помощью уравнения, нужно:</p>
|
||
<ol style="padding-left:22px;line-height:1.85">
|
||
<li><b>прочитать</b> условие и понять, <i>что</i> искать;</li>
|
||
<li><b>обозначить</b> неизвестное буквой $x$ (или другой);</li>
|
||
<li>выразить остальные величины через $x$;</li>
|
||
<li>составить <b>уравнение</b> по условию задачи;</li>
|
||
<li>решить уравнение;</li>
|
||
<li>проверить смысл ответа (например, нельзя «отрицательное число тетрадей»);</li>
|
||
<li>записать ответ.</li>
|
||
</ol>`);
|
||
|
||
html += makeCard('example', 'Задача про возраст', '16.2', `
|
||
<p><b>Условие:</b> Сейчас отцу 36 лет, а сыну 10. Через сколько лет отец будет старше сына в 2 раза?</p>
|
||
<p><b>Решение:</b> пусть это произойдёт через $x$ лет. Тогда отцу будет $36 + x$ лет, сыну — $10 + x$. По условию $36 + x = 2(10 + x)$.</p>
|
||
<p>$36 + x = 20 + 2x \\;\\Rightarrow\\; -x = -16 \\;\\Rightarrow\\; x = 16$.</p>
|
||
<p><b>Ответ:</b> через 16 лет.</p>`);
|
||
|
||
html += makeCard('example', 'Задача про движение', '16.3', `
|
||
<p><b>Условие:</b> Из двух городов навстречу друг другу выехали два велосипедиста. Скорости — 12 и 15 км/ч. Расстояние — 81 км. Через сколько часов они встретятся?</p>
|
||
<p><b>Решение:</b> пусть встреча через $x$ ч. Первый проехал $12x$ км, второй — $15x$ км. Сумма путей равна расстоянию:</p>
|
||
<p>$12x + 15x = 81 \\;\\Rightarrow\\; 27x = 81 \\;\\Rightarrow\\; x = 3$.</p>
|
||
<p><b>Ответ:</b> через 3 часа.</p>`);
|
||
|
||
html += makeCard('example', 'Задача про покупку', '16.4', `
|
||
<p><b>Условие:</b> Купили 7 тетрадей и 5 ручек, заплатили 110 коп. Тетрадь стоит на 5 коп. дороже ручки. Сколько стоит каждая?</p>
|
||
<p><b>Решение:</b> пусть ручка стоит $x$ коп. Тогда тетрадь — $(x+5)$ коп. По условию:</p>
|
||
<p>$7(x+5) + 5x = 110 \\;\\Rightarrow\\; 7x + 35 + 5x = 110 \\;\\Rightarrow\\; 12x = 75 \\;\\Rightarrow\\; x = 6{,}25$.</p>
|
||
<p><b>Ответ:</b> ручка — 6,25 к., тетрадь — 11,25 к.</p>`);
|
||
|
||
html += '<div class="wg" id="p16-iv1">'
|
||
+'<div class="wg-header"><span class="wg-badge">ИНТЕРАКТИВ 1</span><div class="wg-title">Тренажёр текстовых задач</div></div>'
|
||
+'<div class="wg-help">Составь уравнение в уме и реши задачу. Введи только число.</div>'
|
||
+trainerHTML('p16-iv1', 6, 'ответ')
|
||
+'</div>';
|
||
|
||
html += secNav('p15', 'p17') + readButton('p16');
|
||
box.innerHTML = html; renderMath(box);
|
||
|
||
makeTrainer({
|
||
idPrefix:'p16-iv1',
|
||
questions:[
|
||
{ q:'Сумма двух чисел 50. Одно в 4 раза больше другого. Найди меньшее.', a:10 },
|
||
{ q:'Из города A и B выехали навстречу друг другу два автомобиля. Скорости 60 и 80 км/ч. Расстояние 280 км. Через сколько часов встретятся?', a:2 },
|
||
{ q:'У Маши в 3 раза больше марок, чем у Пети. Всего у них 48 марок. Сколько у Пети?', a:12 },
|
||
{ q:'Велосипедист ехал 2 ч со скоростью 12 км/ч и ещё $x$ ч со скоростью 18 км/ч. Всего проехал 78 км. Сколько часов ехал с большей скоростью?', a:3 },
|
||
{ q:'Прямоугольник: длина на 4 см больше ширины, периметр 48 см. Найди ширину (см).', a:10 },
|
||
{ q:'Купили 10 ручек по $a$ коп. и 5 тетрадей по 8 коп. Всего на 90 коп. Сколько стоит ручка?', a:5 },
|
||
],
|
||
onComplete:(s,n)=>{ if(s===n){addXp(18,'p16-iv1');bumpProgress('p16',40);} else if(s>=4){addXp(10,'p16-iv1');bumpProgress('p16',20);} }
|
||
});
|
||
|
||
wireReadBtn('p16');
|
||
}
|
||
function buildP17(){
|
||
const box = document.getElementById('p17-body');
|
||
let html = '';
|
||
|
||
html += makeCard('theory', 'Сравнение двух чисел', '17.1', `
|
||
<p>Число $a$ <b>больше</b> числа $b$, если разность $a - b$ — положительное число:</p>
|
||
\\[a > b \\;\\Leftrightarrow\\; a - b > 0\\]
|
||
<p>Аналогично: $a < b \\Leftrightarrow a - b < 0$; $a = b \\Leftrightarrow a - b = 0$.</p>
|
||
<p>Знаки $\\le$ и $\\ge$ — <b>нестрогие</b> неравенства (могут быть равны). Знаки $<$ и $>$ — <b>строгие</b>.</p>`);
|
||
|
||
html += makeCard('rule', 'Три основных свойства', '17.2', `
|
||
<p><b>Свойство 1 (транзитивность):</b> если $a > b$ и $b > c$, то $a > c$.</p>
|
||
<p><b>Свойство 2 (прибавление числа):</b> к обеим частям неравенства можно прибавить (вычесть) одно и то же число — знак сохраняется.</p>
|
||
<p>Из $a > b$ следует $a + c > b + c$.</p>
|
||
<p><b>Свойство 3 (умножение на число):</b></p>
|
||
<ul style="padding-left:22px;line-height:1.85">
|
||
<li>умножая на <b>положительное</b> число, знак сохраняется: $a > b \\Rightarrow ac > bc$ при $c > 0$;</li>
|
||
<li>умножая на <b>отрицательное</b> — знак <b>меняется на противоположный</b>: $a > b \\Rightarrow ac < bc$ при $c < 0$.</li>
|
||
</ul>`);
|
||
|
||
html += makeCard('rule', 'Сложение и умножение неравенств', '17.3', `
|
||
<p><b>Сложение неравенств одного знака:</b> $\\;a > b,\\;\\; c > d \\;\\Rightarrow\\; a + c > b + d$.</p>
|
||
<p><b>Умножение неравенств с положительными частями:</b> $\\;a > b > 0,\\;\\; c > d > 0 \\;\\Rightarrow\\; ac > bd$.</p>
|
||
<p>Этим пользуются при <b>оценке</b> выражений.</p>
|
||
<p>Например, если $3 < a < 5$ и $2 < b < 4$, то $3 + 2 < a + b < 5 + 4$, т. е. $5 < a + b < 9$.</p>`);
|
||
|
||
html += '<div class="wg" id="p17-iv1">'
|
||
+'<div class="wg-header"><span class="wg-badge">ИНТЕРАКТИВ 1</span><div class="wg-title">Изменится ли знак?</div></div>'
|
||
+'<div class="wg-help">Для каждого действия определи: остаётся ли знак неравенства прежним.</div>'
|
||
+'<div class="score-display"><span>Задача <b id="p17-iv1-i">1</b> / 7</span><span>Очки: <b id="p17-iv1-s">0</b> / 7</span></div>'
|
||
+'<div id="p17-iv1-q" style="padding:14px;background:var(--sec-acc-soft);border-radius:10px;font-size:1.05rem;text-align:center;margin-bottom:10px"></div>'
|
||
+'<div style="display:flex;gap:10px;justify-content:center">'
|
||
+'<button class="btn primary" id="p17-iv1-yes" style="background:#10b981;border-color:#10b981">Знак сохраняется</button>'
|
||
+'<button class="btn primary" id="p17-iv1-no" style="background:#dc2626;border-color:#dc2626">Знак меняется</button>'
|
||
+'</div><div class="feedback" id="p17-iv1-fb"></div></div>';
|
||
|
||
html += '<div class="wg" id="p17-iv2">'
|
||
+'<div class="wg-header"><span class="wg-badge">ИНТЕРАКТИВ 2</span><div class="wg-title">Оценка выражений</div></div>'
|
||
+'<div class="wg-help">По данным двойным неравенствам найди границы. Введи через запятую: <b>нижняя, верхняя</b>.</div>'
|
||
+trainerHTML('p17-iv2', 5, 'низ, верх')
|
||
+'</div>';
|
||
|
||
html += secNav('p16', 'p18') + readButton('p17');
|
||
box.innerHTML = html; renderMath(box);
|
||
|
||
(function(){
|
||
const Q=[
|
||
{ e:'Из $a > b$ прибавили к обеим частям $3$', keep:true },
|
||
{ e:'Из $a > b$ умножили обе части на $-5$', keep:false },
|
||
{ e:'Из $a < b$ умножили обе части на $0{,}1$', keep:true },
|
||
{ e:'Из $-7 < -2$ разделили обе части на $-1$', keep:false },
|
||
{ e:'Из $a \\ge b$ вычли $-7$', keep:true },
|
||
{ e:'Из $a > b$ умножили обе части на $-\\dfrac{1}{2}$', keep:false },
|
||
{ e:'Из $-3 < 5$ умножили обе части на $4$', keep:true },
|
||
];
|
||
let i=0,score=0;
|
||
function show(){
|
||
if(i>=Q.length){ document.getElementById('p17-iv1-q').innerHTML='<b>Готово!</b> '+score+' / '+Q.length; if(score===Q.length){addXp(15,'p17-iv1');bumpProgress('p17',25);} else if(score>=5){addXp(7,'p17-iv1');bumpProgress('p17',15);} return; }
|
||
document.getElementById('p17-iv1-i').textContent=(i+1);
|
||
document.getElementById('p17-iv1-s').textContent=score;
|
||
document.getElementById('p17-iv1-q').innerHTML=Q[i].e;
|
||
renderMath(document.getElementById('p17-iv1-q'));
|
||
document.getElementById('p17-iv1-fb').style.display='none';
|
||
}
|
||
function ans(isYes){
|
||
if(i>=Q.length) return;
|
||
const fb=document.getElementById('p17-iv1-fb');
|
||
if(isYes===Q[i].keep){ score++; feedback(fb,true,'✓ Верно!'); }
|
||
else feedback(fb,false,'✗ Правильно: знак '+(Q[i].keep?'<b>сохраняется</b>':'<b>меняется</b>'));
|
||
document.getElementById('p17-iv1-s').textContent=score;
|
||
i++; setTimeout(show,1100);
|
||
}
|
||
document.getElementById('p17-iv1-yes').addEventListener('click',()=>ans(true));
|
||
document.getElementById('p17-iv1-no').addEventListener('click',()=>ans(false));
|
||
show();
|
||
})();
|
||
|
||
function parsePair(v){ return String(v).trim().replace(',','.').split(/[,;\s]+/).filter(Boolean); }
|
||
makeTrainer({
|
||
idPrefix:'p17-iv2',
|
||
parser:(v)=>v,
|
||
questions:[
|
||
{ q:'Если $3 < a < 5$ и $2 < b < 4$, то $a + b \\in (?, ?)$', a:(v)=>{const m=parsePair(v); return +m[0]===5 && +m[1]===9;}, show:'5, 9' },
|
||
{ q:'Если $5 < a < 9$, то $2a \\in (?, ?)$', a:(v)=>{const m=parsePair(v); return +m[0]===10 && +m[1]===18;}, show:'10, 18' },
|
||
{ q:'Если $-3 \\le b < 8$, то $b + 2 \\in [?, ?)$ (введи $b+2$ снизу/сверху)', a:(v)=>{const m=parsePair(v); return +m[0]===-1 && +m[1]===10;}, show:'-1, 10' },
|
||
{ q:'Если $3{,}9 < a < 5$ и $2 < b < 3$, то $ab \\in (?, ?)$', a:(v)=>{const m=parsePair(v); return Math.abs(+m[0]-7.8)<1e-3 && +m[1]===15;}, show:'7,8 и 15' },
|
||
{ q:'Если $5 < a < 9$, то $-3a \\in (?, ?)$', a:(v)=>{const m=parsePair(v); return +m[0]===-27 && +m[1]===-15;}, show:'-27, -15' },
|
||
],
|
||
onComplete:(s,n)=>{ if(s===n){addXp(15,'p17-iv2');bumpProgress('p17',25);} else if(s>=3){addXp(8,'p17-iv2');bumpProgress('p17',15);} }
|
||
});
|
||
|
||
wireReadBtn('p17');
|
||
}
|
||
function buildP18(){
|
||
const box = document.getElementById('p18-body');
|
||
let html = '';
|
||
|
||
html += makeCard('theory', 'Линейное неравенство', '18.1', `
|
||
<p><b>Линейным неравенством</b> с одной переменной называется неравенство вида $ax > b$ (а также $ax < b$, $ax \\ge b$, $ax \\le b$).</p>
|
||
<p><b>Решением</b> неравенства называется значение переменной, при котором неравенство верно.</p>
|
||
<p>Например, для $x > 5$ решениями являются 6, 7, 100 и т. д.; для $x < -3$ — все числа меньше $-3$.</p>`);
|
||
|
||
html += makeCard('rule', 'Равносильные преобразования', '18.2', `
|
||
<p>Два неравенства называются <b>равносильными</b>, если они имеют одно и то же множество решений.</p>
|
||
<p>Равносильные преобразования (как у уравнений + одно важное!):</p>
|
||
<ol style="padding-left:22px;line-height:1.85">
|
||
<li>прибавить (вычесть) одно и то же число — знак сохраняется;</li>
|
||
<li>умножить (разделить) на <b>положительное</b> число — знак сохраняется;</li>
|
||
<li>умножить (разделить) на <b>отрицательное</b> — знак <b>меняется на противоположный</b>!</li>
|
||
</ol>
|
||
<p><b>Пример:</b> $-2x > 6 \\;\\Rightarrow\\; x < -3$ (поделили на $-2$, знак $>$ стал $<$).</p>`);
|
||
|
||
html += makeCard('algo', 'Алгоритм решения', '18.3', `
|
||
<ol style="padding-left:22px;line-height:1.85">
|
||
<li>раскрыть скобки;</li>
|
||
<li>перенести члены с переменной — налево, числа — направо;</li>
|
||
<li>привести подобные слагаемые;</li>
|
||
<li>разделить обе части на коэффициент при переменной (знак неравенства может измениться!).</li>
|
||
</ol>
|
||
<p><b>Особые случаи:</b></p>
|
||
<ul style="padding-left:22px;line-height:1.85">
|
||
<li>$0 \\cdot x < 3$ — верно при <b>любом</b> $x$ (получили $0 < 3$);</li>
|
||
<li>$0 \\cdot x > 5$ — <b>решений нет</b> ($0 > 5$ — ложь);</li>
|
||
<li>$0 \\cdot x \\ge 0$ — любое $x$;</li>
|
||
<li>$0 \\cdot x < -1$ — решений нет.</li>
|
||
</ul>`);
|
||
|
||
html += makeCard('example', 'Применение', '18.4', `
|
||
<p><b>а)</b> $7x < 21 \\;\\Rightarrow\\; x < 3$.</p>
|
||
<p><b>б)</b> $-4x \\ge 16 \\;\\Rightarrow\\; x \\le -4$ (поделили на $-4$, знак сменился).</p>
|
||
<p><b>в)</b> $3(x + 2) > 4 - x \\;\\Rightarrow\\; 3x + 6 > 4 - x \\;\\Rightarrow\\; 4x > -2 \\;\\Rightarrow\\; x > -0{,}5$.</p>`);
|
||
|
||
html += '<div class="wg" id="p18-iv1">'
|
||
+'<div class="wg-header"><span class="wg-badge">ИНТЕРАКТИВ 1</span><div class="wg-title">Реши неравенство $ax > b$</div></div>'
|
||
+'<div class="wg-help">Введи правую часть после деления на коэффициент. Запиши знак отдельно после числа: например, для $x > 3$ — введи <code>3</code>, выбрав знак ниже.</div>'
|
||
+trainerHTML('p18-iv1', 5, 'число')
|
||
+'</div>';
|
||
|
||
html += '<div class="wg" id="p18-iv2">'
|
||
+'<div class="wg-header"><span class="wg-badge">ИНТЕРАКТИВ 2</span><div class="wg-title">Со скобками</div></div>'
|
||
+'<div class="wg-help">Раскрой, упрости, реши. Введи число (правую часть после деления). Знак можно проверить сразу.</div>'
|
||
+trainerHTML('p18-iv2', 5, 'число')
|
||
+'</div>';
|
||
|
||
html += '<div class="wg" id="p18-iv3">'
|
||
+'<div class="wg-header"><span class="wg-badge">ИНТЕРАКТИВ 3</span><div class="wg-title">Особые случаи</div></div>'
|
||
+'<div class="wg-help">Реши неравенство. Если решений нет — введи <code>нет</code>. Если решением является любое число — введи <code>любое</code>.</div>'
|
||
+trainerHTML('p18-iv3', 4, 'число / «нет» / «любое»')
|
||
+'</div>';
|
||
|
||
html += secNav('p17', 'p19') + readButton('p18');
|
||
box.innerHTML = html; renderMath(box);
|
||
|
||
makeTrainer({
|
||
idPrefix:'p18-iv1',
|
||
questions:[
|
||
{ q:'$7x < 21$. Введи число справа (получится $x < ?$)', a:3 },
|
||
{ q:'$-4x \\ge 16$. После деления на $-4$ получится $x \\le ?$', a:-4 },
|
||
{ q:'$2x \\le -9$. $x \\le ?$', a:-4.5, show:'$-4{,}5$' },
|
||
{ q:'$-5x > -12$. $x < ?$', a:2.4, show:'$2{,}4$' },
|
||
{ q:'$0{,}5x < 6$. $x < ?$', a:12 },
|
||
],
|
||
onComplete:(s,n)=>{ if(s===n){addXp(15,'p18-iv1');bumpProgress('p18',28);} else if(s>=3){addXp(7,'p18-iv1');bumpProgress('p18',15);} }
|
||
});
|
||
|
||
makeTrainer({
|
||
idPrefix:'p18-iv2',
|
||
questions:[
|
||
{ q:'$3(x + 2) > 4 - x$. $x > ?$', a:-0.5, show:'$-0{,}5$' },
|
||
{ q:'$4x - 11 < 2x + 13$. $x < ?$', a:12 },
|
||
{ q:'$2x - 3 > 1$. $x > ?$', a:2 },
|
||
{ q:'$1 - 5x < 6$. $x > ?$', a:-1 },
|
||
{ q:'$3 - 8x \\le 1$. $x \\ge ?$', a:0.25, show:'$0{,}25$' },
|
||
],
|
||
onComplete:(s,n)=>{ if(s===n){addXp(15,'p18-iv2');bumpProgress('p18',28);} else if(s>=3){addXp(7,'p18-iv2');bumpProgress('p18',15);} }
|
||
});
|
||
|
||
makeTrainer({
|
||
idPrefix:'p18-iv3',
|
||
parser:(v)=>{ const t=String(v).trim().toLowerCase(); if(t==='нет'||t==='no') return 'нет'; if(t==='любое'||t==='all'||t==='any') return 'любое'; return parseFloat(t.replace(',','.')); },
|
||
questions:[
|
||
{ q:'$0 \\cdot x > 5$', a:(v)=>String(v).trim().toLowerCase()==='нет', show:'нет решений' },
|
||
{ q:'$0 \\cdot x < 3$', a:(v)=>String(v).trim().toLowerCase()==='любое', show:'любое число' },
|
||
{ q:'$0 \\cdot x \\ge -2$', a:(v)=>String(v).trim().toLowerCase()==='любое', show:'любое число' },
|
||
{ q:'$0 \\cdot x \\le -7$', a:(v)=>String(v).trim().toLowerCase()==='нет', show:'нет решений' },
|
||
],
|
||
onComplete:(s,n)=>{ if(s===n){addXp(12,'p18-iv3');bumpProgress('p18',22);} else if(s>=2){addXp(5,'p18-iv3');bumpProgress('p18',10);} }
|
||
});
|
||
|
||
wireReadBtn('p18');
|
||
}
|
||
function buildP19(){
|
||
const box = document.getElementById('p19-body');
|
||
let html = '';
|
||
|
||
html += makeCard('theory', 'Функция', '19.1', `
|
||
<p><b>Функцией</b> называется такая зависимость между двумя переменными, при которой каждому значению одной переменной соответствует <b>ровно одно</b> значение другой.</p>
|
||
<p>Независимую переменную называют <b>аргументом</b> (обычно $x$), зависимую — <b>значением функции</b> (обычно $y$ или $f(x)$).</p>
|
||
<p>Если каждому $x$ соответствует <b>несколько</b> или <b>ноль</b> значений $y$ — это <b>не функция</b>.</p>
|
||
<p><b>Пример:</b> зависимость стоимости покупки от количества тетрадей — функция (каждому числу — одна цена). Зависимость роста учеников от их оценки — <b>не</b> функция (одну и ту же оценку могут получить ученики разного роста).</p>`);
|
||
|
||
html += makeCard('rule', 'Область определения и множество значений', '19.2', `
|
||
<p><b>Область определения</b> $D(f)$ — все значения аргумента, при которых функция определена (имеет смысл).</p>
|
||
<p><b>Множество значений</b> $E(f)$ — все значения, которые принимает функция.</p>
|
||
<p>Например, для $y = x^2 + 5$: $D(f)$ — все числа, $E(f)$: $y \\ge 5$ (так как $x^2 \\ge 0$).</p>
|
||
<p>Способы задания функции: <b>формулой</b>, <b>таблицей</b>, <b>графиком</b>, <b>словесно</b>.</p>`);
|
||
|
||
html += makeCard('rule', 'Нули функции', '19.3', `
|
||
<p><b>Нулями функции</b> называются те значения аргумента $x$, при которых $f(x) = 0$.</p>
|
||
<p>На графике это точки пересечения с осью $Ox$.</p>
|
||
<p><b>Пример:</b> найти нули $f(x) = 2x - 2{,}8$. Решаем $2x - 2{,}8 = 0$, получаем $x = 1{,}4$. Это нуль функции.</p>`);
|
||
|
||
html += makeCard('theory', 'График функции', '19.4', `
|
||
<p><b>Графиком функции</b> называется множество всех точек координатной плоскости, абсциссы которых равны значениям аргумента, а ординаты — соответствующим значениям функции.</p>
|
||
<p>По графику можно определить:</p>
|
||
<ul style="padding-left:22px;line-height:1.85">
|
||
<li>значение функции $f(x_0)$ — по точке с абсциссой $x_0$;</li>
|
||
<li>нули функции — точки пересечения с осью $Ox$;</li>
|
||
<li>где $f(x) > 0$ — график выше оси $Ox$;</li>
|
||
<li>где $f(x) < 0$ — график ниже.</li>
|
||
</ul>`);
|
||
|
||
html += '<div class="wg" id="p19-iv1">'
|
||
+'<div class="wg-header"><span class="wg-badge">ИНТЕРАКТИВ 1</span><div class="wg-title">Функция или не функция?</div></div>'
|
||
+'<div class="wg-help">Является ли зависимость функцией? Да — каждому $x$ ровно одно $y$.</div>'
|
||
+'<div class="score-display"><span>Задача <b id="p19-iv1-i">1</b> / 6</span><span>Очки: <b id="p19-iv1-s">0</b> / 6</span></div>'
|
||
+'<div id="p19-iv1-q" style="padding:14px;background:var(--sec-acc-soft);border-radius:10px;font-size:1.05rem;text-align:center;margin-bottom:10px"></div>'
|
||
+'<div style="display:flex;gap:10px;justify-content:center"><button class="btn primary" id="p19-iv1-yes" style="background:#10b981;border-color:#10b981">Функция</button><button class="btn primary" id="p19-iv1-no" style="background:#dc2626;border-color:#dc2626">Не функция</button></div>'
|
||
+'<div class="feedback" id="p19-iv1-fb"></div></div>';
|
||
|
||
html += '<div class="wg" id="p19-iv2">'
|
||
+'<div class="wg-header"><span class="wg-badge">ИНТЕРАКТИВ 2</span><div class="wg-title">Найди значение $f(x_0)$</div></div>'
|
||
+'<div class="wg-help">Подставь $x_0$ в формулу и вычисли.</div>'
|
||
+trainerHTML('p19-iv2', 6, '$f(x_0)$')
|
||
+'</div>';
|
||
|
||
html += '<div class="wg" id="p19-iv3">'
|
||
+'<div class="wg-header"><span class="wg-badge">ИНТЕРАКТИВ 3</span><div class="wg-title">Найди нуль функции</div></div>'
|
||
+'<div class="wg-help">Реши уравнение $f(x) = 0$.</div>'
|
||
+trainerHTML('p19-iv3', 5, 'нуль $x$')
|
||
+'</div>';
|
||
|
||
html += secNav('p18', 'p20') + readButton('p19');
|
||
box.innerHTML = html; renderMath(box);
|
||
|
||
(function(){
|
||
const Q=[
|
||
{ e:'Стоимость покупки $S$ от числа купленных тетрадей $n$', isF:true },
|
||
{ e:'Оценка за к/р и номер ученика в журнале', isF:false },
|
||
{ e:'Длина стороны квадрата и его площадь', isF:true },
|
||
{ e:'Периметр треугольника и его наибольшая сторона', isF:false },
|
||
{ e:'Время суток $t$ и температура воздуха $T$ в этот момент', isF:true },
|
||
{ e:'Объём куба $V$ и длина его ребра $a$', isF:true },
|
||
];
|
||
let i=0,score=0;
|
||
function show(){
|
||
if(i>=Q.length){ document.getElementById('p19-iv1-q').innerHTML='<b>Готово!</b> '+score+' / '+Q.length; if(score===Q.length){addXp(12,'p19-iv1');bumpProgress('p19',22);} else if(score>=4){addXp(6,'p19-iv1');bumpProgress('p19',12);} return; }
|
||
document.getElementById('p19-iv1-i').textContent=(i+1);
|
||
document.getElementById('p19-iv1-s').textContent=score;
|
||
document.getElementById('p19-iv1-q').innerHTML=Q[i].e;
|
||
document.getElementById('p19-iv1-fb').style.display='none';
|
||
}
|
||
function ans(yes){
|
||
if(i>=Q.length) return;
|
||
const fb=document.getElementById('p19-iv1-fb');
|
||
if(yes===Q[i].isF){ score++; feedback(fb,true,'✓ Верно!'); }
|
||
else feedback(fb,false,'✗ Это '+(Q[i].isF?'<b>функция</b>':'<b>не функция</b>'));
|
||
document.getElementById('p19-iv1-s').textContent=score;
|
||
i++; setTimeout(show,1100);
|
||
}
|
||
document.getElementById('p19-iv1-yes').addEventListener('click',()=>ans(true));
|
||
document.getElementById('p19-iv1-no').addEventListener('click',()=>ans(false));
|
||
show();
|
||
})();
|
||
|
||
makeTrainer({
|
||
idPrefix:'p19-iv2',
|
||
questions:[
|
||
{ q:'$f(x) = 5x - 1$, найди $f(0)$', a:-1 },
|
||
{ q:'$f(x) = 5x - 1$, найди $f(3)$', a:14 },
|
||
{ q:'$f(x) = -3x + 2$, найди $f(-1)$', a:5 },
|
||
{ q:'$f(x) = x^2 - 4$, найди $f(3)$', a:5 },
|
||
{ q:'$f(x) = \\dfrac{x}{x+5}$, найди $f(0)$', a:0 },
|
||
{ q:'$f(x) = 2x + 7$, найди $f(-3)$', a:1 },
|
||
],
|
||
onComplete:(s,n)=>{ if(s===n){addXp(15,'p19-iv2');bumpProgress('p19',28);} else if(s>=4){addXp(8,'p19-iv2');bumpProgress('p19',15);} }
|
||
});
|
||
|
||
makeTrainer({
|
||
idPrefix:'p19-iv3',
|
||
questions:[
|
||
{ q:'$f(x) = 9x - 1$', a:1/9, show:'$1/9$' },
|
||
{ q:'$f(x) = -6x$', a:0 },
|
||
{ q:'$f(x) = 0{,}1 - 2x$', a:0.05, show:'$0{,}05$' },
|
||
{ q:'$f(x) = 2x - 15$', a:7.5, show:'$7{,}5$' },
|
||
{ q:'$f(x) = 7 - 8x$', a:7/8, show:'$7/8$' },
|
||
],
|
||
onComplete:(s,n)=>{ if(s===n){addXp(15,'p19-iv3');bumpProgress('p19',28);} else if(s>=3){addXp(8,'p19-iv3');bumpProgress('p19',15);} }
|
||
});
|
||
|
||
wireReadBtn('p19');
|
||
}
|
||
function buildP20(){
|
||
const box = document.getElementById('p20-body');
|
||
let html = '';
|
||
|
||
html += makeCard('theory', 'Линейная функция', '20.1', `
|
||
<p><b>Линейной функцией</b> называется функция, заданная формулой $y = kx + b$, где $k$ и $b$ — некоторые числа.</p>
|
||
<p>Примеры: $y = 2x + 5$ ($k = 2, b = 5$); $y = -x$ ($k = -1, b = 0$); $y = 12$ ($k = 0, b = 12$); $y = 16x$ ($k = 16, b = 0$).</p>
|
||
<p><b>Не</b> линейные: $y = \\dfrac{2}{x} - 6$ (деление на $x$), $y = 12x^2 + 7$ ($x$ во второй степени).</p>`);
|
||
|
||
html += makeCard('theory', 'Свойства', '20.2', `
|
||
<p>Область определения линейной функции — <b>все числа</b>: $D(y) = \\mathbb{R}$.</p>
|
||
<p>Множество значений:</p>
|
||
<ul style="padding-left:22px;line-height:1.85">
|
||
<li>если $k \\ne 0$ — все числа: $E(y) = \\mathbb{R}$;</li>
|
||
<li>если $k = 0$ — единственное значение $b$: $E(y) = \\{b\\}$.</li>
|
||
</ul>
|
||
<p><b>Нуль функции:</b> $y = 0 \\Rightarrow kx + b = 0 \\Rightarrow x = -\\dfrac{b}{k}$ (при $k \\ne 0$).</p>`);
|
||
|
||
html += makeCard('theory', 'График линейной функции', '20.3', `
|
||
<p>Графиком линейной функции $y = kx + b$ является <b>прямая</b>.</p>
|
||
<p>Чтобы построить прямую, нужны <b>две точки</b>. Выбираем два значения $x$, вычисляем $y$, отмечаем точки и проводим прямую.</p>
|
||
<p><b>Пример:</b> $y = 2x - 3$. При $x = 0$: $y = -3$ — точка $(0; -3)$. При $x = 2$: $y = 1$ — точка $(2; 1)$. Соединяем.</p>
|
||
<div style="display:flex;justify-content:center;margin-top:10px">${coordSVG({xMin:-3,xMax:5,yMin:-5,yMax:4, lines:[{k:2,b:-3,color:'#7c3aed'}], points:[{x:0,y:-3,label:'(0;-3)',color:'#dc2626'},{x:2,y:1,label:'(2;1)',color:'#dc2626'}]})}</div>`);
|
||
|
||
html += makeCard('rule', 'Геометрический смысл $k$ и $b$', '20.4', `
|
||
<p>Число $k$ называется <b>угловым коэффициентом</b> прямой.</p>
|
||
<ul style="padding-left:22px;line-height:1.85">
|
||
<li>$k > 0$ — прямая образует с положительным направлением оси $Ox$ <b>острый</b> угол;</li>
|
||
<li>$k < 0$ — <b>тупой</b> угол;</li>
|
||
<li>$k = 0$ — прямая <b>параллельна</b> оси $Ox$ (горизонталь).</li>
|
||
</ul>
|
||
<p>Число $b$ — <b>ордината точки пересечения</b> прямой с осью $Oy$ (то есть точка $(0; b)$).</p>`);
|
||
|
||
html += makeCard('rule', 'Взаимное расположение прямых', '20.5', `
|
||
<p>Для двух функций $y = k_1 x + b_1$ и $y = k_2 x + b_2$:</p>
|
||
<ul style="padding-left:22px;line-height:1.85">
|
||
<li>$k_1 = k_2$ и $b_1 \\ne b_2$ — прямые <b>параллельны</b>;</li>
|
||
<li>$k_1 \\ne k_2$ — прямые <b>пересекаются</b>;</li>
|
||
<li>$k_1 = k_2$ и $b_1 = b_2$ — прямые <b>совпадают</b>.</li>
|
||
</ul>`);
|
||
|
||
html += '<div class="wg" id="p20-iv1">'
|
||
+'<div class="wg-header"><span class="wg-badge">ИНТЕРАКТИВ 1</span><div class="wg-title">График $y = kx + b$ — слайдеры</div></div>'
|
||
+'<div class="wg-help">Двигай ползунки и смотри, как меняется прямая. $k$ задаёт <b>наклон</b>, $b$ — <b>сдвиг</b> по вертикали.</div>'
|
||
+'<div class="sliders"><label>$k$ = <b id="p20-k-val">2</b><input type="range" id="p20-k-sl" min="-5" max="5" step="0.5" value="2"></label>'
|
||
+'<label>$b$ = <b id="p20-b-val">-3</b><input type="range" id="p20-b-sl" min="-5" max="5" step="0.5" value="-3"></label></div>'
|
||
+'<div id="p20-iv1-svg" style="display:flex;justify-content:center"></div>'
|
||
+'<div id="p20-iv1-info" style="text-align:center;font-size:.92rem;margin-top:8px"></div></div>';
|
||
|
||
html += '<div class="wg" id="p20-iv2">'
|
||
+'<div class="wg-header"><span class="wg-badge">ИНТЕРАКТИВ 2</span><div class="wg-title">Угол и нуль</div></div>'
|
||
+'<div class="wg-help">По формуле $y = kx + b$ определи: знак $k$ → угол с осью $Ox$; нуль функции.</div>'
|
||
+trainerHTML('p20-iv2', 6, 'число / «остр»/«тупой»')
|
||
+'</div>';
|
||
|
||
html += '<div class="wg" id="p20-iv3">'
|
||
+'<div class="wg-header"><span class="wg-badge">ИНТЕРАКТИВ 3</span><div class="wg-title">Параллельны или пересекаются?</div></div>'
|
||
+'<div class="wg-help">Сравни две линейные функции. Определи их взаимное расположение по $k$ и $b$.</div>'
|
||
+'<div class="score-display"><span>Задача <b id="p20-iv3-i">1</b> / 6</span><span>Очки: <b id="p20-iv3-s">0</b> / 6</span></div>'
|
||
+'<div id="p20-iv3-q" style="padding:14px;background:var(--sec-acc-soft);border-radius:10px;font-size:1.05rem;text-align:center;margin-bottom:10px"></div>'
|
||
+'<div style="display:flex;gap:8px;justify-content:center;flex-wrap:wrap">'
|
||
+'<button class="btn primary" id="p20-iv3-par" style="background:#10b981;border-color:#10b981">Параллельны</button>'
|
||
+'<button class="btn primary" id="p20-iv3-cross" style="background:#7c3aed;border-color:#7c3aed">Пересекаются</button>'
|
||
+'<button class="btn primary" id="p20-iv3-same" style="background:#0891b2;border-color:#0891b2">Совпадают</button>'
|
||
+'</div><div class="feedback" id="p20-iv3-fb"></div></div>';
|
||
|
||
html += '<div class="wg" id="p20-iv4">'
|
||
+'<div class="wg-header"><span class="wg-badge">ИНТЕРАКТИВ 4</span><div class="wg-title">Найди $k$ и $b$ по точкам</div></div>'
|
||
+'<div class="wg-help">Принадлежит ли точка $(x_0; y_0)$ графику $y = kx + b$? Подставь $x_0$ и проверь равенство с $y_0$.</div>'
|
||
+trainerHTML('p20-iv4', 5, 'да / нет')
|
||
+'</div>';
|
||
|
||
html += secNav('p19', 'final3') + readButton('p20');
|
||
box.innerHTML = html; renderMath(box);
|
||
|
||
/* IV1 slider */
|
||
(function(){
|
||
const kSl=document.getElementById('p20-k-sl'), bSl=document.getElementById('p20-b-sl');
|
||
const kV=document.getElementById('p20-k-val'), bV=document.getElementById('p20-b-val');
|
||
const out=document.getElementById('p20-iv1-svg'), info=document.getElementById('p20-iv1-info');
|
||
function update(){
|
||
const k=+kSl.value, b=+bSl.value;
|
||
kV.textContent=k; bV.textContent=b;
|
||
out.innerHTML = coordSVG({xMin:-5,xMax:5,yMin:-5,yMax:5, lines:[{k:k,b:b,color:'#7c3aed'}], points:b>=-5&&b<=5?[{x:0,y:b,label:'(0;'+b+')',color:'#dc2626'}]:[]});
|
||
let angle = k>0 ? 'острый угол с осью $Ox$' : (k<0 ? 'тупой угол с осью $Ox$' : 'параллельна оси $Ox$');
|
||
let zero = k!==0 ? 'нуль функции: $x = '+(-b/k).toFixed(2)+'$' : (b===0 ? 'все числа — нули' : 'нулей нет');
|
||
info.innerHTML = '<b>$y = '+(k===0?'':(k===1?'':k===-1?'-':k))+(k===0?'':'x')+(b>0?' + '+b:b<0?' - '+(-b):'')+(k===0?b:'')+'$</b> — '+angle+'; '+zero;
|
||
renderMath(info);
|
||
}
|
||
kSl.addEventListener('input', update); bSl.addEventListener('input', update); update();
|
||
})();
|
||
|
||
/* IV2 — angle/null */
|
||
makeTrainer({
|
||
idPrefix:'p20-iv2',
|
||
parser:(v)=>v,
|
||
questions:[
|
||
{ q:'$y = 3x - 6$ — какой угол с осью $Ox$? («острый» или «тупой»)', a:(v)=>String(v).trim().toLowerCase().startsWith('остр'), show:'острый ($k>0$)' },
|
||
{ q:'$y = -4x + 2$ — угол?', a:(v)=>String(v).trim().toLowerCase().startsWith('туп'), show:'тупой ($k<0$)' },
|
||
{ q:'$y = 2x - 10$ — нуль функции (число)', a:(v)=>parseFloat(String(v).replace(',','.'))===5, show:'5' },
|
||
{ q:'$y = -3x + 12$ — нуль функции (число)', a:(v)=>parseFloat(String(v).replace(',','.'))===4, show:'4' },
|
||
{ q:'$y = 9x$ — нуль функции', a:(v)=>parseFloat(String(v).replace(',','.'))===0, show:'0' },
|
||
{ q:'$y = 7$ — какой угол с $Ox$? («парал», «острый» или «тупой»)', a:(v)=>String(v).trim().toLowerCase().startsWith('парал'), show:'параллельна ($k=0$)' },
|
||
],
|
||
onComplete:(s,n)=>{ if(s===n){addXp(18,'p20-iv2');bumpProgress('p20',30);} else if(s>=4){addXp(10,'p20-iv2');bumpProgress('p20',18);} }
|
||
});
|
||
|
||
/* IV3 — взаимное расположение */
|
||
(function(){
|
||
const Q=[
|
||
{ e:'$y = x - 6$ и $y = 49x$', ans:'cross' },
|
||
{ e:'$y = x$ и $y = x + 8$', ans:'par' },
|
||
{ e:'$y = 1{,}5x + 5$ и $y = 9$', ans:'cross' },
|
||
{ e:'$y = 0{,}1x$ и $y = 0{,}2x + 0{,}1$', ans:'cross' },
|
||
{ e:'$y = -7x$ и $y = 7x$', ans:'cross' },
|
||
{ e:'$y = 2x + 3$ и $y = 2x + 3$', ans:'same' },
|
||
];
|
||
let i=0,score=0;
|
||
function show(){
|
||
if(i>=Q.length){ document.getElementById('p20-iv3-q').innerHTML='<b>Готово!</b> '+score+' / '+Q.length; if(score===Q.length){addXp(15,'p20-iv3');bumpProgress('p20',25);} else if(score>=4){addXp(7,'p20-iv3');bumpProgress('p20',12);} return; }
|
||
document.getElementById('p20-iv3-i').textContent=(i+1);
|
||
document.getElementById('p20-iv3-s').textContent=score;
|
||
document.getElementById('p20-iv3-q').innerHTML=Q[i].e;
|
||
renderMath(document.getElementById('p20-iv3-q'));
|
||
document.getElementById('p20-iv3-fb').style.display='none';
|
||
}
|
||
function ans(a){
|
||
if(i>=Q.length) return;
|
||
const fb=document.getElementById('p20-iv3-fb');
|
||
if(a===Q[i].ans){ score++; feedback(fb,true,'✓ Верно!'); }
|
||
else{ const lab={par:'параллельны',cross:'пересекаются',same:'совпадают'}; feedback(fb,false,'✗ Правильно: <b>'+lab[Q[i].ans]+'</b>'); }
|
||
document.getElementById('p20-iv3-s').textContent=score;
|
||
i++; setTimeout(show,1100);
|
||
}
|
||
document.getElementById('p20-iv3-par').addEventListener('click',()=>ans('par'));
|
||
document.getElementById('p20-iv3-cross').addEventListener('click',()=>ans('cross'));
|
||
document.getElementById('p20-iv3-same').addEventListener('click',()=>ans('same'));
|
||
show();
|
||
})();
|
||
|
||
/* IV4 — принадлежит ли точка */
|
||
makeTrainer({
|
||
idPrefix:'p20-iv4',
|
||
parser:(v)=>v,
|
||
questions:[
|
||
{ q:'Точка $M(-1; 5)$ и $y = 2x - 3$. Принадлежит? «да»/«нет»', a:(v)=>String(v).trim().toLowerCase().startsWith('н'), show:'нет ($2(-1)-3 = -5 \\ne 5$)' },
|
||
{ q:'Точка $A(2; 7)$ и $y = 3x + 1$. Принадлежит?', a:(v)=>String(v).trim().toLowerCase().startsWith('д'), show:'да ($3\\cdot2+1 = 7$)' },
|
||
{ q:'Точка $B(0; -4)$ и $y = -x - 4$. Принадлежит?', a:(v)=>String(v).trim().toLowerCase().startsWith('д'), show:'да' },
|
||
{ q:'Точка $K(-2; 11)$ и $y = -4x + 3$. Принадлежит?', a:(v)=>String(v).trim().toLowerCase().startsWith('д'), show:'да ($-4(-2)+3 = 11$)' },
|
||
{ q:'Точка $C(3; 0)$ и $y = 2x - 5$. Принадлежит?', a:(v)=>String(v).trim().toLowerCase().startsWith('н'), show:'нет ($2\\cdot3 - 5 = 1 \\ne 0$)' },
|
||
],
|
||
onComplete:(s,n)=>{ if(s===n){addXp(15,'p20-iv4');bumpProgress('p20',25);} else if(s>=3){addXp(7,'p20-iv4');bumpProgress('p20',12);} }
|
||
});
|
||
|
||
wireReadBtn('p20');
|
||
}
|
||
const BOSSES_CH3 = [
|
||
{
|
||
n:1, title:'Босс \xA715-16 — Уравнения и текстовые задачи', color:'#7c3aed',
|
||
steps:[
|
||
{ q:'Реши $-5x = 35$. Введи $x$.', verify:(v)=>+v===-7, hint:'$x = 35 : (-5)$.' },
|
||
{ q:'Реши $5(x-2)+1 = 4x$. Введи $x$.', verify:(v)=>+v===9, hint:'$5x-10+1=4x \\Rightarrow x = 9$.' },
|
||
{ q:'Сумма двух чисел 60, одно в 5 раз больше другого. Меньшее = ?', verify:(v)=>+v===10, hint:'$x+5x=60$.' },
|
||
{ q:'Из городов навстречу, скорости 50 и 60 км/ч, расстояние 220 км. Через сколько часов встретятся?', verify:(v)=>+v===2, hint:'$50t+60t=220$.' },
|
||
{ q:'$0 \\cdot x = 7$ — есть ли корни? Введи «нет» или «есть».', verify:(v)=>['нет','no','-'].includes(String(v).trim().toLowerCase()), hint:'$0 \\ne 7$.' },
|
||
]
|
||
},
|
||
{
|
||
n:2, title:'Босс \xA717 — Числовые неравенства', color:'#c026d3',
|
||
steps:[
|
||
{ q:'Из $a > b$ умножили обе части на $-3$. Знак сохранился? «да»/«нет»', verify:(v)=>['нет','no','-'].includes(String(v).trim().toLowerCase()), hint:'При умножении на отрицательное число знак меняется.' },
|
||
{ q:'Если $3 < a < 5$ и $2 < b < 4$, то $a+b$ больше какого числа?', verify:(v)=>+v===5, hint:'$3+2=5$.' },
|
||
{ q:'Если $5 < a < 9$, то $2a$ меньше какого числа?', verify:(v)=>+v===18, hint:'$2 \\cdot 9 = 18$.' },
|
||
{ q:'Если $a < b$, то $-3a$ и $-3b$ как соотносятся? «больше» или «меньше» — введи знак сравнения $-3a$ и $-3b$ (например, >)', verify:(v)=>String(v).trim()==='>', hint:'Умножили на $-3$ — знак сменился.' },
|
||
{ q:'Сложи $5 < x < 9$ и $-1 < y < 7$. Введи нижнюю границу $x+y$.', verify:(v)=>+v===4, hint:'$5+(-1)=4$.' },
|
||
]
|
||
},
|
||
{
|
||
n:3, title:'Босс \xA718 — Линейные неравенства', color:'#db2777',
|
||
steps:[
|
||
{ q:'Реши $7x < 21$. Введи правую часть в ответе $x < ?$', verify:(v)=>+v===3, hint:'$21:7=3$.' },
|
||
{ q:'Реши $-4x \\ge 16$. Введи правую часть в $x \\le ?$', verify:(v)=>+v===-4, hint:'Делим на $-4$, знак меняется.' },
|
||
{ q:'Реши $3(x+2) > 4 - x$. Введи правую часть в $x > ?$', verify:(v)=>Math.abs(parseFloat(String(v).replace(',','.'))-(-0.5))<1e-6, hint:'$3x+6 > 4-x \\Rightarrow 4x > -2$.' },
|
||
{ q:'$0 \\cdot x < 5$ — введи решение: «нет», «любое» или число.', verify:(v)=>String(v).trim().toLowerCase()==='любое', hint:'$0 < 5$ — всегда верно.' },
|
||
{ q:'$0 \\cdot x > 1$ — введи решение: «нет», «любое» или число.', verify:(v)=>String(v).trim().toLowerCase()==='нет', hint:'$0 > 1$ — ложь.' },
|
||
]
|
||
},
|
||
{
|
||
n:4, title:'Босс \xA719 — Функция', color:'#2563eb',
|
||
steps:[
|
||
{ q:'$f(x) = 5x - 1$. Найди $f(3)$.', verify:(v)=>+v===14, hint:'$5 \\cdot 3 - 1$.' },
|
||
{ q:'$f(x) = -3x + 2$. Найди $f(-1)$.', verify:(v)=>+v===5, hint:'$-3(-1)+2=5$.' },
|
||
{ q:'$f(x) = 9x - 1$ — найди нуль функции (дробь $a/b$).', verify:(v)=>{const m=String(v).match(/^(-?\d+)\s*\/\s*(-?\d+)$/); if(!m) return Math.abs(parseFloat(String(v).replace(',','.'))-1/9)<1e-4; return +m[1]===1 && +m[2]===9;}, hint:'$9x = 1$.' },
|
||
{ q:'$y = x^2 + 5$ — минимальное значение функции?', verify:(v)=>+v===5, hint:'$x^2 \\ge 0$, поэтому $y \\ge 5$.' },
|
||
{ q:'Зависимость стоимости покупки от числа купленных тетрадей — это функция? «да»/«нет»', verify:(v)=>String(v).trim().toLowerCase().startsWith('д'), hint:'Каждому $n$ — одна цена.' },
|
||
]
|
||
},
|
||
{
|
||
n:5, title:'Финальный босс — Линейная функция $y = kx + b$', color:'#dc2626',
|
||
steps:[
|
||
{ q:'У $y = -3x + 4$: введи $k$', verify:(v)=>+v===-3, hint:'Число при $x$.' },
|
||
{ q:'У $y = -3x + 4$: введи $b$', verify:(v)=>+v===4, hint:'Свободный член.' },
|
||
{ q:'$y = 2x + 5$ и $y = 2x - 7$. Что общее? «параллельны» или «пересекаются»', verify:(v)=>String(v).trim().toLowerCase().startsWith('парал'), hint:'$k_1 = k_2 = 2$, $b_1 \\ne b_2$.' },
|
||
{ q:'$y = 4x + 5$ — найди нуль функции (число)', verify:(v)=>Math.abs(parseFloat(String(v).replace(',','.'))-(-1.25))<1e-6, hint:'$4x = -5$.' },
|
||
{ q:'Принадлежит ли точка $A(2; 7)$ графику $y = 3x + 1$? «да»/«нет»', verify:(v)=>String(v).trim().toLowerCase().startsWith('д'), hint:'$3 \\cdot 2 + 1 = 7$.' },
|
||
]
|
||
},
|
||
];
|
||
|
||
function buildFinal3(){
|
||
const box = document.getElementById('final3-body');
|
||
let html = '';
|
||
|
||
html += makeCard('theory', 'Что мы изучили', 'Итог', `
|
||
<p>В этой главе мы:</p>
|
||
<ul style="padding-left:22px;line-height:1.85">
|
||
<li>научились решать <b>линейные уравнения</b> $ax = b$ — три случая по корням;</li>
|
||
<li>освоили <b>составление уравнений</b> для текстовых задач (возраст, движение, покупки);</li>
|
||
<li>выучили <b>3 свойства числовых неравенств</b> и сложение/умножение неравенств;</li>
|
||
<li>научились решать <b>линейные неравенства</b>, помня про смену знака при умножении на отрицательное;</li>
|
||
<li>узнали, что такое <b>функция</b>, аргумент, значение, нуль, область определения;</li>
|
||
<li>освоили <b>линейную функцию</b> $y = kx + b$ — наклон по $k$, сдвиг по $b$.</li>
|
||
</ul>
|
||
<p>Теперь — <b>5 боссов</b>. Каждый — серия из 5 этапов.</p>`);
|
||
|
||
html += '<div id="bosses-container-ch3"></div>';
|
||
html += '<div style="margin-top:22px;padding:18px 20px;background:linear-gradient(135deg,var(--pri-soft),var(--acc-soft));border-radius:14px;border:1.5px solid var(--pri);text-align:center">'
|
||
+'<div style="font-family:\'Unbounded\',sans-serif;font-weight:800;color:var(--pri2);font-size:1.1rem;margin-bottom:6px">Прогресс по боссам</div>'
|
||
+'<div id="boss-overall-ch3" style="font-size:.95rem;color:var(--text);margin-bottom:10px">0 / 5 боссов побеждено</div>'
|
||
+'<div class="hp-boss"><div class="hp-boss-fill" id="boss-overall-fill-ch3" style="width:0%;background:linear-gradient(90deg,#7c3aed,#a78bfa)"></div></div>'
|
||
+'</div>';
|
||
html += secNav('p20', null);
|
||
box.innerHTML = html; renderMath(box);
|
||
|
||
const cont = document.getElementById('bosses-container-ch3');
|
||
const BOSS_STATE = (function(){
|
||
try{ const s=localStorage.getItem('algebra7_ch3_bosses'); if(s) return JSON.parse(s); }catch(e){}
|
||
return BOSSES_CH3.map(()=>({stage:0,defeated:false}));
|
||
})();
|
||
function saveBosses(){ try{ localStorage.setItem('algebra7_ch3_bosses', JSON.stringify(BOSS_STATE)); }catch(e){} }
|
||
function refreshOverall(){
|
||
const won=BOSS_STATE.filter(b=>b.defeated).length;
|
||
const txt=document.getElementById('boss-overall-ch3'); if(txt) txt.textContent=won+' / '+BOSSES_CH3.length+' боссов побеждено';
|
||
const fill=document.getElementById('boss-overall-fill-ch3'); if(fill) fill.style.width=(won*100/BOSSES_CH3.length)+'%';
|
||
if(won>=BOSSES_CH3.length){ bumpProgress('final3',60); achievement('ch3_done','Глава 3 пройдена!'); }
|
||
}
|
||
cont.innerHTML = BOSSES_CH3.map((b,idx)=>{
|
||
return '<div class="boss-card" id="boss-card-'+idx+'" style="border-color:'+b.color+'">'
|
||
+'<div class="boss-head">'
|
||
+'<svg viewBox="0 0 24 24" fill="none" stroke="'+b.color+'" stroke-width="2.2" style="width:28px;height:28px"><polygon points="12,2 22,20 2,20"/></svg>'
|
||
+'<div class="boss-title" style="color:'+b.color+'">'+b.title+'</div>'
|
||
+'<div class="boss-stage" id="boss-'+idx+'-stage">Этап 1 / '+b.steps.length+'</div>'
|
||
+'</div>'
|
||
+'<div class="hp-boss" style="border-color:'+b.color+'66;background:'+b.color+'1a"><div class="hp-boss-fill" id="boss-'+idx+'-fill" style="width:0%;background:linear-gradient(90deg,'+b.color+',#f59e0b)"></div></div>'
|
||
+'<div class="boss-q" id="boss-'+idx+'-q" style="border-color:'+b.color+'"></div>'
|
||
+'<div style="display:flex;gap:8px;align-items:center;flex-wrap:wrap">'
|
||
+'<input type="text" id="boss-'+idx+'-input" class="tinp" placeholder="Ответ" style="width:160px;text-align:center">'
|
||
+'<button class="btn primary" id="boss-'+idx+'-go" style="background:'+b.color+';border-color:'+b.color+'">Атака</button>'
|
||
+'<button class="btn" id="boss-'+idx+'-hint">Подсказка</button>'
|
||
+'<button class="btn" id="boss-'+idx+'-restart">↻</button>'
|
||
+'</div>'
|
||
+'<div class="feedback" id="boss-'+idx+'-fb"></div>'
|
||
+'</div>';
|
||
}).join('');
|
||
|
||
BOSSES_CH3.forEach((b,idx)=>{
|
||
function show(){
|
||
const st=BOSS_STATE[idx];
|
||
const stageEl=document.getElementById('boss-'+idx+'-stage');
|
||
const fill=document.getElementById('boss-'+idx+'-fill');
|
||
const q=document.getElementById('boss-'+idx+'-q');
|
||
const fb=document.getElementById('boss-'+idx+'-fb');
|
||
if(st.defeated){
|
||
stageEl.textContent='✓ Побеждён'; fill.style.width='100%';
|
||
q.innerHTML='<b style="color:'+b.color+'">Босс повержен!</b>';
|
||
document.getElementById('boss-'+idx+'-go').disabled=true;
|
||
document.getElementById('boss-'+idx+'-go').style.opacity=.5;
|
||
return;
|
||
}
|
||
stageEl.textContent='Этап '+(st.stage+1)+' / '+b.steps.length;
|
||
fill.style.width=(st.stage*100/b.steps.length)+'%';
|
||
q.innerHTML=b.steps[st.stage].q;
|
||
document.getElementById('boss-'+idx+'-input').value='';
|
||
fb.style.display='none';
|
||
renderMath(q);
|
||
}
|
||
document.getElementById('boss-'+idx+'-go').addEventListener('click',()=>{
|
||
const st=BOSS_STATE[idx]; if(st.defeated) return;
|
||
const step=b.steps[st.stage];
|
||
const val=document.getElementById('boss-'+idx+'-input').value;
|
||
const fb=document.getElementById('boss-'+idx+'-fb');
|
||
if(!val.trim()){ feedback(fb,false,'✗ Введи ответ.'); return; }
|
||
if(step.verify(val)){
|
||
st.stage++;
|
||
if(st.stage>=b.steps.length){
|
||
st.defeated=true; saveBosses();
|
||
feedback(fb,true,'✓ Босс '+b.n+' побеждён! +20 XP');
|
||
addXp(20,'boss-'+b.n); bumpProgress('final3',18); refreshOverall();
|
||
setTimeout(show,1400);
|
||
if(window.confetti) try{confetti();}catch(e){}
|
||
}else{
|
||
saveBosses(); feedback(fb,true,'✓ Верно! +3 XP'); addXp(3,'boss-step'); setTimeout(show,1100);
|
||
}
|
||
}else{ feedback(fb,false,'✗ Промах.'); }
|
||
});
|
||
document.getElementById('boss-'+idx+'-hint').addEventListener('click',()=>{
|
||
const st=BOSS_STATE[idx]; if(st.defeated) return;
|
||
const fb=document.getElementById('boss-'+idx+'-fb');
|
||
fb.className='feedback ok';
|
||
fb.innerHTML='<span style="color:#92400e">\u{1F4A1} Подсказка:</span> '+b.steps[st.stage].hint;
|
||
fb.style.display='block';
|
||
fb.style.background='var(--warn-bg)'; fb.style.color='#92400e'; fb.style.borderLeftColor='var(--warn)';
|
||
renderMath(fb);
|
||
});
|
||
document.getElementById('boss-'+idx+'-restart').addEventListener('click',()=>{
|
||
BOSS_STATE[idx]={stage:0,defeated:false}; saveBosses();
|
||
document.getElementById('boss-'+idx+'-go').disabled=false;
|
||
document.getElementById('boss-'+idx+'-go').style.opacity=1;
|
||
show(); refreshOverall();
|
||
});
|
||
show();
|
||
});
|
||
|
||
refreshOverall();
|
||
}
|
||
</script>
|
||
|
||
</body>
|
||
</html>
|