699fdcc7fb
- migration 014: parent_slug column + algebra-8 hub row + rename old algebra-8 → algebra-8-ch1 (progress сохраняется через стабильный textbook_id=3) - backend/routes/textbooks.js: GET / фильтрует parent_slug IS NULL; aggregated progress для хабов; новый GET /:slug/children - algebra_8_hub.html: новая хаб-страница с 3 карточками глав, hero с общим прогрессом, XP-бейдж, ссылки на главы - algebra_8/ch2/ch3: кнопки cross-chapter заменены на одну «К алгебре 8» в шапке Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
3178 lines
208 KiB
HTML
3178 lines
208 KiB
HTML
<!doctype html>
|
||
<html lang="ru">
|
||
<head>
|
||
<meta charset="UTF-8">
|
||
<meta http-equiv="Cache-Control" content="no-cache, no-store, must-revalidate">
|
||
<meta http-equiv="Pragma" content="no-cache">
|
||
<meta http-equiv="Expires" content="0">
|
||
<title>Алгебра 8 · Глава 3 · Неравенства</title>
|
||
<meta name="viewport" content="width=device-width,initial-scale=1">
|
||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/katex@0.16.9/dist/katex.min.css">
|
||
<script defer src="https://cdn.jsdelivr.net/npm/katex@0.16.9/dist/katex.min.js"></script>
|
||
<script defer src="https://cdn.jsdelivr.net/npm/katex@0.16.9/dist/contrib/auto-render.min.js"
|
||
onload="renderMathInElement(document.body,{delimiters:[{left:'$$',right:'$$',display:true},{left:'$',right:'$',display:false},{left:'\\[',right:'\\]',display:true},{left:'\\(',right:'\\)',display:false}],throwOnError:false})"></script>
|
||
<script src="/js/api.js" defer></script>
|
||
<script src="/js/xp.js" defer></script>
|
||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800&family=Manrope:wght@600;700;800;900&family=Unbounded:wght@700;800;900&family=JetBrains+Mono:wght@500;700&display=swap" rel="stylesheet">
|
||
<style>
|
||
:root{
|
||
--bg:#fafafa; --card:#fff; --card-soft:#f8fafc; --text:#0f172a; --ink:#0f172a; --muted:#64748b;
|
||
--border:#e2e8f0; --sh:0 1px 3px rgba(0,0,0,.06); --sh2:0 4px 14px rgba(0,0,0,.08);
|
||
--pri:#6366f1; --pri2:#4338ca; --pri-soft:#e0e7ff;
|
||
--acc:#06b6d4; --acc2:#0e7490; --acc-soft:#cffafe;
|
||
--ok:#10b981; --ok-bg:#d1fae5; --warn:#f59e0b; --warn-bg:#fef3c7;
|
||
--bad:#ef4444; --fail:#dc2626; --fail-bg:#fee2e2;
|
||
}
|
||
.dark{--bg:#0a0e1a; --card:#111827; --card-soft:#0f1729; --text:#e2e8f0; --ink:#e2e8f0; --muted:#94a3b8; --border:#1e293b}
|
||
*{margin:0;padding:0;box-sizing:border-box;-webkit-tap-highlight-color:transparent}
|
||
html,body{font-family:'Inter',system-ui,sans-serif;background:var(--bg);color:var(--text);line-height:1.55;font-size:15px}
|
||
button,input,select,textarea{font-family:inherit;font-size:inherit}
|
||
button{cursor:pointer;border:0;background:transparent;color:inherit}
|
||
a{color:inherit;text-decoration:none}
|
||
.ic{width:16px;height:16px;display:inline-block;flex-shrink:0;stroke:currentColor;fill:none;stroke-width:2;stroke-linecap:round;stroke-linejoin:round;vertical-align:middle}
|
||
|
||
/* HEADER */
|
||
.hdr{position:relative;background:linear-gradient(110deg,#4338ca 0%,#6366f1 50%,#8b5cf6 100%);color:#fff;padding:46px 22px 30px;overflow:hidden;border-bottom:2px solid rgba(199,180,255,.2);min-height:130px}
|
||
.hdr::before{content:'ГЛАВА 3';position:absolute;right:-12px;top:50%;transform:translateY(-50%);font-family:'Unbounded',sans-serif;font-size:clamp(5rem,15vw,11rem);font-weight:900;letter-spacing:-.04em;color:transparent;-webkit-text-stroke:1.5px rgba(220,210,255,.1);line-height:1;pointer-events:none;user-select:none;z-index:0}
|
||
.hdr-row{position:relative;z-index:1;display:flex;align-items:center;gap:14px;flex-wrap:wrap}
|
||
.hdr h1{font-family:'Unbounded',sans-serif;font-size:1.5rem;font-weight:900;letter-spacing:-.01em;line-height:1.3;padding-top:4px}
|
||
.hdr-sub{font-size:.85rem;opacity:.88;margin-top:6px;font-weight:500;line-height:1.4}
|
||
.hdr-side{margin-left:auto;display:flex;gap:8px;align-items:center;flex-wrap:wrap}
|
||
.hdr-btn{padding:7px 12px;border-radius:9px;background:rgba(255,255,255,.14);color:#fff;font-weight:600;font-size:.82rem;display:inline-flex;align-items:center;gap:6px;transition:background .15s;text-decoration:none}
|
||
.hdr-btn:hover{background:rgba(255,255,255,.24)}
|
||
|
||
/* MAIN GRID */
|
||
.main{max-width:1240px;margin:0 auto;padding:22px;width:100%;display:grid;grid-template-columns:1fr 280px;gap:24px}
|
||
@media(max-width:980px){.main{grid-template-columns:1fr;padding:14px}}
|
||
.col-main{min-width:0}
|
||
|
||
/* HERO */
|
||
.hero{background:linear-gradient(135deg,var(--pri-soft) 0%,var(--acc-soft) 50%,var(--pri-soft) 100%);background-size:200% 200%;animation:heroShift 12s ease-in-out infinite;border:1px solid var(--border);border-radius:18px;padding:24px 22px;margin-bottom:24px;position:relative;overflow:hidden}
|
||
@keyframes heroShift{0%,100%{background-position:0% 50%}50%{background-position:100% 50%}}
|
||
.hero::before{content:'x > a';position:absolute;right:-10px;top:-20px;font-family:'JetBrains Mono',monospace;font-size:clamp(2rem,8vw,5.5rem);font-weight:900;color:var(--pri);opacity:.10;line-height:1;pointer-events:none}
|
||
.hero h2{font-family:'Unbounded',sans-serif;font-size:1.55rem;font-weight:800;color:var(--pri2);margin-bottom:10px;letter-spacing:-.01em}
|
||
.hero p{font-size:.95rem;color:var(--text);opacity:.88;margin-bottom:14px;max-width:640px}
|
||
.hero-row{display:flex;gap:14px;flex-wrap:wrap;align-items:center}
|
||
.btn-primary{padding:11px 22px;background:linear-gradient(135deg,var(--pri),var(--pri2));color:#fff;border-radius:11px;font-weight:700;font-size:.92rem;display:inline-flex;align-items:center;gap:8px;box-shadow:var(--sh2);transition:transform .15s,box-shadow .15s}
|
||
.btn-primary:hover{transform:translateY(-1px);box-shadow:0 8px 28px rgba(99,102,241,.32)}
|
||
.hero-progress{flex:1;min-width:200px;max-width:280px}
|
||
.hp-label{font-size:.74rem;font-weight:700;color:var(--muted);text-transform:uppercase;letter-spacing:.06em;display:block;margin-bottom:5px}
|
||
.hp-bar{height:8px;background:rgba(99,102,241,.18);border-radius:5px;overflow:hidden}
|
||
.hp-fill{height:100%;background:linear-gradient(90deg,var(--pri),var(--acc));border-radius:5px;width:0%;transition:width .6s cubic-bezier(.16,1,.3,1)}
|
||
.hp-text{font-size:.78rem;color:var(--muted);font-weight:700;margin-top:4px;display:block}
|
||
.hero-xp-badge{display:inline-flex;align-items:center;gap:6px;padding:6px 12px;background:linear-gradient(135deg,var(--warn,#f59e0b),var(--pri));color:#fff;border-radius:99px;font-size:.82rem;font-weight:800;letter-spacing:.02em;box-shadow:0 4px 12px rgba(99,102,241,.22);font-family:'Unbounded',sans-serif}
|
||
.hero-xp-badge svg{flex-shrink:0}
|
||
|
||
/* PARA SELECTOR */
|
||
.psel{margin-bottom:24px}
|
||
.psel-title{font-size:.72rem;font-weight:800;color:var(--muted);text-transform:uppercase;letter-spacing:.08em;margin-bottom:10px}
|
||
.psel-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(170px,1fr));gap:10px}
|
||
.psel-card{background:var(--card);border:1.5px solid var(--border);border-radius:13px;padding:14px;cursor:pointer;transition:transform .2s,box-shadow .2s,border-color .2s;text-align:left;position:relative}
|
||
.psel-card:hover{transform:translateY(-3px);box-shadow:var(--sh2);border-color:var(--pri)}
|
||
.psel-card.active{border-color:var(--pri);background:linear-gradient(135deg,var(--pri-soft),var(--card));box-shadow:var(--sh2)}
|
||
.psel-card.active::after{content:'';position:absolute;top:0;left:0;right:0;height:3px;background:linear-gradient(90deg,var(--pri),var(--acc));border-radius:13px 13px 0 0}
|
||
.psel-num{font-family:'Unbounded',sans-serif;font-size:.72rem;font-weight:800;color:var(--pri);text-transform:uppercase;letter-spacing:.08em;margin-bottom:5px}
|
||
.psel-name{font-size:.88rem;font-weight:700;color:var(--text);line-height:1.3;margin-bottom:8px}
|
||
.psel-prog{height:4px;background:rgba(99,102,241,.10);border-radius:3px;overflow:hidden}
|
||
.psel-prog-fill{height:100%;background:var(--pri);width:0%;transition:width .4s}
|
||
.psel-card.final{background:linear-gradient(135deg,#fff5e1,#e0e7ff)}
|
||
.psel-card.final .psel-num{color:var(--warn)}
|
||
|
||
/* SECTION COLORS */
|
||
.sec[id="sec-p13"] { --sec-acc:#6366f1; --sec-acc-d:#4338ca; --sec-acc-soft:#e0e7ff; }
|
||
.sec[id="sec-p14"] { --sec-acc:#0ea5e9; --sec-acc-d:#0369a1; --sec-acc-soft:#e0f2fe; }
|
||
.sec[id="sec-p15"] { --sec-acc:#10b981; --sec-acc-d:#047857; --sec-acc-soft:#d1fae5; }
|
||
.sec[id="sec-p16"] { --sec-acc:#f59e0b; --sec-acc-d:#b45309; --sec-acc-soft:#fef3c7; }
|
||
.sec[id="sec-p17"] { --sec-acc:#8b5cf6; --sec-acc-d:#6d28d9; --sec-acc-soft:#ede9fe; }
|
||
.sec[id="sec-p18"] { --sec-acc:#e11d48; --sec-acc-d:#9f1239; --sec-acc-soft:#ffe4e6; }
|
||
.sec[id="sec-final3"] { --sec-acc:#d97706; --sec-acc-d:#b45309; --sec-acc-soft:#fef3c7; }
|
||
|
||
.sec{display:none;position:relative;animation:fadeIn .35s ease}
|
||
.sec.active{display:block}
|
||
@keyframes fadeIn{from{opacity:0;transform:translateY(8px)}to{opacity:1;transform:none}}
|
||
.sec::before{content:attr(data-watermark);position:absolute;right:-20px;top:10%;font-family:'Unbounded',sans-serif;font-size:clamp(6rem,18vw,14rem);font-weight:900;color:transparent;-webkit-text-stroke:1.5px var(--sec-acc-soft,var(--pri-soft));line-height:1;pointer-events:none;user-select:none;z-index:0;opacity:.35}
|
||
.sec-header{margin-bottom:22px;padding-bottom:14px;border-bottom:2px solid var(--sec-acc-soft,var(--pri-soft));position:relative;z-index:1}
|
||
.sec-num{display:inline-block;padding:4px 10px;background:linear-gradient(135deg,var(--sec-acc,var(--pri)),var(--sec-acc-d,var(--pri2)));color:#fff;border-radius:7px;font-family:'Unbounded',sans-serif;font-size:.78rem;font-weight:800;letter-spacing:.04em;margin-bottom:8px}
|
||
.sec-h{font-family:'Unbounded',sans-serif;font-size:1.7rem;font-weight:800;color:var(--sec-acc-d,var(--pri2));letter-spacing:-.01em;line-height:1.25}
|
||
|
||
/* CARDS */
|
||
.card{background:var(--card);border:1px solid var(--border);border-radius:14px;padding:18px 20px;margin-bottom:16px;box-shadow:0 1px 3px rgba(0,0,0,.04),0 8px 24px rgba(99,102,241,.06);position:relative;z-index:1;transition:transform .25s cubic-bezier(.16,1,.3,1),box-shadow .25s}
|
||
.card:hover{transform:translateY(-2px);box-shadow:0 4px 10px rgba(0,0,0,.06),0 16px 36px rgba(99,102,241,.12)}
|
||
.card-header{display:flex;align-items:center;gap:10px;margin-bottom:12px;padding-bottom:10px;border-bottom:1px dashed var(--border)}
|
||
.card-icon{width:32px;height:32px;border-radius:9px;display:flex;align-items:center;justify-content:center;flex-shrink:0;color:#fff;outline:2px solid var(--sec-acc-soft,transparent);outline-offset:1px}
|
||
.card-icon.repeat{background:#0ea5e9}
|
||
.card-icon.theory{background:#8b5cf6}
|
||
.card-icon.algo{background:#f59e0b}
|
||
.card-icon.rule{background:#ec4899}
|
||
.card-icon.example{background:#10b981}
|
||
.card-icon.oral{background:#06b6d4}
|
||
.card-icon.class{background:#3b82f6}
|
||
.card-icon.home{background:#f97316}
|
||
.card-icon .ic{width:18px;height:18px}
|
||
.card-title{font-family:'Unbounded',sans-serif;font-size:.82rem;font-weight:800;text-transform:uppercase;letter-spacing:.06em;color:var(--muted);flex:1}
|
||
.card-num{font-size:.74rem;font-weight:700;color:var(--muted);background:var(--sec-acc-soft,var(--pri-soft));padding:3px 7px;border-radius:5px}
|
||
.card-body{font-size:.94rem;line-height:1.65}
|
||
.card-body p{margin-bottom:8px}
|
||
.card-body p:last-child{margin-bottom:0}
|
||
|
||
/* WIDGET */
|
||
.wg{background:linear-gradient(135deg,var(--card),var(--sec-acc-soft,var(--pri-soft)));border:1.5px solid var(--sec-acc,var(--pri));border-radius:14px;padding:18px 20px;margin-bottom:18px;box-shadow:var(--sh2);position:relative;z-index:1;transition:box-shadow .25s}
|
||
.wg:hover{box-shadow:0 4px 12px rgba(0,0,0,.08),0 16px 40px rgba(99,102,241,.18)}
|
||
.wg-header{display:flex;align-items:center;gap:8px;margin-bottom:14px}
|
||
.wg-badge{padding:4px 9px;background:var(--sec-acc,var(--pri));color:#fff;border-radius:6px;font-family:'Unbounded',sans-serif;font-size:.68rem;font-weight:800;text-transform:uppercase;letter-spacing:.06em}
|
||
.wg-title{font-family:'Unbounded',sans-serif;font-size:1.05rem;font-weight:800;color:var(--sec-acc-d,var(--pri2));flex:1}
|
||
.wg-help{font-size:.88rem;color:var(--text);margin-bottom:12px;line-height:1.55;background:linear-gradient(135deg,var(--warn-bg,#fef3c7),var(--sec-acc-soft,var(--pri-soft)));border-left:4px solid var(--warn,#f59e0b);padding:9px 14px;border-radius:9px;position:relative}
|
||
.wg-help::before{content:'?';position:absolute;left:-13px;top:50%;transform:translateY(-50%);width:22px;height:22px;border-radius:50%;background:var(--warn,#f59e0b);color:#fff;display:flex;align-items:center;justify-content:center;font-weight:900;font-size:.78rem;box-shadow:0 2px 6px rgba(0,0,0,.18)}
|
||
.dark .wg-help{background:linear-gradient(135deg,rgba(245,158,11,.18),rgba(99,102,241,.10))}
|
||
|
||
/* BUTTONS */
|
||
.btn{padding:8px 16px;border-radius:8px;background:var(--card);color:var(--text);border:1.5px solid var(--border);font-weight:600;font-size:.88rem;transition:background .15s,border-color .15s,transform .1s}
|
||
.btn:hover{background:var(--sec-acc-soft,var(--pri-soft));border-color:var(--sec-acc,var(--pri))}
|
||
.btn:active{transform:scale(.96)}
|
||
.btn.primary{background:var(--sec-acc,var(--pri));color:#fff;border-color:var(--sec-acc,var(--pri))}
|
||
.btn.primary:hover{background:var(--sec-acc-d,var(--pri2));border-color:var(--sec-acc-d,var(--pri2))}
|
||
.btn.acc{background:var(--acc);color:#fff;border-color:var(--acc)}
|
||
.btn.ok{background:var(--ok);color:#fff;border-color:var(--ok)}
|
||
.btn.small{padding:5px 11px;font-size:.78rem}
|
||
|
||
/* INPUTS */
|
||
.tinp{padding:8px 12px;border:1.5px solid var(--border);border-radius:8px;background:var(--card);color:var(--text);transition:border-color .15s,box-shadow .15s}
|
||
.tinp:focus{outline:0;border-color:var(--sec-acc,var(--pri));box-shadow:0 0 0 3px var(--sec-acc-soft,var(--pri-soft))}
|
||
|
||
/* CHIPS / FEEDBACK */
|
||
.chip{display:inline-flex;align-items:center;gap:5px;padding:5px 10px;background:var(--sec-acc-soft,var(--pri-soft));border-radius:7px;font-size:.84rem;font-weight:600;color:var(--sec-acc-d,var(--pri2))}
|
||
.feedback{padding:10px 14px;border-radius:9px;font-weight:600;font-size:.88rem;margin-top:8px;display:none}
|
||
.feedback.ok{display:block;background:var(--ok-bg);color:#065f46;border-left:4px solid var(--ok)}
|
||
.feedback.fail{display:block;background:var(--fail-bg);color:#7f1d1d;border-left:4px solid var(--fail)}
|
||
|
||
/* SIDEBAR */
|
||
.col-side{position:sticky;top:14px;align-self:start;height:fit-content;max-height:calc(100vh - 28px);overflow-y:auto}
|
||
.sidecard{background:var(--card);border:1px solid var(--border);border-radius:14px;padding:16px;margin-bottom:14px;box-shadow:var(--sh)}
|
||
.sidecard h4{font-family:'Unbounded',sans-serif;font-size:.74rem;font-weight:800;color:var(--pri2);text-transform:uppercase;letter-spacing:.07em;margin-bottom:10px;padding-bottom:8px;border-bottom:1px solid var(--border)}
|
||
.sidecard-row{margin-bottom:8px;font-size:.86rem;line-height:1.6}
|
||
.sidecard-row b{color:var(--pri);font-weight:700}
|
||
.sidecard-row:last-child{margin-bottom:0}
|
||
@media(max-width:980px){.col-side{position:static;max-height:none}}
|
||
|
||
/* XP card */
|
||
.xp-card{background:linear-gradient(135deg,var(--acc-soft),var(--pri-soft));border:1.5px solid var(--acc);border-radius:12px;padding:14px;margin-bottom:14px}
|
||
.xp-card-title{font-size:.68rem;font-weight:800;color:var(--acc2);text-transform:uppercase;letter-spacing:.07em;margin-bottom:8px;display:flex;align-items:center;justify-content:space-between}
|
||
.xp-level{font-size:1.1rem;font-weight:900;color:var(--acc2);font-family:'Unbounded',sans-serif}
|
||
.xp-bar{height:9px;background:rgba(3,169,244,.15);border-radius:6px;overflow:hidden;margin:7px 0}
|
||
.xp-fill{height:100%;background:linear-gradient(90deg,var(--acc),var(--pri));border-radius:6px;transition:width .5s cubic-bezier(.4,0,.2,1)}
|
||
.xp-nums{font-size:.74rem;color:var(--muted);display:flex;justify-content:space-between}
|
||
|
||
/* SPOILER */
|
||
.spoiler{border:1px solid var(--border);border-radius:10px;background:var(--card);margin:10px 0;overflow:hidden}
|
||
.spoiler summary{padding:8px 14px;background:var(--sec-acc-soft,var(--pri-soft));font-weight:700;cursor:pointer;font-size:.88rem;color:var(--sec-acc-d,var(--pri2));list-style:none;display:flex;align-items:center;gap:8px}
|
||
.spoiler summary::-webkit-details-marker{display:none}
|
||
.spoiler summary::before{content:'+';font-size:1.2rem;font-weight:900;color:var(--sec-acc,var(--pri));width:18px}
|
||
.spoiler[open] summary::before{content:'−'}
|
||
.spoiler-body{padding:10px 14px;font-size:.92rem;line-height:1.6}
|
||
|
||
.sec-nav{display:flex;gap:10px;margin-top:24px;padding-top:20px;border-top:1px solid var(--border);justify-content:space-between;flex-wrap:wrap}
|
||
.foot{text-align:center;padding:30px 16px;color:var(--muted);font-size:.78rem;border-top:1px solid var(--border);margin-top:30px}
|
||
|
||
/* TABLES */
|
||
.tbl{width:100%;border-collapse:collapse;margin:12px 0;font-size:.88rem}
|
||
.tbl th,.tbl td{padding:7px 10px;border:1px solid var(--border);text-align:center;font-family:'JetBrains Mono',monospace}
|
||
.tbl th{background:var(--sec-acc-soft,var(--pri-soft));color:var(--sec-acc-d,var(--pri2));font-weight:700}
|
||
|
||
/* ACH popup */
|
||
.ach-popup{position:fixed;top:80px;right:18px;background:linear-gradient(135deg,#10b981,#06b6d4);color:#fff;padding:12px 18px;border-radius:11px;font-weight:700;font-size:.9rem;box-shadow:0 8px 28px rgba(16,185,129,.45);z-index:1002;display:none;align-items:center;gap:8px;animation:achIn .45s cubic-bezier(.34,1.56,.64,1) forwards;max-width:340px}
|
||
.ach-popup.show{display:flex}
|
||
@keyframes achIn{from{opacity:0;transform:translateX(40px)}to{opacity:1;transform:none}}
|
||
|
||
/* SCORE */
|
||
.score-display{display:flex;gap:14px;flex-wrap:wrap;align-items:center;padding:10px 14px;background:var(--sec-acc-soft,var(--pri-soft));border-radius:10px;margin-bottom:12px}
|
||
.score-display b{color:var(--sec-acc-d,var(--pri2));font-size:1.15rem}
|
||
|
||
/* SLIDERS / DROP / ACTIONS */
|
||
.sliders{display:grid;grid-template-columns:repeat(auto-fit,minmax(180px,1fr));gap:10px;margin-bottom:10px}
|
||
.sliders label{display:block;font-size:.92rem;color:var(--muted);background:var(--card);padding:8px 12px;border-radius:8px;border:1px solid var(--border);line-height:1.5}
|
||
.sliders label .katex{font-size:1em}
|
||
.sliders label b{font-family:'JetBrains Mono',monospace;font-size:1.05rem;color:var(--sec-acc-d,var(--pri2));margin-left:4px}
|
||
.sliders label input[type="range"]{display:block;width:100%;margin-top:6px;accent-color:var(--sec-acc,var(--pri))}
|
||
.drop-box{background:var(--card);border:1.5px dashed var(--border);border-radius:10px;padding:10px;min-height:90px;transition:border-color .15s,background .15s}
|
||
.drop-box:hover{border-color:var(--sec-acc,var(--pri));background:var(--sec-acc-soft,var(--pri-soft))}
|
||
.drop-box h5{font-family:'Unbounded',sans-serif;font-size:.78rem;color:var(--sec-acc-d,var(--pri2));margin-bottom:8px;text-transform:uppercase;letter-spacing:.05em}
|
||
.drop-items{display:flex;flex-wrap:wrap;gap:6px;min-height:32px}
|
||
.actions{display:flex;gap:8px;flex-wrap:wrap;margin-top:10px}
|
||
|
||
/* DRAG & DROP — sortable chips */
|
||
.dnd-pool{display:flex;flex-wrap:wrap;gap:8px;margin-bottom:14px;padding:10px;border:1.5px dashed var(--border);border-radius:10px;min-height:54px;transition:border-color .18s,background .18s}
|
||
.dnd-pool.over{border-color:var(--sec-acc,var(--pri));background:var(--sec-acc-soft,var(--pri-soft));border-style:solid}
|
||
.dnd-pool.col{flex-direction:column;align-items:stretch}
|
||
.dnd-pool.col .dnd-chip{width:auto}
|
||
.dnd-chip{display:inline-flex;align-items:center;gap:6px;padding:6px 12px;background:var(--card);border:1.5px solid var(--border);border-radius:10px;cursor:grab;user-select:none;font-size:.92rem;line-height:1.4;transition:transform .12s,box-shadow .12s,border-color .12s;touch-action:none;max-width:100%}
|
||
.dnd-chip:hover{transform:translateY(-1px);border-color:var(--sec-acc,var(--pri));box-shadow:var(--sh)}
|
||
.dnd-chip:active{cursor:grabbing}
|
||
.dnd-chip.armed{border-color:var(--sec-acc,var(--pri));background:var(--sec-acc-soft,var(--pri-soft));box-shadow:0 0 0 3px rgba(99,102,241,.22);transform:translateY(-1px)}
|
||
.dnd-chip.dragging{opacity:.28}
|
||
.dnd-chip.placed{background:var(--sec-acc-soft,var(--pri-soft));border-color:var(--sec-acc,var(--pri))}
|
||
.dnd-chip .dnd-x{padding:0 5px;color:var(--muted);font-weight:700;font-size:1.05rem;border-radius:4px;cursor:pointer}
|
||
.dnd-chip .dnd-x:hover{color:var(--bad,var(--fail));background:var(--fail-bg)}
|
||
.drop-box.over{border-color:var(--sec-acc,var(--pri));background:var(--sec-acc-soft,var(--pri-soft));border-style:solid;transform:scale(1.015)}
|
||
.dnd-hint{font-size:.78rem;color:var(--muted);margin-bottom:8px;display:flex;align-items:center;gap:6px}
|
||
.dnd-hint svg{width:14px;height:14px;flex-shrink:0}
|
||
|
||
/* SIDEBAR DRAWER for narrow viewports */
|
||
.col-side-backdrop{position:fixed;inset:0;background:rgba(0,0,0,.42);z-index:9990;display:none;animation:fadeIn .18s ease}
|
||
.col-side-backdrop.show{display:block}
|
||
@media(max-width:980px){
|
||
.col-side{position:fixed;top:0;right:0;height:100vh;width:300px;max-width:88vw;background:var(--bg);box-shadow:-12px 0 24px rgba(0,0,0,.18);padding:18px 16px;overflow-y:auto;transform:translateX(100%);transition:transform .25s ease;z-index:9991;max-height:none}
|
||
.col-side.open{transform:none}
|
||
}
|
||
|
||
/* NUMBER LINE — спец-виджет для §15-§17 */
|
||
.numline{background:var(--card);border:1px solid var(--border);border-radius:10px;padding:14px 6px;margin:10px 0;overflow-x:auto}
|
||
.numline svg{width:100%;min-width:520px;height:88px;display:block}
|
||
|
||
/* GLOSSARY tooltip */
|
||
.gloss-term{border-bottom:1.5px dotted var(--sec-acc,var(--pri));cursor:help;color:var(--sec-acc-d,var(--pri2));font-weight:600;padding:0 1px}
|
||
.gloss-term:hover{background:var(--sec-acc-soft,var(--pri-soft));border-radius:3px}
|
||
.gloss-tip{position:fixed;max-width:320px;padding:11px 14px;background:var(--card);border:1.5px solid var(--sec-acc,var(--pri));border-radius:11px;font-size:.84rem;line-height:1.55;box-shadow:0 12px 32px rgba(0,0,0,.18);z-index:9994;display:none;pointer-events:none;color:var(--text)}
|
||
.gloss-tip.show{display:block;animation:tipIn .15s ease}
|
||
.gloss-tip b{color:var(--sec-acc-d,var(--pri2));font-size:.92rem}
|
||
@keyframes tipIn{from{opacity:0;transform:translateY(4px)}to{opacity:1;transform:none}}
|
||
|
||
/* SEARCH MODAL */
|
||
.search-modal{position:fixed;inset:0;background:rgba(15,23,42,.55);backdrop-filter:blur(4px);z-index:9993;display:none;align-items:flex-start;justify-content:center;padding-top:14vh}
|
||
.search-modal.show{display:flex;animation:fadeIn .15s ease}
|
||
.search-box{background:var(--bg);border:1px solid var(--border);border-radius:14px;width:560px;max-width:92vw;max-height:70vh;display:flex;flex-direction:column;overflow:hidden;box-shadow:0 24px 64px rgba(0,0,0,.4)}
|
||
.search-input{padding:14px 16px;font-size:1rem;border:0;border-bottom:1px solid var(--border);background:transparent;color:var(--text);outline:none}
|
||
.search-results{flex:1;overflow-y:auto;padding:6px 0}
|
||
.search-row{display:block;padding:8px 16px;cursor:pointer;border-bottom:1px solid var(--border);text-align:left;background:transparent;border-left:0;border-right:0;border-top:0;width:100%;color:var(--text)}
|
||
.search-row:hover,.search-row.active{background:var(--sec-acc-soft,var(--pri-soft))}
|
||
.search-row .sr-kind{font-size:.7rem;font-weight:800;color:var(--muted);text-transform:uppercase;letter-spacing:.06em;margin-bottom:2px}
|
||
.search-row .sr-title{font-weight:700;font-size:.92rem;color:var(--text)}
|
||
.search-row .sr-desc{font-size:.8rem;color:var(--muted);margin-top:2px}
|
||
.search-empty{padding:20px;text-align:center;color:var(--muted);font-size:.88rem}
|
||
.search-foot{padding:8px 14px;border-top:1px solid var(--border);font-size:.74rem;color:var(--muted);display:flex;gap:14px;background:var(--card-soft,transparent)}
|
||
.search-foot kbd{padding:2px 6px;background:var(--card);border:1px solid var(--border);border-radius:4px;font-family:'JetBrains Mono',monospace;font-size:.72rem}
|
||
</style>
|
||
</head>
|
||
<body>
|
||
|
||
<header class="hdr">
|
||
<div class="hdr-row">
|
||
<div>
|
||
<h1>Алгебра 8 · Глава 3</h1>
|
||
<div class="hdr-sub">Неравенства с одной переменной</div>
|
||
</div>
|
||
<div class="hdr-side">
|
||
<a href="/textbook/algebra-8" class="hdr-btn" title="К Алгебре 8 — все главы">
|
||
<svg class="ic" viewBox="0 0 24 24"><polyline points="15 18 9 12 15 6"/></svg>
|
||
К алгебре 8
|
||
</a>
|
||
<button id="search-btn" class="hdr-btn" title="Поиск (Ctrl+K)">
|
||
<svg class="ic" viewBox="0 0 24 24"><circle cx="11" cy="11" r="7"/><path d="m21 21-4-4"/></svg>
|
||
<span>Поиск</span>
|
||
</button>
|
||
<button id="sidebar-btn" class="hdr-btn" title="Шпаргалка">
|
||
<svg class="ic" viewBox="0 0 24 24"><line x1="4" y1="6" x2="20" y2="6"/><line x1="4" y1="12" x2="20" y2="12"/><line x1="4" y1="18" x2="14" y2="18"/></svg>
|
||
Шпаргалка
|
||
</button>
|
||
<button id="theme-btn" class="hdr-btn" title="Сменить тему">
|
||
<svg class="ic" viewBox="0 0 24 24"><path d="M21 12.8A9 9 0 1 1 11.2 3a7 7 0 0 0 9.8 9.8z"/></svg>
|
||
<span id="theme-lab">Тёмная</span>
|
||
</button>
|
||
</div>
|
||
</div>
|
||
</header>
|
||
|
||
<main class="main">
|
||
<div class="col-main">
|
||
|
||
<section class="hero">
|
||
<h2>Когда математика говорит «больше или меньше»</h2>
|
||
<p>Равенства — это точечный ответ: $x = 5$. Неравенства — это <b>множество</b> ответов: $x > 3$ означает «3, 4, 5, и так далее, бесконечно». Эта глава научит сравнивать числа, решать неравенства всех типов и понимать <b>метод интервалов</b> — универсальный инструмент.</p>
|
||
<div class="hero-row">
|
||
<button class="btn-primary" onclick="goTo('p13')">
|
||
<svg class="ic" viewBox="0 0 24 24"><polygon points="6 4 20 12 6 20 6 4" fill="currentColor" stroke="none"/></svg>
|
||
Начать § 13
|
||
</button>
|
||
<div class="hero-progress">
|
||
<span class="hp-label">Прогресс по главе</span>
|
||
<div class="hp-bar"><div id="hero-hp-fill" class="hp-fill"></div></div>
|
||
<span id="hero-hp-text" class="hp-text">0%</span>
|
||
</div>
|
||
<div id="hero-xp-badge" class="hero-xp-badge" title="Опыт"></div>
|
||
</div>
|
||
</section>
|
||
|
||
<section class="psel">
|
||
<div class="psel-title">Параграфы главы</div>
|
||
<div id="psel-grid" class="psel-grid"></div>
|
||
</section>
|
||
|
||
<section id="sec-p13" class="sec" data-watermark="<>">
|
||
<div class="sec-header"><span class="sec-num">§ 13</span><h2 class="sec-h">Числовые неравенства и их свойства</h2></div>
|
||
<div id="p13-body"></div>
|
||
</section>
|
||
|
||
<section id="sec-p14" class="sec" data-watermark="±">
|
||
<div class="sec-header"><span class="sec-num">§ 14</span><h2 class="sec-h">Сложение и умножение числовых неравенств. Оценка значений</h2></div>
|
||
<div id="p14-body"></div>
|
||
</section>
|
||
|
||
<section id="sec-p15" class="sec" data-watermark="[;]">
|
||
<div class="sec-header"><span class="sec-num">§ 15</span><h2 class="sec-h">Числовые промежутки. Линейные неравенства</h2></div>
|
||
<div id="p15-body"></div>
|
||
</section>
|
||
|
||
<section id="sec-p16" class="sec" data-watermark="∩">
|
||
<div class="sec-header"><span class="sec-num">§ 16</span><h2 class="sec-h">Системы и совокупности линейных неравенств</h2></div>
|
||
<div id="p16-body"></div>
|
||
</section>
|
||
|
||
<section id="sec-p17" class="sec" data-watermark="x²">
|
||
<div class="sec-header"><span class="sec-num">§ 17</span><h2 class="sec-h">Квадратные неравенства. Метод интервалов</h2></div>
|
||
<div id="p17-body"></div>
|
||
</section>
|
||
|
||
<section id="sec-p18" class="sec" data-watermark="1/x">
|
||
<div class="sec-header"><span class="sec-num">§ 18</span><h2 class="sec-h">Дробно-рациональные неравенства</h2></div>
|
||
<div id="p18-body"></div>
|
||
</section>
|
||
|
||
<section id="sec-final3" class="sec" data-watermark="★">
|
||
<div class="sec-header"><span class="sec-num" style="background:linear-gradient(135deg,#f59e0b,#8b5cf6)">Финал главы</span><h2 class="sec-h">Итоги. Практическая и увлекательная математика</h2></div>
|
||
<div id="final3-body"></div>
|
||
</section>
|
||
|
||
</div>
|
||
|
||
<aside class="col-side" id="col-side"><div id="sidebar-content"></div></aside>
|
||
<div class="col-side-backdrop" id="col-side-backdrop"></div>
|
||
</main>
|
||
|
||
<footer class="foot">Интерактивный учебник «Алгебра 8» · Глава 3 · LearnSpace · версия 1.0</footer>
|
||
|
||
<div id="ach-popup" class="ach-popup">
|
||
<svg class="ic" viewBox="0 0 24 24" style="width:22px;height:22px"><circle cx="12" cy="8" r="5"/><path d="M8 13l-2 8 6-4 6 4-2-8"/></svg>
|
||
<span id="ach-text">Достижение!</span>
|
||
</div>
|
||
|
||
<div id="gloss-tip" class="gloss-tip"></div>
|
||
|
||
<div id="search-modal" class="search-modal" role="dialog" aria-label="Поиск по главе">
|
||
<div class="search-box">
|
||
<input type="text" id="search-input" class="search-input" placeholder="Поиск: понятие, формула, параграф…" autocomplete="off">
|
||
<div id="search-results" class="search-results"></div>
|
||
<div class="search-foot">
|
||
<span><kbd>↑↓</kbd> навигация</span>
|
||
<span><kbd>Enter</kbd> открыть</span>
|
||
<span><kbd>Esc</kbd> закрыть</span>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<script>
|
||
'use strict';
|
||
|
||
/* STATE & PROGRESS */
|
||
const STATE = {
|
||
current: 'p13',
|
||
progress: { p13:0, p14:0, p15:0, p16:0, p17:0, p18:0, final3:0 },
|
||
achievements: new Map(),
|
||
xp: 0,
|
||
level: 1,
|
||
};
|
||
|
||
const XP_LEVELS = null;
|
||
function calcLevel(xp){ return Math.floor(Math.sqrt((xp || 0) / 100)) + 1; }
|
||
function _xpForLevel(lv){ return (lv - 1) * (lv - 1) * 100; }
|
||
|
||
const ACH_LABELS = {
|
||
start: 'Начало главы 3!',
|
||
p13_compare: 'Сравнил числа',
|
||
p13_props: 'Свойства неравенств',
|
||
p13_sign: 'Знак не меняется',
|
||
p13_flip: 'Знак меняется',
|
||
p13_chain: 'Цепочка свойств',
|
||
p14_estimate: 'Оценил выражение',
|
||
p14_add: 'Сложил неравенства',
|
||
p14_mul: 'Перемножил неравенства',
|
||
p14_drag: 'Сортировка операций',
|
||
p14_train: 'Тренажёр оценки',
|
||
p15_line: 'Промежутки на прямой',
|
||
p15_convert: 'Конвертация записи',
|
||
p15_solver: 'Линейное решено',
|
||
p15_train: 'Тренажёр линейных',
|
||
p15_drag: 'Промежуток к неравенству',
|
||
p16_intersect: 'Пересечение промежутков',
|
||
p16_solver: 'Система решена',
|
||
p16_union: 'Совокупность',
|
||
p16_train: 'Тренажёр систем',
|
||
p17_parab: 'Парабола и знак',
|
||
p17_intervals: 'Метод интервалов',
|
||
p17_solver: 'Квадратное неравенство',
|
||
p17_train: 'Тренажёр квадратных',
|
||
p17_drag: 'График к решению',
|
||
p18_intervals: 'Интервалы для дробей',
|
||
p18_solver: 'Дробно-рациональное',
|
||
p18_odz: 'ОДЗ учтена',
|
||
boss_b1: 'Босс §13 повержен',
|
||
boss_b2: 'Босс §14 повержен',
|
||
boss_b3: 'Босс §15 повержен',
|
||
boss_b4: 'Босс §16 повержен',
|
||
boss_b5: 'Босс §17 повержен',
|
||
boss_b6: 'Босс §18 повержен',
|
||
boss_b7: 'Чемпион неравенств',
|
||
all_bosses: 'Все 7 боссов побеждены!',
|
||
prac_streak: 'Серия из 5 верных',
|
||
};
|
||
|
||
function loadProgress(){
|
||
try{
|
||
const s = localStorage.getItem('algebra8_ch3_progress');
|
||
if(s) Object.assign(STATE.progress, JSON.parse(s));
|
||
const a = localStorage.getItem('algebra8_ch3_achievements');
|
||
if(a){
|
||
const p = JSON.parse(a);
|
||
if(Array.isArray(p)) p.forEach(id => STATE.achievements.set(id, ACH_LABELS[id] || id));
|
||
else if(p && typeof p === 'object'){
|
||
for(const [id, t] of Object.entries(p)) STATE.achievements.set(id, (t && t !== id) ? t : (ACH_LABELS[id] || id));
|
||
}
|
||
}
|
||
// Общий XP для всех глав
|
||
let xp = localStorage.getItem('algebra8_xp');
|
||
if(xp === null){
|
||
const c1 = +(localStorage.getItem('algebra8_ch1_xp') || 0);
|
||
const c2 = +(localStorage.getItem('algebra8_ch2_xp') || 0);
|
||
xp = c1 + c2;
|
||
try { localStorage.setItem('algebra8_xp', String(xp)); } catch(e){}
|
||
}
|
||
STATE.xp = +xp || 0; STATE.level = calcLevel(STATE.xp);
|
||
}catch(e){}
|
||
}
|
||
function saveProgress(){
|
||
try{
|
||
localStorage.setItem('algebra8_ch3_progress', JSON.stringify(STATE.progress));
|
||
localStorage.setItem('algebra8_ch3_achievements', JSON.stringify(Object.fromEntries(STATE.achievements)));
|
||
localStorage.setItem('algebra8_xp', String(STATE.xp));
|
||
}catch(e){}
|
||
}
|
||
function bumpProgress(key, delta){
|
||
STATE.progress[key] = Math.max(0, Math.min(100, (STATE.progress[key]||0) + delta));
|
||
saveProgress();
|
||
refreshProgressUI();
|
||
if(STATE.progress[key] >= 50) markParaRead(key);
|
||
}
|
||
|
||
/* Server sync прогресса */
|
||
const _TB_SLUG = 'algebra-8-ch3';
|
||
const _markedRead = new Set();
|
||
let _pendingProgressBody = null, _progressTimer = null;
|
||
function _flushProgress(){
|
||
const body = _pendingProgressBody;
|
||
_pendingProgressBody = null;
|
||
if(!body) return;
|
||
const tok = (window.LS && LS.getToken) ? LS.getToken() : '';
|
||
if(!tok) return;
|
||
fetch('/api/textbooks/' + _TB_SLUG + '/progress', {
|
||
method:'POST',
|
||
headers:{ 'Content-Type':'application/json', 'Authorization':'Bearer ' + tok },
|
||
body: JSON.stringify(body),
|
||
keepalive: true,
|
||
}).catch(()=>{});
|
||
}
|
||
function _queueProgress(patch){
|
||
_pendingProgressBody = Object.assign(_pendingProgressBody || {}, patch);
|
||
if(_progressTimer) clearTimeout(_progressTimer);
|
||
_progressTimer = setTimeout(_flushProgress, 600);
|
||
}
|
||
function markLastPara(id){ _queueProgress({ last_para: id }); }
|
||
function markParaRead(id){
|
||
if(_markedRead.has(id)) return;
|
||
_markedRead.add(id);
|
||
_queueProgress({ mark_read: id });
|
||
}
|
||
window.addEventListener('beforeunload', _flushProgress);
|
||
function loadServerReadState(){
|
||
const tok = (window.LS && LS.getToken) ? LS.getToken() : '';
|
||
if(!tok) return;
|
||
fetch('/api/textbooks/' + _TB_SLUG, { headers:{ 'Authorization':'Bearer ' + tok } })
|
||
.then(r => r.ok ? r.json() : null)
|
||
.then(d => {
|
||
if(!d || !d.progress) return;
|
||
(d.progress.read || []).forEach(k => {
|
||
_markedRead.add(k);
|
||
if((STATE.progress[k] || 0) < 50) STATE.progress[k] = 100;
|
||
});
|
||
saveProgress(); refreshProgressUI();
|
||
})
|
||
.catch(()=>{});
|
||
}
|
||
|
||
function addXp(n, src){
|
||
if(!n) return;
|
||
const prev = STATE.level;
|
||
STATE.xp = Math.max(0, (STATE.xp || 0) + n);
|
||
STATE.level = calcLevel(STATE.xp);
|
||
saveProgress();
|
||
refreshProgressUI();
|
||
if(window.LS && window.LS.xp) window.LS.xp.add(n, 'algebra8-ch3-' + (src || 'misc'));
|
||
if(STATE.level > prev){
|
||
const pop = document.getElementById('ach-popup');
|
||
if(pop){
|
||
document.getElementById('ach-text').textContent = 'Уровень ' + STATE.level + '!';
|
||
pop.classList.add('show');
|
||
setTimeout(()=>pop.classList.remove('show'), 2600);
|
||
}
|
||
if(window.confetti) try { confetti(); } catch(e){}
|
||
}
|
||
}
|
||
function refreshProgressUI(){
|
||
const total = Math.round(Object.values(STATE.progress).reduce((a,b)=>a+b,0) / 7);
|
||
const f = document.getElementById('hero-hp-fill');
|
||
if(f) f.style.width = total + '%';
|
||
const t = document.getElementById('hero-hp-text');
|
||
if(t) t.textContent = total + '% пройдено';
|
||
document.querySelectorAll('[data-prog-card]').forEach(el=>{
|
||
const k = el.dataset.progCard;
|
||
const fl = el.querySelector('.psel-prog-fill');
|
||
if(fl) fl.style.width = (STATE.progress[k]||0) + '%';
|
||
});
|
||
const xpBadge = document.getElementById('hero-xp-badge');
|
||
if(xpBadge){
|
||
xpBadge.innerHTML = '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" style="width:13px;height:13px"><polygon points="12 2 15.09 8.26 22 9.27 17 14.14 18.18 21.02 12 17.77 5.82 21.02 7 14.14 2 9.27 8.91 8.26 12 2"/></svg> Ур. ' + STATE.level + ' · ' + (STATE.xp || 0) + ' XP';
|
||
}
|
||
if(STATE.current && document.getElementById('sidebar-content')){
|
||
try { buildSidebar(STATE.current); } catch(e){}
|
||
}
|
||
}
|
||
function achievement(id, text){
|
||
if(STATE.achievements.has(id)) return;
|
||
STATE.achievements.set(id, text || ACH_LABELS[id] || id);
|
||
saveProgress();
|
||
const pop = document.getElementById('ach-popup');
|
||
if(pop){
|
||
document.getElementById('ach-text').textContent = text || ACH_LABELS[id] || id;
|
||
pop.classList.add('show');
|
||
setTimeout(()=>pop.classList.remove('show'), 3300);
|
||
}
|
||
addXp(20, 'ach-' + id);
|
||
}
|
||
|
||
/* PARAGRAPH LIST */
|
||
const PARAS = [
|
||
{ id:'p13', num:'§ 13', name:'Числовые неравенства', sub:'Свойства' },
|
||
{ id:'p14', num:'§ 14', name:'Сложение и умножение', sub:'Оценка значений' },
|
||
{ id:'p15', num:'§ 15', name:'Промежутки и линейные', sub:'Графика и решение' },
|
||
{ id:'p16', num:'§ 16', name:'Системы неравенств', sub:'Пересечение и объединение' },
|
||
{ id:'p17', num:'§ 17', name:'Квадратные неравенства', sub:'Метод интервалов' },
|
||
{ id:'p18', num:'§ 18', name:'Дробно-рациональные', sub:'Интервалы и ОДЗ' },
|
||
{ id:'final3', num:'★', name:'Финал главы', sub:'Итоги · Практика', final:true },
|
||
];
|
||
|
||
function buildParaSelector(){
|
||
const g = document.getElementById('psel-grid');
|
||
g.innerHTML = '';
|
||
PARAS.forEach(p=>{
|
||
const card = document.createElement('div');
|
||
card.className = 'psel-card' + (p.final ? ' final' : '');
|
||
card.dataset.id = p.id;
|
||
card.dataset.progCard = p.id;
|
||
card.innerHTML = `
|
||
<div class="psel-num">${p.num}</div>
|
||
<div class="psel-name">${p.name}</div>
|
||
<div class="psel-prog"><div class="psel-prog-fill"></div></div>`;
|
||
card.addEventListener('click', ()=>goTo(p.id));
|
||
g.appendChild(card);
|
||
});
|
||
}
|
||
|
||
const BUILT = new Set();
|
||
const BUILDERS = {
|
||
p13:()=>buildP13(), p14:()=>buildP14(),
|
||
p15:()=>buildP15stub(), p16:()=>buildP16stub(),
|
||
p17:()=>buildP17stub(), p18:()=>buildP18stub(),
|
||
final3:()=>buildFinal3stub(),
|
||
};
|
||
function ensureBuilt(id){
|
||
if(BUILT.has(id)) return;
|
||
const fn = BUILDERS[id];
|
||
if(fn){ fn(); BUILT.add(id); }
|
||
}
|
||
function goTo(id){
|
||
STATE.current = id;
|
||
ensureBuilt(id);
|
||
document.querySelectorAll('.sec').forEach(s=>s.classList.remove('active'));
|
||
const el = document.getElementById('sec-' + id);
|
||
if(el) el.classList.add('active');
|
||
document.querySelectorAll('.psel-card').forEach(c=>c.classList.toggle('active', c.dataset.id === id));
|
||
buildSidebar(id);
|
||
window.scrollTo({top:0, behavior:'smooth'});
|
||
if((STATE.progress[id]||0) < 10) bumpProgress(id, 10);
|
||
if(window.renderMathInElement) setTimeout(()=>renderMath(el), 0);
|
||
setTimeout(()=>{ try { wrapGlossary(el); } catch(e){} }, 60);
|
||
markLastPara(id);
|
||
}
|
||
|
||
/* SIDEBAR */
|
||
const SIDEBARS = {
|
||
p13: { title:'Шпаргалка § 13', rows:[
|
||
['$a > b$','значит $a - b > 0$'],
|
||
['Транзитивность','$a > b,\\ b > c \\Rightarrow a > c$'],
|
||
['$+c$ к обеим','знак не меняется'],
|
||
['$\\times k > 0$','знак не меняется'],
|
||
['$\\times k < 0$','знак МЕНЯЕТСЯ'],
|
||
]},
|
||
p14: { title:'Шпаргалка § 14', rows:[
|
||
['Сложение','$a < b,\\ c < d \\Rightarrow a + c < b + d$'],
|
||
['Умножение (+)','$0<a<b,\\ 0<c<d \\Rightarrow ac<bd$'],
|
||
['Оценка','если $a \\leq x \\leq b$ и $c \\leq y \\leq d$, то $a+c \\leq x+y \\leq b+d$'],
|
||
['Разность','$x - y$: $a - d \\leq x-y \\leq b - c$'],
|
||
]},
|
||
p15: { title:'Шпаргалка § 15', rows:[
|
||
['$[a;b]$','отрезок: $a \\leq x \\leq b$'],
|
||
['$(a;b)$','интервал: $a < x < b$'],
|
||
['$[a;b)$','полуинтервал'],
|
||
['$(-\\infty;a)$','луч $x < a$'],
|
||
['Линейное','$ax + b > 0 \\Rightarrow$ изоляция $x$'],
|
||
]},
|
||
p16: { title:'Шпаргалка § 16', rows:[
|
||
['Система','И · пересечение решений'],
|
||
['Совокупность','ИЛИ · объединение решений'],
|
||
['Открытая точка','знак $<$ или $>$ без равенства'],
|
||
]},
|
||
p17: { title:'Шпаргалка § 17', rows:[
|
||
['Метод интервалов','найти корни → отметить → знаки'],
|
||
['$a > 0$','парабола вверх'],
|
||
['$a < 0$','парабола вниз'],
|
||
['$D < 0$','знак не меняется'],
|
||
]},
|
||
p18: { title:'Шпаргалка § 18', rows:[
|
||
['$\\dfrac{f(x)}{g(x)} \\geq 0$','знаки числителя и знаменателя'],
|
||
['ОДЗ','знаменатель $\\neq 0$'],
|
||
['Полюс','выколотая точка на прямой'],
|
||
]},
|
||
final3: { title:'Финал главы', rows:[
|
||
['7 боссов','один на каждый § + общий'],
|
||
['Награда','«Чемпион неравенств»'],
|
||
['Практика','случайные задачи всей главы'],
|
||
]},
|
||
};
|
||
|
||
const TIPS = [
|
||
{ sec:'p13', html:'При умножении на $-1$ <b>знак неравенства меняется</b>. Это самое частое место ошибок.' },
|
||
{ sec:'p14', html:'Складывать можно <b>одного знака</b>: $a<b$ и $c<d$, но НЕ $a<b$ и $c>d$.' },
|
||
{ sec:'p15', html:'Квадратная скобка $[$ — точка <b>входит</b> в множество. Круглая $($ — <b>выколота</b>.' },
|
||
{ sec:'p16', html:'Система — это «И», пересечение. Совокупность — «ИЛИ», объединение. Не путайте.' },
|
||
{ sec:'p17', html:'Знак между корнями определяется коэффициентом $a$: если $a>0$, между корнями знак минус.' },
|
||
{ sec:'p18', html:'Точки, где знаменатель $=0$, всегда <b>выколотые</b>, даже при нестрогом неравенстве.' },
|
||
{ sec:'final3', html:'Метод интервалов — универсальный. Освойте его на §17, и §18 пойдёт легко.' },
|
||
];
|
||
|
||
function buildSidebar(id){
|
||
const box = document.getElementById('sidebar-content');
|
||
const sb = SIDEBARS[id] || SIDEBARS.p13;
|
||
let html = '';
|
||
|
||
// XP card
|
||
const xpForLv = _xpForLevel(STATE.level);
|
||
const xpNext = _xpForLevel(STATE.level + 1);
|
||
const xpInLv = STATE.xp - xpForLv;
|
||
const xpRange = xpNext - xpForLv;
|
||
const xpPct = xpRange > 0 ? Math.round(xpInLv / xpRange * 100) : 100;
|
||
html += `<div class="xp-card">
|
||
<div class="xp-card-title">
|
||
<span>XP-прогресс</span>
|
||
<span class="xp-level">Ур. ${STATE.level}</span>
|
||
</div>
|
||
<div class="xp-bar"><div class="xp-fill" style="width:${xpPct}%"></div></div>
|
||
<div class="xp-nums"><span>${STATE.xp} XP</span><span>${xpNext} XP</span></div>
|
||
</div>`;
|
||
|
||
// Шпаргалка
|
||
html += `<div class="sidecard"><h4>${sb.title}</h4>`;
|
||
sb.rows.forEach(([k,v])=>{
|
||
html += `<div class="sidecard-row"><b>${k}</b> ${v ? '— ' + v : ''}</div>`;
|
||
});
|
||
html += '</div>';
|
||
|
||
// Совет дня
|
||
const tip = TIPS.find(t => t.sec === id) || TIPS[0];
|
||
html += `<div class="sidecard" style="background:linear-gradient(135deg,var(--warn-bg,#fef3c7),var(--pri-soft));border-color:var(--warn,#f59e0b)">
|
||
<h4 style="color:#92400e;display:flex;align-items:center;gap:6px">
|
||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" style="width:14px;height:14px"><path d="M12 2v2M12 20v2M4.93 4.93l1.41 1.41M17.66 17.66l1.41 1.41M2 12h2M20 12h2M4.93 19.07l1.41-1.41M17.66 6.34l1.41-1.41"/><circle cx="12" cy="12" r="4"/></svg>
|
||
Подсказка
|
||
</h4>
|
||
<div class="sidecard-row" style="margin-bottom:0;font-size:.84rem;line-height:1.55">${tip.html}</div>
|
||
</div>`;
|
||
|
||
// Достижения
|
||
if(STATE.achievements.size > 0){
|
||
html += `<div class="sidecard"><h4>Достижения <span style="color:var(--warn);float:right">${STATE.achievements.size}</span></h4>`;
|
||
[...STATE.achievements.values()].slice(-4).forEach(text=>{
|
||
html += `<div class="sidecard-row" style="font-size:.78rem;color:var(--ok)">✓ ${text}</div>`;
|
||
});
|
||
html += '</div>';
|
||
}
|
||
|
||
box.innerHTML = html;
|
||
if(window.renderMathInElement) try{ renderMath(box); }catch(e){}
|
||
}
|
||
|
||
/* THEME */
|
||
function initTheme(){
|
||
const t = localStorage.getItem('algebra8_ch3_theme') || 'light';
|
||
if(t === 'dark') document.documentElement.classList.add('dark');
|
||
document.getElementById('theme-lab').textContent = t === 'dark' ? 'Светлая' : 'Тёмная';
|
||
document.getElementById('theme-btn').addEventListener('click', ()=>{
|
||
document.documentElement.classList.toggle('dark');
|
||
const dark = document.documentElement.classList.contains('dark');
|
||
localStorage.setItem('algebra8_ch3_theme', dark ? 'dark' : 'light');
|
||
document.getElementById('theme-lab').textContent = dark ? 'Светлая' : 'Тёмная';
|
||
});
|
||
}
|
||
|
||
/* HELPERS */
|
||
function $(sel, root){ return (root||document).querySelector(sel); }
|
||
function $$(sel, root){ return [...(root||document).querySelectorAll(sel)]; }
|
||
function el(tag, attrs, html){
|
||
const e = document.createElement(tag);
|
||
if(attrs) Object.entries(attrs).forEach(([k,v])=>{
|
||
if(k === 'class') e.className = v;
|
||
else if(k === 'style') e.style.cssText = v;
|
||
else if(k.startsWith('on') && typeof v === 'function') e.addEventListener(k.slice(2), v);
|
||
else e.setAttribute(k, v);
|
||
});
|
||
if(html != null) e.innerHTML = html;
|
||
return e;
|
||
}
|
||
function renderMath(root){
|
||
if(window.renderMathInElement){
|
||
try{ renderMathInElement(root, {delimiters:[{left:'$$',right:'$$',display:true},{left:'$',right:'$',display:false},{left:'\\[',right:'\\]',display:true},{left:'\\(',right:'\\)',display:false}],throwOnError:false}); }catch(e){}
|
||
}
|
||
}
|
||
function feedback(elm, ok, text){
|
||
elm.className = 'feedback ' + (ok ? 'ok' : 'fail');
|
||
elm.innerHTML = text || (ok ? '✓ Верно!' : '✗ Неверно');
|
||
}
|
||
function sleep(ms){ return new Promise(r => setTimeout(r, ms)); }
|
||
function fmt(n){ if(!isFinite(n)) return '?'; if(Number.isInteger(n)) return String(n); return Math.abs(n - Math.round(n)) < 1e-9 ? String(Math.round(n)) : (+n.toFixed(4)).toString(); }
|
||
|
||
const ICONS = {
|
||
repeat: '<svg class="ic" viewBox="0 0 24 24"><polyline points="9 11 12 14 22 4"/><path d="M21 12v7a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h11"/></svg>',
|
||
theory: '<svg class="ic" viewBox="0 0 24 24"><path d="M4 19.5A2.5 2.5 0 0 1 6.5 17H20"/><path d="M6.5 2H20v20H6.5A2.5 2.5 0 0 1 4 19.5v-15A2.5 2.5 0 0 1 6.5 2z"/></svg>',
|
||
algo: '<svg class="ic" viewBox="0 0 24 24"><polyline points="17 11 21 7 17 3"/><line x1="21" y1="7" x2="9" y2="7"/><polyline points="7 13 3 17 7 21"/><line x1="3" y1="17" x2="15" y2="17"/></svg>',
|
||
rule: '<svg class="ic" viewBox="0 0 24 24"><path d="M6 8a6 6 0 0 1 12 0c0 7 3 9 3 9H3s3-2 3-9"/><path d="M10.3 21a1.94 1.94 0 0 0 3.4 0"/></svg>',
|
||
example: '<svg class="ic" viewBox="0 0 24 24"><path d="M9 18h6"/><path d="M10 22h4"/><path d="M12 2a7 7 0 0 0-4 13c1 1 2 2 2 4h4c0-2 1-3 2-4a7 7 0 0 0-4-13z"/></svg>',
|
||
oral: '<svg class="ic" viewBox="0 0 24 24"><circle cx="12" cy="12" r="10"/><path d="M9.09 9a3 3 0 0 1 5.83 1c0 2-3 3-3 3"/><line x1="12" y1="17" x2="12.01" y2="17"/></svg>',
|
||
class: '<svg class="ic" viewBox="0 0 24 24"><rect x="3" y="3" width="18" height="14" rx="1"/><line x1="3" y1="21" x2="21" y2="21"/><polyline points="7 14 10 11 13 14 17 10"/></svg>',
|
||
home: '<svg class="ic" viewBox="0 0 24 24"><path d="M3 9l9-7 9 7v11a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z"/><polyline points="9 22 9 12 15 12 15 22"/></svg>',
|
||
};
|
||
function makeCard(kind, title, num, body){
|
||
const labels = {repeat:'Повторение',theory:'Теория',algo:'Алгоритм',rule:'Правило',example:'Пример',oral:'Устно',class:'Класс',home:'Домашка'};
|
||
return `<div class="card">
|
||
<div class="card-header">
|
||
<div class="card-icon ${kind}">${ICONS[kind]}</div>
|
||
<div class="card-title">${labels[kind] || ''} ${title && title !== labels[kind] ? '· ' + title : ''}</div>
|
||
${num ? `<div class="card-num">${num}</div>` : ''}
|
||
</div>
|
||
<div class="card-body">${body}</div>
|
||
</div>`;
|
||
}
|
||
function widget(title, badge, helpText, body){
|
||
return `<div class="wg">
|
||
<div class="wg-header"><span class="wg-badge">${badge||'INTERACT'}</span><div class="wg-title">${title}</div></div>
|
||
${helpText ? `<div class="wg-help">${helpText}</div>` : ''}
|
||
${body}
|
||
</div>`;
|
||
}
|
||
function secNav(prev, next){
|
||
const NAMES = {p13:'§13',p14:'§14',p15:'§15',p16:'§16',p17:'§17',p18:'§18',final3:'Финал'};
|
||
let h = '<div class="sec-nav">';
|
||
h += prev ? `<button class="btn" onclick="goTo('${prev}')"><svg class="ic" viewBox="0 0 24 24"><polyline points="15 18 9 12 15 6"/></svg> ${NAMES[prev]}</button>` : '<span></span>';
|
||
h += next ? `<button class="btn primary" onclick="goTo('${next}')">${NAMES[next]} <svg class="ic" viewBox="0 0 24 24"><polyline points="9 18 15 12 9 6"/></svg></button>` : '<span></span>';
|
||
h += '</div>';
|
||
return h;
|
||
}
|
||
|
||
/* CONFETTI */
|
||
let _confettiCanvas = null, _confettiParticles = [], _confettiRaf = null;
|
||
function confetti(){
|
||
if(!_confettiCanvas){
|
||
_confettiCanvas = document.createElement('canvas');
|
||
_confettiCanvas.style.cssText = 'position:fixed;top:0;left:0;width:100%;height:100%;pointer-events:none;z-index:9999';
|
||
document.body.appendChild(_confettiCanvas);
|
||
}
|
||
const c = _confettiCanvas;
|
||
c.width = window.innerWidth; c.height = window.innerHeight;
|
||
const ctx = c.getContext('2d');
|
||
const colors = ['#6366f1','#8b5cf6','#06b6d4','#10b981','#f59e0b'];
|
||
for(let i = 0; i < 80; i++){
|
||
_confettiParticles.push({
|
||
x: window.innerWidth/2 + (Math.random()-0.5)*200,
|
||
y: window.innerHeight/2,
|
||
vx: (Math.random()-0.5)*14,
|
||
vy: -10 - Math.random()*10,
|
||
g: 0.4, life: 100, color: colors[i%colors.length], r: 4+Math.random()*4, rot: 0, vRot: (Math.random()-0.5)*0.3,
|
||
});
|
||
}
|
||
if(_confettiRaf) cancelAnimationFrame(_confettiRaf);
|
||
function frame(){
|
||
ctx.clearRect(0,0,c.width,c.height);
|
||
_confettiParticles = _confettiParticles.filter(p=>{
|
||
p.x += p.vx; p.y += p.vy; p.vy += p.g; p.life--; p.rot += p.vRot;
|
||
ctx.save(); ctx.translate(p.x, p.y); ctx.rotate(p.rot);
|
||
ctx.fillStyle = p.color;
|
||
ctx.fillRect(-p.r, -p.r/2, p.r*2, p.r);
|
||
ctx.restore();
|
||
return p.life > 0 && p.y < c.height + 50;
|
||
});
|
||
if(_confettiParticles.length > 0) _confettiRaf = requestAnimationFrame(frame);
|
||
else { ctx.clearRect(0,0,c.width,c.height); _confettiRaf = null; }
|
||
}
|
||
frame();
|
||
}
|
||
|
||
/* DnD shared sorter */
|
||
function setupSorter(cfg){
|
||
const placed = {};
|
||
const pool = document.getElementById(cfg.poolId);
|
||
const scope = document.querySelector(cfg.scopeSelector);
|
||
if(!pool || !scope) return { placed, render: ()=>{}, reset: ()=>{} };
|
||
pool.classList.add('dnd-pool');
|
||
if(cfg.columnLayout) pool.classList.add('col');
|
||
let armed = null;
|
||
function buildChip(it, isPlaced){
|
||
const e = document.createElement('div');
|
||
e.className = 'dnd-chip' + (isPlaced ? ' placed' : '');
|
||
e.dataset.id = it.id;
|
||
e.innerHTML = '<span class="dnd-txt">' + it.html + '</span><span class="dnd-x" title="Убрать">×</span>';
|
||
attach(e, it.id);
|
||
return e;
|
||
}
|
||
function attach(elm, itId){
|
||
let ghost = null, dragging = false, sx = 0, sy = 0;
|
||
elm.addEventListener('pointerdown', ev => {
|
||
if(ev.button !== undefined && ev.button !== 0) return;
|
||
if(ev.target.classList && ev.target.classList.contains('dnd-x')){
|
||
ev.stopPropagation();
|
||
if(placed[itId]){ delete placed[itId]; render(); }
|
||
else if(armed === itId){ armed = null; render(); }
|
||
return;
|
||
}
|
||
sx = ev.clientX; sy = ev.clientY;
|
||
const r = elm.getBoundingClientRect();
|
||
const ox = ev.clientX - r.left, oy = ev.clientY - r.top;
|
||
try { elm.setPointerCapture(ev.pointerId); } catch(e){}
|
||
function onMove(e){
|
||
const dx = e.clientX - sx, dy = e.clientY - sy;
|
||
if(!dragging && Math.hypot(dx, dy) > 8){
|
||
dragging = true;
|
||
ghost = elm.cloneNode(true);
|
||
ghost.classList.remove('armed');
|
||
ghost.style.cssText = 'position:fixed;z-index:9999;pointer-events:none;opacity:.9;transform:rotate(-2.5deg);box-shadow:0 14px 36px rgba(0,0,0,.32);width:' + r.width + 'px;left:' + (e.clientX - ox) + 'px;top:' + (e.clientY - oy) + 'px';
|
||
document.body.appendChild(ghost);
|
||
elm.classList.add('dragging');
|
||
}
|
||
if(dragging && ghost){
|
||
ghost.style.left = (e.clientX - ox) + 'px';
|
||
ghost.style.top = (e.clientY - oy) + 'px';
|
||
const under = document.elementsFromPoint(e.clientX, e.clientY);
|
||
scope.querySelectorAll('.drop-box.over, .dnd-pool.over').forEach(n => n.classList.remove('over'));
|
||
const tgt = under.find(n => n.classList && (n.classList.contains('drop-box') || n.classList.contains('dnd-pool')));
|
||
if(tgt) tgt.classList.add('over');
|
||
}
|
||
}
|
||
function onUp(e){
|
||
elm.removeEventListener('pointermove', onMove);
|
||
elm.removeEventListener('pointerup', onUp);
|
||
elm.removeEventListener('pointercancel', onUp);
|
||
elm.classList.remove('dragging');
|
||
if(ghost){ ghost.remove(); ghost = null; }
|
||
scope.querySelectorAll('.drop-box.over, .dnd-pool.over').forEach(n => n.classList.remove('over'));
|
||
if(dragging){
|
||
const under = document.elementsFromPoint(e.clientX, e.clientY);
|
||
const box = under.find(n => n.classList && n.classList.contains('drop-box'));
|
||
const pl = under.find(n => n.classList && n.classList.contains('dnd-pool'));
|
||
if(box){
|
||
const di = box.querySelector('[data-cat]');
|
||
if(di){ placed[itId] = di.dataset.cat; armed = null; render(); return; }
|
||
} else if(pl){ delete placed[itId]; armed = null; render(); return; }
|
||
} else {
|
||
if(placed[itId]){ delete placed[itId]; armed = null; render(); }
|
||
else { armed = (armed === itId) ? null : itId; render(); }
|
||
}
|
||
dragging = false;
|
||
}
|
||
elm.addEventListener('pointermove', onMove);
|
||
elm.addEventListener('pointerup', onUp);
|
||
elm.addEventListener('pointercancel', onUp);
|
||
});
|
||
}
|
||
function attachBoxTaps(){
|
||
scope.querySelectorAll('.drop-box').forEach(box => {
|
||
box.addEventListener('click', ev => {
|
||
if(!armed) return;
|
||
if(ev.target.closest('.dnd-chip')) return;
|
||
const di = box.querySelector('[data-cat]');
|
||
if(di){ placed[armed] = di.dataset.cat; armed = null; render(); }
|
||
});
|
||
});
|
||
}
|
||
function render(){
|
||
pool.innerHTML = '';
|
||
cfg.items.forEach(it => { if(placed[it.id]) return; const c = buildChip(it, false); if(armed === it.id) c.classList.add('armed'); pool.appendChild(c); });
|
||
cfg.cats.forEach(cat => {
|
||
const box = scope.querySelector('.drop-items[data-cat="' + cat + '"]');
|
||
if(!box) return;
|
||
box.innerHTML = '';
|
||
cfg.items.forEach(it => { if(placed[it.id] !== cat) return; box.appendChild(buildChip(it, true)); });
|
||
});
|
||
if(window.renderMathInElement) try { renderMath(scope); } catch(_){}
|
||
}
|
||
attachBoxTaps();
|
||
render();
|
||
return { placed, render, reset(){ for(const k in placed) delete placed[k]; armed = null; render(); } };
|
||
}
|
||
const DND_HINT_HTML = '<div class="dnd-hint"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M9 11V6a3 3 0 0 1 6 0v5"/><path d="M9 11h6v8a4 4 0 0 1-8 0z"/></svg> Перетащите карточку или нажмите её, затем — на нужный ящик.</div>';
|
||
|
||
/* GLOSSARY */
|
||
const GLOSSARY = [
|
||
{ term:'числовое неравенство', def:'Запись вида $a > b$ или $a < b$, где $a, b$ — числа.', sec:'p13', aliases:['числовое неравенство','числовых неравенств','числового неравенства'] },
|
||
{ term:'свойства неравенств', def:'Транзитивность, прибавление, умножение на положительное/отрицательное (смена знака).', sec:'p13', aliases:['свойства неравенств','свойств неравенств'] },
|
||
{ term:'оценка значения', def:'Если $a \\leq x \\leq b$, найти границы для $x+y$, $xy$, $x/y$, и т.п.', sec:'p14', aliases:['оценка значения','оценка значений'] },
|
||
{ term:'числовой промежуток', def:'Отрезок, интервал, полуинтервал или луч на числовой прямой.', sec:'p15', aliases:['числовой промежуток','числового промежутка','числовые промежутки','числовых промежутков','промежуток','промежутка','промежутки','промежутков'] },
|
||
{ term:'линейное неравенство', def:'Неравенство вида $ax + b > 0$ (или с другим знаком), $a \\neq 0$.', sec:'p15', aliases:['линейное неравенство','линейных неравенств','линейные неравенства','линейного неравенства'] },
|
||
{ term:'система неравенств', def:'Несколько неравенств, выполненных одновременно (И). Решение — пересечение.', sec:'p16', aliases:['система неравенств','системы неравенств','систем неравенств','системе неравенств'] },
|
||
{ term:'совокупность неравенств', def:'Несколько неравенств, из которых выполняется хотя бы одно (ИЛИ). Решение — объединение.', sec:'p16', aliases:['совокупность неравенств','совокупности неравенств','совокупностей неравенств'] },
|
||
{ term:'квадратное неравенство', def:'Неравенство $ax^2 + bx + c \\gtrless 0$, $a \\neq 0$.', sec:'p17', aliases:['квадратное неравенство','квадратных неравенств','квадратные неравенства','квадратного неравенства'] },
|
||
{ term:'метод интервалов', def:'Найти корни → отметить на прямой → определить знаки на интервалах.', sec:'p17', aliases:['метод интервалов','методом интервалов','методу интервалов'] },
|
||
{ term:'дробно-рациональное неравенство', def:'Неравенство, содержащее дробь $\\dfrac{f(x)}{g(x)}$. Решается методом интервалов с учётом ОДЗ.', sec:'p18', aliases:['дробно-рациональное','дробно-рациональных','дробно-рациональные','дробно-рациональным'] },
|
||
{ term:'ОДЗ', def:'Область допустимых значений. Для дробей — знаменатель $\\neq 0$.', sec:'p18', aliases:['ОДЗ','область допустимых значений'] },
|
||
{ term:'выколотая точка', def:'Точка на прямой, не входящая в множество (например, при строгом $<$ или знаменатель $=0$).', sec:'p15', aliases:['выколотая точка','выколотой точки','выколотых точек','выколотые точки'] },
|
||
];
|
||
function wrapGlossary(root){
|
||
if(!root || root.__glossDone) return;
|
||
const allAliases = [];
|
||
GLOSSARY.forEach((g, i) => g.aliases.forEach(a => allAliases.push({ a, i })));
|
||
allAliases.sort((x, y) => y.a.length - x.a.length);
|
||
const re = new RegExp('(?<![\\w-])(' + allAliases.map(x => x.a.replace(/[.*+?^${}()|[\\]\\\\]/g,'\\$&')).join('|') + ')(?![\\w-])', 'iu');
|
||
const walker = document.createTreeWalker(root, NodeFilter.SHOW_TEXT, {
|
||
acceptNode(node){
|
||
const p = node.parentElement;
|
||
if(!p) return NodeFilter.FILTER_REJECT;
|
||
if(p.closest('.katex, .gloss-term, button, input, select, .wg-badge, .card-icon, .sec-num, .psel-num, .hdr, .ach-popup, script, style, .search-modal, .sidecard, .gloss-tip')) return NodeFilter.FILTER_REJECT;
|
||
if(!re.test(node.nodeValue)) return NodeFilter.FILTER_REJECT;
|
||
return NodeFilter.FILTER_ACCEPT;
|
||
}
|
||
});
|
||
const nodes = [];
|
||
let n; while((n = walker.nextNode())) nodes.push(n);
|
||
nodes.forEach(node => {
|
||
const text = node.nodeValue;
|
||
const out = document.createDocumentFragment();
|
||
let cursor = 0;
|
||
const global = new RegExp(re.source, 'giu');
|
||
let m;
|
||
while((m = global.exec(text)) !== null){
|
||
if(m.index > cursor) out.appendChild(document.createTextNode(text.slice(cursor, m.index)));
|
||
const found = m[0].toLowerCase();
|
||
const hit = allAliases.find(x => x.a.toLowerCase() === found);
|
||
const g = hit ? GLOSSARY[hit.i] : null;
|
||
const sp = document.createElement('span');
|
||
sp.className = 'gloss-term';
|
||
sp.dataset.gloss = g ? g.term : '';
|
||
sp.textContent = m[0];
|
||
out.appendChild(sp);
|
||
cursor = m.index + m[0].length;
|
||
}
|
||
if(cursor < text.length) out.appendChild(document.createTextNode(text.slice(cursor)));
|
||
node.parentNode.replaceChild(out, node);
|
||
});
|
||
root.__glossDone = true;
|
||
}
|
||
function initGlossaryTip(){
|
||
const tip = document.getElementById('gloss-tip');
|
||
if(!tip) return;
|
||
let lockOpen = null;
|
||
function show(elm){
|
||
const g = GLOSSARY.find(x => x.term === elm.dataset.gloss);
|
||
if(!g) return;
|
||
tip.innerHTML = '<b>' + g.term[0].toUpperCase() + g.term.slice(1) + '</b><div style="margin-top:4px">' + g.def + '</div><div style="margin-top:6px;font-size:.72rem;color:var(--muted);text-transform:uppercase;letter-spacing:.06em">См. § ' + g.sec.replace('p','') + '</div>';
|
||
if(window.renderMathInElement) renderMath(tip);
|
||
const r = elm.getBoundingClientRect();
|
||
tip.classList.add('show');
|
||
const tw = tip.offsetWidth, th = tip.offsetHeight;
|
||
let left = r.left, top = r.bottom + 8;
|
||
if(left + tw > window.innerWidth - 12) left = window.innerWidth - tw - 12;
|
||
if(top + th > window.innerHeight - 12) top = r.top - th - 8;
|
||
tip.style.left = Math.max(8, left) + 'px';
|
||
tip.style.top = Math.max(8, top) + 'px';
|
||
}
|
||
function hide(){ tip.classList.remove('show'); }
|
||
document.addEventListener('mouseover', e => {
|
||
const elm = e.target.closest && e.target.closest('.gloss-term');
|
||
if(elm && !lockOpen) show(elm);
|
||
});
|
||
document.addEventListener('mouseout', e => {
|
||
const elm = e.target.closest && e.target.closest('.gloss-term');
|
||
if(elm && !lockOpen) hide();
|
||
});
|
||
document.addEventListener('click', e => {
|
||
const elm = e.target.closest && e.target.closest('.gloss-term');
|
||
if(elm){
|
||
if(lockOpen === elm){ lockOpen = null; hide(); }
|
||
else { lockOpen = elm; show(elm); }
|
||
} else if(lockOpen && !e.target.closest('.gloss-tip')){
|
||
lockOpen = null; hide();
|
||
}
|
||
});
|
||
}
|
||
|
||
/* SEARCH */
|
||
const SEARCH_INDEX = (function(){
|
||
const arr = [];
|
||
PARAS.forEach(p => arr.push({ kind:'Параграф', title:p.num + ' ' + p.name, desc:p.sub || '', sec:p.id }));
|
||
GLOSSARY.forEach(g => arr.push({ kind:'Понятие', title:g.term, desc:g.def.replace(/\$/g,''), sec:g.sec, gloss:g.term }));
|
||
[
|
||
['Формула','a > b ⟺ a − b > 0','§13 — определение','p13'],
|
||
['Формула','×k<0 ⟹ знак МЕНЯЕТСЯ','§13 — главное правило','p13'],
|
||
['Формула','a<b, c<d ⟹ a+c<b+d','§14 — сложение неравенств','p14'],
|
||
['Формула','[a;b], (a;b), [a;b)','§15 — обозначения промежутков','p15'],
|
||
['Формула','Метод интервалов: корни → знаки','§17 — алгоритм','p17'],
|
||
].forEach(([k,t,d,s]) => arr.push({ kind:k, title:t, desc:d, sec:s }));
|
||
arr.push({ kind:'Финал', title:'Боссы главы', desc:'7 проверочных боссов', sec:'final3' });
|
||
return arr;
|
||
})();
|
||
function initSearch(){
|
||
const modal = document.getElementById('search-modal');
|
||
const inp = document.getElementById('search-input');
|
||
const out = document.getElementById('search-results');
|
||
const btn = document.getElementById('search-btn');
|
||
if(!modal || !inp || !out) return;
|
||
let cur = 0, rows = [];
|
||
function score(q, it){
|
||
const t = (it.title + ' ' + it.desc).toLowerCase();
|
||
if(t.includes(q)) return 100 + (it.title.toLowerCase().startsWith(q) ? 50 : 0);
|
||
let s = 0; q.split(/\s+/).forEach(w => { if(w && t.includes(w)) s += 10; });
|
||
return s;
|
||
}
|
||
function rank(q){
|
||
q = q.trim().toLowerCase();
|
||
if(!q) return SEARCH_INDEX.slice(0, 12);
|
||
return SEARCH_INDEX.map(it => ({ it, s: score(q, it) })).filter(x => x.s > 0).sort((a,b) => b.s - a.s).slice(0, 20).map(x => x.it);
|
||
}
|
||
function render(){
|
||
cur = 0;
|
||
if(!rows.length){ out.innerHTML = '<div class="search-empty">Ничего не найдено</div>'; return; }
|
||
out.innerHTML = rows.map((r, i) => `<button class="search-row${i === 0 ? ' active' : ''}" data-i="${i}"><div class="sr-kind">${r.kind}</div><div class="sr-title">${r.title}</div>${r.desc ? `<div class="sr-desc">${r.desc.length > 90 ? r.desc.slice(0, 90) + '…' : r.desc}</div>` : ''}</button>`).join('');
|
||
out.querySelectorAll('.search-row').forEach(b => b.addEventListener('click', ()=>{ cur = +b.dataset.i; pick(); }));
|
||
}
|
||
function pick(){
|
||
const r = rows[cur]; if(!r) return;
|
||
close();
|
||
goTo(r.sec);
|
||
if(r.gloss){
|
||
setTimeout(()=>{
|
||
const sec = document.getElementById('sec-' + r.sec);
|
||
const elm = sec && sec.querySelector('[data-gloss="' + r.gloss + '"]');
|
||
if(elm){ elm.scrollIntoView({ behavior:'smooth', block:'center' }); elm.style.transition = 'background .3s'; elm.style.background = 'var(--warn,#f59e0b)'; setTimeout(()=>{ elm.style.background = ''; }, 1400); }
|
||
}, 400);
|
||
}
|
||
}
|
||
function move(d){
|
||
const items = out.querySelectorAll('.search-row'); if(!items.length) return;
|
||
items[cur] && items[cur].classList.remove('active');
|
||
cur = (cur + d + items.length) % items.length;
|
||
items[cur].classList.add('active');
|
||
items[cur].scrollIntoView({ block:'nearest' });
|
||
}
|
||
function open(){ modal.classList.add('show'); inp.value = ''; rows = rank(''); render(); setTimeout(()=>inp.focus(), 50); }
|
||
function close(){ modal.classList.remove('show'); }
|
||
btn && btn.addEventListener('click', open);
|
||
modal.addEventListener('click', e => { if(e.target === modal) close(); });
|
||
inp.addEventListener('input', ()=>{ rows = rank(inp.value); render(); });
|
||
inp.addEventListener('keydown', e => {
|
||
if(e.key === 'ArrowDown'){ e.preventDefault(); move(1); }
|
||
else if(e.key === 'ArrowUp'){ e.preventDefault(); move(-1); }
|
||
else if(e.key === 'Enter'){ e.preventDefault(); pick(); }
|
||
else if(e.key === 'Escape'){ e.preventDefault(); close(); }
|
||
});
|
||
document.addEventListener('keydown', e => {
|
||
if((e.ctrlKey || e.metaKey) && (e.key === 'k' || e.key === 'K')){
|
||
e.preventDefault();
|
||
if(modal.classList.contains('show')) close(); else open();
|
||
}
|
||
});
|
||
}
|
||
|
||
/* SIDEBAR TOGGLE */
|
||
function initSidebarToggle(){
|
||
const side = document.getElementById('col-side');
|
||
const back = document.getElementById('col-side-backdrop');
|
||
const btn = document.getElementById('sidebar-btn');
|
||
if(!side || !btn) return;
|
||
function open(){ side.classList.add('open'); back.classList.add('show'); }
|
||
function close(){ side.classList.remove('open'); back.classList.remove('show'); }
|
||
btn.addEventListener('click', ()=>{
|
||
if(side.classList.contains('open')) close();
|
||
else open();
|
||
});
|
||
back.addEventListener('click', close);
|
||
document.addEventListener('keydown', e=>{ if(e.key === 'Escape') close(); });
|
||
}
|
||
|
||
/* INIT */
|
||
function init(){
|
||
loadProgress();
|
||
initTheme();
|
||
initSidebarToggle();
|
||
initGlossaryTip();
|
||
initSearch();
|
||
buildParaSelector();
|
||
refreshProgressUI();
|
||
loadServerReadState();
|
||
goTo('p13');
|
||
setTimeout(()=>achievement('start','Начало главы 3!'), 600);
|
||
if(window.LS && window.LS.xp){
|
||
window.LS.xp.load().then(function(s){
|
||
if(s && s.xp > STATE.xp){
|
||
STATE.xp = s.xp;
|
||
STATE.level = calcLevel(STATE.xp);
|
||
saveProgress();
|
||
refreshProgressUI();
|
||
if(STATE.current) buildSidebar(STATE.current);
|
||
}
|
||
});
|
||
}
|
||
}
|
||
document.addEventListener('DOMContentLoaded', init);
|
||
|
||
/* STUBS */
|
||
function buildP15stub(){ buildP15(); }
|
||
function buildP16stub(){ buildP16(); }
|
||
|
||
/* Helper: SVG-рисовалка интервала / луча / системы */
|
||
function drawNumLine(opts){
|
||
const W = 520, H = 80, M = 30;
|
||
const min = opts.min != null ? opts.min : -10;
|
||
const max = opts.max != null ? opts.max : 10;
|
||
const x = v => M + (v - min) * (W - 2*M) / (max - min);
|
||
let s = '<svg viewBox="0 0 ' + W + ' ' + H + '">';
|
||
// ось
|
||
s += '<line x1="' + M + '" y1="40" x2="' + (W - M) + '" y2="40" stroke="#94a3b8" stroke-width="1.5"/>';
|
||
// стрелка вправо
|
||
s += '<polygon points="' + (W - M) + ',35 ' + (W - M + 10) + ',40 ' + (W - M) + ',45" fill="#94a3b8"/>';
|
||
// деления и подписи
|
||
for(let v = Math.ceil(min); v <= Math.floor(max); v++){
|
||
const px = x(v);
|
||
s += '<line x1="' + px + '" y1="36" x2="' + px + '" y2="44" stroke="#94a3b8"/>';
|
||
if(v % Math.max(1, Math.floor((max - min) / 10)) === 0 || max - min <= 12) s += '<text x="' + px + '" y="62" text-anchor="middle" font-size="11" fill="#64748b">' + v + '</text>';
|
||
}
|
||
// интервалы
|
||
(opts.intervals || []).forEach(it => {
|
||
const color = it.color || 'var(--sec-acc)';
|
||
const a = it.a != null ? Math.max(min, it.a) : min;
|
||
const b = it.b != null ? Math.min(max, it.b) : max;
|
||
const xa = x(a), xb = x(b);
|
||
// полоска
|
||
s += '<line x1="' + xa + '" y1="40" x2="' + xb + '" y2="40" stroke="' + color + '" stroke-width="6" opacity="0.55"/>';
|
||
// концы
|
||
if(it.a != null){
|
||
if(it.openA) s += '<circle cx="' + xa + '" cy="40" r="6" fill="white" stroke="' + color + '" stroke-width="2.5"/>';
|
||
else s += '<circle cx="' + xa + '" cy="40" r="6" fill="' + color + '"/>';
|
||
} else {
|
||
s += '<polygon points="' + (M-2) + ',32 ' + (M-12) + ',40 ' + (M-2) + ',48" fill="' + color + '"/>';
|
||
}
|
||
if(it.b != null){
|
||
if(it.openB) s += '<circle cx="' + xb + '" cy="40" r="6" fill="white" stroke="' + color + '" stroke-width="2.5"/>';
|
||
else s += '<circle cx="' + xb + '" cy="40" r="6" fill="' + color + '"/>';
|
||
} else {
|
||
s += '<polygon points="' + (W-M+2) + ',32 ' + (W-M+12) + ',40 ' + (W-M+2) + ',48" fill="' + color + '"/>';
|
||
}
|
||
});
|
||
s += '</svg>';
|
||
return s;
|
||
}
|
||
|
||
/* ============================================================
|
||
§ 15 — ЧИСЛОВЫЕ ПРОМЕЖУТКИ. ЛИНЕЙНЫЕ НЕРАВЕНСТВА
|
||
============================================================ */
|
||
function buildP15(){
|
||
const box = document.getElementById('p15-body');
|
||
let html = '';
|
||
|
||
html += makeCard('repeat','Повторение',null,`
|
||
<ul style="margin-left:18px;line-height:1.7">
|
||
<li>Свойства неравенств (§13): при делении на отрицательное знак меняется.</li>
|
||
<li>Линейное уравнение $ax + b = 0$ имеет корень $x = -b/a$.</li>
|
||
<li>Числовая прямая: точки слева меньше, справа — больше.</li>
|
||
</ul>`);
|
||
|
||
html += makeCard('theory','5 видов промежутков','15.1',`
|
||
<table class="tbl" style="margin:8px 0">
|
||
<thead><tr><th>Название</th><th>Неравенство</th><th>Запись</th><th>Изображение</th></tr></thead>
|
||
<tbody>
|
||
<tr><td>Отрезок</td><td>$a \\leq x \\leq b$</td><td>$[a;\\,b]$</td><td>● ━━━ ●</td></tr>
|
||
<tr><td>Интервал</td><td>$a < x < b$</td><td>$(a;\\,b)$</td><td>○ ━━━ ○</td></tr>
|
||
<tr><td>Полуинтервал</td><td>$a \\leq x < b$</td><td>$[a;\\,b)$</td><td>● ━━━ ○</td></tr>
|
||
<tr><td>Луч</td><td>$x \\geq a$</td><td>$[a;\\,+\\infty)$</td><td>● ━━━ →</td></tr>
|
||
<tr><td>Открытый луч</td><td>$x < a$</td><td>$(-\\infty;\\,a)$</td><td>← ━━━ ○</td></tr>
|
||
</tbody>
|
||
</table>
|
||
<p style="font-size:.88rem;color:var(--muted)">Запомните: <b>квадратная</b> скобка $[\\,]$ — точка <b>входит</b>. <b>Круглая</b> $(\\,)$ — <b>выколота</b>. У бесконечности всегда круглая.</p>`);
|
||
|
||
html += makeCard('algo','Решение линейного неравенства','15.2',`
|
||
<ol style="margin-left:18px;line-height:1.9">
|
||
<li>Раскрыть скобки, если есть.</li>
|
||
<li>Перенести члены с $x$ в одну часть, числа — в другую (со сменой знака при переносе).</li>
|
||
<li>Привести подобные.</li>
|
||
<li>Разделить на коэффициент при $x$. Если он <b>отрицательный — знак неравенства меняется</b>.</li>
|
||
<li>Записать ответ как промежуток.</li>
|
||
</ol>`);
|
||
|
||
html += makeCard('example','Пример',null,`
|
||
<p><b>Решим:</b> $3x - 7 > 2x + 1$.</p>
|
||
<p>1) $3x - 2x > 1 + 7$. 2) $x > 8$. 3) Ответ: $x \\in (8;\\,+\\infty)$.</p>
|
||
<p style="margin-top:6px"><b>С отрицательным коэффициентом:</b> $-2x \\geq 6$ → делим на $-2$: $x \\leq -3$. Ответ: $x \\in (-\\infty;\\,-3]$.</p>`);
|
||
|
||
/* INT 1 — Конструктор промежутка */
|
||
html += widget('Конструктор промежутка','INTERACT 1','Выбери концы $a$, $b$ и тип. Промежуток нарисуется на числовой прямой.',`
|
||
<div class="sliders">
|
||
<label>$a$ = <b id="p15c-a-val">-2</b><input type="range" min="-9" max="9" step="1" value="-2" id="p15c-a"></label>
|
||
<label>$b$ = <b id="p15c-b-val">5</b><input type="range" min="-9" max="9" step="1" value="5" id="p15c-b"></label>
|
||
</div>
|
||
<div style="display:flex;gap:6px;justify-content:center;flex-wrap:wrap;margin:8px 0">
|
||
<button class="btn small p15c-type active" data-t="cc">$[a;b]$</button>
|
||
<button class="btn small p15c-type" data-t="oo">$(a;b)$</button>
|
||
<button class="btn small p15c-type" data-t="co">$[a;b)$</button>
|
||
<button class="btn small p15c-type" data-t="oc">$(a;b]$</button>
|
||
<button class="btn small p15c-type" data-t="rcr">$[a;+\\infty)$</button>
|
||
<button class="btn small p15c-type" data-t="rol">$(-\\infty;b)$</button>
|
||
</div>
|
||
<div class="numline" id="p15c-line"></div>
|
||
<div id="p15c-out" style="padding:12px;background:var(--sec-acc-soft);border-radius:10px;text-align:center;font-size:1.1rem"></div>`);
|
||
|
||
/* INT 2 — Конвертация: запись → неравенство */
|
||
html += widget('Конвертация записи','INTERACT 2','Найди соответствие между записью промежутка и неравенством.',`
|
||
<div class="score-display"><span>Раунд <b id="p15v-i">1</b> / 8</span><span>Очки: <b id="p15v-score">0</b></span></div>
|
||
<div id="p15v-task" style="padding:14px;background:var(--sec-acc-soft);border-radius:10px;font-size:1.1rem;text-align:center;margin-bottom:10px"></div>
|
||
<div id="p15v-opts" style="display:flex;flex-direction:column;gap:6px"></div>
|
||
<div class="feedback" id="p15v-fb" style="display:none;margin-top:10px"></div>
|
||
<button class="btn primary" id="p15v-start" style="margin-top:10px">Начать</button>`);
|
||
|
||
/* INT 3 — Пошаговый решатель линейного */
|
||
html += widget('Пошаговый решатель','INTERACT 3','Введите $a, b, c, d$ для $ax + b \\geq cx + d$ и нажимайте «Дальше».',`
|
||
<div style="display:flex;gap:8px;flex-wrap:wrap;align-items:center;justify-content:center;margin-bottom:10px;font-size:1.02rem">
|
||
<input type="number" id="p15s-a" value="3" class="tinp" style="width:55px">$x +$
|
||
<input type="number" id="p15s-b" value="-7" class="tinp" style="width:55px">$\\geq$
|
||
<input type="number" id="p15s-c" value="2" class="tinp" style="width:55px">$x +$
|
||
<input type="number" id="p15s-d" value="1" class="tinp" style="width:55px">
|
||
<button class="btn primary" id="p15s-go">Старт</button>
|
||
<button class="btn" id="p15s-next" style="display:none">Дальше</button>
|
||
<button class="btn" id="p15s-reset" style="display:none">Сначала</button>
|
||
</div>
|
||
<div id="p15s-stage" style="padding:14px;background:var(--card-soft);border-radius:10px;min-height:80px"></div>`);
|
||
|
||
/* INT 4 — Тренажёр линейных */
|
||
html += widget('Тренажёр линейных','INTERACT 4','Решите неравенство и введите ответ в формате $(a; +\\infty)$ или $[-\\infty; b)$. Используйте `inf` для бесконечности.',`
|
||
<div class="score-display"><span>Задача <b id="p15t-i">1</b> / 8</span><span>Очки: <b id="p15t-score">0</b></span></div>
|
||
<div id="p15t-task" style="font-size:1.3rem;text-align:center;padding:16px;background:var(--sec-acc-soft);border-radius:10px;margin-bottom:10px"></div>
|
||
<div style="display:flex;gap:6px;justify-content:center;flex-wrap:wrap">
|
||
<button class="btn" data-cmp="gt" id="p15t-gt">$x > k$</button>
|
||
<button class="btn" data-cmp="ge" id="p15t-ge">$x \\geq k$</button>
|
||
<button class="btn" data-cmp="lt" id="p15t-lt">$x < k$</button>
|
||
<button class="btn" data-cmp="le" id="p15t-le">$x \\leq k$</button>
|
||
</div>
|
||
<div style="display:flex;gap:6px;justify-content:center;flex-wrap:wrap;margin-top:6px">
|
||
<input type="number" id="p15t-k" placeholder="k =" class="tinp" style="width:90px">
|
||
<button class="btn primary" id="p15t-go">Ответ</button>
|
||
</div>
|
||
<div class="feedback" id="p15t-fb" style="display:none;margin-top:10px"></div>
|
||
<button class="btn primary" id="p15t-start" style="margin-top:10px">Начать</button>`);
|
||
|
||
/* INT 5 — Drag: какой промежуток */
|
||
html += widget('Сопоставь неравенство и промежуток','INTERACT 5','Отнеси каждое неравенство к правильной записи промежутка.',`
|
||
${DND_HINT_HTML}
|
||
<div id="p15d-pool"></div>
|
||
<div class="drop-row" style="display:grid;grid-template-columns:repeat(auto-fit,minmax(140px,1fr));gap:10px">
|
||
<div class="drop-box"><h5>$[a;b]$</h5><div class="drop-items" data-cat="cc"></div></div>
|
||
<div class="drop-box"><h5>$(a;b)$</h5><div class="drop-items" data-cat="oo"></div></div>
|
||
<div class="drop-box"><h5>$(a;+\\infty)$</h5><div class="drop-items" data-cat="rop"></div></div>
|
||
<div class="drop-box"><h5>$(-\\infty;b]$</h5><div class="drop-items" data-cat="lcr"></div></div>
|
||
</div>
|
||
<div class="actions"><button class="btn primary" id="p15d-check">Проверить</button><button class="btn" id="p15d-reset">Сначала</button></div>
|
||
<div class="feedback" id="p15d-fb" style="display:none"></div>`);
|
||
|
||
html += makeCard('oral','Устно',null,`
|
||
<ol style="margin-left:18px;line-height:1.8">
|
||
<li>Запишите промежуток для $x > 5$.</li>
|
||
<li>Что означает скобка $[$?</li>
|
||
<li>Решите устно: $2x > 6$.</li>
|
||
</ol>`);
|
||
|
||
html += makeCard('class','Класс — решите',null,`
|
||
<ol style="margin-left:18px;line-height:1.8">
|
||
<li>$5x - 3 < 12$</li>
|
||
<li>$-3x + 1 \\geq 7$</li>
|
||
<li>$2(x - 1) > 3(x + 2)$</li>
|
||
<li>Запишите $[-2; 5)$ через неравенство.</li>
|
||
</ol>`);
|
||
|
||
html += makeCard('home','Домашка',null,`
|
||
<ol style="margin-left:18px;line-height:1.8">
|
||
<li>$7x + 2 \\leq 4x - 7$</li>
|
||
<li>$-\\dfrac{x}{3} > 2$</li>
|
||
<li>$3 - 2x \\leq x + 9$</li>
|
||
<li>Изобразите $(-\\infty; -1) \\cup [3; +\\infty)$.</li>
|
||
</ol>`);
|
||
|
||
html += secNav('p14', 'p16');
|
||
box.innerHTML = html;
|
||
if(window.renderMathInElement) setTimeout(()=>renderMath(box), 0);
|
||
|
||
/* INIT 1 — Конструктор */
|
||
(function(){
|
||
const aE = document.getElementById('p15c-a'), bE = document.getElementById('p15c-b');
|
||
const lineE = document.getElementById('p15c-line'), out = document.getElementById('p15c-out');
|
||
let type = 'cc', done = false;
|
||
document.querySelectorAll('.p15c-type').forEach(btn => btn.addEventListener('click', ()=>{
|
||
document.querySelectorAll('.p15c-type').forEach(x => x.classList.remove('active'));
|
||
btn.classList.add('active'); type = btn.dataset.t; refresh();
|
||
}));
|
||
function refresh(){
|
||
const a = +aE.value, b = +bE.value;
|
||
document.getElementById('p15c-a-val').textContent = a;
|
||
document.getElementById('p15c-b-val').textContent = b;
|
||
let interval = null, label = '', ineq = '';
|
||
if(type === 'cc'){ if(b < a){ out.innerHTML = 'Нужно $a \\leq b$'; renderMath(out); return; } interval = { a, b, openA:false, openB:false }; label = '$[' + a + ';\\,' + b + ']$'; ineq = a + ' \\leq x \\leq ' + b; }
|
||
else if(type === 'oo'){ if(b <= a){ out.innerHTML = 'Нужно $a < b$'; renderMath(out); return; } interval = { a, b, openA:true, openB:true }; label = '$(' + a + ';\\,' + b + ')$'; ineq = a + ' < x < ' + b; }
|
||
else if(type === 'co'){ if(b <= a){ out.innerHTML = 'Нужно $a < b$'; renderMath(out); return; } interval = { a, b, openA:false, openB:true }; label = '$[' + a + ';\\,' + b + ')$'; ineq = a + ' \\leq x < ' + b; }
|
||
else if(type === 'oc'){ if(b <= a){ out.innerHTML = 'Нужно $a < b$'; renderMath(out); return; } interval = { a, b, openA:true, openB:false }; label = '$(' + a + ';\\,' + b + ']$'; ineq = a + ' < x \\leq ' + b; }
|
||
else if(type === 'rcr'){ interval = { a, b:null, openA:false, openB:false }; label = '$[' + a + ';\\,+\\infty)$'; ineq = 'x \\geq ' + a; }
|
||
else if(type === 'rol'){ interval = { a:null, b, openA:false, openB:true }; label = '$(-\\infty;\\,' + b + ')$'; ineq = 'x < ' + b; }
|
||
lineE.innerHTML = drawNumLine({ intervals:[interval] });
|
||
out.innerHTML = '<div><b>Запись:</b> ' + label + '</div><div><b>Неравенство:</b> $' + ineq + '$</div>';
|
||
renderMath(out);
|
||
if(!done){ done = true; setTimeout(()=>{ achievement('p15_line'); bumpProgress('p15', 14); }, 300); }
|
||
}
|
||
[aE,bE].forEach(e => e.addEventListener('input', refresh));
|
||
refresh();
|
||
})();
|
||
|
||
/* INIT 2 — Конвертация */
|
||
(function(){
|
||
const tasks = [
|
||
{ q:'Запись: $[3;\\,7]$. Какое неравенство?', opts:['$3 \\leq x \\leq 7$','$3 < x < 7$','$x \\geq 3$','$3 \\leq x < 7$'], ok:0 },
|
||
{ q:'Неравенство: $-2 < x \\leq 5$. Какая запись?', opts:['$(-2;\\,5]$','$[-2;\\,5)$','$[-2;\\,5]$','$(-2;\\,5)$'], ok:0 },
|
||
{ q:'Запись: $(-\\infty;\\,4)$. Что значит?', opts:['$x < 4$','$x \\leq 4$','$x > 4$','$-\\infty < x < 4$ строго'], ok:0 },
|
||
{ q:'Неравенство: $x \\geq 6$. Какая запись?', opts:['$[6;\\,+\\infty)$','$(6;\\,+\\infty)$','$(-\\infty;\\,6]$','$[-6;\\,+\\infty)$'], ok:0 },
|
||
{ q:'Запись: $(0;\\,1)$. Содержит ли точку $0$?', opts:['Нет, выколота','Да, входит','Только если $0 > 0$','Зависит от контекста'], ok:0 },
|
||
{ q:'Запись с двумя круглыми скобками означает:', opts:['Оба конца выколоты','Оба конца входят','Один выколот, один входит','Только справа выколот'], ok:0 },
|
||
{ q:'Какой записью обозначить «все числа меньше или равны $-3$»?', opts:['$(-\\infty;\\,-3]$','$(-\\infty;\\,-3)$','$[-3;\\,+\\infty)$','$(-3;\\,+\\infty)$'], ok:0 },
|
||
{ q:'Промежуток $[2;\\,2]$ содержит:', opts:['Только число $2$','Все числа от $0$ до $2$','Пуст','Все числа $\\geq 2$'], ok:0 },
|
||
];
|
||
let cur = null, i = 1, score = 0, shuffled = [];
|
||
function show(){
|
||
cur = shuffled[i-1];
|
||
document.getElementById('p15v-i').textContent = i;
|
||
document.getElementById('p15v-task').innerHTML = cur.q;
|
||
renderMath(document.getElementById('p15v-task'));
|
||
const opts = document.getElementById('p15v-opts'); opts.innerHTML = '';
|
||
cur.opts.forEach((o, k)=>{
|
||
const b = document.createElement('button');
|
||
b.className = 'btn'; b.innerHTML = o; b.style.cssText = 'text-align:left';
|
||
b.addEventListener('click', ()=>{
|
||
const fb = document.getElementById('p15v-fb'); fb.style.display = 'block';
|
||
if(k === cur.ok){ score++; b.classList.add('ok'); feedback(fb, true, '✓'); }
|
||
else { b.classList.add('fail'); feedback(fb, false, 'Не то.'); }
|
||
document.getElementById('p15v-score').textContent = score;
|
||
if(i >= shuffled.length){ setTimeout(()=>{ feedback(fb, score >= 6, 'Итог: ' + score + '/' + shuffled.length); if(score >= 6){ achievement('p15_convert'); bumpProgress('p15', 14); confetti(); } }, 600); }
|
||
else { i++; setTimeout(show, 800); }
|
||
});
|
||
opts.appendChild(b);
|
||
});
|
||
renderMath(opts);
|
||
document.getElementById('p15v-fb').style.display = 'none';
|
||
}
|
||
document.getElementById('p15v-start').addEventListener('click', ()=>{ i=1; score=0; document.getElementById('p15v-score').textContent = 0; shuffled = [...tasks].sort(()=>Math.random()-0.5); show(); });
|
||
})();
|
||
|
||
/* INIT 3 — Шаговый решатель */
|
||
(function(){
|
||
const stage = document.getElementById('p15s-stage');
|
||
const goBtn = document.getElementById('p15s-go'), nextBtn = document.getElementById('p15s-next'), resetBtn = document.getElementById('p15s-reset');
|
||
let steps = [], idx = 0, awarded = false;
|
||
function build(a, b, c, d){
|
||
// ax + b >= cx + d -> (a-c)x >= d-b
|
||
const k = a - c, r = d - b;
|
||
const arr = [];
|
||
arr.push('<b>Дано:</b> $' + a + 'x ' + (b >= 0 ? '+ ' + b : '- ' + Math.abs(b)) + ' \\geq ' + c + 'x ' + (d >= 0 ? '+ ' + d : '- ' + Math.abs(d)) + '$');
|
||
arr.push('<b>Шаг 1.</b> Переносим $x$ влево, числа — вправо: $' + a + 'x - ' + c + 'x \\geq ' + d + ' - (' + b + ') \\Rightarrow ' + k + 'x \\geq ' + r + '$');
|
||
if(k === 0){
|
||
arr.push('<b>Шаг 2.</b> $0 \\cdot x \\geq ' + r + '$. ' + (r <= 0 ? 'Верно для любого $x$ — решение $x \\in \\mathbb{R}$.' : 'Невозможно — решений нет.'));
|
||
arr.push('<b>Ответ:</b> ' + (r <= 0 ? '$(-\\infty;\\,+\\infty)$' : 'нет решений'));
|
||
} else if(k > 0){
|
||
arr.push('<b>Шаг 2.</b> Делим на $' + k + ' > 0$: $x \\geq ' + r + '/' + k + ' = ' + fmt(r/k) + '$');
|
||
arr.push('<b>Ответ:</b> $x \\in [' + fmt(r/k) + ';\\,+\\infty)$');
|
||
} else {
|
||
arr.push('<b>Шаг 2.</b> Делим на $' + k + ' < 0$ — <span style="color:var(--bad);font-weight:700">знак меняется!</span> $x \\leq ' + r + '/' + k + ' = ' + fmt(r/k) + '$');
|
||
arr.push('<b>Ответ:</b> $x \\in (-\\infty;\\,' + fmt(r/k) + ']$');
|
||
}
|
||
return arr;
|
||
}
|
||
function render(){
|
||
stage.innerHTML = steps.slice(0, idx + 1).map(s => `<div style="margin:6px 0;padding:9px 12px;background:var(--card);border-left:3px solid var(--sec-acc);border-radius:7px;animation:fadeIn .3s ease">${s}</div>`).join('');
|
||
renderMath(stage);
|
||
if(idx >= steps.length - 1){
|
||
nextBtn.disabled = true; nextBtn.textContent = 'Готово';
|
||
if(!awarded){ awarded = true; achievement('p15_solver'); bumpProgress('p15', 14); confetti(); }
|
||
} else { nextBtn.disabled = false; nextBtn.textContent = 'Дальше (' + (idx + 1) + '/' + steps.length + ')'; }
|
||
}
|
||
goBtn.addEventListener('click', ()=>{
|
||
const a = +document.getElementById('p15s-a').value, b = +document.getElementById('p15s-b').value;
|
||
const c = +document.getElementById('p15s-c').value, d = +document.getElementById('p15s-d').value;
|
||
steps = build(a, b, c, d); idx = 0; awarded = false;
|
||
goBtn.style.display = 'none'; nextBtn.style.display = ''; resetBtn.style.display = '';
|
||
render();
|
||
});
|
||
nextBtn.addEventListener('click', ()=>{ if(idx < steps.length - 1){ idx++; render(); } });
|
||
resetBtn.addEventListener('click', ()=>{ idx = 0; stage.innerHTML = ''; goBtn.style.display = ''; nextBtn.style.display = 'none'; resetBtn.style.display = 'none'; });
|
||
})();
|
||
|
||
/* INIT 4 — Тренажёр линейных */
|
||
(function(){
|
||
function gen(){
|
||
const a = (Math.random() < 0.5 ? -1 : 1) * (1 + Math.floor(Math.random()*4));
|
||
const b = -5 + Math.floor(Math.random()*11);
|
||
const c = -5 + Math.floor(Math.random()*11);
|
||
// ax + b > c
|
||
const v = (c - b) / a;
|
||
// знак ответа зависит от знака a
|
||
const baseCmp = ['gt','ge','lt','le'][Math.floor(Math.random()*4)];
|
||
const flipped = a < 0 ? { gt:'lt', ge:'le', lt:'gt', le:'ge' }[baseCmp] : baseCmp;
|
||
return { a, b, c, baseCmp, ansCmp: flipped, ansK: v, txt: a + 'x ' + (b >= 0 ? '+ ' + b : '- ' + Math.abs(b)) + ' ' + ({gt:'>',ge:'\\geq',lt:'<',le:'\\leq'})[baseCmp] + ' ' + c };
|
||
}
|
||
let cur = null, i = 1, score = 0, chosen = null;
|
||
function show(){
|
||
cur = gen();
|
||
document.getElementById('p15t-i').textContent = i;
|
||
document.getElementById('p15t-task').innerHTML = '$' + cur.txt + '$';
|
||
renderMath(document.getElementById('p15t-task'));
|
||
document.getElementById('p15t-k').value = '';
|
||
chosen = null;
|
||
document.querySelectorAll('#p15t-gt,#p15t-ge,#p15t-lt,#p15t-le').forEach(b => b.classList.remove('primary'));
|
||
document.getElementById('p15t-fb').style.display = 'none';
|
||
}
|
||
function check(){
|
||
const fb = document.getElementById('p15t-fb'); fb.style.display = 'block';
|
||
const u = +document.getElementById('p15t-k').value;
|
||
const ok = chosen === cur.ansCmp && Math.abs(u - cur.ansK) < 1e-6;
|
||
if(ok){ score++; feedback(fb, true, '✓ $x ' + ({gt:'>',ge:'\\geq',lt:'<',le:'\\leq'})[cur.ansCmp] + ' ' + fmt(cur.ansK) + '$'); renderMath(fb); }
|
||
else feedback(fb, false, 'Правильно: $x ' + ({gt:'>',ge:'\\geq',lt:'<',le:'\\leq'})[cur.ansCmp] + ' ' + fmt(cur.ansK) + '$');
|
||
renderMath(fb);
|
||
document.getElementById('p15t-score').textContent = score;
|
||
if(i >= 8){ setTimeout(()=>{ feedback(fb, score >= 5, 'Итог: ' + score + '/8'); if(score >= 5){ achievement('p15_train'); bumpProgress('p15', 16); confetti(); } }, 700); }
|
||
else { i++; setTimeout(show, 900); }
|
||
}
|
||
document.getElementById('p15t-start').addEventListener('click', ()=>{ i=1; score=0; document.getElementById('p15t-score').textContent = 0; show(); });
|
||
['gt','ge','lt','le'].forEach(c => document.getElementById('p15t-' + c).addEventListener('click', ()=>{
|
||
chosen = c;
|
||
document.querySelectorAll('#p15t-gt,#p15t-ge,#p15t-lt,#p15t-le').forEach(b => b.classList.remove('primary'));
|
||
document.getElementById('p15t-' + c).classList.add('primary');
|
||
}));
|
||
document.getElementById('p15t-go').addEventListener('click', ()=>{ if(!chosen){ const fb = document.getElementById('p15t-fb'); fb.style.display='block'; feedback(fb, false, 'Выберите знак.'); return; } check(); });
|
||
})();
|
||
|
||
/* INIT 5 — Drag */
|
||
(function(){
|
||
const items = [
|
||
{ id:1, html:'$2 \\leq x \\leq 7$', cat:'cc' },
|
||
{ id:2, html:'$-3 < x < 4$', cat:'oo' },
|
||
{ id:3, html:'$x > 5$', cat:'rop' },
|
||
{ id:4, html:'$x \\leq 0$', cat:'lcr' },
|
||
{ id:5, html:'$-1 \\leq x \\leq 6$', cat:'cc' },
|
||
{ id:6, html:'$0 < x < 10$', cat:'oo' },
|
||
{ id:7, html:'$x > 8$', cat:'rop' },
|
||
{ id:8, html:'$x \\leq -2$', cat:'lcr' },
|
||
];
|
||
const sorter = setupSorter({ poolId:'p15d-pool', cats:['cc','oo','rop','lcr'], items, scopeSelector:'#p15-body' });
|
||
document.getElementById('p15d-check').addEventListener('click', ()=>{
|
||
const fb = document.getElementById('p15d-fb'); fb.style.display = 'block';
|
||
if(Object.keys(sorter.placed).length < items.length){ feedback(fb, false, '⚠ Разложите все.'); return; }
|
||
let ok = 0; items.forEach(it=>{ if(sorter.placed[it.id] === it.cat) ok++; });
|
||
if(ok === items.length){ feedback(fb, true, '✓ Все верно!'); achievement('p15_drag'); bumpProgress('p15', 14); confetti(); }
|
||
else feedback(fb, false, 'Верно ' + ok + ' из ' + items.length);
|
||
});
|
||
document.getElementById('p15d-reset').addEventListener('click', ()=>{ sorter.reset(); document.getElementById('p15d-fb').style.display='none'; });
|
||
})();
|
||
}
|
||
|
||
/* ============================================================
|
||
§ 16 — СИСТЕМЫ И СОВОКУПНОСТИ НЕРАВЕНСТВ
|
||
============================================================ */
|
||
function buildP16(){
|
||
const box = document.getElementById('p16-body');
|
||
let html = '';
|
||
|
||
html += makeCard('repeat','Повторение § 15',null,`
|
||
<ul style="margin-left:18px;line-height:1.7">
|
||
<li>Промежутки: $[a;b]$, $(a;b)$, лучи и др.</li>
|
||
<li>Решение линейного: изоляция $x$, не забывать про знак при делении на отрицательное.</li>
|
||
</ul>`);
|
||
|
||
html += makeCard('theory','Система и совокупность','16.1',`
|
||
<p><b>Система</b> неравенств — несколько неравенств, выполненных <b>одновременно</b> («И»). Решение — <b>пересечение</b> решений каждого. Записывается фигурной скобкой:</p>
|
||
<div style="background:var(--sec-acc-soft);border-radius:10px;padding:10px;margin:8px 0;text-align:center;font-size:1.05rem">$$\\begin{cases} x \\geq 1 \\\\ x < 5 \\end{cases} \\Rightarrow x \\in [1;\\,5)$$</div>
|
||
<p><b>Совокупность</b> — неравенства, из которых выполняется <b>хотя бы одно</b> («ИЛИ»). Решение — <b>объединение</b>. Записывается квадратной скобкой:</p>
|
||
<div style="background:var(--sec-acc-soft);border-radius:10px;padding:10px;margin:8px 0;text-align:center;font-size:1.05rem">$$\\left[\\begin{array}{l} x < -2 \\\\ x \\geq 3 \\end{array}\\right. \\Rightarrow x \\in (-\\infty;\\,-2) \\cup [3;\\,+\\infty)$$</div>`);
|
||
|
||
html += makeCard('algo','Алгоритм решения системы',null,`
|
||
<ol style="margin-left:18px;line-height:1.9">
|
||
<li>Решить каждое неравенство отдельно.</li>
|
||
<li>Изобразить решения на одной числовой прямой.</li>
|
||
<li>Найти пересечение (общую часть для системы) или объединение (для совокупности).</li>
|
||
<li>Записать ответ как промежуток.</li>
|
||
</ol>`);
|
||
|
||
html += makeCard('example','Пример',null,`
|
||
<p><b>Решим систему:</b> $\\begin{cases} 2x - 1 > 3 \\\\ x + 4 \\leq 10 \\end{cases}$</p>
|
||
<p>Первое: $2x > 4 \\Rightarrow x > 2$, т.е. $(2;\\,+\\infty)$.</p>
|
||
<p>Второе: $x \\leq 6$, т.е. $(-\\infty;\\,6]$.</p>
|
||
<p>Пересечение: $x \\in (2;\\,6]$.</p>`);
|
||
|
||
/* INT 1 — Пересечение промежутков */
|
||
html += widget('Пересечение двух промежутков','INTERACT 1','Сдвигай границы каждого промежутка и наблюдай пересечение.',`
|
||
<div class="sliders">
|
||
<label>$a_1$ = <b id="p16i-a1-val">-3</b><input type="range" min="-9" max="9" step="1" value="-3" id="p16i-a1"></label>
|
||
<label>$b_1$ = <b id="p16i-b1-val">4</b><input type="range" min="-9" max="9" step="1" value="4" id="p16i-b1"></label>
|
||
<label>$a_2$ = <b id="p16i-a2-val">1</b><input type="range" min="-9" max="9" step="1" value="1" id="p16i-a2"></label>
|
||
<label>$b_2$ = <b id="p16i-b2-val">7</b><input type="range" min="-9" max="9" step="1" value="7" id="p16i-b2"></label>
|
||
</div>
|
||
<div class="numline" id="p16i-line"></div>
|
||
<div id="p16i-out" style="padding:12px;background:var(--sec-acc-soft);border-radius:10px;text-align:center;font-size:1.05rem;line-height:1.8"></div>`);
|
||
|
||
/* INT 2 — Пошаговый решатель */
|
||
html += widget('Шаговый решатель системы','INTERACT 2','Решаем систему пошагово: каждое неравенство отдельно, затем пересечение.',`
|
||
<p style="margin-bottom:10px"><b>Система:</b> $\\begin{cases} 3x - 5 \\geq 1 \\\\ -2x + 4 > -6 \\end{cases}$</p>
|
||
<div id="p16s-stage" style="padding:14px;background:var(--sec-acc-soft);border-radius:10px;line-height:1.8;min-height:80px"></div>
|
||
<div class="actions" style="margin-top:10px"><button class="btn primary" id="p16s-go">Старт</button><button class="btn" id="p16s-next" style="display:none">Дальше</button><button class="btn" id="p16s-reset" style="display:none">Сначала</button></div>`);
|
||
|
||
/* INT 3 — Drag: система или совокупность */
|
||
html += widget('Что это: система или совокупность?','INTERACT 3','По логической связке («И» / «ИЛИ») определи тип записи.',`
|
||
${DND_HINT_HTML}
|
||
<div id="p16d-pool"></div>
|
||
<div class="drop-row" style="display:grid;grid-template-columns:1fr 1fr;gap:10px">
|
||
<div class="drop-box"><h5>Система (И, пересечение)</h5><div class="drop-items" data-cat="sys"></div></div>
|
||
<div class="drop-box"><h5>Совокупность (ИЛИ, объединение)</h5><div class="drop-items" data-cat="sov"></div></div>
|
||
</div>
|
||
<div class="actions"><button class="btn primary" id="p16d-check">Проверить</button><button class="btn" id="p16d-reset">Сначала</button></div>
|
||
<div class="feedback" id="p16d-fb" style="display:none"></div>`);
|
||
|
||
/* INT 4 — Тренажёр систем */
|
||
html += widget('Тренажёр систем','INTERACT 4','Решите систему и введите ответ как промежуток.',`
|
||
<div class="score-display"><span>Задача <b id="p16t-i">1</b> / 6</span><span>Очки: <b id="p16t-score">0</b></span></div>
|
||
<div id="p16t-task" style="padding:14px;background:var(--sec-acc-soft);border-radius:10px;font-size:1.1rem;text-align:center;margin-bottom:10px"></div>
|
||
<div id="p16t-opts" style="display:flex;flex-direction:column;gap:6px"></div>
|
||
<div class="feedback" id="p16t-fb" style="display:none;margin-top:10px"></div>
|
||
<button class="btn primary" id="p16t-start" style="margin-top:10px">Начать</button>`);
|
||
|
||
/* INT 5 — Совокупность визуально */
|
||
html += widget('Совокупность: объединение','INTERACT 5','Двигай $a$ и $b$, смотри, как меняется $(-\\infty;a) \\cup (b;+\\infty)$.',`
|
||
<div class="sliders">
|
||
<label>$a$ = <b id="p16u-a-val">-2</b><input type="range" min="-9" max="9" step="1" value="-2" id="p16u-a"></label>
|
||
<label>$b$ = <b id="p16u-b-val">3</b><input type="range" min="-9" max="9" step="1" value="3" id="p16u-b"></label>
|
||
</div>
|
||
<div class="numline" id="p16u-line"></div>
|
||
<div id="p16u-out" style="padding:12px;background:var(--sec-acc-soft);border-radius:10px;text-align:center;font-size:1.05rem"></div>`);
|
||
|
||
html += makeCard('class','Класс — решите',null,`
|
||
<ol style="margin-left:18px;line-height:1.8">
|
||
<li>$\\begin{cases} x > 2 \\\\ x < 8 \\end{cases}$</li>
|
||
<li>$\\begin{cases} 2x - 1 \\leq 5 \\\\ x + 3 > -2 \\end{cases}$</li>
|
||
<li>$\\left[\\begin{array}{l} x < -1 \\\\ x \\geq 4 \\end{array}\\right.$</li>
|
||
</ol>`);
|
||
|
||
html += makeCard('home','Домашка',null,`
|
||
<ol style="margin-left:18px;line-height:1.8">
|
||
<li>$\\begin{cases} 3x + 1 > 7 \\\\ x - 2 \\leq 3 \\end{cases}$</li>
|
||
<li>$\\begin{cases} 5 - 2x \\geq 1 \\\\ x + 1 > -3 \\end{cases}$</li>
|
||
<li>$\\left[\\begin{array}{l} 2x > 8 \\\\ -x \\geq 2 \\end{array}\\right.$</li>
|
||
</ol>`);
|
||
|
||
html += secNav('p15', 'p17');
|
||
box.innerHTML = html;
|
||
if(window.renderMathInElement) setTimeout(()=>renderMath(box), 0);
|
||
|
||
/* INIT 1 — Пересечение промежутков */
|
||
(function(){
|
||
const ids = ['p16i-a1','p16i-b1','p16i-a2','p16i-b2'];
|
||
const lineE = document.getElementById('p16i-line'), out = document.getElementById('p16i-out');
|
||
let done = false;
|
||
function refresh(){
|
||
const a1 = +document.getElementById('p16i-a1').value, b1 = +document.getElementById('p16i-b1').value;
|
||
const a2 = +document.getElementById('p16i-a2').value, b2 = +document.getElementById('p16i-b2').value;
|
||
document.getElementById('p16i-a1-val').textContent = a1;
|
||
document.getElementById('p16i-b1-val').textContent = b1;
|
||
document.getElementById('p16i-a2-val').textContent = a2;
|
||
document.getElementById('p16i-b2-val').textContent = b2;
|
||
const intervals = [];
|
||
if(a1 <= b1) intervals.push({ a:a1, b:b1, openA:false, openB:false, color:'#6366f1' });
|
||
if(a2 <= b2) intervals.push({ a:a2, b:b2, openA:false, openB:false, color:'#f59e0b' });
|
||
const lo = Math.max(a1, a2), hi = Math.min(b1, b2);
|
||
if(lo <= hi) intervals.push({ a:lo, b:hi, openA:false, openB:false, color:'#10b981' });
|
||
lineE.innerHTML = drawNumLine({ intervals });
|
||
let s = '<div><b style="color:#6366f1">[' + a1 + ';' + b1 + ']</b> ∩ <b style="color:#f59e0b">[' + a2 + ';' + b2 + ']</b> = ';
|
||
if(lo > hi) s += '<span style="color:var(--bad)">∅ (пусто)</span></div>';
|
||
else s += '<b style="color:#10b981">[' + lo + ';' + hi + ']</b></div>';
|
||
out.innerHTML = s;
|
||
if(!done){ done = true; setTimeout(()=>{ achievement('p16_intersect'); bumpProgress('p16', 14); }, 300); }
|
||
}
|
||
ids.forEach(id => document.getElementById(id).addEventListener('input', refresh));
|
||
refresh();
|
||
})();
|
||
|
||
/* INIT 2 — Шаговый решатель */
|
||
(function(){
|
||
const stage = document.getElementById('p16s-stage');
|
||
const goBtn = document.getElementById('p16s-go'), nextBtn = document.getElementById('p16s-next'), resetBtn = document.getElementById('p16s-reset');
|
||
const steps = [
|
||
'<b>Шаг 1.</b> Решим первое: $3x - 5 \\geq 1 \\Rightarrow 3x \\geq 6 \\Rightarrow x \\geq 2$, т.е. $[2;\\,+\\infty)$.',
|
||
'<b>Шаг 2.</b> Решим второе: $-2x + 4 > -6 \\Rightarrow -2x > -10 \\Rightarrow x < 5$ (знак сменился!), т.е. $(-\\infty;\\,5)$.',
|
||
'<b>Шаг 3.</b> Пересечение: $[2;\\,+\\infty) \\cap (-\\infty;\\,5) = [2;\\,5)$.',
|
||
'<b>Ответ:</b> $x \\in [2;\\,5)$.',
|
||
];
|
||
let idx = 0, awarded = false;
|
||
function render(){
|
||
stage.innerHTML = steps.slice(0, idx + 1).map(s => `<div style="margin:6px 0;padding:9px 12px;background:var(--card);border-left:3px solid var(--sec-acc);border-radius:7px;animation:fadeIn .3s ease">${s}</div>`).join('');
|
||
renderMath(stage);
|
||
if(idx >= steps.length - 1){
|
||
nextBtn.disabled = true; nextBtn.textContent = 'Готово';
|
||
if(!awarded){ awarded = true; achievement('p16_solver'); bumpProgress('p16', 14); confetti(); }
|
||
} else { nextBtn.disabled = false; nextBtn.textContent = 'Дальше (' + (idx + 1) + '/' + steps.length + ')'; }
|
||
}
|
||
goBtn.addEventListener('click', ()=>{ idx = 0; awarded = false; goBtn.style.display = 'none'; nextBtn.style.display = ''; resetBtn.style.display = ''; render(); });
|
||
nextBtn.addEventListener('click', ()=>{ if(idx < steps.length - 1){ idx++; render(); } });
|
||
resetBtn.addEventListener('click', ()=>{ idx = 0; stage.innerHTML = ''; goBtn.style.display = ''; nextBtn.style.display = 'none'; resetBtn.style.display = 'none'; });
|
||
})();
|
||
|
||
/* INIT 3 — Drag система/совокупность */
|
||
(function(){
|
||
const items = [
|
||
{ id:1, html:'$\\begin{cases} x > 2 \\\\ x \\leq 5 \\end{cases}$', cat:'sys' },
|
||
{ id:2, html:'$\\left[\\begin{array}{l} x < -3 \\\\ x \\geq 4 \\end{array}\\right.$', cat:'sov' },
|
||
{ id:3, html:'Оба условия одновременно', cat:'sys' },
|
||
{ id:4, html:'Хотя бы одно условие', cat:'sov' },
|
||
{ id:5, html:'$\\begin{cases} x \\geq -1 \\\\ x < 7 \\end{cases}$', cat:'sys' },
|
||
{ id:6, html:'$\\left[\\begin{array}{l} x \\leq 0 \\\\ x > 5 \\end{array}\\right.$', cat:'sov' },
|
||
{ id:7, html:'Пересечение решений', cat:'sys' },
|
||
{ id:8, html:'Объединение решений', cat:'sov' },
|
||
];
|
||
const sorter = setupSorter({ poolId:'p16d-pool', cats:['sys','sov'], items, scopeSelector:'#p16-body' });
|
||
document.getElementById('p16d-check').addEventListener('click', ()=>{
|
||
const fb = document.getElementById('p16d-fb'); fb.style.display = 'block';
|
||
if(Object.keys(sorter.placed).length < items.length){ feedback(fb, false, '⚠ Разложите все.'); return; }
|
||
let ok = 0; items.forEach(it=>{ if(sorter.placed[it.id] === it.cat) ok++; });
|
||
if(ok === items.length){ feedback(fb, true, '✓ Все верно!'); achievement('p16_union'); bumpProgress('p16', 14); confetti(); }
|
||
else feedback(fb, false, 'Верно ' + ok + ' из ' + items.length);
|
||
});
|
||
document.getElementById('p16d-reset').addEventListener('click', ()=>{ sorter.reset(); document.getElementById('p16d-fb').style.display='none'; });
|
||
})();
|
||
|
||
/* INIT 4 — Тренажёр систем */
|
||
(function(){
|
||
const tasks = [
|
||
{ q:'$\\begin{cases} x > 3 \\\\ x \\leq 7 \\end{cases}$', opts:['$(3;\\,7]$','$[3;\\,7)$','$(3;\\,7)$','$\\emptyset$'], ok:0 },
|
||
{ q:'$\\begin{cases} x \\geq -2 \\\\ x < 4 \\end{cases}$', opts:['$[-2;\\,4)$','$(-2;\\,4)$','$[-2;\\,4]$','$\\emptyset$'], ok:0 },
|
||
{ q:'$\\begin{cases} x > 5 \\\\ x < 2 \\end{cases}$', opts:['$\\emptyset$ (нет решений)','$(2;\\,5)$','$(5;\\,2)$','любое $x$'], ok:0 },
|
||
{ q:'$\\left[\\begin{array}{l} x < -1 \\\\ x \\geq 3 \\end{array}\\right.$', opts:['$(-\\infty;\\,-1) \\cup [3;\\,+\\infty)$','$(-1;\\,3)$','$[-1;\\,3)$','$\\emptyset$'], ok:0 },
|
||
{ q:'$\\begin{cases} 2x \\geq 6 \\\\ x \\leq 10 \\end{cases}$', opts:['$[3;\\,10]$','$[3;\\,10)$','$(3;\\,10]$','$[6;\\,10]$'], ok:0 },
|
||
{ q:'$\\begin{cases} x > 0 \\\\ x < 0 \\end{cases}$', opts:['$\\emptyset$','$\\{0\\}$','$\\mathbb{R}$','$(-\\infty;\\,0)$'], ok:0 },
|
||
];
|
||
let cur = null, i = 1, score = 0, shuffled = [];
|
||
function show(){
|
||
cur = shuffled[i-1];
|
||
document.getElementById('p16t-i').textContent = i;
|
||
document.getElementById('p16t-task').innerHTML = cur.q;
|
||
renderMath(document.getElementById('p16t-task'));
|
||
const opts = document.getElementById('p16t-opts'); opts.innerHTML = '';
|
||
cur.opts.forEach((o, k)=>{
|
||
const b = document.createElement('button');
|
||
b.className = 'btn'; b.innerHTML = o; b.style.cssText = 'text-align:left';
|
||
b.addEventListener('click', ()=>{
|
||
const fb = document.getElementById('p16t-fb'); fb.style.display = 'block';
|
||
if(k === cur.ok){ score++; b.classList.add('ok'); feedback(fb, true, '✓'); }
|
||
else { b.classList.add('fail'); feedback(fb, false, 'Не то.'); }
|
||
document.getElementById('p16t-score').textContent = score;
|
||
if(i >= shuffled.length){ setTimeout(()=>{ feedback(fb, score >= 4, 'Итог: ' + score + '/' + shuffled.length); if(score >= 4){ achievement('p16_train'); bumpProgress('p16', 16); confetti(); } }, 700); }
|
||
else { i++; setTimeout(show, 900); }
|
||
});
|
||
opts.appendChild(b);
|
||
});
|
||
renderMath(opts);
|
||
document.getElementById('p16t-fb').style.display = 'none';
|
||
}
|
||
document.getElementById('p16t-start').addEventListener('click', ()=>{ i=1; score=0; document.getElementById('p16t-score').textContent = 0; shuffled = [...tasks].sort(()=>Math.random()-0.5); show(); });
|
||
})();
|
||
|
||
/* INIT 5 — Совокупность визуально */
|
||
(function(){
|
||
const aE = document.getElementById('p16u-a'), bE = document.getElementById('p16u-b');
|
||
const lineE = document.getElementById('p16u-line'), out = document.getElementById('p16u-out');
|
||
function refresh(){
|
||
const a = +aE.value, b = +bE.value;
|
||
document.getElementById('p16u-a-val').textContent = a;
|
||
document.getElementById('p16u-b-val').textContent = b;
|
||
const intervals = [
|
||
{ a:null, b:a, openA:false, openB:true, color:'#8b5cf6' },
|
||
{ a:b, b:null, openA:true, openB:false, color:'#8b5cf6' },
|
||
];
|
||
lineE.innerHTML = drawNumLine({ intervals });
|
||
out.innerHTML = '<b>Совокупность:</b> $x < ' + a + '$ или $x > ' + b + '$. Решение: $(-\\infty;\\,' + a + ') \\cup (' + b + ';\\,+\\infty)$.';
|
||
renderMath(out);
|
||
}
|
||
[aE,bE].forEach(e => e.addEventListener('input', refresh));
|
||
refresh();
|
||
})();
|
||
}
|
||
function buildP17stub(){ buildP17(); }
|
||
function buildP18stub(){ buildP18(); }
|
||
|
||
/* ============================================================
|
||
§ 17 — КВАДРАТНЫЕ НЕРАВЕНСТВА. МЕТОД ИНТЕРВАЛОВ
|
||
============================================================ */
|
||
function buildP17(){
|
||
const box = document.getElementById('p17-body');
|
||
let html = '';
|
||
|
||
html += makeCard('repeat','Повторение',null,`
|
||
<ul style="margin-left:18px;line-height:1.7">
|
||
<li>Квадратное уравнение $ax^2 + bx + c = 0$ (Глава 2).</li>
|
||
<li>Дискриминант $D = b^2 - 4ac$, корни $x_{1,2} = \\dfrac{-b \\pm \\sqrt{D}}{2a}$.</li>
|
||
<li>Парабола: при $a > 0$ ветви вверх, при $a < 0$ — вниз.</li>
|
||
</ul>`);
|
||
|
||
html += makeCard('theory','Что такое квадратное неравенство','17.1',`
|
||
<p><b>Определение.</b> Неравенство вида $ax^2 + bx + c \\gtrless 0$, $a \\neq 0$.</p>
|
||
<p style="margin-top:6px"><b>Геометрический смысл:</b> найти, где парабола $y = ax^2 + bx + c$ <i>выше</i> или <i>ниже</i> оси OX (в зависимости от знака неравенства).</p>`);
|
||
|
||
html += makeCard('algo','Метод интервалов','17.2',`
|
||
<ol style="margin-left:18px;line-height:1.9">
|
||
<li>Найти корни уравнения $ax^2 + bx + c = 0$.</li>
|
||
<li>Если $D < 0$ — знак выражения постоянен (совпадает со знаком $a$).</li>
|
||
<li>Если $D \\geq 0$ — отметить корни на числовой прямой.</li>
|
||
<li>Определить знак выражения на каждом интервале (можно подставить пробную точку).</li>
|
||
<li>Выбрать интервалы, удовлетворяющие неравенству.</li>
|
||
</ol>
|
||
<p style="margin-top:8px"><b>Правило знаков для квадратного:</b> при $a > 0$ — между корнями знак минус, вне — плюс. При $a < 0$ — наоборот.</p>`);
|
||
|
||
html += makeCard('example','Пример',null,`
|
||
<p><b>Решить</b> $x^2 - 5x + 6 > 0$.</p>
|
||
<p>Корни: $x_1 = 2$, $x_2 = 3$. $a = 1 > 0$ — парабола вверх.</p>
|
||
<p>Знаки: $+$ при $x<2$, $-$ при $2<x<3$, $+$ при $x>3$.</p>
|
||
<p><b>Ответ:</b> $x \\in (-\\infty;\\,2) \\cup (3;\\,+\\infty)$.</p>`);
|
||
|
||
/* INT 1 — Парабола + закраска */
|
||
html += widget('Парабола и знак','INTERACT 1','Двигай $a, b, c$. Парабола показывает, где выражение положительно (зелёное) и отрицательно (красное).',`
|
||
<div class="sliders">
|
||
<label>$a$ = <b id="p17p-a-val">1</b><input type="range" min="-3" max="3" step="0.5" value="1" id="p17p-a"></label>
|
||
<label>$b$ = <b id="p17p-b-val">-1</b><input type="range" min="-6" max="6" step="0.5" value="-1" id="p17p-b"></label>
|
||
<label>$c$ = <b id="p17p-c-val">-6</b><input type="range" min="-8" max="8" step="0.5" value="-6" id="p17p-c"></label>
|
||
</div>
|
||
<svg id="p17p-svg" viewBox="-10 -10 240 180" style="width:100%;max-width:520px;display:block;margin:10px auto;background:#fafafa;border:1px solid var(--border);border-radius:10px"></svg>
|
||
<div id="p17p-out" style="padding:12px;background:var(--sec-acc-soft);border-radius:10px;line-height:1.7"></div>`);
|
||
|
||
/* INT 2 — Шаговый решатель */
|
||
html += widget('Метод интервалов: шаг за шагом','INTERACT 2','Введите $a, b, c$ для $ax^2 + bx + c \\geq 0$ и нажимайте «Дальше».',`
|
||
<div style="display:flex;gap:8px;flex-wrap:wrap;align-items:center;justify-content:center;margin-bottom:10px">
|
||
<label>$a$ = <input type="number" id="p17s-a" value="1" class="tinp" style="width:60px"></label>
|
||
<label>$b$ = <input type="number" id="p17s-b" value="-5" class="tinp" style="width:60px"></label>
|
||
<label>$c$ = <input type="number" id="p17s-c" value="6" class="tinp" style="width:60px"></label>
|
||
<button class="btn primary" id="p17s-go">Старт</button>
|
||
<button class="btn" id="p17s-next" style="display:none">Дальше</button>
|
||
<button class="btn" id="p17s-reset" style="display:none">Сначала</button>
|
||
</div>
|
||
<div id="p17s-stage" style="padding:14px;background:var(--card-soft);border-radius:10px;min-height:80px"></div>`);
|
||
|
||
/* INT 3 — Тренажёр */
|
||
html += widget('Тренажёр квадратных неравенств','INTERACT 3','Решите неравенство и выберите правильный промежуток-ответ.',`
|
||
<div class="score-display"><span>Задача <b id="p17t-i">1</b> / 6</span><span>Очки: <b id="p17t-score">0</b></span></div>
|
||
<div id="p17t-task" style="font-size:1.2rem;text-align:center;padding:16px;background:var(--sec-acc-soft);border-radius:10px;margin-bottom:10px"></div>
|
||
<div id="p17t-opts" style="display:flex;flex-direction:column;gap:6px"></div>
|
||
<div class="feedback" id="p17t-fb" style="display:none;margin-top:10px"></div>
|
||
<button class="btn primary" id="p17t-start" style="margin-top:10px">Начать</button>`);
|
||
|
||
/* INT 4 — Drag: парабола ↔ ответ */
|
||
html += widget('Сопоставь параболу и неравенство','INTERACT 4','По описанию ситуации (направление ветвей + расположение корней) подбери решение неравенства $\\geq 0$.',`
|
||
${DND_HINT_HTML}
|
||
<div id="p17d-pool"></div>
|
||
<div class="drop-row" style="display:grid;grid-template-columns:repeat(auto-fit,minmax(160px,1fr));gap:10px">
|
||
<div class="drop-box"><h5>$(-\\infty; x_1] \\cup [x_2; +\\infty)$</h5><div class="drop-items" data-cat="out"></div></div>
|
||
<div class="drop-box"><h5>$[x_1; x_2]$</h5><div class="drop-items" data-cat="in"></div></div>
|
||
<div class="drop-box"><h5>$\\mathbb{R}$ (все числа)</h5><div class="drop-items" data-cat="all"></div></div>
|
||
<div class="drop-box"><h5>$\\emptyset$ (нет решений)</h5><div class="drop-items" data-cat="none"></div></div>
|
||
</div>
|
||
<div class="actions"><button class="btn primary" id="p17d-check">Проверить</button><button class="btn" id="p17d-reset">Сначала</button></div>
|
||
<div class="feedback" id="p17d-fb" style="display:none"></div>`);
|
||
|
||
/* INT 5 — Знак между / вне корней */
|
||
html += widget('Где плюс, где минус?','INTERACT 5','Дана парабола. Кликни на интервал — раскрась знак.',`
|
||
<p style="margin-bottom:10px">Пусть $y = x^2 - 4x + 3$. Корни: $x_1 = 1$, $x_2 = 3$. Кликни по каждому интервалу и поставь знак.</p>
|
||
<div id="p17z-line" style="background:var(--card);border:1px solid var(--border);border-radius:8px;padding:12px"></div>
|
||
<div class="feedback" id="p17z-fb" style="display:none;margin-top:10px"></div>`);
|
||
|
||
html += makeCard('class','Класс — решите',null,`
|
||
<ol style="margin-left:18px;line-height:1.8">
|
||
<li>$x^2 - 6x + 5 \\leq 0$</li>
|
||
<li>$-x^2 + 4x - 3 > 0$</li>
|
||
<li>$x^2 + 2x + 5 > 0$ (без корней — особый случай)</li>
|
||
<li>$4x^2 - 9 \\geq 0$</li>
|
||
</ol>`);
|
||
|
||
html += makeCard('home','Домашка',null,`
|
||
<ol style="margin-left:18px;line-height:1.8">
|
||
<li>$x^2 - 7x + 10 < 0$</li>
|
||
<li>$3x^2 + 2x - 1 \\geq 0$</li>
|
||
<li>$x^2 + 1 \\leq 0$ (особый случай)</li>
|
||
<li>При каких $m$ неравенство $x^2 + 6x + m > 0$ верно для всех $x$?</li>
|
||
</ol>`);
|
||
|
||
html += secNav('p16', 'p18');
|
||
box.innerHTML = html;
|
||
if(window.renderMathInElement) setTimeout(()=>renderMath(box), 0);
|
||
|
||
/* INIT 1 — Парабола */
|
||
(function(){
|
||
const aE = document.getElementById('p17p-a'), bE = document.getElementById('p17p-b'), cE = document.getElementById('p17p-c');
|
||
const svg = document.getElementById('p17p-svg'), out = document.getElementById('p17p-out');
|
||
let done = false;
|
||
const W = 220, H = 160, x0 = W/2, y0 = H/2, sx = 14, sy = 14;
|
||
function refresh(){
|
||
const a = +aE.value, b = +bE.value, c = +cE.value;
|
||
document.getElementById('p17p-a-val').textContent = a;
|
||
document.getElementById('p17p-b-val').textContent = b;
|
||
document.getElementById('p17p-c-val').textContent = c;
|
||
let path = '';
|
||
for(let i = 0; i <= 240; i++){
|
||
const x = -W/(2*sx) + i * (W/sx) / 240;
|
||
const y = a*x*x + b*x + c;
|
||
const cx = x0 + x*sx, cy = y0 - y*sy;
|
||
path += (i === 0 ? 'M' : 'L') + cx.toFixed(2) + ',' + cy.toFixed(2) + ' ';
|
||
}
|
||
const D = b*b - 4*a*c;
|
||
const xs = D >= 0 ? [(-b - Math.sqrt(D))/(2*a), (-b + Math.sqrt(D))/(2*a)].sort((p,q)=>p-q) : [];
|
||
// фон зон
|
||
let s = '';
|
||
if(a !== 0){
|
||
if(D > 0){
|
||
// полосы зелёный/красный на оси
|
||
const x1 = x0 + xs[0]*sx, x2 = x0 + xs[1]*sx;
|
||
if(a > 0){
|
||
s += '<rect x="0" y="' + (y0-3) + '" width="' + x1 + '" height="6" fill="#10b981" opacity="0.35"/>';
|
||
s += '<rect x="' + x1 + '" y="' + (y0-3) + '" width="' + (x2-x1) + '" height="6" fill="#ef4444" opacity="0.35"/>';
|
||
s += '<rect x="' + x2 + '" y="' + (y0-3) + '" width="' + (W-x2) + '" height="6" fill="#10b981" opacity="0.35"/>';
|
||
} else {
|
||
s += '<rect x="0" y="' + (y0-3) + '" width="' + x1 + '" height="6" fill="#ef4444" opacity="0.35"/>';
|
||
s += '<rect x="' + x1 + '" y="' + (y0-3) + '" width="' + (x2-x1) + '" height="6" fill="#10b981" opacity="0.35"/>';
|
||
s += '<rect x="' + x2 + '" y="' + (y0-3) + '" width="' + (W-x2) + '" height="6" fill="#ef4444" opacity="0.35"/>';
|
||
}
|
||
} else {
|
||
s += '<rect x="0" y="' + (y0-3) + '" width="' + W + '" height="6" fill="' + (a*c >= 0 ? '#10b981' : '#ef4444') + '" opacity="0.35"/>';
|
||
}
|
||
}
|
||
s += '<line x1="0" y1="' + y0 + '" x2="' + W + '" y2="' + y0 + '" stroke="#94a3b8" stroke-width="0.8"/>';
|
||
s += '<line x1="' + x0 + '" y1="0" x2="' + x0 + '" y2="' + H + '" stroke="#94a3b8" stroke-width="0.8"/>';
|
||
s += '<path d="' + path + '" fill="none" stroke="#6366f1" stroke-width="2"/>';
|
||
xs.forEach(r => { s += '<circle cx="' + (x0 + r*sx).toFixed(2) + '" cy="' + y0 + '" r="4" fill="#6366f1"/>'; });
|
||
svg.innerHTML = s;
|
||
let info = '<div><b>$D$ = ' + D.toFixed(2) + '</b></div>';
|
||
if(D > 0){
|
||
info += '<div>Корни: $x_1 = ' + fmt(xs[0]) + ',\\ x_2 = ' + fmt(xs[1]) + '$</div>';
|
||
if(a > 0) info += '<div><b>$ax^2+bx+c > 0$:</b> $x < ' + fmt(xs[0]) + '$ или $x > ' + fmt(xs[1]) + '$</div><div><b>$ax^2+bx+c < 0$:</b> $' + fmt(xs[0]) + ' < x < ' + fmt(xs[1]) + '$</div>';
|
||
else info += '<div><b>$ax^2+bx+c > 0$:</b> $' + fmt(xs[0]) + ' < x < ' + fmt(xs[1]) + '$</div><div><b>$ax^2+bx+c < 0$:</b> $x < ' + fmt(xs[0]) + '$ или $x > ' + fmt(xs[1]) + '$</div>';
|
||
} else if(D === 0){
|
||
const r = -b/(2*a);
|
||
info += '<div>Один корень: $x = ' + fmt(r) + '$</div>';
|
||
info += '<div>Парабола касается оси. Знак — везде ' + (a > 0 ? 'положителен (кроме точки $x = ' + fmt(r) + '$)' : 'отрицателен') + '.</div>';
|
||
} else {
|
||
info += '<div>Корней нет. Парабола ' + (a > 0 ? 'выше' : 'ниже') + ' оси.</div>';
|
||
info += '<div>Знак выражения везде ' + (a > 0 ? 'положителен' : 'отрицателен') + '.</div>';
|
||
}
|
||
out.innerHTML = info; renderMath(out);
|
||
if(!done){ done = true; setTimeout(()=>{ achievement('p17_parab'); bumpProgress('p17', 14); }, 300); }
|
||
}
|
||
[aE,bE,cE].forEach(e => e.addEventListener('input', refresh));
|
||
refresh();
|
||
})();
|
||
|
||
/* INIT 2 — Метод интервалов шаговый */
|
||
(function(){
|
||
const stage = document.getElementById('p17s-stage');
|
||
const goBtn = document.getElementById('p17s-go'), nextBtn = document.getElementById('p17s-next'), resetBtn = document.getElementById('p17s-reset');
|
||
let steps = [], idx = 0, awarded = false;
|
||
function build(a, b, c){
|
||
const arr = [];
|
||
arr.push('<b>Дано:</b> $' + a + 'x^2 ' + (b >= 0 ? '+ ' + b : '- ' + Math.abs(b)) + 'x ' + (c >= 0 ? '+ ' + c : '- ' + Math.abs(c)) + ' \\geq 0$');
|
||
const D = b*b - 4*a*c;
|
||
arr.push('<b>Шаг 1.</b> $D = b^2 - 4ac = ' + (b*b) + ' - ' + (4*a*c) + ' = ' + D + '$');
|
||
if(D < 0){
|
||
arr.push('<b>Шаг 2.</b> $D < 0$ — корней нет. Знак совпадает со знаком $a = ' + a + '$.');
|
||
if(a > 0) arr.push('<b>Шаг 3.</b> $a > 0$ — выражение всегда $> 0$, тем более $\\geq 0$. Ответ: $\\mathbb{R}$.');
|
||
else arr.push('<b>Шаг 3.</b> $a < 0$ — выражение всегда $< 0$, ни одна точка не удовлетворяет $\\geq 0$. Ответ: $\\emptyset$.');
|
||
} else if(D === 0){
|
||
const r = -b/(2*a);
|
||
arr.push('<b>Шаг 2.</b> $D = 0$ — один корень: $x = ' + fmt(r) + '$.');
|
||
if(a > 0) arr.push('<b>Шаг 3.</b> $a > 0$ — выражение всюду $\\geq 0$, равно нулю только в $x = ' + fmt(r) + '$. Ответ: $\\mathbb{R}$.');
|
||
else arr.push('<b>Шаг 3.</b> $a < 0$ — выражение всюду $\\leq 0$, равно нулю только в $x = ' + fmt(r) + '$. Ответ: $\\{' + fmt(r) + '\\}$.');
|
||
} else {
|
||
const x1 = (-b - Math.sqrt(D))/(2*a), x2 = (-b + Math.sqrt(D))/(2*a);
|
||
const lo = Math.min(x1, x2), hi = Math.max(x1, x2);
|
||
arr.push('<b>Шаг 2.</b> Корни: $x_1 = ' + fmt(lo) + ',\\ x_2 = ' + fmt(hi) + '$');
|
||
if(a > 0){
|
||
arr.push('<b>Шаг 3.</b> $a > 0$ — парабола вверх. Знак: $+$ при $x < x_1$, $-$ между корнями, $+$ при $x > x_2$.');
|
||
arr.push('<b>Ответ:</b> $x \\in (-\\infty;\\,' + fmt(lo) + '] \\cup [' + fmt(hi) + ';\\,+\\infty)$');
|
||
} else {
|
||
arr.push('<b>Шаг 3.</b> $a < 0$ — парабола вниз. Знак: $-$ вне, $+$ между корнями.');
|
||
arr.push('<b>Ответ:</b> $x \\in [' + fmt(lo) + ';\\,' + fmt(hi) + ']$');
|
||
}
|
||
}
|
||
return arr;
|
||
}
|
||
function render(){
|
||
stage.innerHTML = steps.slice(0, idx + 1).map(s => `<div style="margin:6px 0;padding:9px 12px;background:var(--card);border-left:3px solid var(--sec-acc);border-radius:7px;animation:fadeIn .3s ease">${s}</div>`).join('');
|
||
renderMath(stage);
|
||
if(idx >= steps.length - 1){
|
||
nextBtn.disabled = true; nextBtn.textContent = 'Готово';
|
||
if(!awarded){ awarded = true; achievement('p17_solver'); bumpProgress('p17', 16); confetti(); }
|
||
} else { nextBtn.disabled = false; nextBtn.textContent = 'Дальше (' + (idx + 1) + '/' + steps.length + ')'; }
|
||
}
|
||
goBtn.addEventListener('click', ()=>{
|
||
const a = +document.getElementById('p17s-a').value, b = +document.getElementById('p17s-b').value, c = +document.getElementById('p17s-c').value;
|
||
if(!a){ stage.innerHTML = '<p>$a \\neq 0$</p>'; renderMath(stage); return; }
|
||
steps = build(a, b, c); idx = 0; awarded = false;
|
||
goBtn.style.display = 'none'; nextBtn.style.display = ''; resetBtn.style.display = '';
|
||
render();
|
||
});
|
||
nextBtn.addEventListener('click', ()=>{ if(idx < steps.length - 1){ idx++; render(); } });
|
||
resetBtn.addEventListener('click', ()=>{ idx = 0; stage.innerHTML = ''; goBtn.style.display = ''; nextBtn.style.display = 'none'; resetBtn.style.display = 'none'; });
|
||
})();
|
||
|
||
/* INIT 3 — Тренажёр */
|
||
(function(){
|
||
const tasks = [
|
||
{ q:'$x^2 - 5x + 6 > 0$', opts:['$(-\\infty;\\,2) \\cup (3;\\,+\\infty)$','$[2;\\,3]$','$(2;\\,3)$','$\\mathbb{R}$'], ok:0 },
|
||
{ q:'$x^2 - 4x + 3 \\leq 0$', opts:['$[1;\\,3]$','$(-\\infty;\\,1] \\cup [3;\\,+\\infty)$','$(1;\\,3)$','$\\emptyset$'], ok:0 },
|
||
{ q:'$x^2 + 1 > 0$', opts:['$\\mathbb{R}$','$\\emptyset$','$x \\neq 0$','$x > 0$'], ok:0 },
|
||
{ q:'$x^2 + 2x + 5 < 0$', opts:['$\\emptyset$','$\\mathbb{R}$','$(-1;\\,1)$','$x < -1$'], ok:0 },
|
||
{ q:'$-x^2 + 4 \\geq 0$', opts:['$[-2;\\,2]$','$(-\\infty;\\,-2] \\cup [2;\\,+\\infty)$','$(-2;\\,2)$','$\\mathbb{R}$'], ok:0 },
|
||
{ q:'$x^2 - 9 < 0$', opts:['$(-3;\\,3)$','$[-3;\\,3]$','$(-\\infty;\\,-3) \\cup (3;\\,+\\infty)$','$\\emptyset$'], ok:0 },
|
||
];
|
||
let cur = null, i = 1, score = 0, shuffled = [];
|
||
function show(){
|
||
cur = shuffled[i-1];
|
||
document.getElementById('p17t-i').textContent = i;
|
||
document.getElementById('p17t-task').innerHTML = '$' + cur.q + '$';
|
||
renderMath(document.getElementById('p17t-task'));
|
||
const opts = document.getElementById('p17t-opts'); opts.innerHTML = '';
|
||
cur.opts.forEach((o, k)=>{
|
||
const b = document.createElement('button');
|
||
b.className = 'btn'; b.innerHTML = o; b.style.cssText = 'text-align:left';
|
||
b.addEventListener('click', ()=>{
|
||
const fb = document.getElementById('p17t-fb'); fb.style.display = 'block';
|
||
if(k === cur.ok){ score++; b.classList.add('ok'); feedback(fb, true, '✓'); }
|
||
else { b.classList.add('fail'); feedback(fb, false, 'Не то.'); }
|
||
document.getElementById('p17t-score').textContent = score;
|
||
if(i >= shuffled.length){ setTimeout(()=>{ feedback(fb, score >= 4, 'Итог: ' + score + '/' + shuffled.length); if(score >= 4){ achievement('p17_train'); bumpProgress('p17', 16); confetti(); } }, 700); }
|
||
else { i++; setTimeout(show, 900); }
|
||
});
|
||
opts.appendChild(b);
|
||
});
|
||
renderMath(opts);
|
||
document.getElementById('p17t-fb').style.display = 'none';
|
||
}
|
||
document.getElementById('p17t-start').addEventListener('click', ()=>{ i=1; score=0; document.getElementById('p17t-score').textContent = 0; shuffled = [...tasks].sort(()=>Math.random()-0.5); show(); });
|
||
})();
|
||
|
||
/* INIT 4 — Drag парабола ↔ ответ */
|
||
(function(){
|
||
const items = [
|
||
{ id:1, html:'$a > 0$, есть 2 корня, неравенство $\\geq 0$', cat:'out' },
|
||
{ id:2, html:'$a > 0$, есть 2 корня, неравенство $\\leq 0$', cat:'in' },
|
||
{ id:3, html:'$a < 0$, есть 2 корня, неравенство $\\geq 0$', cat:'in' },
|
||
{ id:4, html:'$a > 0$, $D < 0$, неравенство $\\geq 0$', cat:'all' },
|
||
{ id:5, html:'$a > 0$, $D < 0$, неравенство $\\leq 0$', cat:'none' },
|
||
{ id:6, html:'$a < 0$, $D < 0$, неравенство $\\geq 0$', cat:'none' },
|
||
{ id:7, html:'$a < 0$, $D < 0$, неравенство $\\leq 0$', cat:'all' },
|
||
{ id:8, html:'$a < 0$, есть 2 корня, неравенство $\\leq 0$', cat:'out' },
|
||
];
|
||
const sorter = setupSorter({ poolId:'p17d-pool', cats:['out','in','all','none'], items, scopeSelector:'#p17-body', columnLayout:true });
|
||
document.getElementById('p17d-check').addEventListener('click', ()=>{
|
||
const fb = document.getElementById('p17d-fb'); fb.style.display = 'block';
|
||
if(Object.keys(sorter.placed).length < items.length){ feedback(fb, false, '⚠ Разложите все.'); return; }
|
||
let ok = 0; items.forEach(it=>{ if(sorter.placed[it.id] === it.cat) ok++; });
|
||
if(ok === items.length){ feedback(fb, true, '✓ Все верно!'); achievement('p17_drag'); bumpProgress('p17', 14); confetti(); }
|
||
else feedback(fb, false, 'Верно ' + ok + ' из ' + items.length);
|
||
});
|
||
document.getElementById('p17d-reset').addEventListener('click', ()=>{ sorter.reset(); document.getElementById('p17d-fb').style.display='none'; });
|
||
})();
|
||
|
||
/* INIT 5 — Знаки на интервалах (клик) */
|
||
(function(){
|
||
// 3 интервала: (-inf, 1), (1, 3), (3, inf). Должны быть: +, -, +.
|
||
const correct = ['+','-','+'];
|
||
const labels = ['$x < 1$', '$1 < x < 3$', '$x > 3$'];
|
||
const lineE = document.getElementById('p17z-line');
|
||
function build(){
|
||
let s = '<div style="display:flex;gap:8px;flex-wrap:wrap;justify-content:center;align-items:center;font-size:1.1rem">';
|
||
s += '<span>$-\\infty$</span>';
|
||
[0, 1, 2].forEach(i => {
|
||
s += '<button class="btn p17z-int" data-i="' + i + '" data-sign="" style="min-width:90px">' + labels[i] + '<br><span class="sg">?</span></button>';
|
||
if(i < 2) s += '<span style="font-family:JetBrains Mono,monospace;font-size:1.2rem;color:var(--sec-acc-d)">●' + (i === 0 ? ' 1 ●' : ' 3 ●').replace('●','') + '</span>';
|
||
});
|
||
s += '<span>$+\\infty$</span></div>';
|
||
lineE.innerHTML = s;
|
||
renderMath(lineE);
|
||
document.querySelectorAll('.p17z-int').forEach(btn => {
|
||
btn.addEventListener('click', ()=>{
|
||
const cur = btn.dataset.sign;
|
||
const next = cur === '' ? '+' : cur === '+' ? '-' : '';
|
||
btn.dataset.sign = next;
|
||
btn.querySelector('.sg').textContent = next || '?';
|
||
btn.style.background = next === '+' ? 'rgba(16,185,129,.2)' : next === '-' ? 'rgba(239,68,68,.2)' : '';
|
||
checkAll();
|
||
});
|
||
});
|
||
}
|
||
function checkAll(){
|
||
const all = [...document.querySelectorAll('.p17z-int')];
|
||
const got = all.map(b => b.dataset.sign);
|
||
if(got.every((s, i) => s === correct[i])){
|
||
const fb = document.getElementById('p17z-fb'); fb.style.display = 'block';
|
||
feedback(fb, true, '✓ Точно! Между корнями знак минус (так как $a > 0$).');
|
||
achievement('p17_intervals'); bumpProgress('p17', 14); confetti();
|
||
}
|
||
}
|
||
build();
|
||
})();
|
||
}
|
||
|
||
/* ============================================================
|
||
§ 18 — ДРОБНО-РАЦИОНАЛЬНЫЕ НЕРАВЕНСТВА
|
||
============================================================ */
|
||
function buildP18(){
|
||
const box = document.getElementById('p18-body');
|
||
let html = '';
|
||
|
||
html += makeCard('repeat','Повторение',null,`
|
||
<ul style="margin-left:18px;line-height:1.7">
|
||
<li>Метод интервалов из § 17 — корни, знаки на интервалах.</li>
|
||
<li>ОДЗ (Глава 2 § 12): знаменатель $\\neq 0$.</li>
|
||
<li>$\\dfrac{a}{b}$ имеет тот же знак, что и $a \\cdot b$.</li>
|
||
</ul>`);
|
||
|
||
html += makeCard('theory','Что такое дробно-рациональное неравенство','18.1',`
|
||
<p><b>Дробно-рациональное</b> — неравенство, в котором есть дроби с переменной в знаменателе:</p>
|
||
<div style="background:var(--sec-acc-soft);border-radius:10px;padding:10px;margin:8px 0;text-align:center;font-size:1.1rem">$$\\dfrac{f(x)}{g(x)} \\gtrless 0$$</div>
|
||
<p><b>Ключевая идея:</b> знак дроби определяется произведением знаков числителя и знаменателя. Метод интервалов работает, но точки, где знаменатель $= 0$, всегда <b>выколотые</b> (не входят в ОДЗ).</p>`);
|
||
|
||
html += makeCard('algo','Алгоритм','18.2',`
|
||
<ol style="margin-left:18px;line-height:1.9">
|
||
<li>Привести к виду $\\dfrac{f(x)}{g(x)} \\gtrless 0$ (всё в одну часть, общий знаменатель).</li>
|
||
<li>Найти нули числителя $f(x) = 0$ и знаменателя $g(x) = 0$.</li>
|
||
<li>Отметить точки на прямой: нули $f$ — закрашены (если знак $\\geq, \\leq$), нули $g$ — всегда выколотые.</li>
|
||
<li>Определить знак выражения на каждом интервале.</li>
|
||
<li>Выбрать интервалы, удовлетворяющие неравенству.</li>
|
||
</ol>`);
|
||
|
||
html += makeCard('example','Пример',null,`
|
||
<p><b>Решим:</b> $\\dfrac{x - 1}{x + 2} \\geq 0$.</p>
|
||
<p>Нули: числитель $x = 1$ (входит), знаменатель $x = -2$ (выколот).</p>
|
||
<p>Знаки на интервалах: $(-\\infty;\\,-2)$ — $+$ (минус на минус), $(-2;\\,1)$ — $-$ (плюс на минус), $(1;\\,+\\infty)$ — $+$.</p>
|
||
<p><b>Ответ:</b> $x \\in (-\\infty;\\,-2) \\cup [1;\\,+\\infty)$.</p>`);
|
||
|
||
/* INT 1 — Пошаговый решатель */
|
||
html += widget('Пошаговый решатель дроби','INTERACT 1','Решаем $\\dfrac{x-a}{x-b} \\geq 0$ пошагово.',`
|
||
<div style="display:flex;gap:8px;flex-wrap:wrap;align-items:center;justify-content:center;margin-bottom:10px;font-size:1.05rem">
|
||
<span>$\\dfrac{x -$</span><input type="number" id="p18s-a" value="1" class="tinp" style="width:55px"><span>$}{x -$</span><input type="number" id="p18s-b" value="-2" class="tinp" style="width:55px"><span>$} \\geq 0$</span>
|
||
<button class="btn primary" id="p18s-go">Старт</button>
|
||
<button class="btn" id="p18s-next" style="display:none">Дальше</button>
|
||
<button class="btn" id="p18s-reset" style="display:none">Сначала</button>
|
||
</div>
|
||
<div id="p18s-stage" style="padding:14px;background:var(--card-soft);border-radius:10px;min-height:80px"></div>`);
|
||
|
||
/* INT 2 — Тренажёр */
|
||
html += widget('Тренажёр дробно-рациональных','INTERACT 2','Выбери правильный ответ.',`
|
||
<div class="score-display"><span>Задача <b id="p18t-i">1</b> / 6</span><span>Очки: <b id="p18t-score">0</b></span></div>
|
||
<div id="p18t-task" style="font-size:1.2rem;text-align:center;padding:16px;background:var(--sec-acc-soft);border-radius:10px;margin-bottom:10px"></div>
|
||
<div id="p18t-opts" style="display:flex;flex-direction:column;gap:6px"></div>
|
||
<div class="feedback" id="p18t-fb" style="display:none;margin-top:10px"></div>
|
||
<button class="btn primary" id="p18t-start" style="margin-top:10px">Начать</button>`);
|
||
|
||
/* INT 3 — Найди ОДЗ */
|
||
html += widget('Найди ОДЗ','INTERACT 3','По выражению определи запрещённые точки (где знаменатель = 0).',`
|
||
<div class="score-display"><span>Раунд <b id="p18o-i">1</b> / 5</span><span>Очки: <b id="p18o-score">0</b></span></div>
|
||
<div id="p18o-task" style="font-size:1.15rem;text-align:center;padding:16px;background:var(--sec-acc-soft);border-radius:10px;margin-bottom:10px"></div>
|
||
<div style="display:flex;gap:8px;justify-content:center;flex-wrap:wrap">
|
||
<input type="text" id="p18o-inp" placeholder="x = ... ; x = ..." class="tinp" style="width:220px">
|
||
<button class="btn primary" id="p18o-go">Ответ</button>
|
||
</div>
|
||
<div class="feedback" id="p18o-fb" style="display:none;margin-top:10px"></div>
|
||
<button class="btn primary" id="p18o-start" style="margin-top:10px">Начать</button>`);
|
||
|
||
/* INT 4 — Drag: закрашена/выколота */
|
||
html += widget('Закрашена или выколота?','INTERACT 4','Отнеси каждую точку к нужной категории.',`
|
||
${DND_HINT_HTML}
|
||
<div id="p18d-pool"></div>
|
||
<div class="drop-row" style="display:grid;grid-template-columns:1fr 1fr;gap:10px">
|
||
<div class="drop-box"><h5>Закрашена (входит)</h5><div class="drop-items" data-cat="full"></div></div>
|
||
<div class="drop-box"><h5>Выколота (не входит)</h5><div class="drop-items" data-cat="open"></div></div>
|
||
</div>
|
||
<div class="actions"><button class="btn primary" id="p18d-check">Проверить</button><button class="btn" id="p18d-reset">Сначала</button></div>
|
||
<div class="feedback" id="p18d-fb" style="display:none"></div>`);
|
||
|
||
html += makeCard('class','Класс — решите',null,`
|
||
<ol style="margin-left:18px;line-height:1.8">
|
||
<li>$\\dfrac{x - 2}{x + 3} > 0$</li>
|
||
<li>$\\dfrac{x + 1}{x - 4} \\leq 0$</li>
|
||
<li>$\\dfrac{x^2 - 4}{x - 1} \\geq 0$</li>
|
||
</ol>`);
|
||
|
||
html += makeCard('home','Домашка',null,`
|
||
<ol style="margin-left:18px;line-height:1.8">
|
||
<li>$\\dfrac{x - 3}{x + 1} \\geq 0$</li>
|
||
<li>$\\dfrac{x + 5}{x^2 - 4} > 0$</li>
|
||
<li>$\\dfrac{x^2 - 9}{x^2 + 2x - 8} \\leq 0$</li>
|
||
</ol>`);
|
||
|
||
html += secNav('p17', 'final3');
|
||
box.innerHTML = html;
|
||
if(window.renderMathInElement) setTimeout(()=>renderMath(box), 0);
|
||
|
||
/* INIT 1 — Пошаговый */
|
||
(function(){
|
||
const stage = document.getElementById('p18s-stage');
|
||
const goBtn = document.getElementById('p18s-go'), nextBtn = document.getElementById('p18s-next'), resetBtn = document.getElementById('p18s-reset');
|
||
let steps = [], idx = 0, awarded = false;
|
||
function build(a, b){
|
||
const arr = [];
|
||
arr.push('<b>Дано:</b> $\\dfrac{x - (' + a + ')}{x - (' + b + ')} \\geq 0$');
|
||
arr.push('<b>Шаг 1.</b> Нули числителя: $x = ' + a + '$ (входит, $\\geq$). Нули знаменателя: $x = ' + b + '$ (всегда выколот).');
|
||
const lo = Math.min(a, b), hi = Math.max(a, b);
|
||
arr.push('<b>Шаг 2.</b> Отметим на прямой: ' + (a < b ? ('$' + a + '$ (закрашена), $' + b + '$ (выколота)') : ('$' + b + '$ (выколота), $' + a + '$ (закрашена)')) + '.');
|
||
arr.push('<b>Шаг 3.</b> Знаки: подставим $x = ' + (hi + 1) + '$ — числитель $' + (hi + 1 - a) + ' > 0$, знаменатель $' + (hi + 1 - b) + ' > 0$, дробь $> 0$. Чередуем знаки от правого края: $+,\\ -,\\ +$.');
|
||
if(a < b){
|
||
arr.push('<b>Шаг 4.</b> $\\geq 0$ — берём $+$. Это $(-\\infty;\\,' + a + ']$ и $(' + b + ';\\,+\\infty)$.');
|
||
arr.push('<b>Ответ:</b> $x \\in (-\\infty;\\,' + a + '] \\cup (' + b + ';\\,+\\infty)$');
|
||
} else if(a > b){
|
||
arr.push('<b>Шаг 4.</b> $\\geq 0$ — берём $+$. Это $(-\\infty;\\,' + b + ')$ и $[' + a + ';\\,+\\infty)$.');
|
||
arr.push('<b>Ответ:</b> $x \\in (-\\infty;\\,' + b + ') \\cup [' + a + ';\\,+\\infty)$');
|
||
} else {
|
||
arr.push('<b>Шаг 4.</b> $a = b$ — дробь равна 1 везде, кроме $x = ' + a + '$ (выколота). $1 \\geq 0$. Ответ: $x \\neq ' + a + '$.');
|
||
}
|
||
return arr;
|
||
}
|
||
function render(){
|
||
stage.innerHTML = steps.slice(0, idx + 1).map(s => `<div style="margin:6px 0;padding:9px 12px;background:var(--card);border-left:3px solid var(--sec-acc);border-radius:7px;animation:fadeIn .3s ease">${s}</div>`).join('');
|
||
renderMath(stage);
|
||
if(idx >= steps.length - 1){
|
||
nextBtn.disabled = true; nextBtn.textContent = 'Готово';
|
||
if(!awarded){ awarded = true; achievement('p18_solver'); bumpProgress('p18', 16); confetti(); }
|
||
} else { nextBtn.disabled = false; nextBtn.textContent = 'Дальше (' + (idx + 1) + '/' + steps.length + ')'; }
|
||
}
|
||
goBtn.addEventListener('click', ()=>{
|
||
const a = +document.getElementById('p18s-a').value, b = +document.getElementById('p18s-b').value;
|
||
steps = build(a, b); idx = 0; awarded = false;
|
||
goBtn.style.display = 'none'; nextBtn.style.display = ''; resetBtn.style.display = '';
|
||
render();
|
||
});
|
||
nextBtn.addEventListener('click', ()=>{ if(idx < steps.length - 1){ idx++; render(); } });
|
||
resetBtn.addEventListener('click', ()=>{ idx = 0; stage.innerHTML = ''; goBtn.style.display = ''; nextBtn.style.display = 'none'; resetBtn.style.display = 'none'; });
|
||
})();
|
||
|
||
/* INIT 2 — Тренажёр */
|
||
(function(){
|
||
const tasks = [
|
||
{ q:'$\\dfrac{x - 3}{x + 1} > 0$', opts:['$(-\\infty;\\,-1) \\cup (3;\\,+\\infty)$','$(-1;\\,3)$','$[-1;\\,3]$','$\\emptyset$'], ok:0 },
|
||
{ q:'$\\dfrac{x + 2}{x - 5} \\leq 0$', opts:['$[-2;\\,5)$','$(-2;\\,5)$','$[-2;\\,5]$','$(-\\infty;\\,-2] \\cup (5;\\,+\\infty)$'], ok:0 },
|
||
{ q:'$\\dfrac{1}{x - 4} > 0$', opts:['$(4;\\,+\\infty)$','$(-\\infty;\\,4)$','$\\mathbb{R} \\setminus \\{4\\}$','$[4;\\,+\\infty)$'], ok:0 },
|
||
{ q:'$\\dfrac{x - 1}{x + 3} \\geq 0$', opts:['$(-\\infty;\\,-3) \\cup [1;\\,+\\infty)$','$[-3;\\,1]$','$(-3;\\,1)$','$\\mathbb{R}$'], ok:0 },
|
||
{ q:'$\\dfrac{x + 6}{x} < 0$', opts:['$(-6;\\,0)$','$[-6;\\,0)$','$(-\\infty;\\,-6)$','$(0;\\,+\\infty)$'], ok:0 },
|
||
{ q:'$\\dfrac{x - 2}{x - 2} > 0$', opts:['$\\mathbb{R} \\setminus \\{2\\}$','$\\mathbb{R}$','$\\{2\\}$','$\\emptyset$'], ok:0 },
|
||
];
|
||
let cur = null, i = 1, score = 0, shuffled = [];
|
||
function show(){
|
||
cur = shuffled[i-1];
|
||
document.getElementById('p18t-i').textContent = i;
|
||
document.getElementById('p18t-task').innerHTML = '$' + cur.q.replace(/^\$|\$$/g,'') + '$';
|
||
renderMath(document.getElementById('p18t-task'));
|
||
const opts = document.getElementById('p18t-opts'); opts.innerHTML = '';
|
||
cur.opts.forEach((o, k)=>{
|
||
const b = document.createElement('button');
|
||
b.className = 'btn'; b.innerHTML = o; b.style.cssText = 'text-align:left';
|
||
b.addEventListener('click', ()=>{
|
||
const fb = document.getElementById('p18t-fb'); fb.style.display = 'block';
|
||
if(k === cur.ok){ score++; b.classList.add('ok'); feedback(fb, true, '✓'); }
|
||
else { b.classList.add('fail'); feedback(fb, false, 'Не то.'); }
|
||
document.getElementById('p18t-score').textContent = score;
|
||
if(i >= shuffled.length){ setTimeout(()=>{ feedback(fb, score >= 4, 'Итог: ' + score + '/' + shuffled.length); if(score >= 4){ achievement('p18_intervals'); bumpProgress('p18', 16); confetti(); } }, 700); }
|
||
else { i++; setTimeout(show, 900); }
|
||
});
|
||
opts.appendChild(b);
|
||
});
|
||
renderMath(opts);
|
||
document.getElementById('p18t-fb').style.display = 'none';
|
||
}
|
||
document.getElementById('p18t-start').addEventListener('click', ()=>{ i=1; score=0; document.getElementById('p18t-score').textContent = 0; shuffled = [...tasks].sort(()=>Math.random()-0.5); show(); });
|
||
})();
|
||
|
||
/* INIT 3 — ОДЗ */
|
||
(function(){
|
||
const tasks = [
|
||
{ q:'$\\dfrac{x + 1}{x - 3}$', ans:[3] },
|
||
{ q:'$\\dfrac{x^2 + 1}{x(x - 5)}$', ans:[0, 5] },
|
||
{ q:'$\\dfrac{1}{x^2 - 4}$', ans:[-2, 2] },
|
||
{ q:'$\\dfrac{x + 3}{x^2 - 6x + 9}$', ans:[3] },
|
||
{ q:'$\\dfrac{1}{x} + \\dfrac{1}{x + 1}$', ans:[-1, 0] },
|
||
];
|
||
let cur = null, i = 1, score = 0;
|
||
function show(){
|
||
cur = tasks[i-1];
|
||
document.getElementById('p18o-i').textContent = i;
|
||
document.getElementById('p18o-task').innerHTML = 'Запрещённые точки для ' + cur.q;
|
||
renderMath(document.getElementById('p18o-task'));
|
||
document.getElementById('p18o-inp').value = '';
|
||
document.getElementById('p18o-fb').style.display = 'none';
|
||
}
|
||
document.getElementById('p18o-go').addEventListener('click', ()=>{
|
||
const fb = document.getElementById('p18o-fb'); fb.style.display = 'block';
|
||
const u = document.getElementById('p18o-inp').value.replace(/[xX\s=]/g, '').split(/[;,]+/).filter(Boolean).map(Number).sort((a,b)=>a-b);
|
||
const a = [...cur.ans].sort((p,q)=>p-q);
|
||
const ok = u.length === a.length && a.every((v, k) => v === u[k]);
|
||
if(ok){ score++; feedback(fb, true, '✓'); }
|
||
else feedback(fb, false, 'Правильно: ' + a.join(', '));
|
||
document.getElementById('p18o-score').textContent = score;
|
||
if(i >= tasks.length){ setTimeout(()=>{ feedback(fb, score >= 3, 'Итог: ' + score + '/' + tasks.length); if(score >= 3){ achievement('p18_odz'); bumpProgress('p18', 14); confetti(); } }, 700); }
|
||
else { i++; setTimeout(show, 900); }
|
||
});
|
||
document.getElementById('p18o-start').addEventListener('click', ()=>{ i=1; score=0; document.getElementById('p18o-score').textContent = 0; show(); });
|
||
})();
|
||
|
||
/* INIT 4 — Drag закрашена/выколота */
|
||
(function(){
|
||
const items = [
|
||
{ id:1, html:'нуль числителя при $\\geq 0$', cat:'full' },
|
||
{ id:2, html:'нуль знаменателя', cat:'open' },
|
||
{ id:3, html:'нуль числителя при $> 0$ строго', cat:'open' },
|
||
{ id:4, html:'нуль числителя при $\\leq 0$', cat:'full' },
|
||
{ id:5, html:'точка вне ОДЗ', cat:'open' },
|
||
{ id:6, html:'граница в системе с $\\leq$', cat:'full' },
|
||
{ id:7, html:'граница в системе с $<$', cat:'open' },
|
||
{ id:8, html:'нуль знаменателя — всегда', cat:'open' },
|
||
];
|
||
const sorter = setupSorter({ poolId:'p18d-pool', cats:['full','open'], items, scopeSelector:'#p18-body', columnLayout:true });
|
||
document.getElementById('p18d-check').addEventListener('click', ()=>{
|
||
const fb = document.getElementById('p18d-fb'); fb.style.display = 'block';
|
||
if(Object.keys(sorter.placed).length < items.length){ feedback(fb, false, '⚠ Разложите все.'); return; }
|
||
let ok = 0; items.forEach(it=>{ if(sorter.placed[it.id] === it.cat) ok++; });
|
||
if(ok === items.length){ feedback(fb, true, '✓ Все верно!'); achievement('p18_odz'); bumpProgress('p18', 14); confetti(); }
|
||
else feedback(fb, false, 'Верно ' + ok + ' из ' + items.length);
|
||
});
|
||
document.getElementById('p18d-reset').addEventListener('click', ()=>{ sorter.reset(); document.getElementById('p18d-fb').style.display='none'; });
|
||
})();
|
||
}
|
||
function buildFinal3stub(){ buildFinal3(); }
|
||
function buildFinal3(){
|
||
const box = document.getElementById('final3-body');
|
||
let html = '';
|
||
|
||
html += `<div class="card"><div class="card-header"><div class="card-icon theory">${ICONS.theory}</div><div class="card-title">Поздравляем!</div></div><div class="card-body">
|
||
<p>Вы прошли 6 параграфов главы «Неравенства с одной переменной». В финале вас ждут <b>7 боссов</b> — по одному на каждый параграф и один общий. Победите всех — получите титул «Чемпион неравенств».</p>
|
||
</div></div>`;
|
||
|
||
html += widget('Боссы главы 3','BOSS ARENA','Каждый босс — 5 заданий. Победите всех 7, чтобы открыть финальный титул.',`
|
||
<div id="boss-grid" style="display:grid;grid-template-columns:repeat(auto-fit,minmax(150px,1fr));gap:10px"></div>
|
||
<div id="boss-arena" style="margin-top:14px;display:none">
|
||
<div id="boss-head" style="display:flex;align-items:center;gap:14px;padding:14px;background:linear-gradient(135deg,#1f2937,#0f172a);border-radius:10px;color:#fff;margin-bottom:10px">
|
||
<div id="boss-emoji" style="font-size:2.4rem;line-height:1">★</div>
|
||
<div style="flex:1">
|
||
<div id="boss-name" style="font-family:'Unbounded',sans-serif;font-weight:800;font-size:1.05rem"></div>
|
||
<div id="boss-subname" style="font-size:.85rem;opacity:.75"></div>
|
||
<div class="hp-bar" style="margin-top:6px;background:rgba(255,255,255,.15);height:8px;border-radius:4px;overflow:hidden"><div id="boss-hp" style="height:100%;background:linear-gradient(90deg,#10b981,#fbbf24,#ef4444);width:100%;transition:width .5s"></div></div>
|
||
</div>
|
||
<button class="btn" id="boss-quit" style="background:rgba(255,255,255,.15);color:#fff;border-color:transparent">Выйти</button>
|
||
</div>
|
||
<div id="boss-task" style="padding:16px;background:var(--card);border:1.5px solid var(--sec-acc);border-radius:10px;min-height:120px"></div>
|
||
<div id="boss-opts" style="display:flex;flex-direction:column;gap:6px;margin-top:10px"></div>
|
||
<div id="boss-fb" class="feedback" style="display:none;margin-top:10px"></div>
|
||
</div>`);
|
||
|
||
html += widget('Увлекательная математика','BONUS','3 факта про неравенства.',`
|
||
<details class="spoiler" style="margin-bottom:8px"><summary>Почему меняется знак при умножении на отрицательное?</summary>
|
||
<div class="spoiler-body">Геометрически: умножение на $-1$ — это отражение числовой прямой относительно нуля. То, что было правее (больше), теперь оказалось левее (меньше). Поэтому «больше» становится «меньше».</div>
|
||
</details>
|
||
<details class="spoiler" style="margin-bottom:8px"><summary>Кто придумал знаки $<$ и $>$?</summary>
|
||
<div class="spoiler-body">Английский математик Томас Хэрриот в 1631 году предложил эти знаки. Левый конец — узкий, правый — широкий: с маленькой стороны меньшее, с широкой — большее. Знак <i>точка слева, точка справа</i> наглядно показывает направление сравнения.</div>
|
||
</details>
|
||
<details class="spoiler"><summary>Неравенство Коши</summary>
|
||
<div class="spoiler-body">Знаменитое неравенство: $\\dfrac{a + b}{2} \\geq \\sqrt{ab}$ для $a, b \\geq 0$ — среднее арифметическое не меньше среднего геометрического. Равенство — когда $a = b$. Это краеугольный камень многих доказательств в высшей математике.</div>
|
||
</details>`);
|
||
|
||
html += widget('Финальная практика','PRACTICE','Случайные задачи из всей главы.',`
|
||
<div class="score-display"><span>Решено: <b id="prac-i">0</b></span><span>Правильно: <b id="prac-score">0</b></span><span>Серия: <b id="prac-streak">0</b></span></div>
|
||
<div id="prac-task" style="padding:14px;background:var(--sec-acc-soft);border-radius:10px;font-size:1.05rem;line-height:1.6;margin-bottom:10px"></div>
|
||
<div style="display:flex;gap:8px;flex-wrap:wrap;align-items:center;justify-content:center">
|
||
<input type="text" id="prac-inp" placeholder="ответ" class="tinp" style="width:200px">
|
||
<button class="btn primary" id="prac-go">Ответ</button>
|
||
<button class="btn" id="prac-next">Следующая</button>
|
||
</div>
|
||
<div class="feedback" id="prac-fb" style="display:none;margin-top:10px"></div>`);
|
||
|
||
html += `<div class="card"><div class="card-header"><div class="card-icon home">${ICONS.home}</div><div class="card-title">Сертификат прохождения</div></div><div class="card-body">
|
||
<p>Как вы оцените своё знание неравенств?</p>
|
||
<div id="cert-state" style="margin-top:10px;font-size:.9rem;color:var(--muted)"></div>
|
||
</div></div>`;
|
||
|
||
html += secNav('p18', null);
|
||
box.innerHTML = html;
|
||
if(window.renderMathInElement) setTimeout(()=>renderMath(box), 0);
|
||
|
||
/* BOSSES */
|
||
const BOSSES = [
|
||
{ id:'b1', name:'Хранитель сравнения', sub:'§ 13 · свойства неравенств', icon:'<>', hp:5, tasks:[
|
||
{ t:'select', q:'Что больше: $-7$ или $-3$?', opts:['$-3$','$-7$','равны','зависит'], ok:0 },
|
||
{ t:'yesno', q:'При умножении на $-2$ знак неравенства меняется.', ok:true },
|
||
{ t:'input', q:'Дано $a > b$. Найдите $c$ такое, что $a + c$ и $b + c$ остаются в том же порядке. (введите любое число, например 5)', ans:[5] },
|
||
{ t:'select', q:'$a > b$, $b > c$. Тогда:', opts:['$a > c$','$a < c$','$a = c$','зависит'], ok:0 },
|
||
{ t:'yesno', q:'Из $a > b > 0$ следует $a^2 > b^2$.', ok:true },
|
||
]},
|
||
{ id:'b2', name:'Алхимик границ', sub:'§ 14 · оценка значений', icon:'±', hp:5, tasks:[
|
||
{ t:'input', q:'$1 \\leq x \\leq 3$, $2 \\leq y \\leq 5$. Найдите верхнюю границу $x + y$.', ans:[8] },
|
||
{ t:'input', q:'Те же границы. Найдите нижнюю границу $x - y$.', ans:[-4] },
|
||
{ t:'input', q:'Те же границы (все $>0$). Найдите верхнюю границу $xy$.', ans:[15] },
|
||
{ t:'yesno', q:'Можно сложить $a > b$ и $c < d$, чтобы получить $a + c > b + d$.', ok:false },
|
||
{ t:'select', q:'Если $0 < a < b$ и $0 < c < d$, то:', opts:['$ac < bd$','$ac > bd$','$ac = bd$','неизвестно'], ok:0 },
|
||
]},
|
||
{ id:'b3', name:'Архитектор промежутков', sub:'§ 15 · линейные', icon:'[]', hp:5, tasks:[
|
||
{ t:'select', q:'Решение $2x - 4 > 0$:', opts:['$(2;\\,+\\infty)$','$[2;\\,+\\infty)$','$(-\\infty;\\,2)$','$\\{2\\}$'], ok:0 },
|
||
{ t:'select', q:'Решение $-3x \\geq 9$:', opts:['$(-\\infty;\\,-3]$','$[-3;\\,+\\infty)$','$(-\\infty;\\,3]$','$[3;\\,+\\infty)$'], ok:0 },
|
||
{ t:'yesno', q:'Промежуток $[2;\\,5)$ включает точку $5$.', ok:false },
|
||
{ t:'select', q:'Запись $x > 4$ соответствует:', opts:['$(4;\\,+\\infty)$','$[4;\\,+\\infty)$','$(-\\infty;\\,4)$','$(-\\infty;\\,4]$'], ok:0 },
|
||
{ t:'input', q:'Корень неравенства $5x = 25$ (граница). Чему равен $x$?', ans:[5] },
|
||
]},
|
||
{ id:'b4', name:'Дирижёр пересечений', sub:'§ 16 · системы', icon:'∩', hp:5, tasks:[
|
||
{ t:'select', q:'$\\begin{cases} x > 2 \\\\ x \\leq 5 \\end{cases}$:', opts:['$(2;\\,5]$','$[2;\\,5)$','$(2;\\,5)$','$\\emptyset$'], ok:0 },
|
||
{ t:'yesno', q:'Система $\\begin{cases} x > 5 \\\\ x < 3 \\end{cases}$ имеет решение.', ok:false },
|
||
{ t:'select', q:'Совокупность $\\left[\\begin{array}{l} x < 0 \\\\ x \\geq 4 \\end{array}\\right.$:', opts:['$(-\\infty;\\,0) \\cup [4;\\,+\\infty)$','$[0;\\,4)$','$\\emptyset$','$\\mathbb{R}$'], ok:0 },
|
||
{ t:'yesno', q:'Система использует логическое «И», совокупность — «ИЛИ».', ok:true },
|
||
{ t:'select', q:'Пересечение $[1;\\,5]$ и $[3;\\,8]$:', opts:['$[3;\\,5]$','$[1;\\,8]$','$\\emptyset$','$[1;\\,3]$'], ok:0 },
|
||
]},
|
||
{ id:'b5', name:'Мастер параболы', sub:'§ 17 · квадратные', icon:'∪', hp:5, tasks:[
|
||
{ t:'select', q:'Решение $x^2 - 4 > 0$:', opts:['$(-\\infty;\\,-2) \\cup (2;\\,+\\infty)$','$(-2;\\,2)$','$[-2;\\,2]$','$\\emptyset$'], ok:0 },
|
||
{ t:'select', q:'$x^2 + 1 > 0$:', opts:['$\\mathbb{R}$','$\\emptyset$','$x \\neq 0$','$x > 0$'], ok:0 },
|
||
{ t:'yesno', q:'При $D < 0$ и $a > 0$ выражение $ax^2 + bx + c$ всегда положительно.', ok:true },
|
||
{ t:'select', q:'$x^2 - 5x + 6 \\leq 0$ имеет решение:', opts:['$[2;\\,3]$','$(2;\\,3)$','$(-\\infty;\\,2) \\cup (3;\\,+\\infty)$','$\\emptyset$'], ok:0 },
|
||
{ t:'input', q:'Сколько решений у $x^2 + 9 < 0$ (количество, 0 если нет)?', ans:[0] },
|
||
]},
|
||
{ id:'b6', name:'Властелин ОДЗ', sub:'§ 18 · дробно-рац.', icon:'1/x', hp:5, tasks:[
|
||
{ t:'input', q:'Запрещённая точка для $\\dfrac{1}{x - 5}$:', ans:[5] },
|
||
{ t:'select', q:'$\\dfrac{x - 1}{x + 2} > 0$:', opts:['$(-\\infty;\\,-2) \\cup (1;\\,+\\infty)$','$(-2;\\,1)$','$[-2;\\,1]$','$\\emptyset$'], ok:0 },
|
||
{ t:'yesno', q:'Точка, где знаменатель равен нулю, ВСЕГДА выколотая.', ok:true },
|
||
{ t:'select', q:'$\\dfrac{x + 3}{x - 4} \\leq 0$:', opts:['$[-3;\\,4)$','$(-3;\\,4)$','$[-3;\\,4]$','$\\emptyset$'], ok:0 },
|
||
{ t:'yesno', q:'$\\dfrac{1}{x} > 0$ верно при любом $x \\neq 0$.', ok:false },
|
||
]},
|
||
{ id:'b7', name:'Чемпион неравенств', sub:'Финал · вся глава', icon:'★', hp:7, tasks:[
|
||
{ t:'input', q:'$4x + 3 > 11$. Граничное значение $x$:', ans:[2] },
|
||
{ t:'select', q:'Знак $a > b$ после умножения на $-5$:', opts:['Меняется','Не меняется','Зависит','Исчезает'], ok:0 },
|
||
{ t:'select', q:'$1 \\leq x \\leq 4$, $2 \\leq y \\leq 6$. Верхняя граница $xy$:', opts:['$24$','$8$','$12$','$10$'], ok:0 },
|
||
{ t:'select', q:'$\\begin{cases} x \\geq 0 \\\\ x \\leq 10 \\end{cases}$:', opts:['$[0;\\,10]$','$(0;\\,10)$','$\\mathbb{R}$','$\\emptyset$'], ok:0 },
|
||
{ t:'select', q:'$x^2 - 9 < 0$:', opts:['$(-3;\\,3)$','$[-3;\\,3]$','$(-\\infty;\\,-3) \\cup (3;\\,+\\infty)$','$\\emptyset$'], ok:0 },
|
||
{ t:'input', q:'Запрещённая точка для $\\dfrac{x + 1}{x - 7}$:', ans:[7] },
|
||
{ t:'yesno', q:'Метод интервалов работает только для квадратных неравенств.', ok:false },
|
||
]},
|
||
];
|
||
|
||
const BOSS_STATE = (function(){
|
||
try { return JSON.parse(localStorage.getItem('algebra8_ch3_bosses') || '{}'); } catch(e){ return {}; }
|
||
})();
|
||
function saveBosses(){ try{ localStorage.setItem('algebra8_ch3_bosses', JSON.stringify(BOSS_STATE)); }catch(e){} }
|
||
|
||
function renderBossGrid(){
|
||
const g = document.getElementById('boss-grid');
|
||
g.innerHTML = '';
|
||
BOSSES.forEach(b=>{
|
||
const won = BOSS_STATE[b.id];
|
||
const c = document.createElement('div');
|
||
c.style.cssText = 'background:' + (won ? 'linear-gradient(135deg,var(--ok-bg),#d1fae5)' : 'var(--card)') + ';border:1.5px solid ' + (won ? 'var(--ok)' : 'var(--border)') + ';border-radius:10px;padding:10px 12px;cursor:pointer;text-align:center;transition:transform .15s,box-shadow .15s';
|
||
c.innerHTML = '<div style="font-size:1.5rem;line-height:1;margin-bottom:4px;font-family:JetBrains Mono,monospace">' + b.icon + '</div><div style="font-family:Unbounded,sans-serif;font-size:.78rem;font-weight:800;color:var(--sec-acc-d)">' + b.name + '</div><div style="font-size:.7rem;color:var(--muted);margin-top:2px">' + b.sub + '</div>' + (won ? '<div style="color:var(--ok);font-weight:700;font-size:.8rem;margin-top:4px">✓ Побеждён</div>' : '<div style="margin-top:4px"><button class="btn small" style="font-size:.72rem">В бой</button></div>');
|
||
c.addEventListener('click', ()=>startBoss(b.id));
|
||
c.addEventListener('mouseover', ()=>{ c.style.transform='translateY(-2px)'; c.style.boxShadow='var(--sh2)'; });
|
||
c.addEventListener('mouseout', ()=>{ c.style.transform=''; c.style.boxShadow=''; });
|
||
g.appendChild(c);
|
||
});
|
||
if(window.renderMathInElement) renderMath(g);
|
||
}
|
||
|
||
let currentBoss = null, taskIdx = 0, hpLeft = 0;
|
||
function startBoss(id){
|
||
const b = BOSSES.find(x => x.id === id);
|
||
if(!b) return;
|
||
currentBoss = b; taskIdx = 0; hpLeft = b.hp;
|
||
document.getElementById('boss-arena').style.display = 'block';
|
||
document.getElementById('boss-name').textContent = b.name;
|
||
document.getElementById('boss-subname').textContent = b.sub;
|
||
document.getElementById('boss-emoji').textContent = b.icon;
|
||
showTask();
|
||
}
|
||
function showTask(){
|
||
const t = currentBoss.tasks[taskIdx];
|
||
if(!t){ winBoss(); return; }
|
||
document.getElementById('boss-hp').style.width = (hpLeft / currentBoss.hp * 100) + '%';
|
||
const tEl = document.getElementById('boss-task');
|
||
tEl.innerHTML = '<div style="font-size:.75rem;color:var(--muted);margin-bottom:6px">Задание ' + (taskIdx + 1) + ' / ' + currentBoss.tasks.length + '</div><div style="font-size:1.05rem">' + t.q + '</div>';
|
||
renderMath(tEl);
|
||
const opts = document.getElementById('boss-opts'); opts.innerHTML = '';
|
||
document.getElementById('boss-fb').style.display = 'none';
|
||
if(t.t === 'select'){
|
||
t.opts.forEach((o, k)=>{
|
||
const b = document.createElement('button');
|
||
b.className = 'btn'; b.innerHTML = o; b.style.cssText = 'text-align:left';
|
||
b.addEventListener('click', ()=>checkAnswer(k === t.ok, b));
|
||
opts.appendChild(b);
|
||
});
|
||
renderMath(opts);
|
||
} else if(t.t === 'yesno'){
|
||
['Да','Нет'].forEach((lab, k)=>{
|
||
const b = document.createElement('button');
|
||
b.className = 'btn'; b.textContent = lab;
|
||
b.addEventListener('click', ()=>checkAnswer((k === 0) === t.ok, b));
|
||
opts.appendChild(b);
|
||
});
|
||
} else if(t.t === 'input'){
|
||
const wrap = document.createElement('div');
|
||
wrap.style.cssText = 'display:flex;gap:8px;align-items:center';
|
||
const inp = document.createElement('input');
|
||
inp.type = 'text'; inp.placeholder = 'ваш ответ'; inp.className = 'tinp'; inp.style.flex = '1';
|
||
const go = document.createElement('button'); go.className = 'btn primary'; go.textContent = 'Ответ';
|
||
go.addEventListener('click', ()=>{
|
||
const u = parseFloat(inp.value.replace(',','.'));
|
||
const ok = t.ans.some(a => Math.abs(u - a) < 1e-6);
|
||
checkAnswer(ok, go);
|
||
});
|
||
inp.addEventListener('keyup', e=>{ if(e.key === 'Enter') go.click(); });
|
||
wrap.appendChild(inp); wrap.appendChild(go); opts.appendChild(wrap);
|
||
}
|
||
}
|
||
function checkAnswer(ok, btn){
|
||
const fb = document.getElementById('boss-fb'); fb.style.display = 'block';
|
||
if(ok){
|
||
if(btn) btn.classList.add('ok');
|
||
feedback(fb, true, '✓ Точное попадание!');
|
||
taskIdx++;
|
||
setTimeout(showTask, 700);
|
||
} else {
|
||
if(btn) btn.classList.add('fail');
|
||
feedback(fb, false, '✗ Промах! Попробуйте ещё.');
|
||
}
|
||
}
|
||
function winBoss(){
|
||
BOSS_STATE[currentBoss.id] = 1; saveBosses();
|
||
achievement('boss_' + currentBoss.id, 'Победил: ' + currentBoss.name);
|
||
bumpProgress('final3', 14); confetti();
|
||
document.getElementById('boss-task').innerHTML = '<div style="text-align:center;padding:20px"><div style="font-size:3rem;font-family:JetBrains Mono,monospace">' + currentBoss.icon + '</div><h3 style="color:var(--ok);margin:10px 0">Победа!</h3><p>Босс <b>«' + currentBoss.name + '»</b> повержен.</p></div>';
|
||
document.getElementById('boss-opts').innerHTML = '<button class="btn primary" id="boss-back">К списку боссов</button>';
|
||
document.getElementById('boss-fb').style.display = 'none';
|
||
document.getElementById('boss-back').addEventListener('click', closeBoss);
|
||
renderBossGrid();
|
||
if(BOSSES.every(b => BOSS_STATE[b.id])){
|
||
setTimeout(()=>{ achievement('all_bosses', 'Чемпион неравенств!'); confetti(); refreshCert(); }, 800);
|
||
}
|
||
}
|
||
function closeBoss(){ document.getElementById('boss-arena').style.display = 'none'; currentBoss = null; }
|
||
document.getElementById('boss-quit').addEventListener('click', closeBoss);
|
||
renderBossGrid();
|
||
|
||
/* PRACTICE */
|
||
(function(){
|
||
let cur = null, total = 0, score = 0, streak = 0;
|
||
function gen(){
|
||
const t = Math.floor(Math.random()*5);
|
||
if(t === 0){
|
||
// линейное
|
||
const a = (Math.random()<0.5?-1:1)*(1+Math.floor(Math.random()*4));
|
||
const b = -5+Math.floor(Math.random()*11);
|
||
const c = -5+Math.floor(Math.random()*11);
|
||
const v = (c - b) / a;
|
||
const flip = a < 0;
|
||
return { q:'Решите $' + a + 'x ' + (b>=0?'+ '+b:'- '+Math.abs(b)) + ' > ' + c + '$. Введите граничное значение $x$.', ans:[v] };
|
||
}
|
||
if(t === 1){
|
||
// оценка
|
||
const a = 1+Math.floor(Math.random()*3), b = a+1+Math.floor(Math.random()*3);
|
||
const c = 1+Math.floor(Math.random()*3), d = c+1+Math.floor(Math.random()*3);
|
||
return { q:'Дано: $' + a + ' \\leq x \\leq ' + b + '$, $' + c + ' \\leq y \\leq ' + d + '$. Верхняя граница $x + y$:', ans:[b + d] };
|
||
}
|
||
if(t === 2){
|
||
// система
|
||
const a = -5+Math.floor(Math.random()*5), b = a+2+Math.floor(Math.random()*5);
|
||
return { q:'$\\begin{cases} x > ' + a + ' \\\\ x \\leq ' + b + ' \\end{cases}$. Введите верхнюю границу.', ans:[b] };
|
||
}
|
||
if(t === 3){
|
||
// квадратное
|
||
const r = 1+Math.floor(Math.random()*5);
|
||
return { q:'Сколько корней у $x^2 - ' + (r*r) + ' = 0$?', ans:[2] };
|
||
}
|
||
// дробное
|
||
const r = (Math.random()<0.5?-1:1)*(1+Math.floor(Math.random()*8));
|
||
return { q:'Запрещённая точка для $\\dfrac{1}{x - ' + r + '}$:', ans:[r] };
|
||
}
|
||
function show(){
|
||
cur = gen();
|
||
document.getElementById('prac-task').innerHTML = cur.q;
|
||
renderMath(document.getElementById('prac-task'));
|
||
document.getElementById('prac-inp').value = '';
|
||
document.getElementById('prac-fb').style.display = 'none';
|
||
}
|
||
document.getElementById('prac-go').addEventListener('click', ()=>{
|
||
const fb = document.getElementById('prac-fb'); fb.style.display = 'block';
|
||
const u = parseFloat(document.getElementById('prac-inp').value.replace(',','.'));
|
||
const ok = cur.ans.some(a => Math.abs(u - a) < 1e-6);
|
||
total++;
|
||
if(ok){ score++; streak++; feedback(fb, true, '✓'); if(streak === 5){ achievement('prac_streak', 'Серия из 5!'); confetti(); } }
|
||
else { streak = 0; feedback(fb, false, 'Правильно: ' + cur.ans.join(', ')); }
|
||
document.getElementById('prac-i').textContent = total;
|
||
document.getElementById('prac-score').textContent = score;
|
||
document.getElementById('prac-streak').textContent = streak;
|
||
if(total >= 5) bumpProgress('final3', 4);
|
||
});
|
||
document.getElementById('prac-next').addEventListener('click', show);
|
||
document.getElementById('prac-inp').addEventListener('keyup', e=>{ if(e.key === 'Enter') document.getElementById('prac-go').click(); });
|
||
show();
|
||
})();
|
||
|
||
function refreshCert(){
|
||
const won = BOSSES.filter(b => BOSS_STATE[b.id]).length;
|
||
const cs = document.getElementById('cert-state');
|
||
if(!cs) return;
|
||
if(won === BOSSES.length){
|
||
cs.innerHTML = '<div style="padding:18px;background:linear-gradient(135deg,#fef3c7,#ede9fe);border:2px solid #8b5cf6;border-radius:12px;text-align:center"><div style="font-family:Unbounded,sans-serif;font-size:1.2rem;font-weight:900;color:#6d28d9">ЧЕМПИОН НЕРАВЕНСТВ</div><div style="font-size:.9rem;color:#5b21b6;margin-top:4px">Все 7 боссов главы 3 повержены — вы освоили §§ 13–18.</div></div>';
|
||
} else {
|
||
cs.innerHTML = 'Побеждено боссов: <b>' + won + ' / ' + BOSSES.length + '</b>. Победите всех, чтобы получить титул.';
|
||
}
|
||
}
|
||
refreshCert();
|
||
}
|
||
</script>
|
||
|
||
<script>
|
||
/* ============================================================
|
||
§ 13 — ЧИСЛОВЫЕ НЕРАВЕНСТВА И ИХ СВОЙСТВА
|
||
============================================================ */
|
||
function buildP13(){
|
||
const box = document.getElementById('p13-body');
|
||
let html = '';
|
||
|
||
html += makeCard('repeat','Повторение',null,`
|
||
<ul style="margin-left:18px;line-height:1.7">
|
||
<li>Действительные числа на числовой прямой: левее — меньше, правее — больше.</li>
|
||
<li>Знаки: $<$ (меньше), $>$ (больше), $\\leq$, $\\geq$, $\\neq$.</li>
|
||
<li>$|a|$ — модуль: расстояние от точки $a$ до нуля.</li>
|
||
</ul>`);
|
||
|
||
html += makeCard('theory','Что значит «$a > b$»','13.1',`
|
||
<p><b>Определение.</b> Число $a$ больше числа $b$, если разность $a - b$ — положительное число.</p>
|
||
<div style="background:var(--sec-acc-soft);border-radius:10px;padding:14px;margin:10px 0;text-align:center;font-size:1.15rem">$$a > b \\iff a - b > 0,\\qquad a < b \\iff a - b < 0$$</div>
|
||
<p>Это удобно, потому что любое сравнение сводится к проверке знака разности.</p>`);
|
||
|
||
html += makeCard('rule','5 главных свойств','13.2',`
|
||
<ol style="margin-left:18px;line-height:1.9">
|
||
<li><b>Транзитивность:</b> $a > b$ и $b > c \\Rightarrow a > c$.</li>
|
||
<li><b>Прибавление:</b> $a > b \\Rightarrow a + c > b + c$ (знак не меняется).</li>
|
||
<li><b>Умножение/деление на положительное:</b> $a > b,\\ k > 0 \\Rightarrow ak > bk$.</li>
|
||
<li><b>Умножение/деление на отрицательное:</b> $a > b,\\ k < 0 \\Rightarrow ak < bk$ — <span style="color:var(--bad);font-weight:700">знак меняется!</span></li>
|
||
<li><b>Возведение в квадрат для положительных:</b> $a > b > 0 \\Rightarrow a^2 > b^2$.</li>
|
||
</ol>`);
|
||
|
||
html += makeCard('example','Примеры',null,`
|
||
<p><b>1)</b> Сравним $\\sqrt{5}$ и $2{,}2$. Возведём в квадрат: $5$ и $4{,}84$. Поскольку $5 > 4{,}84$ и обе величины положительные, $\\sqrt{5} > 2{,}2$.</p>
|
||
<p><b>2)</b> Из $a > b$ умножим на $-3$: получим $-3a < -3b$. Знак ПОМЕНЯЛСЯ.</p>
|
||
<p><b>3)</b> Дано $a > 0$ и $b < 0$. Тогда $a - b > 0$, то есть $a > b$ (естественно: положительное больше отрицательного).</p>`);
|
||
|
||
/* INT 1 — Сравни числа (drag-сортировка) */
|
||
html += widget('Расставь по возрастанию','INTERACT 1','Перетащи числа в правильном порядке — слева направо. Проверь нажатием.',`
|
||
${DND_HINT_HTML}
|
||
<div id="p13a-pool"></div>
|
||
<div class="drop-row" style="display:grid;grid-template-columns:repeat(5, 1fr);gap:6px">
|
||
<div class="drop-box" style="min-height:60px"><h5>1-й</h5><div class="drop-items" data-cat="1"></div></div>
|
||
<div class="drop-box" style="min-height:60px"><h5>2-й</h5><div class="drop-items" data-cat="2"></div></div>
|
||
<div class="drop-box" style="min-height:60px"><h5>3-й</h5><div class="drop-items" data-cat="3"></div></div>
|
||
<div class="drop-box" style="min-height:60px"><h5>4-й</h5><div class="drop-items" data-cat="4"></div></div>
|
||
<div class="drop-box" style="min-height:60px"><h5>5-й</h5><div class="drop-items" data-cat="5"></div></div>
|
||
</div>
|
||
<div class="actions"><button class="btn primary" id="p13a-check">Проверить</button><button class="btn" id="p13a-new">Новая партия</button></div>
|
||
<div class="feedback" id="p13a-fb" style="display:none"></div>`);
|
||
|
||
/* INT 2 — Свойства: знак меняется или нет */
|
||
html += widget('Знак меняется или нет?','INTERACT 2','Дано $a > b$. Что произойдёт со знаком после операции?',`
|
||
<div class="score-display"><span>Раунд <b id="p13s-i">1</b> / 8</span><span>Очки: <b id="p13s-score">0</b></span></div>
|
||
<div id="p13s-task" style="font-size:1.2rem;text-align:center;padding:16px;background:var(--sec-acc-soft);border-radius:10px;margin-bottom:10px"></div>
|
||
<div style="display:flex;gap:8px;justify-content:center;flex-wrap:wrap">
|
||
<button class="btn" id="p13s-keep">Не меняется</button>
|
||
<button class="btn" id="p13s-flip">Меняется</button>
|
||
</div>
|
||
<div class="feedback" id="p13s-fb" style="display:none;margin-top:10px"></div>
|
||
<button class="btn primary" id="p13s-start" style="margin-top:10px">Начать</button>`);
|
||
|
||
/* INT 3 — Конструктор: a > b + операция */
|
||
html += widget('Конструктор операций','INTERACT 3','Выбери $a$, $b$, операцию и число. Смотри, как меняется неравенство.',`
|
||
<div class="sliders">
|
||
<label>$a$ = <b id="p13c-a-val">8</b><input type="range" min="-10" max="10" step="1" value="8" id="p13c-a"></label>
|
||
<label>$b$ = <b id="p13c-b-val">3</b><input type="range" min="-10" max="10" step="1" value="3" id="p13c-b"></label>
|
||
<label>$k$ = <b id="p13c-k-val">2</b><input type="range" min="-5" max="5" step="1" value="2" id="p13c-k"></label>
|
||
</div>
|
||
<div style="display:flex;gap:6px;flex-wrap:wrap;justify-content:center;margin:10px 0">
|
||
<button class="btn small p13c-op active" data-op="add">$+ k$</button>
|
||
<button class="btn small p13c-op" data-op="sub">$- k$</button>
|
||
<button class="btn small p13c-op" data-op="mul">$\\times k$</button>
|
||
<button class="btn small p13c-op" data-op="div">$\\div k$</button>
|
||
</div>
|
||
<div id="p13c-out" style="padding:14px;background:var(--sec-acc-soft);border-radius:10px;font-size:1.05rem;line-height:1.8;text-align:center"></div>`);
|
||
|
||
/* INT 4 — Цепочка свойств: пошаговое доказательство */
|
||
html += widget('Цепочка свойств','INTERACT 4','Дано: $a > 5$. Подбери правильный шаг для каждого следующего вывода.',`
|
||
<div id="p13ch-stage" style="padding:14px;background:var(--sec-acc-soft);border-radius:10px;line-height:1.8;min-height:60px"></div>
|
||
<div id="p13ch-opts" style="display:flex;flex-direction:column;gap:6px;margin-top:10px"></div>
|
||
<div class="feedback" id="p13ch-fb" style="display:none;margin-top:10px"></div>
|
||
<button class="btn primary" id="p13ch-start" style="margin-top:10px">Начать</button>`);
|
||
|
||
/* INT 5 — Drag классификация: какое свойство */
|
||
html += widget('Какое свойство применили?','INTERACT 5','Отнеси каждый переход к нужному свойству.',`
|
||
${DND_HINT_HTML}
|
||
<div id="p13d-pool"></div>
|
||
<div class="drop-row" style="display:grid;grid-template-columns:repeat(auto-fit,minmax(150px,1fr));gap:10px">
|
||
<div class="drop-box"><h5>+/− число</h5><div class="drop-items" data-cat="add"></div></div>
|
||
<div class="drop-box"><h5>×/÷ на +</h5><div class="drop-items" data-cat="mulpos"></div></div>
|
||
<div class="drop-box"><h5>×/÷ на −</h5><div class="drop-items" data-cat="mulneg"></div></div>
|
||
<div class="drop-box"><h5>Транзитивность</h5><div class="drop-items" data-cat="trans"></div></div>
|
||
</div>
|
||
<div class="actions"><button class="btn primary" id="p13d-check">Проверить</button><button class="btn" id="p13d-reset">Сначала</button></div>
|
||
<div class="feedback" id="p13d-fb" style="display:none"></div>`);
|
||
|
||
/* INT 6 — Тренажёр «Что больше?» */
|
||
html += widget('Что больше?','INTERACT 6','Сравни два выражения за 30 секунд каждое.',`
|
||
<div class="score-display"><span>Задача <b id="p13t-i">1</b> / 10</span><span>Очки: <b id="p13t-score">0</b></span></div>
|
||
<div id="p13t-task" style="display:flex;align-items:center;gap:14px;justify-content:center;padding:18px;background:var(--sec-acc-soft);border-radius:10px;margin-bottom:10px">
|
||
<span id="p13t-left" style="font-size:1.5rem;font-weight:700"></span>
|
||
<span style="font-size:1.3rem;color:var(--muted)">vs</span>
|
||
<span id="p13t-right" style="font-size:1.5rem;font-weight:700"></span>
|
||
</div>
|
||
<div style="display:flex;gap:6px;justify-content:center;flex-wrap:wrap">
|
||
<button class="btn" id="p13t-lt">$<$</button>
|
||
<button class="btn" id="p13t-eq">$=$</button>
|
||
<button class="btn" id="p13t-gt">$>$</button>
|
||
</div>
|
||
<div class="feedback" id="p13t-fb" style="display:none;margin-top:10px"></div>
|
||
<button class="btn primary" id="p13t-start" style="margin-top:10px">Начать</button>`);
|
||
|
||
html += makeCard('oral','Устные вопросы',null,`
|
||
<ol style="margin-left:18px;line-height:1.8">
|
||
<li>Что означает запись $a > b$ через разность?</li>
|
||
<li>Можно ли прибавить $-7$ к обеим частям $a > b$?</li>
|
||
<li>Что произойдёт со знаком при умножении на $-2$?</li>
|
||
<li>Сравните $-5$ и $-3$. Какое больше?</li>
|
||
</ol>`);
|
||
|
||
html += makeCard('class','Класс — выполните',null,`
|
||
<ol style="margin-left:18px;line-height:1.8">
|
||
<li>Сравните $\\sqrt{7}$ и $2{,}6$.</li>
|
||
<li>Дано $a > b$. Сравните: а) $a + 5$ и $b + 5$; б) $-a$ и $-b$; в) $a/3$ и $b/3$.</li>
|
||
<li>Докажите: если $a > b > 0$, то $\\dfrac{1}{a} < \\dfrac{1}{b}$.</li>
|
||
</ol>`);
|
||
|
||
html += makeCard('home','Домашка',null,`
|
||
<ol style="margin-left:18px;line-height:1.8">
|
||
<li>Сравните $\\sqrt{10}$ и $3{,}2$.</li>
|
||
<li>Дано $x < y$. Что больше: $7 - 3x$ или $7 - 3y$?</li>
|
||
<li>Известно, что $-2 < a < 3$. Найдите границы для $5a + 1$.</li>
|
||
<li>Докажите: $(a-b)^2 + (b-c)^2 + (a-c)^2 \\geq 0$ для любых $a, b, c$.</li>
|
||
</ol>`);
|
||
|
||
html += secNav(null, 'p14');
|
||
box.innerHTML = html;
|
||
if(window.renderMathInElement) setTimeout(()=>renderMath(box), 0);
|
||
|
||
/* INIT 1 — Сортировка по возрастанию */
|
||
(function(){
|
||
const sets = [
|
||
[{v:-3},{v:0},{v:2},{v:5},{v:7}],
|
||
[{v:-5,html:'$-5$'},{v:-2,html:'$-2$'},{v:0,html:'$0$'},{v:3,html:'$3$'},{v:6,html:'$6$'}],
|
||
[{v:Math.sqrt(2),html:'$\\sqrt{2}$'},{v:1.5,html:'$1{,}5$'},{v:Math.sqrt(3),html:'$\\sqrt{3}$'},{v:2,html:'$2$'},{v:Math.sqrt(5),html:'$\\sqrt{5}$'}],
|
||
[{v:-0.5,html:'$-\\dfrac{1}{2}$'},{v:0,html:'$0$'},{v:0.3,html:'$0{,}3$'},{v:0.5,html:'$\\dfrac{1}{2}$'},{v:1,html:'$1$'}],
|
||
[{v:-3.14,html:'$-\\pi$'},{v:-3,html:'$-3$'},{v:0,html:'$0$'},{v:Math.sqrt(2),html:'$\\sqrt{2}$'},{v:Math.PI,html:'$\\pi$'}],
|
||
];
|
||
let setIdx = 0, items = [], sorter;
|
||
function build(){
|
||
const cur = sets[setIdx];
|
||
items = cur.map((c, i) => ({ id: i + 1, html: c.html || ('$' + c.v + '$'), v: c.v }));
|
||
const shuffled = [...items].sort(()=>Math.random()-0.5);
|
||
sorter = setupSorter({ poolId:'p13a-pool', cats:['1','2','3','4','5'], items: shuffled, scopeSelector:'#p13-body' });
|
||
document.getElementById('p13a-fb').style.display = 'none';
|
||
}
|
||
document.getElementById('p13a-check').addEventListener('click', ()=>{
|
||
const fb = document.getElementById('p13a-fb'); fb.style.display = 'block';
|
||
if(Object.keys(sorter.placed).length < items.length){ feedback(fb, false, '⚠ Расставьте все 5 чисел.'); return; }
|
||
const sorted = [...items].sort((a,b)=>a.v-b.v);
|
||
let ok = true;
|
||
for(let i = 0; i < sorted.length; i++){
|
||
if(sorter.placed[sorted[i].id] !== String(i + 1)){ ok = false; break; }
|
||
}
|
||
if(ok){ feedback(fb, true, '✓ Правильно!'); achievement('p13_compare'); bumpProgress('p13', 14); confetti(); }
|
||
else feedback(fb, false, 'Не совсем. Проверьте знаки и сравнение корней.');
|
||
});
|
||
document.getElementById('p13a-new').addEventListener('click', ()=>{ setIdx = (setIdx + 1) % sets.length; build(); });
|
||
build();
|
||
})();
|
||
|
||
/* INIT 2 — Знак меняется или нет */
|
||
(function(){
|
||
const tasks = [
|
||
{ txt:'Прибавим $+5$ к обеим частям', flip:false },
|
||
{ txt:'Умножим обе части на $-3$', flip:true },
|
||
{ txt:'Разделим обе части на $2$', flip:false },
|
||
{ txt:'Прибавим $-7$', flip:false },
|
||
{ txt:'Умножим на $-1$', flip:true },
|
||
{ txt:'Разделим на $-4$', flip:true },
|
||
{ txt:'Возведём положительные части в квадрат', flip:false },
|
||
{ txt:'Умножим на $\\dfrac{1}{2}$ (положительное)', flip:false },
|
||
];
|
||
let cur = null, i = 1, score = 0, shuffled = [];
|
||
function show(){
|
||
cur = shuffled[i-1];
|
||
document.getElementById('p13s-i').textContent = i;
|
||
document.getElementById('p13s-task').innerHTML = '<b>Дано:</b> $a > b$. ' + cur.txt + '.';
|
||
renderMath(document.getElementById('p13s-task'));
|
||
document.getElementById('p13s-fb').style.display = 'none';
|
||
}
|
||
function answer(flip){
|
||
const fb = document.getElementById('p13s-fb'); fb.style.display = 'block';
|
||
if(flip === cur.flip){ score++; feedback(fb, true, '✓'); }
|
||
else feedback(fb, false, 'Не то. Правильно: ' + (cur.flip ? 'знак МЕНЯЕТСЯ' : 'знак не меняется'));
|
||
document.getElementById('p13s-score').textContent = score;
|
||
if(i >= 8){ setTimeout(()=>{ feedback(fb, score >= 6, 'Итог: ' + score + '/8'); if(score >= 6){ achievement(score === 8 ? 'p13_flip' : 'p13_sign'); bumpProgress('p13', 14); confetti(); } }, 600); }
|
||
else { i++; setTimeout(show, 800); }
|
||
}
|
||
document.getElementById('p13s-start').addEventListener('click', ()=>{ i=1; score=0; document.getElementById('p13s-score').textContent = 0; shuffled = [...tasks].sort(()=>Math.random()-0.5); show(); });
|
||
document.getElementById('p13s-keep').addEventListener('click', ()=>answer(false));
|
||
document.getElementById('p13s-flip').addEventListener('click', ()=>answer(true));
|
||
})();
|
||
|
||
/* INIT 3 — Конструктор */
|
||
(function(){
|
||
const aE = document.getElementById('p13c-a'), bE = document.getElementById('p13c-b'), kE = document.getElementById('p13c-k');
|
||
const out = document.getElementById('p13c-out');
|
||
let op = 'add', done = false;
|
||
document.querySelectorAll('.p13c-op').forEach(btn => btn.addEventListener('click', ()=>{
|
||
document.querySelectorAll('.p13c-op').forEach(x=>x.classList.remove('active'));
|
||
btn.classList.add('active'); op = btn.dataset.op; refresh();
|
||
}));
|
||
function refresh(){
|
||
const a = +aE.value, b = +bE.value, k = +kE.value;
|
||
document.getElementById('p13c-a-val').textContent = a;
|
||
document.getElementById('p13c-b-val').textContent = b;
|
||
document.getElementById('p13c-k-val').textContent = k;
|
||
const sign = a > b ? '>' : a < b ? '<' : '=';
|
||
let na, nb, sym;
|
||
const flip = (op === 'mul' || op === 'div') && k < 0;
|
||
if(op === 'add'){ na = a + k; nb = b + k; sym = sign; }
|
||
else if(op === 'sub'){ na = a - k; nb = b - k; sym = sign; }
|
||
else if(op === 'mul'){ if(k === 0){ na = 0; nb = 0; sym = '='; } else { na = a * k; nb = b * k; sym = flip ? (sign === '>' ? '<' : sign === '<' ? '>' : '=') : sign; } }
|
||
else { if(k === 0){ out.innerHTML = 'Деление на $0$ не определено.'; renderMath(out); return; } na = a / k; nb = b / k; sym = flip ? (sign === '>' ? '<' : sign === '<' ? '>' : '=') : sign; }
|
||
let s = '<div>Исходно: $' + a + ' ' + sign + ' ' + b + '$</div>';
|
||
s += '<div>После операции: $' + fmt(na) + ' ' + sym + ' ' + fmt(nb) + '$</div>';
|
||
if(flip && sign !== '=') s += '<div style="color:var(--bad);font-weight:700;margin-top:4px">Знак изменился!</div>';
|
||
out.innerHTML = s; renderMath(out);
|
||
if(!done){ done = true; setTimeout(()=>{ achievement('p13_props'); bumpProgress('p13', 12); }, 300); }
|
||
}
|
||
[aE,bE,kE].forEach(e => e.addEventListener('input', refresh));
|
||
refresh();
|
||
})();
|
||
|
||
/* INIT 4 — Цепочка */
|
||
(function(){
|
||
const steps = [
|
||
{ q:'Дано $a > 5$. Прибавим $3$ к обеим частям. Что получим?', opts:['$a + 3 > 8$','$a + 3 > 5$','$a + 3 < 8$','$3a > 15$'], ok:0 },
|
||
{ q:'Из $a + 3 > 8$ умножим на $2$. Что получим?', opts:['$2a + 6 > 16$','$2a + 3 > 8$','$2a + 6 < 16$','$a + 6 > 16$'], ok:0 },
|
||
{ q:'Из $2a + 6 > 16$ вычтем $6$. Что получим?', opts:['$2a > 10$','$2a > 22$','$2a < 10$','$a > 10$'], ok:0 },
|
||
{ q:'Из $2a > 10$ разделим на $-2$. Что получим?', opts:['$-a < -5$','$-a > -5$','$-a < 5$','$a < 5$'], ok:0 },
|
||
{ q:'Какой исходный шаг мы могли бы сделать одной операцией: «из $a > 5$ к $-a < -5$»?', opts:['Умножить на $-1$','Прибавить $-5$','Возвести в квадрат','Прибавить $5$'], ok:0 },
|
||
];
|
||
let i = 0;
|
||
function show(){
|
||
const s = steps[i];
|
||
document.getElementById('p13ch-stage').innerHTML = '<b>Шаг ' + (i + 1) + ' / ' + steps.length + '.</b> ' + s.q;
|
||
renderMath(document.getElementById('p13ch-stage'));
|
||
const opts = document.getElementById('p13ch-opts'); opts.innerHTML = '';
|
||
s.opts.forEach((o, k)=>{
|
||
const b = document.createElement('button');
|
||
b.className = 'btn'; b.innerHTML = o; b.style.cssText = 'text-align:left';
|
||
b.addEventListener('click', ()=>{
|
||
const fb = document.getElementById('p13ch-fb'); fb.style.display = 'block';
|
||
if(k === s.ok){ b.classList.add('ok'); feedback(fb, true, '✓'); if(i >= steps.length - 1){ feedback(fb, true, '✓ Цепочка пройдена!'); achievement('p13_chain'); bumpProgress('p13', 14); confetti(); } else setTimeout(()=>{ i++; show(); }, 700); }
|
||
else { b.classList.add('fail'); feedback(fb, false, 'Не то. Подумайте.'); }
|
||
});
|
||
opts.appendChild(b);
|
||
});
|
||
renderMath(opts);
|
||
document.getElementById('p13ch-fb').style.display = 'none';
|
||
}
|
||
document.getElementById('p13ch-start').addEventListener('click', ()=>{ i = 0; show(); });
|
||
})();
|
||
|
||
/* INIT 5 — Drag классификация */
|
||
(function(){
|
||
const items = [
|
||
{ id:1, html:'$a > b \\Rightarrow a + 7 > b + 7$', cat:'add' },
|
||
{ id:2, html:'$a > b \\Rightarrow 5a > 5b$', cat:'mulpos' },
|
||
{ id:3, html:'$a > b \\Rightarrow -2a < -2b$', cat:'mulneg' },
|
||
{ id:4, html:'$a > b,\\ b > c \\Rightarrow a > c$', cat:'trans' },
|
||
{ id:5, html:'$a > b \\Rightarrow a - 4 > b - 4$', cat:'add' },
|
||
{ id:6, html:'$a > b \\Rightarrow \\dfrac{a}{3} > \\dfrac{b}{3}$', cat:'mulpos' },
|
||
{ id:7, html:'$a > b \\Rightarrow \\dfrac{a}{-2} < \\dfrac{b}{-2}$', cat:'mulneg' },
|
||
{ id:8, html:'$x > 3,\\ 3 > y \\Rightarrow x > y$', cat:'trans' },
|
||
];
|
||
const sorter = setupSorter({ poolId:'p13d-pool', cats:['add','mulpos','mulneg','trans'], items, scopeSelector:'#p13-body' });
|
||
document.getElementById('p13d-check').addEventListener('click', ()=>{
|
||
const fb = document.getElementById('p13d-fb'); fb.style.display = 'block';
|
||
if(Object.keys(sorter.placed).length < items.length){ feedback(fb, false, '⚠ Разложите все ' + items.length + '.'); return; }
|
||
let ok = 0; items.forEach(it=>{ if(sorter.placed[it.id] === it.cat) ok++; });
|
||
if(ok === items.length){ feedback(fb, true, '✓ Идеально!'); bumpProgress('p13', 12); confetti(); }
|
||
else feedback(fb, false, 'Верно ' + ok + ' из ' + items.length);
|
||
});
|
||
document.getElementById('p13d-reset').addEventListener('click', ()=>{ sorter.reset(); document.getElementById('p13d-fb').style.display='none'; });
|
||
})();
|
||
|
||
/* INIT 6 — Что больше? */
|
||
(function(){
|
||
function gen(){
|
||
const t = Math.floor(Math.random()*4);
|
||
if(t === 0){
|
||
const a = -5 + Math.floor(Math.random()*11);
|
||
const b = -5 + Math.floor(Math.random()*11);
|
||
return { L: '$' + a + '$', R: '$' + b + '$', cmp: a < b ? '<' : a > b ? '>' : '=' };
|
||
}
|
||
if(t === 1){
|
||
const a = 1 + Math.floor(Math.random()*8);
|
||
const b = (2 + Math.random() * 4).toFixed(1);
|
||
return { L: '$\\sqrt{' + a + '}$', R: '$' + b + '$', cmp: a < +b * +b ? '<' : a > +b * +b ? '>' : '=' };
|
||
}
|
||
if(t === 2){
|
||
const a = 1 + Math.floor(Math.random()*5), b = 1 + Math.floor(Math.random()*5);
|
||
const c = 1 + Math.floor(Math.random()*5), d = 1 + Math.floor(Math.random()*5);
|
||
const L = a/b, R = c/d;
|
||
return { L: '$\\dfrac{' + a + '}{' + b + '}$', R: '$\\dfrac{' + c + '}{' + d + '}$', cmp: L < R ? '<' : L > R ? '>' : '=' };
|
||
}
|
||
const sign = Math.random() < 0.5 ? -1 : 1;
|
||
const a = 1 + Math.floor(Math.random()*5);
|
||
const b = 1 + Math.floor(Math.random()*5);
|
||
return { L: '$' + (sign * a) + '$', R: '$' + (sign * b) + '$', cmp: sign*a < sign*b ? '<' : sign*a > sign*b ? '>' : '=' };
|
||
}
|
||
let cur = null, i = 1, score = 0;
|
||
function show(){
|
||
cur = gen();
|
||
document.getElementById('p13t-i').textContent = i;
|
||
document.getElementById('p13t-left').innerHTML = cur.L;
|
||
document.getElementById('p13t-right').innerHTML = cur.R;
|
||
renderMath(document.getElementById('p13t-task'));
|
||
document.getElementById('p13t-fb').style.display = 'none';
|
||
}
|
||
function answer(sym){
|
||
const fb = document.getElementById('p13t-fb'); fb.style.display = 'block';
|
||
if(sym === cur.cmp){ score++; feedback(fb, true, '✓'); }
|
||
else feedback(fb, false, 'Не то. Правильно: ' + cur.cmp);
|
||
document.getElementById('p13t-score').textContent = score;
|
||
if(i >= 10){ setTimeout(()=>{ feedback(fb, score >= 7, 'Итог: ' + score + '/10'); if(score >= 7){ achievement('p13_compare'); bumpProgress('p13', 16); confetti(); } }, 600); }
|
||
else { i++; setTimeout(show, 700); }
|
||
}
|
||
document.getElementById('p13t-start').addEventListener('click', ()=>{ i=1; score=0; document.getElementById('p13t-score').textContent = 0; show(); });
|
||
document.getElementById('p13t-lt').addEventListener('click', ()=>answer('<'));
|
||
document.getElementById('p13t-eq').addEventListener('click', ()=>answer('='));
|
||
document.getElementById('p13t-gt').addEventListener('click', ()=>answer('>'));
|
||
})();
|
||
}
|
||
</script>
|
||
<script>
|
||
/* ============================================================
|
||
§ 14 — СЛОЖЕНИЕ И УМНОЖЕНИЕ НЕРАВЕНСТВ. ОЦЕНКА
|
||
============================================================ */
|
||
function buildP14(){
|
||
const box = document.getElementById('p14-body');
|
||
let html = '';
|
||
|
||
html += makeCard('repeat','Повторение § 13',null,`
|
||
<ul style="margin-left:18px;line-height:1.7">
|
||
<li>Свойства неравенств: прибавление, умножение/деление на положительное (знак сохраняется), на отрицательное (меняется).</li>
|
||
<li>$a > b \\iff a - b > 0$.</li>
|
||
</ul>`);
|
||
|
||
html += makeCard('theory','Сложение неравенств','14.1',`
|
||
<p><b>Правило.</b> Если $a < b$ и $c < d$, то $a + c < b + d$.</p>
|
||
<div style="background:var(--sec-acc-soft);border-radius:10px;padding:12px;margin:8px 0;text-align:center;font-size:1.1rem">$$\\begin{aligned} a &< b \\\\ c &< d \\\\ \\hline a + c &< b + d \\end{aligned}$$</div>
|
||
<p><b>Важно:</b> неравенства должны быть одного направления. Складывать $a < b$ и $c > d$ напрямую нельзя.</p>`);
|
||
|
||
html += makeCard('rule','Умножение неравенств','14.2',`
|
||
<p>Для <b>положительных</b> чисел: если $0 < a < b$ и $0 < c < d$, то $ac < bd$.</p>
|
||
<div style="background:var(--sec-acc-soft);border-radius:10px;padding:12px;margin:8px 0;text-align:center;font-size:1.1rem">$$0 < a < b,\\quad 0 < c < d \\Rightarrow ac < bd$$</div>
|
||
<p><b>Внимание:</b> требование «положительные» обязательно. Например, $-3 < 2$ и $-3 < 2$, но $(-3)(-3) = 9 > 2 \\cdot 2 = 4$ — формула не работает для отрицательных.</p>`);
|
||
|
||
html += makeCard('algo','Оценка значений','14.3',`
|
||
<p>Если $a \\leq x \\leq b$ и $c \\leq y \\leq d$, то:</p>
|
||
<table class="tbl" style="margin:8px 0">
|
||
<thead><tr><th>Выражение</th><th>Нижняя граница</th><th>Верхняя граница</th></tr></thead>
|
||
<tbody>
|
||
<tr><td>$x + y$</td><td>$a + c$</td><td>$b + d$</td></tr>
|
||
<tr><td>$x - y$</td><td>$a - d$</td><td>$b - c$</td></tr>
|
||
<tr><td>$xy$ (все $\\geq 0$)</td><td>$ac$</td><td>$bd$</td></tr>
|
||
<tr><td>$\\dfrac{x}{y}$ (все $> 0$)</td><td>$\\dfrac{a}{d}$</td><td>$\\dfrac{b}{c}$</td></tr>
|
||
</tbody>
|
||
</table>
|
||
<p style="font-size:.88rem;color:var(--muted)">Запомни: для разности и деления — у второй переменной границы «крест-накрест».</p>`);
|
||
|
||
html += makeCard('example','Пример',null,`
|
||
<p><b>Дано:</b> $2 \\leq x \\leq 5$, $1 \\leq y \\leq 3$. <b>Найдите границы для $x + y$, $x - y$, $xy$.</b></p>
|
||
<p><b>$x + y$:</b> $2 + 1 \\leq x + y \\leq 5 + 3 \\Rightarrow 3 \\leq x+y \\leq 8$.</p>
|
||
<p><b>$x - y$:</b> $2 - 3 \\leq x - y \\leq 5 - 1 \\Rightarrow -1 \\leq x-y \\leq 4$.</p>
|
||
<p><b>$xy$ (все $> 0$):</b> $2 \\cdot 1 \\leq xy \\leq 5 \\cdot 3 \\Rightarrow 2 \\leq xy \\leq 15$.</p>`);
|
||
|
||
/* INT 1 — Калькулятор оценки */
|
||
html += widget('Калькулятор оценок','INTERACT 1','Введите границы для $x$ и $y$ — увидите границы для всех 4 выражений сразу.',`
|
||
<div style="display:grid;grid-template-columns:repeat(auto-fit,minmax(170px,1fr));gap:10px;margin-bottom:10px">
|
||
<label class="tinp" style="display:flex;align-items:center;gap:6px;background:var(--card)">$x \\geq$ <input type="number" id="p14e-a" value="2" class="tinp" style="width:60px;padding:4px;border:0"></label>
|
||
<label class="tinp" style="display:flex;align-items:center;gap:6px;background:var(--card)">$x \\leq$ <input type="number" id="p14e-b" value="5" class="tinp" style="width:60px;padding:4px;border:0"></label>
|
||
<label class="tinp" style="display:flex;align-items:center;gap:6px;background:var(--card)">$y \\geq$ <input type="number" id="p14e-c" value="1" class="tinp" style="width:60px;padding:4px;border:0"></label>
|
||
<label class="tinp" style="display:flex;align-items:center;gap:6px;background:var(--card)">$y \\leq$ <input type="number" id="p14e-d" value="3" class="tinp" style="width:60px;padding:4px;border:0"></label>
|
||
</div>
|
||
<div id="p14e-out" style="padding:14px;background:var(--sec-acc-soft);border-radius:10px;line-height:1.8;font-size:1rem"></div>`);
|
||
|
||
/* INT 2 — Тренажёр оценки */
|
||
html += widget('Тренажёр: найдите границу','INTERACT 2','По заданным границам $x$ и $y$ найдите верхнюю или нижнюю границу выражения.',`
|
||
<div class="score-display"><span>Задача <b id="p14t-i">1</b> / 8</span><span>Очки: <b id="p14t-score">0</b></span></div>
|
||
<div id="p14t-task" style="padding:14px;background:var(--sec-acc-soft);border-radius:10px;font-size:1.04rem;line-height:1.7;margin-bottom:10px"></div>
|
||
<div style="display:flex;gap:8px;justify-content:center;flex-wrap:wrap">
|
||
<input type="number" id="p14t-inp" placeholder="ваш ответ" class="tinp" style="width:140px">
|
||
<button class="btn primary" id="p14t-go">Ответ</button>
|
||
</div>
|
||
<div class="feedback" id="p14t-fb" style="display:none;margin-top:10px"></div>
|
||
<button class="btn primary" id="p14t-start" style="margin-top:10px">Начать</button>`);
|
||
|
||
/* INT 3 — Drag: можно/нельзя складывать */
|
||
html += widget('Можно ли применить правило?','INTERACT 3','Отнеси каждую пару неравенств к одной из категорий.',`
|
||
${DND_HINT_HTML}
|
||
<div id="p14d-pool"></div>
|
||
<div class="drop-row" style="display:grid;grid-template-columns:repeat(auto-fit,minmax(180px,1fr));gap:10px">
|
||
<div class="drop-box"><h5>Можно сложить</h5><div class="drop-items" data-cat="add"></div></div>
|
||
<div class="drop-box"><h5>Можно перемножить</h5><div class="drop-items" data-cat="mul"></div></div>
|
||
<div class="drop-box"><h5>Ни того, ни другого</h5><div class="drop-items" data-cat="none"></div></div>
|
||
</div>
|
||
<div class="actions"><button class="btn primary" id="p14d-check">Проверить</button><button class="btn" id="p14d-reset">Сначала</button></div>
|
||
<div class="feedback" id="p14d-fb" style="display:none"></div>`);
|
||
|
||
/* INT 4 — Пошаговое сложение */
|
||
html += widget('Пошаговое сложение и оценка','INTERACT 4','По заданным $x, y$ — пройди 4 шага: сложение, разность, произведение, итог.',`
|
||
<p style="margin-bottom:10px"><b>Дано:</b> $1 \\leq x \\leq 4$, $2 \\leq y \\leq 6$.</p>
|
||
<div id="p14p-stage" style="padding:14px;background:var(--sec-acc-soft);border-radius:10px;line-height:1.8;min-height:80px"></div>
|
||
<div class="actions" style="margin-top:10px"><button class="btn primary" id="p14p-go">Старт</button><button class="btn" id="p14p-next" style="display:none">Дальше</button><button class="btn" id="p14p-reset" style="display:none">Сначала</button></div>`);
|
||
|
||
/* INT 5 — Складываем неравенства (упражнение) */
|
||
html += widget('Сложи неравенства','INTERACT 5','Дано: $a < b$ и $c < d$. Подбери, что получится после сложения.',`
|
||
<div class="score-display"><span>Раунд <b id="p14a-i">1</b> / 6</span><span>Очки: <b id="p14a-score">0</b></span></div>
|
||
<div id="p14a-task" style="padding:14px;background:var(--sec-acc-soft);border-radius:10px;font-size:1.05rem;line-height:1.6;margin-bottom:10px"></div>
|
||
<div id="p14a-opts" style="display:flex;flex-direction:column;gap:6px"></div>
|
||
<div class="feedback" id="p14a-fb" style="display:none;margin-top:10px"></div>
|
||
<button class="btn primary" id="p14a-start" style="margin-top:10px">Начать</button>`);
|
||
|
||
html += makeCard('oral','Устные вопросы',null,`
|
||
<ol style="margin-left:18px;line-height:1.8">
|
||
<li>Можно ли складывать $a > b$ и $c < d$? Почему?</li>
|
||
<li>Какое условие нужно для перемножения неравенств?</li>
|
||
<li>Если $1 \\leq x \\leq 3$, какие границы у $-x$?</li>
|
||
</ol>`);
|
||
|
||
html += makeCard('class','Класс — выполните',null,`
|
||
<ol style="margin-left:18px;line-height:1.8">
|
||
<li>$3 \\leq x \\leq 7$, $2 \\leq y \\leq 5$. Найдите границы для $x + y$, $x - y$, $xy$.</li>
|
||
<li>Сложите неравенства $a < b$ и $-c < -d$ (где $d < c$).</li>
|
||
<li>$0{,}5 \\leq a \\leq 2$, $1 \\leq b \\leq 4$. Найдите границы $\\dfrac{a}{b}$.</li>
|
||
</ol>`);
|
||
|
||
html += makeCard('home','Домашка',null,`
|
||
<ol style="margin-left:18px;line-height:1.8">
|
||
<li>$-2 \\leq x \\leq 1$, $0 \\leq y \\leq 4$. Границы для $x + y$ и $x - y$.</li>
|
||
<li>$1 \\leq a \\leq 5$, $2 \\leq b \\leq 6$. Границы $ab$ и $\\dfrac{a}{b}$.</li>
|
||
<li>Известно: периметр треугольника $P$ удовлетворяет $12 \\leq P \\leq 18$. Найти границы половины периметра $\\dfrac{P}{2}$.</li>
|
||
</ol>`);
|
||
|
||
html += secNav('p13', 'p15');
|
||
box.innerHTML = html;
|
||
if(window.renderMathInElement) setTimeout(()=>renderMath(box), 0);
|
||
|
||
/* INIT 1 — Калькулятор оценки */
|
||
(function(){
|
||
const ids = ['p14e-a','p14e-b','p14e-c','p14e-d'];
|
||
const out = document.getElementById('p14e-out');
|
||
let done = false;
|
||
function refresh(){
|
||
const a = +document.getElementById('p14e-a').value;
|
||
const b = +document.getElementById('p14e-b').value;
|
||
const c = +document.getElementById('p14e-c').value;
|
||
const d = +document.getElementById('p14e-d').value;
|
||
if(b < a || d < c){ out.innerHTML = '<span style="color:var(--bad)">Неверные границы: нижняя должна быть $\\leq$ верхней.</span>'; renderMath(out); return; }
|
||
const sumLo = a + c, sumHi = b + d;
|
||
const subLo = a - d, subHi = b - c;
|
||
const prods = [a*c, a*d, b*c, b*d];
|
||
const prdLo = Math.min(...prods), prdHi = Math.max(...prods);
|
||
const positives = a > 0 && c > 0;
|
||
let s = '<div><b>$x + y$:</b> $' + fmt(sumLo) + ' \\leq x+y \\leq ' + fmt(sumHi) + '$</div>';
|
||
s += '<div><b>$x - y$:</b> $' + fmt(subLo) + ' \\leq x-y \\leq ' + fmt(subHi) + '$</div>';
|
||
s += '<div><b>$xy$:</b> $' + fmt(prdLo) + ' \\leq xy \\leq ' + fmt(prdHi) + '$</div>';
|
||
if(positives){
|
||
s += '<div><b>$\\dfrac{x}{y}$:</b> $' + fmt(a/d) + ' \\leq \\dfrac{x}{y} \\leq ' + fmt(b/c) + '$ (все положительные)</div>';
|
||
} else {
|
||
s += '<div style="color:var(--muted)">Для $\\dfrac{x}{y}$ нужны все положительные — иначе формула сложнее.</div>';
|
||
}
|
||
out.innerHTML = s; renderMath(out);
|
||
if(!done){ done = true; setTimeout(()=>{ achievement('p14_estimate'); bumpProgress('p14', 14); }, 300); }
|
||
}
|
||
ids.forEach(id => document.getElementById(id).addEventListener('input', refresh));
|
||
refresh();
|
||
})();
|
||
|
||
/* INIT 2 — Тренажёр оценки */
|
||
(function(){
|
||
function gen(){
|
||
const a = 1 + Math.floor(Math.random()*4);
|
||
const b = a + 1 + Math.floor(Math.random()*4);
|
||
const c = 1 + Math.floor(Math.random()*4);
|
||
const d = c + 1 + Math.floor(Math.random()*4);
|
||
const ops = [
|
||
{ txt:'верхняя граница $x + y$', ans: b + d },
|
||
{ txt:'нижняя граница $x + y$', ans: a + c },
|
||
{ txt:'верхняя граница $x - y$', ans: b - c },
|
||
{ txt:'нижняя граница $x - y$', ans: a - d },
|
||
{ txt:'верхняя граница $xy$', ans: b * d },
|
||
{ txt:'нижняя граница $xy$', ans: a * c },
|
||
];
|
||
const op = ops[Math.floor(Math.random()*ops.length)];
|
||
return { a, b, c, d, op };
|
||
}
|
||
let cur = null, i = 1, score = 0;
|
||
function show(){
|
||
cur = gen();
|
||
document.getElementById('p14t-i').textContent = i;
|
||
document.getElementById('p14t-task').innerHTML = '<b>Дано:</b> $' + cur.a + ' \\leq x \\leq ' + cur.b + '$, $' + cur.c + ' \\leq y \\leq ' + cur.d + '$.<br><b>Найдите:</b> ' + cur.op.txt + '.';
|
||
renderMath(document.getElementById('p14t-task'));
|
||
document.getElementById('p14t-inp').value = '';
|
||
document.getElementById('p14t-fb').style.display = 'none';
|
||
}
|
||
function check(){
|
||
const fb = document.getElementById('p14t-fb'); fb.style.display = 'block';
|
||
const u = +document.getElementById('p14t-inp').value;
|
||
if(u === cur.op.ans){ score++; feedback(fb, true, '✓ ' + cur.op.ans); }
|
||
else feedback(fb, false, 'Не то. Правильно: ' + cur.op.ans);
|
||
document.getElementById('p14t-score').textContent = score;
|
||
if(i >= 8){ setTimeout(()=>{ feedback(fb, score >= 6, 'Итог: ' + score + '/8'); if(score >= 6){ achievement('p14_train'); bumpProgress('p14', 16); confetti(); } }, 600); }
|
||
else { i++; setTimeout(show, 800); }
|
||
}
|
||
document.getElementById('p14t-start').addEventListener('click', ()=>{ i=1; score=0; document.getElementById('p14t-score').textContent = 0; show(); });
|
||
document.getElementById('p14t-go').addEventListener('click', check);
|
||
document.getElementById('p14t-inp').addEventListener('keyup', e=>{ if(e.key === 'Enter') check(); });
|
||
})();
|
||
|
||
/* INIT 3 — Drag складывать/перемножать */
|
||
(function(){
|
||
const items = [
|
||
{ id:1, html:'$2 < 5$ и $3 < 7$', cat:'mul' },
|
||
{ id:2, html:'$a < b$ и $c < d$', cat:'add' },
|
||
{ id:3, html:'$a > b$ и $c < d$', cat:'none' },
|
||
{ id:4, html:'$-3 < 1$ и $-2 < 4$', cat:'add' },
|
||
{ id:5, html:'$5 > 2$ и $4 > 1$', cat:'mul' },
|
||
{ id:6, html:'$a < b$ и $c > d$', cat:'none' },
|
||
{ id:7, html:'$0 < x < 3$ и $1 < y < 5$', cat:'mul' },
|
||
{ id:8, html:'$-2 < a < 1$ и $-1 < b < 4$', cat:'add' },
|
||
];
|
||
const sorter = setupSorter({ poolId:'p14d-pool', cats:['add','mul','none'], items, scopeSelector:'#p14-body' });
|
||
document.getElementById('p14d-check').addEventListener('click', ()=>{
|
||
const fb = document.getElementById('p14d-fb'); fb.style.display = 'block';
|
||
if(Object.keys(sorter.placed).length < items.length){ feedback(fb, false, '⚠ Разложите все.'); return; }
|
||
let ok = 0; items.forEach(it=>{ if(sorter.placed[it.id] === it.cat) ok++; });
|
||
if(ok === items.length){ feedback(fb, true, '✓ Все ' + items.length + ' верно!'); achievement('p14_drag'); bumpProgress('p14', 14); confetti(); }
|
||
else feedback(fb, false, 'Верно ' + ok + ' из ' + items.length);
|
||
});
|
||
document.getElementById('p14d-reset').addEventListener('click', ()=>{ sorter.reset(); document.getElementById('p14d-fb').style.display='none'; });
|
||
})();
|
||
|
||
/* INIT 4 — Пошаговое */
|
||
(function(){
|
||
const stage = document.getElementById('p14p-stage');
|
||
const goBtn = document.getElementById('p14p-go');
|
||
const nextBtn = document.getElementById('p14p-next');
|
||
const resetBtn = document.getElementById('p14p-reset');
|
||
const steps = [
|
||
'<b>Шаг 1.</b> $x + y$: складываем границы соответствующих частей. $1 + 2 = 3$ — низ, $4 + 6 = 10$ — верх. Итог: $3 \\leq x+y \\leq 10$.',
|
||
'<b>Шаг 2.</b> $x - y$: внимание на «крест-накрест». Низ: $1 - 6 = -5$. Верх: $4 - 2 = 2$. Итог: $-5 \\leq x-y \\leq 2$.',
|
||
'<b>Шаг 3.</b> $xy$ (все положительные): низ — произведение нижних, $1 \\cdot 2 = 2$. Верх — произведение верхних, $4 \\cdot 6 = 24$. Итог: $2 \\leq xy \\leq 24$.',
|
||
'<b>Шаг 4.</b> $\\dfrac{x}{y}$ (все $> 0$): низ — $\\dfrac{\\text{мин}\\ x}{\\text{макс}\\ y} = \\dfrac{1}{6}$. Верх — $\\dfrac{\\text{макс}\\ x}{\\text{мин}\\ y} = \\dfrac{4}{2} = 2$. Итог: $\\dfrac{1}{6} \\leq \\dfrac{x}{y} \\leq 2$.',
|
||
'<b>Ответ:</b> все 4 границы найдены. Заметьте — для разности и деления у $y$ границы поменялись местами.',
|
||
];
|
||
let idx = 0, awarded = false;
|
||
function render(){
|
||
stage.innerHTML = steps.slice(0, idx + 1).map(s => `<div style="margin:6px 0;padding:9px 12px;background:var(--card);border-left:3px solid var(--sec-acc);border-radius:7px;animation:fadeIn .3s ease">${s}</div>`).join('');
|
||
renderMath(stage);
|
||
if(idx >= steps.length - 1){
|
||
nextBtn.disabled = true; nextBtn.textContent = 'Готово';
|
||
if(!awarded){ awarded = true; achievement('p14_add'); bumpProgress('p14', 14); confetti(); }
|
||
} else { nextBtn.disabled = false; nextBtn.textContent = 'Дальше (' + (idx + 1) + '/' + steps.length + ')'; }
|
||
}
|
||
goBtn.addEventListener('click', ()=>{ idx = 0; awarded = false; goBtn.style.display = 'none'; nextBtn.style.display = ''; resetBtn.style.display = ''; render(); });
|
||
nextBtn.addEventListener('click', ()=>{ if(idx < steps.length - 1){ idx++; render(); } });
|
||
resetBtn.addEventListener('click', ()=>{ idx = 0; stage.innerHTML = ''; goBtn.style.display = ''; nextBtn.style.display = 'none'; resetBtn.style.display = 'none'; });
|
||
})();
|
||
|
||
/* INIT 5 — Сложи неравенства */
|
||
(function(){
|
||
const tasks = [
|
||
{ q:'$3 < 5$ и $2 < 4$. Результат сложения:', opts:['$5 < 9$','$3 < 9$','$5 < 4$','$1 < 1$'], ok:0 },
|
||
{ q:'$-1 < 4$ и $0 < 7$. Результат сложения:', opts:['$-1 < 11$','$0 < 11$','$-1 < 7$','$0 < 4$'], ok:0 },
|
||
{ q:'$a < b$ и $c < d$, всегда верно:', opts:['$a + c < b + d$','$a + c > b + d$','$ac < bd$','ничего из перечисленного'], ok:0 },
|
||
{ q:'$2 < 6$ и $3 < 4$. Перемножение (все положительные):', opts:['$6 < 24$','$5 < 10$','$2 < 24$','$6 < 4$'], ok:0 },
|
||
{ q:'$-3 < 2$ и $-1 < 5$. Можно сложить?', opts:['Да: $-4 < 7$','Нет, неравенства разных знаков','Только если они положительные','Нет, потому что $-1 > -3$'], ok:0 },
|
||
{ q:'$a > b$ и $c > d$, тогда $a + c$ ? $b + d$:', opts:['$>$','$<$','$=$','непредсказуемо'], ok:0 },
|
||
];
|
||
let cur = null, i = 1, score = 0, shuffled = [];
|
||
function show(){
|
||
cur = shuffled[i-1];
|
||
document.getElementById('p14a-i').textContent = i;
|
||
document.getElementById('p14a-task').innerHTML = '<b>Задача ' + i + '.</b> ' + cur.q;
|
||
renderMath(document.getElementById('p14a-task'));
|
||
const opts = document.getElementById('p14a-opts'); opts.innerHTML = '';
|
||
cur.opts.forEach((o, k)=>{
|
||
const b = document.createElement('button');
|
||
b.className = 'btn'; b.innerHTML = o; b.style.cssText = 'text-align:left';
|
||
b.addEventListener('click', ()=>{
|
||
const fb = document.getElementById('p14a-fb'); fb.style.display = 'block';
|
||
if(k === cur.ok){ score++; b.classList.add('ok'); feedback(fb, true, '✓'); }
|
||
else { b.classList.add('fail'); feedback(fb, false, 'Не то.'); }
|
||
document.getElementById('p14a-score').textContent = score;
|
||
if(i >= shuffled.length){ setTimeout(()=>{ feedback(fb, score >= 4, 'Итог: ' + score + '/' + shuffled.length); if(score >= 4){ achievement('p14_mul'); bumpProgress('p14', 14); confetti(); } }, 700); }
|
||
else { i++; setTimeout(show, 800); }
|
||
});
|
||
opts.appendChild(b);
|
||
});
|
||
renderMath(opts);
|
||
document.getElementById('p14a-fb').style.display = 'none';
|
||
}
|
||
document.getElementById('p14a-start').addEventListener('click', ()=>{ i=1; score=0; document.getElementById('p14a-score').textContent = 0; shuffled = [...tasks].sort(()=>Math.random()-0.5); show(); });
|
||
})();
|
||
}
|
||
</script>
|
||
|
||
</body>
|
||
</html>
|