Files
Learn_System/frontend/textbooks/chemistry_8_intro.html
T
Maxim Dolgolyov 6ea140af54 @
feat(chemistry-8): Phase 1 — раздел «Количественные понятия» (§1–9 + ПР1)

Полноценная интерактивная страница chemistry_8_intro.html (9 § + ПР1 + босс):
- §1 карта элементов (Z, название, Ar), §2 калькулятор Mr по формуле
- §3 «порция вещества» n⇒N,m, §4 счётчик частиц N=n·N_A, §5 M+молярный объём
- §6 звёздный виджет: интерактивный треугольник n–m–M
- §7 универсальный калькулятор газа (m–n–V–N), §8 балансировщик уравнений
- §9 пошаговый решатель по уравнению; босс раздела (4 задачи) + ачивка «Счёт в химии»
- прогресс/XP через /api/textbooks/chemistry-8-intro/progress, scrollspy, тема

chem8_svg.js: реализованы движки — molarMass (школьные Ar: Mr(H2O)=18),
elementCounts, moleTriangle, equationBalancer (+ fmt, arOf).

Фикс порядка загрузки: инициализация обёрнута в DOMContentLoaded (defer-скрипты
готовы к этому моменту). Генератор каркасов получил skip-if-exists (--force для перезаписи).

Тесты: chemistry8.test.js (14) + chemistry8-dom.test.js (jsdom-смоук виджетов, 3) — 17/17.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@
2026-05-30 14:36:31 +03:00

783 lines
55 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!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">
<meta name="viewport" content="width=device-width,initial-scale=1.0">
<title>Химия 8 · Вводный раздел · «Количественные понятия в химии»</title>
<link href="https://fonts.googleapis.com/css2?family=Outfit:wght@400;500;600;700;800;900&family=Inter:wght@400;500;600;700&family=Unbounded:wght@700;800;900&family=JetBrains+Mono:wght@500;700&display=swap" rel="stylesheet">
<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}],throwOnError:false})"></script>
<script src="/js/api.js" defer></script>
<script src="/js/xp.js" defer></script>
<script src="/js/biochem-core.js" defer></script>
<script src="/js/chem8_svg.js" defer></script>
<style>
:root{
--bg:#fffbeb; --card:#fff; --card-soft:#fef9ec; --text:#1c1917; --muted:#78716c; --border:#f0e6cf;
--pri:#d97706; --pri-d:#b45309; --pri-l:#fbbf24; --pri-soft:#fef3c7;
--ok:#15803d; --ok-bg:#dcfce7; --bad:#b91c1c; --bad-bg:#fee2e2; --warn:#b45309; --warn-bg:#fef3c7;
--sh:0 1px 3px rgba(120,80,10,.07); --sh2:0 8px 28px rgba(120,80,10,.13);
--mono:'JetBrains Mono',ui-monospace,monospace;
}
html.dark{ --bg:#1c1410; --card:#271c14; --card-soft:#2e2118; --text:#fef3c7; --muted:#c9ab82; --border:#4a3520;
--pri-soft:rgba(217,119,6,.18); --ok-bg:rgba(21,128,61,.2); --bad-bg:rgba(185,28,28,.2); --warn-bg:rgba(180,83,9,.2); }
*{margin:0;padding:0;box-sizing:border-box;-webkit-tap-highlight-color:transparent}
html,body{min-height:100vh}
body{font-family:'Inter',system-ui,sans-serif;background:var(--bg);color:var(--text);line-height:1.6;transition:background .25s,color .25s}
a{color:inherit}
.ic{width:16px;height:16px;stroke:currentColor;fill:none;stroke-width:2;stroke-linecap:round;stroke-linejoin:round;flex-shrink:0}
/* HEADER */
.hdr{position:relative;background:linear-gradient(110deg,#92400e 0%,#d97706 55%,#fbbf24 100%);color:#fff;padding:34px 24px 30px;overflow:hidden;border-bottom:2px solid rgba(255,255,255,.18)}
.hdr::before{content:'ВВОДНЫЙ РАЗДЕЛ';position:absolute;right:-12px;top:50%;transform:translateY(-50%);font-family:'Unbounded',sans-serif;font-size:clamp(3rem,9vw,7rem);font-weight:900;letter-spacing:-.04em;color:transparent;-webkit-text-stroke:1.5px rgba(255,255,255,.12);line-height:1;pointer-events:none;user-select:none;z-index:0;white-space:nowrap}
.hdr-inner{position:relative;z-index:1;max-width:1180px;margin:0 auto;display:flex;align-items:center;gap:16px;flex-wrap:wrap}
.hdr-back{display:inline-flex;align-items:center;gap:8px;padding:8px 14px;background:rgba(255,255,255,.16);border-radius:9px;color:#fff;text-decoration:none;font-size:.85rem;font-weight:600;transition:background .15s}
.hdr-back:hover{background:rgba(255,255,255,.26)}
.hdr-kicker{font-size:.72rem;font-weight:800;text-transform:uppercase;letter-spacing:.14em;opacity:.85}
.hdr h1{font-family:'Outfit',sans-serif;font-size:1.5rem;font-weight:900;letter-spacing:-.01em;line-height:1.25;margin-top:3px}
.hdr-side{margin-left:auto;display:flex;gap:8px}
.hdr-btn{padding:8px 12px;background:rgba(255,255,255,.16);border:none;color:#fff;border-radius:9px;cursor:pointer;font-weight:600;font-size:.82rem;display:inline-flex;align-items:center;gap:6px;transition:background .15s;font-family:inherit}
.hdr-btn:hover{background:rgba(255,255,255,.26)}
/* HERO */
.hero{max-width:1180px;margin:18px auto 0;padding:0 24px}
.hero-card{background:linear-gradient(135deg,var(--pri-soft),rgba(251,191,36,.1));border:1px solid var(--border);border-radius:16px;padding:16px 20px;display:flex;gap:16px;align-items:center;flex-wrap:wrap}
.hero-ic{width:46px;height:46px;border-radius:12px;background:linear-gradient(135deg,#d97706,#fbbf24);color:#fff;display:flex;align-items:center;justify-content:center;flex-shrink:0;font-family:'Outfit';font-weight:900;font-size:1.2rem}
.hero-t{flex:1;min-width:180px}
.hero-lab{font-size:.74rem;font-weight:700;color:var(--muted);text-transform:uppercase;letter-spacing:.06em}
.hero-bar{height:8px;background:rgba(217,119,6,.16);border-radius:5px;overflow:hidden;margin-top:6px}
.hero-fill{height:100%;background:linear-gradient(90deg,var(--pri),var(--pri-l));border-radius:5px;width:0;transition:width .5s}
.hero-xp{display:inline-flex;align-items:center;gap:6px;padding:6px 14px;background:linear-gradient(135deg,#f59e0b,var(--pri));color:#fff;border-radius:99px;font-size:.8rem;font-weight:800;font-family:'Unbounded'}
/* LAYOUT */
.wrap{max-width:1180px;margin:0 auto;padding:24px;display:grid;grid-template-columns:240px 1fr;gap:26px;align-items:start}
@media(max-width:900px){.wrap{grid-template-columns:1fr;padding:16px}}
.side{position:sticky;top:14px;background:var(--card);border:1px solid var(--border);border-radius:14px;padding:10px;box-shadow:var(--sh);max-height:calc(100vh - 28px);overflow:auto}
@media(max-width:900px){.side{position:static;max-height:none}}
.side-h{font-size:.7rem;font-weight:800;text-transform:uppercase;letter-spacing:.08em;color:var(--muted);padding:6px 10px}
.side a{display:flex;gap:9px;align-items:center;padding:8px 10px;border-radius:9px;text-decoration:none;font-size:.86rem;color:var(--text);transition:background .14s}
.side a:hover{background:var(--pri-soft)}
.side a.active{background:var(--pri-soft);color:var(--pri-d);font-weight:700}
.side a.note{color:var(--muted);font-size:.8rem}
.side-num{font-weight:800;color:var(--pri);min-width:30px;font-size:.82rem}
.side a.done .side-num::after{content:'\2713';margin-left:3px;color:var(--ok)}
/* SECTIONS */
.col{min-width:0;display:flex;flex-direction:column;gap:26px}
.sec{scroll-margin-top:14px}
.ph{border-radius:16px;padding:20px 22px;color:#fff;position:relative;overflow:hidden;background:linear-gradient(135deg,var(--pri-d),var(--pri) 60%,var(--pri-l))}
.ph::after{content:'';position:absolute;right:-30px;top:-30px;width:150px;height:150px;border-radius:50%;background:rgba(255,255,255,.1)}
.ph-num{font-size:.72rem;font-weight:800;letter-spacing:.1em;text-transform:uppercase;opacity:.85}
.ph h2{font-family:'Outfit';font-size:1.22rem;font-weight:800;margin:4px 0 8px;line-height:1.25;position:relative;z-index:1}
.ph-key{display:inline-block;background:rgba(255,255,255,.18);border:1px solid rgba(255,255,255,.25);border-radius:10px;padding:6px 14px;font-size:.95rem;font-weight:700;position:relative;z-index:1}
.card{background:var(--card);border:1px solid var(--border);border-radius:14px;padding:16px 18px;box-shadow:var(--sh)}
.card+.card{margin-top:14px}
.card h3{font-family:'Outfit';font-size:1rem;font-weight:800;margin-bottom:8px;display:flex;align-items:center;gap:8px}
.card h3 .tg{font-size:.64rem;font-weight:800;text-transform:uppercase;letter-spacing:.05em;padding:2px 8px;border-radius:99px;background:var(--pri-soft);color:var(--pri-d)}
.card p{font-size:.94rem;margin-bottom:8px}
.card p:last-child{margin-bottom:0}
.card ul{margin:6px 0 8px 20px;font-size:.92rem}
.card li{margin-bottom:4px}
.def{background:var(--pri-soft);border-left:4px solid var(--pri);border-radius:0 10px 10px 0;padding:11px 14px;font-size:.94rem;margin:8px 0}
.def b{color:var(--pri-d)}
html.dark .def b{color:var(--pri-l)}
.exa{background:var(--card-soft);border:1px dashed var(--border);border-radius:10px;padding:12px 14px;font-size:.92rem;margin-top:8px}
.exa .step{margin:5px 0;padding-left:14px;position:relative}
.exa .step::before{content:'';position:absolute;left:0;top:9px;width:6px;height:6px;border-radius:50%;background:var(--pri)}
.note-safe{display:flex;gap:9px;background:var(--warn-bg);border:1px solid var(--pri-l);border-radius:10px;padding:10px 13px;font-size:.88rem;margin-top:8px}
.note-safe svg{stroke:var(--pri-d);margin-top:2px}
/* WIDGET shell */
.wgt{background:var(--card);border:1.5px solid var(--pri-soft);border-radius:14px;padding:16px 18px;box-shadow:var(--sh);margin-top:14px}
.wgt-h{font-family:'Outfit';font-size:.96rem;font-weight:800;color:var(--pri-d);margin-bottom:10px;display:flex;align-items:center;gap:8px}
html.dark .wgt-h{color:var(--pri-l)}
.wgt-h svg{stroke:var(--pri)}
.fld{display:flex;gap:8px;align-items:center;flex-wrap:wrap;margin:8px 0}
.fld label{font-size:.86rem;font-weight:600;color:var(--muted)}
input[type=text],input[type=number],select{font-family:inherit;font-size:.95rem;padding:8px 11px;border:1.5px solid var(--border);border-radius:9px;background:var(--card);color:var(--text);transition:border-color .14s}
input:focus,select:focus{outline:0;border-color:var(--pri);box-shadow:0 0 0 3px var(--pri-soft)}
.btn{font-family:inherit;font-weight:700;font-size:.9rem;padding:8px 16px;border-radius:9px;border:1.5px solid var(--border);background:var(--card);color:var(--text);cursor:pointer;transition:.14s}
.btn:hover{border-color:var(--pri);background:var(--pri-soft)}
.btn.primary{background:linear-gradient(135deg,var(--pri),var(--pri-l));color:#fff;border-color:transparent}
.btn.primary:hover{filter:brightness(1.07)}
.out{margin-top:10px;padding:11px 14px;border-radius:10px;font-size:.94rem;background:var(--card-soft);border:1px solid var(--border)}
.out.ok{background:var(--ok-bg);border-color:#86efac;color:var(--ok)}
.out.bad{background:var(--bad-bg);border-color:#fca5a5;color:var(--bad)}
.bd{font-family:var(--mono);font-size:.9rem;line-height:1.7}
/* mole triangle */
.mtri{display:grid;grid-template-columns:170px 1fr;gap:16px;align-items:center}
@media(max-width:560px){.mtri{grid-template-columns:1fr}}
.mtri-svg{width:170px;height:128px;color:var(--pri)}
.mtri-fields{display:flex;flex-direction:column;gap:9px}
.mtri-f{display:flex;flex-direction:column;gap:3px}
.mtri-lab{font-size:.78rem;font-weight:700;color:var(--muted)}
.mtri-f input{width:100%}
.mtri-out{grid-column:1/-1;padding:10px 13px;border-radius:10px;background:var(--card-soft);border:1px solid var(--border);font-size:.92rem}
.mtri-out.ok{background:var(--ok-bg);border-color:#86efac;color:var(--ok)}
.mtri-out b{display:block;font-size:1.02rem}
.mtri-form{display:block;font-family:var(--mono);font-size:.84rem;opacity:.85;margin-top:3px}
/* equation balancer */
.ceqb-row{display:flex;align-items:center;gap:6px;flex-wrap:wrap;font-size:1.05rem;font-weight:600;margin-bottom:12px}
.ceqb-sp{display:inline-flex;align-items:center;gap:3px}
.ceqb-coef{width:46px;text-align:center;padding:6px 4px;font-weight:800}
.ceqb-f{font-weight:700}
.ceqb-plus,.ceqb-arrow{color:var(--muted);font-weight:800;padding:0 2px}
.ceqb-arrow{color:var(--pri);font-size:1.2rem}
.ceqb-actions{display:flex;gap:8px;flex-wrap:wrap}
.ceqb-out{margin-top:10px}
.ceqb-msg{font-weight:700;margin-bottom:6px}
.ceqb-out.ok .ceqb-msg{color:var(--ok)}
.ceqb-out.bad .ceqb-msg{color:var(--bad)}
.ceqb-tab{border-collapse:collapse;font-size:.86rem;font-family:var(--mono)}
.ceqb-tab th,.ceqb-tab td{border:1px solid var(--border);padding:4px 12px;text-align:center}
.ceqb-tab tr.ne td{background:var(--bad-bg);color:var(--bad)}
.ceqb-tab tr.eq td{background:var(--ok-bg);color:var(--ok)}
/* element lookup */
.el-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(54px,1fr));gap:6px;margin-top:8px}
.el-cell{aspect-ratio:1;border:1px solid var(--border);border-radius:8px;background:var(--card);display:flex;flex-direction:column;align-items:center;justify-content:center;cursor:pointer;transition:.12s;padding:2px}
.el-cell:hover,.el-cell.on{background:var(--pri-soft);border-color:var(--pri);transform:translateY(-2px)}
.el-cell .z{font-size:.6rem;color:var(--muted)}
.el-cell .s{font-size:1.05rem;font-weight:800;color:var(--pri-d)}
html.dark .el-cell .s{color:var(--pri-l)}
.el-cell .a{font-size:.56rem;color:var(--muted)}
.el-info{margin-top:10px;padding:12px 14px;border-radius:10px;background:var(--card-soft);border:1px solid var(--border);font-size:.94rem;min-height:48px}
/* trainer / proverka */
.tr{margin-top:12px;background:var(--card-soft);border:1px solid var(--border);border-radius:12px;padding:13px 15px}
.tr-q{font-size:.94rem;font-weight:600;margin-bottom:9px}
.tr-row{display:flex;gap:8px;align-items:center;flex-wrap:wrap}
.tr-fb{margin-top:8px;font-size:.88rem;font-weight:600;display:none}
.tr-fb.show{display:block}
.tr-fb.ok{color:var(--ok)}
.tr-fb.bad{color:var(--bad)}
.opt{padding:7px 13px;border:1.5px solid var(--border);border-radius:9px;background:var(--card);cursor:pointer;font-size:.9rem;font-weight:600;transition:.12s}
.opt:hover{border-color:var(--pri)}
.opt.ok{background:var(--ok-bg);border-color:#86efac;color:var(--ok)}
.opt.bad{background:var(--bad-bg);border-color:#fca5a5;color:var(--bad)}
/* mark read */
.mark{margin-top:14px;display:flex;gap:10px;align-items:center;flex-wrap:wrap}
.mark .btn.primary{padding:9px 18px}
.mark .done-lab{font-size:.86rem;font-weight:700;color:var(--ok);display:none;align-items:center;gap:6px}
.mark.done .done-lab{display:inline-flex}
.mark.done .btn{display:none}
/* boss */
.boss{background:var(--card);border:2px solid var(--pri-soft);border-radius:16px;padding:18px;box-shadow:var(--sh2)}
.boss-h{font-family:'Outfit';font-size:1.1rem;font-weight:800;color:var(--pri-d);display:flex;align-items:center;gap:9px;margin-bottom:6px}
html.dark .boss-h{color:var(--pri-l)}
.boss-sub{font-size:.88rem;color:var(--muted);margin-bottom:12px}
.bossbar{display:flex;gap:12px;align-items:center;margin-bottom:14px;flex-wrap:wrap}
.bossbar .lab{font-weight:700;font-size:.9rem}
.bossbar .bar{flex:1;min-width:140px;height:8px;background:var(--pri-soft);border-radius:5px;overflow:hidden}
.bossbar .fill{height:100%;background:linear-gradient(90deg,var(--pri),var(--pri-l));width:0;transition:width .5s}
.bcard{border:1.5px solid var(--border);border-radius:12px;padding:14px;margin-bottom:11px;transition:.3s}
.bcard.solved{border-color:#86efac;box-shadow:0 0 0 3px var(--ok-bg)}
.bcard .q{font-size:.95rem;margin-bottom:10px}
.bcard .q b{color:var(--pri-d)}
html.dark .bcard .q b{color:var(--pri-l)}
.bcard .row{display:flex;gap:8px;align-items:center;flex-wrap:wrap}
.bcard .fb{margin-top:8px;font-size:.88rem;font-weight:600;display:none}
.bcard .fb.show{display:block}
.bcard .fb.ok{color:var(--ok)}.bcard .fb.bad{color:var(--bad)}
.hintbox{margin-top:8px;padding:9px 12px;background:var(--warn-bg);border-left:3px solid var(--pri);border-radius:7px;font-size:.86rem;display:none}
.hintbox.show{display:block}
.final-cta{margin-top:14px;padding:15px 18px;border-radius:13px;background:linear-gradient(135deg,#fef3c7,#fde68a);border:1.5px solid var(--pri-l);display:none;gap:13px;align-items:center;flex-wrap:wrap}
.final-cta.show{display:flex}
html.dark .final-cta{background:linear-gradient(135deg,rgba(217,119,6,.2),rgba(180,83,9,.18))}
.final-cta b{color:#92400e;font-family:'Outfit'}
html.dark .final-cta b{color:#fde68a}
.final-cta a{margin-left:auto;padding:9px 16px;border-radius:9px;background:var(--pri);color:#fff;text-decoration:none;font-weight:700;font-size:.88rem}
.foot{text-align:center;padding:26px 16px;color:var(--muted);font-size:.78rem;border-top:1px solid var(--border);margin-top:10px}
.popup{position:fixed;bottom:20px;left:50%;transform:translateX(-50%) translateY(120px);background:linear-gradient(135deg,var(--pri),var(--pri-l));color:#fff;padding:12px 22px;border-radius:12px;font-weight:700;box-shadow:var(--sh2);z-index:50;transition:transform .35s;font-size:.92rem}
.popup.show{transform:translateX(-50%) translateY(0)}
</style>
</head>
<body>
<header class="hdr">
<div class="hdr-inner">
<a href="/textbook/chemistry-8" class="hdr-back">
<svg class="ic" viewBox="0 0 24 24"><polyline points="15 18 9 12 15 6"/></svg>
К разделам
</a>
<div>
<div class="hdr-kicker">Вводный раздел &middot; § 19 &middot; ПР 1</div>
<h1>Количественные понятия в химии</h1>
</div>
<div class="hdr-side">
<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>
<div class="hero">
<div class="hero-card">
<div class="hero-ic">n</div>
<div class="hero-t">
<div class="hero-lab">Прогресс раздела</div>
<div id="prog-text" style="font-weight:700">0 из 9 параграфов · 0%</div>
<div class="hero-bar"><div class="hero-fill" id="prog-fill"></div></div>
</div>
<div class="hero-xp" id="xp-badge">0 XP</div>
</div>
</div>
<div class="wrap">
<nav class="side" id="side">
<div class="side-h">Содержание</div>
</nav>
<main class="col" id="col">
<section class="sec" id="p1">
<div class="ph"><div class="ph-num">§ 1</div><h2>Атомы. Химические элементы. Относительная атомная масса</h2><span class="ph-key">$A_r(\text{O}) = 16$</span></div>
<div class="card">
<h3><span class="tg">теория</span> Атом и химический элемент</h3>
<p><b>Атом</b> — мельчайшая химически неделимая частица вещества. <b>Химический элемент</b> — вид атомов с одинаковым зарядом ядра. Каждый элемент имеет символ (например, <b>H</b>, <b>O</b>, <b>Fe</b>) и порядковый номер $Z$ в периодической системе.</p>
<div class="def"><b>Относительная атомная масса</b> $A_r$ показывает, во сколько раз масса атома больше $\tfrac{1}{12}$ массы атома углерода-12. Величина безразмерная: $A_r(\text{H})=1$, $A_r(\text{O})=16$, $A_r(\text{Fe})=56$.</div>
</div>
<div class="wgt">
<div class="wgt-h"><svg class="ic" viewBox="0 0 24 24"><circle cx="12" cy="12" r="9"/><path d="M12 7v10M7 12h10"/></svg> Карта элементов: клик → $Z$, название, $A_r$</div>
<div class="el-grid" id="el-grid"></div>
<div class="el-info" id="el-info">Выберите элемент, чтобы увидеть его характеристики.</div>
</div>
<div class="tr" data-tr="t1">
<div class="tr-q">Во сколько раз атом серы ($A_r=32$) тяжелее атома кислорода ($A_r=16$)?</div>
<div class="tr-row" data-opts><button class="opt">в 2 раза</button><button class="opt">в 16 раз</button><button class="opt">в 48 раз</button></div>
<div class="tr-fb"></div>
</div>
<div class="mark" data-mark="p1"><button class="btn primary">Отметить изученным (+5 XP)</button><span class="done-lab"><svg class="ic" viewBox="0 0 24 24"><polyline points="20 6 9 17 4 12"/></svg> Изучено</span></div>
</section>
<section class="sec" id="p2">
<div class="ph"><div class="ph-num">§ 2</div><h2>Молекулы. Простые и сложные вещества. Химические формулы. $M_r$</h2><span class="ph-key">$M_r=\sum A_r$</span></div>
<div class="card">
<h3><span class="tg">теория</span> Вещества и формулы</h3>
<p><b>Простое вещество</b> образовано атомами одного элемента ($\text{O}_2$, $\text{Fe}$), <b>сложное</b> — разных ($\text{H}_2\text{O}$, $\text{CaCO}_3$). <b>Химическая формула</b> показывает качественный и количественный состав: индекс — число атомов элемента.</p>
<div class="def"><b>Относительная молекулярная масса</b> $M_r$ равна сумме относительных атомных масс всех атомов в формуле. Например, $M_r(\text{H}_2\text{O}) = 2\cdot1 + 16 = 18$.</div>
</div>
<div class="wgt">
<div class="wgt-h"><svg class="ic" viewBox="0 0 24 24"><path d="M4 7h16M4 12h16M4 17h10"/></svg> Калькулятор $M_r$ по формуле</div>
<div class="fld">
<label>Формула</label>
<input type="text" id="mr-in" value="CaCO3" style="width:160px;font-family:var(--mono)">
<button class="btn primary" id="mr-go">Вычислить</button>
</div>
<div class="fld" style="gap:6px">
<button class="btn mr-ex" data-f="H2O">H&#8322;O</button>
<button class="btn mr-ex" data-f="H2SO4">H&#8322;SO&#8324;</button>
<button class="btn mr-ex" data-f="Ca(OH)2">Ca(OH)&#8322;</button>
<button class="btn mr-ex" data-f="Al2(SO4)3">Al&#8322;(SO&#8324;)&#8323;</button>
</div>
<div class="out" id="mr-out">Введите формулу и нажмите «Вычислить».</div>
</div>
<div class="mark" data-mark="p2"><button class="btn primary">Отметить изученным (+5 XP)</button><span class="done-lab"><svg class="ic" viewBox="0 0 24 24"><polyline points="20 6 9 17 4 12"/></svg> Изучено</span></div>
</section>
<section class="sec" id="p3">
<div class="ph"><div class="ph-num">§ 3</div><h2>Химическое количество вещества</h2><span class="ph-key">$n$, моль</span></div>
<div class="card">
<h3><span class="tg">теория</span> Порция вещества</h3>
<p>Считать атомы и молекулы поштучно невозможно — их слишком много. Поэтому ввели специальную «порцию» — <b>химическое количество вещества</b> $n$, единица — <b>моль</b>. Одна и та же порция ($1$ моль) любого вещества содержит одинаковое число частиц.</p>
<div class="def">Химическое количество $n$ связывает массу $m$, число частиц $N$ и объём газа $V$. Это «мост» между миром атомов и граммами на весах.</div>
</div>
<div class="wgt">
<div class="wgt-h"><svg class="ic" viewBox="0 0 24 24"><circle cx="6" cy="6" r="2"/><circle cx="12" cy="6" r="2"/><circle cx="18" cy="6" r="2"/><circle cx="9" cy="12" r="2"/><circle cx="15" cy="12" r="2"/><circle cx="12" cy="18" r="2"/></svg> Порция вещества: $n \Rightarrow N$ и $m$</div>
<div class="fld">
<label>Вещество</label>
<select id="port-sub"><option value="H2O">вода H&#8322;O (M=18)</option><option value="O2">кислород O&#8322; (M=32)</option><option value="CO2">углекислый газ CO&#8322; (M=44)</option><option value="NaCl">соль NaCl (M=58,5)</option></select>
<label>n, моль</label>
<input type="range" id="port-n" min="0.1" max="5" step="0.1" value="1" style="vertical-align:middle">
<span class="bd" id="port-nv">1,0</span>
</div>
<div class="out" id="port-out"></div>
</div>
<div class="mark" data-mark="p3"><button class="btn primary">Отметить изученным (+5 XP)</button><span class="done-lab"><svg class="ic" viewBox="0 0 24 24"><polyline points="20 6 9 17 4 12"/></svg> Изучено</span></div>
</section>
<section class="sec" id="p4">
<div class="ph"><div class="ph-num">§ 4</div><h2>Моль — единица химического количества. Постоянная Авогадро</h2><span class="ph-key">$N = n\cdot N_A$</span></div>
<div class="card">
<h3><span class="tg">теория</span> Постоянная Авогадро</h3>
<div class="def"><b>1 моль</b> — это химическое количество вещества, содержащее столько же частиц, сколько атомов в $12$ г углерода-$12$, а именно $N_A = 6{,}02\cdot10^{23}$ частиц/моль — <b>постоянная Авогадро</b>.</div>
<p>Число частиц: $N = n\cdot N_A$. Отсюда $n = \dfrac{N}{N_A}$.</p>
</div>
<div class="wgt">
<div class="wgt-h"><svg class="ic" viewBox="0 0 24 24"><path d="M12 2v20M2 12h20"/></svg> Счётчик частиц $N = n\cdot N_A$</div>
<div class="fld"><label>n, моль</label><input type="range" id="av-n" min="0.25" max="10" step="0.25" value="2"><span class="bd" id="av-nv">2,0</span></div>
<div class="out" id="av-out"></div>
</div>
<div class="tr" data-tr="t4">
<div class="tr-q">Сколько молекул содержится в $0{,}5$ моль воды? ($N_A=6{,}02\cdot10^{23}$)</div>
<div class="tr-row" data-opts><button class="opt" data-ok>$3{,}01\cdot10^{23}$</button><button class="opt">$6{,}02\cdot10^{23}$</button><button class="opt">$12{,}04\cdot10^{23}$</button></div>
<div class="tr-fb"></div>
</div>
<div class="mark" data-mark="p4"><button class="btn primary">Отметить изученным (+5 XP)</button><span class="done-lab"><svg class="ic" viewBox="0 0 24 24"><polyline points="20 6 9 17 4 12"/></svg> Изучено</span></div>
</section>
<section class="sec" id="p5">
<div class="ph"><div class="ph-num">§ 5</div><h2>Молярная масса. Молярный объём газов</h2><span class="ph-key">$V_m=22{,}4$ л/моль</span></div>
<div class="card">
<h3><span class="tg">теория</span> M и Vm</h3>
<div class="def"><b>Молярная масса</b> $M$ — масса $1$ моль вещества (г/моль). Численно $M$ равна $M_r$: $M(\text{H}_2\text{O})=18$ г/моль.</div>
<div class="def"><b>Молярный объём</b> $V_m$ — объём $1$ моль газа. При нормальных условиях (н.у.) $V_m = 22{,}4$ л/моль для любого газа (закон Авогадро).</div>
</div>
<div class="wgt">
<div class="wgt-h"><svg class="ic" viewBox="0 0 24 24"><path d="M4 7h16M4 12h16M4 17h16"/></svg> M по формуле и объём 1 моль газа</div>
<div class="fld"><label>Формула газа</label><input type="text" id="m5-in" value="CO2" style="width:140px;font-family:var(--mono)"><button class="btn primary" id="m5-go">Найти M</button></div>
<div class="out" id="m5-out">M(CO&#8322;) и объём при н.у. появятся здесь.</div>
</div>
<div class="mark" data-mark="p5"><button class="btn primary">Отметить изученным (+5 XP)</button><span class="done-lab"><svg class="ic" viewBox="0 0 24 24"><polyline points="20 6 9 17 4 12"/></svg> Изучено</span></div>
</section>
<section class="sec" id="p6">
<div class="ph"><div class="ph-num">§ 6 &middot; звёздный виджет</div><h2>Вычисление $n$ по массе и массы по $n$</h2><span class="ph-key">$n = \dfrac{m}{M}$</span></div>
<div class="card">
<h3><span class="tg">правило</span> Треугольник nmM</h3>
<p>Три величины связаны формулой $m = n\cdot M$. Закрой искомую — получишь формулу: $n=\dfrac{m}{M}$, $m=n\cdot M$, $M=\dfrac{m}{n}$.</p>
<div class="exa">
<div class="step">Дано: $m=36$ г воды, $M=18$ г/моль.</div>
<div class="step">$n = \dfrac{m}{M} = \dfrac{36}{18} = 2$ моль.</div>
</div>
</div>
<div class="wgt">
<div class="wgt-h"><svg class="ic" viewBox="0 0 24 24"><path d="M12 3 2 21h20z"/></svg> Интерактивный треугольник n–m–M</div>
<div class="fld"><label>Подставить M вещества</label><select id="mt-sub"><option value="">— вручную —</option><option value="H2O">H&#8322;O · 18</option><option value="CO2">CO&#8322; · 44</option><option value="NaOH">NaOH · 40</option><option value="CaCO3">CaCO&#8323; · 100</option><option value="H2SO4">H&#8322;SO&#8324; · 98</option></select></div>
<div id="mt-mount"></div>
</div>
<div class="mark" data-mark="p6"><button class="btn primary">Отметить изученным (+5 XP)</button><span class="done-lab"><svg class="ic" viewBox="0 0 24 24"><polyline points="20 6 9 17 4 12"/></svg> Изучено</span></div>
</section>
<section class="sec" id="p7">
<div class="ph"><div class="ph-num">§ 7</div><h2>Вычисление $n$ газа по объёму и объёма по $n$</h2><span class="ph-key">$n = \dfrac{V}{V_m}$</span></div>
<div class="card">
<h3><span class="tg">правило</span> Связка m – n – V – N</h3>
<p>Для газа при н.у.: $n=\dfrac{V}{V_m}$, $V=n\cdot V_m$ ($V_m=22{,}4$ л/моль). Вместе с $n=\dfrac{m}{M}$ и $N=n\cdot N_A$ это единая система: зная одно — найдёшь всё.</p>
</div>
<div class="wgt">
<div class="wgt-h"><svg class="ic" viewBox="0 0 24 24"><path d="M3 12h18M12 3v18"/></svg> Универсальный калькулятор газа</div>
<div class="fld"><label>Газ</label><select id="g7-sub"><option value="O2">O&#8322; · M=32</option><option value="CO2">CO&#8322; · M=44</option><option value="H2">H&#8322; · M=2</option><option value="N2">N&#8322; · M=28</option></select></div>
<div class="fld"><label>Известно</label>
<select id="g7-key"><option value="n">n, моль</option><option value="m">m, г</option><option value="V">V, л (н.у.)</option><option value="N">N, частиц</option></select>
<input type="text" id="g7-val" value="1" style="width:120px;font-family:var(--mono)">
<button class="btn primary" id="g7-go">Рассчитать</button>
</div>
<div class="out" id="g7-out"></div>
</div>
<div class="mark" data-mark="p7"><button class="btn primary">Отметить изученным (+5 XP)</button><span class="done-lab"><svg class="ic" viewBox="0 0 24 24"><polyline points="20 6 9 17 4 12"/></svg> Изучено</span></div>
</section>
<section class="sec" id="pr1">
<div class="ph" style="background:linear-gradient(135deg,#7c2d12,#ea580c 60%,#fb923c)"><div class="ph-num">Практическая работа 1</div><h2>Химическое количество вещества</h2><span class="ph-key">$n=\dfrac{m}{M}$</span></div>
<div class="card">
<h3><span class="tg">практика</span> Порядок работы</h3>
<ul>
<li>Взвесь на весах образцы веществ (например, $\text{NaCl}$, $\text{CuSO}_4$).</li>
<li>Запиши массу $m$ и определи молярную массу $M$ по формуле.</li>
<li>Вычисли химическое количество $n=\dfrac{m}{M}$ и число частиц $N=n\cdot N_A$.</li>
<li>Оформи вывод: какому числу частиц соответствует взятая масса.</li>
</ul>
<div class="note-safe"><svg class="ic" viewBox="0 0 24 24"><path d="M12 9v4M12 17h.01"/><path d="M10.3 3.9 1.8 18a2 2 0 0 0 1.7 3h17a2 2 0 0 0 1.7-3L13.7 3.9a2 2 0 0 0-3.4 0z"/></svg> Работай аккуратно с реактивами и весами; не пробуй вещества на вкус.</div>
</div>
<div class="mark" data-mark="pr1"><button class="btn primary">Отметить выполненной (+5 XP)</button><span class="done-lab"><svg class="ic" viewBox="0 0 24 24"><polyline points="20 6 9 17 4 12"/></svg> Выполнено</span></div>
</section>
<section class="sec" id="p8">
<div class="ph"><div class="ph-num">§ 8 &middot; звёздный виджет</div><h2>Химические реакции</h2><span class="ph-key">закон сохранения массы</span></div>
<div class="card">
<h3><span class="tg">теория</span> Уравнение реакции</h3>
<p>В химической реакции одни вещества превращаются в другие, но <b>атомы не исчезают и не появляются</b> (закон сохранения массы М. В. Ломоносова, А. Лавуазье). Поэтому уравнение реакции <b>уравнивают коэффициентами</b> — число атомов каждого элемента слева и справа равно.</p>
<p>Типы реакций: <b>соединения</b> ($A+B\to AB$), <b>разложения</b> ($AB\to A+B$), <b>замещения</b> ($A+BC\to AC+B$), <b>обмена</b> ($AB+CD\to AD+CB$).</p>
</div>
<div class="wgt">
<div class="wgt-h"><svg class="ic" viewBox="0 0 24 24"><path d="M3 12h18M14 6l6 6-6 6"/></svg> Балансировщик: расставь коэффициенты</div>
<div class="fld" style="gap:6px"><label>Реакция</label>
<select id="bal-pick">
<option value="H2 + O2 -> H2O|2,1,2">H&#8322; + O&#8322; &rarr; H&#8322;O</option>
<option value="Fe + O2 -> Fe2O3|4,3,2">Fe + O&#8322; &rarr; Fe&#8322;O&#8323;</option>
<option value="Al + HCl -> AlCl3 + H2|2,6,2,3">Al + HCl &rarr; AlCl&#8323; + H&#8322;</option>
<option value="CH4 + O2 -> CO2 + H2O|1,2,1,2">CH&#8324; + O&#8322; &rarr; CO&#8322; + H&#8322;O</option>
</select>
</div>
<div id="bal-mount"></div>
</div>
<div class="mark" data-mark="p8"><button class="btn primary">Отметить изученным (+5 XP)</button><span class="done-lab"><svg class="ic" viewBox="0 0 24 24"><polyline points="20 6 9 17 4 12"/></svg> Изучено</span></div>
</section>
<section class="sec" id="p9">
<div class="ph"><div class="ph-num">§ 9 &middot; звёздный виджет</div><h2>Количественные расчёты по уравнениям реакций</h2><span class="ph-key">по мольным отношениям</span></div>
<div class="card">
<h3><span class="tg">правило</span> Алгоритм расчёта</h3>
<ul>
<li>Записать и уравнять уравнение реакции.</li>
<li>Найти $n$ известного вещества: $n=\dfrac{m}{M}$ (или $\dfrac{V}{V_m}$).</li>
<li>По коэффициентам найти $n$ искомого (мольное отношение).</li>
<li>Перейти к массе/объёму: $m=n\cdot M$ ($V=n\cdot V_m$).</li>
</ul>
</div>
<div class="wgt">
<div class="wgt-h"><svg class="ic" viewBox="0 0 24 24"><path d="M9 11l3 3L22 4"/><path d="M21 12v7a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h11"/></svg> Пошаговый решатель по уравнению</div>
<div class="fld"><label>Задача</label><select id="st-pick"></select></div>
<div class="out" id="st-out"></div>
<div class="fld"><button class="btn" id="st-step">Следующий шаг ▸</button><button class="btn" id="st-all">Показать всё решение</button></div>
</div>
<div class="mark" data-mark="p9"><button class="btn primary">Отметить изученным (+5 XP)</button><span class="done-lab"><svg class="ic" viewBox="0 0 24 24"><polyline points="20 6 9 17 4 12"/></svg> Изучено</span></div>
</section>
<section class="sec" id="boss">
<div class="boss">
<div class="boss-h"><svg class="ic" viewBox="0 0 24 24" style="width:22px;height:22px"><path d="M7 4h10v6a5 5 0 0 1-10 0V4z"/><path d="M9 20h6M12 15v5"/></svg> Босс раздела: количественные понятия</div>
<div class="boss-sub">4 задачи на всё, что изучено. За каждую — +10 XP. Победишь всех — ачивка «Счёт в химии» и +30 XP.</div>
<div class="bossbar"><span class="lab" id="boss-lab">Решено: 0 / 4</span><div class="bar"><div class="fill" id="boss-fill"></div></div></div>
<div id="boss-cont"></div>
<div class="final-cta" id="boss-cta">
<svg class="ic" viewBox="0 0 24 24" style="width:26px;height:26px;stroke:var(--pri-d)"><path d="M6 9H4l-1-3h18l-1 3h-2M6 9l1 6h10l1-6"/></svg>
<b>Вводный раздел пройден! Ачивка «Счёт в химии» получена.</b>
<a href="/textbook/chemistry-8">К разделам →</a>
</div>
</div>
</section>
</main>
</div>
<div class="popup" id="popup"></div>
<footer class="foot">Интерактивный учебник «Химия — 8 класс» · Вводный раздел · LearnSpace</footer>
<script>
'use strict';
/* Инициализация — после DOMContentLoaded: к этому моменту отложенные (defer)
chem8_svg.js / biochem-core.js / api.js / xp.js уже выполнены и доступны. */
document.addEventListener('DOMContentLoaded', function(){
const _TB_SLUG = 'chemistry-8-intro';
const C = window.Chem8 || {};
const Nav = [
{id:'p1',n:'§ 1',t:'Атомы. Относительная атомная масса'},
{id:'p2',n:'§ 2',t:'Формулы. Молекулярная масса'},
{id:'p3',n:'§ 3',t:'Химическое количество'},
{id:'p4',n:'§ 4',t:'Моль. Постоянная Авогадро'},
{id:'p5',n:'§ 5',t:'Молярная масса и объём'},
{id:'p6',n:'§ 6',t:'Треугольник n–m–M'},
{id:'p7',n:'§ 7',t:'Расчёты для газов'},
{id:'pr1',n:'ПР',t:'Практическая работа 1',note:true},
{id:'p8',n:'§ 8',t:'Химические реакции'},
{id:'p9',n:'§ 9',t:'Расчёты по уравнениям'},
{id:'boss',n:'★',t:'Босс раздела',note:true}
];
const READ_IDS = ['p1','p2','p3','p4','p5','p6','p7','p8','p9'];
/* theme */
(function(){
var saved = localStorage.getItem('chemistry8_theme') || localStorage.getItem('theme') || 'light';
if (saved === 'dark') document.documentElement.classList.add('dark');
var lab = document.getElementById('theme-lab');
if (lab) lab.textContent = saved === 'dark' ? 'Светлая' : 'Тёмная';
document.getElementById('theme-btn').addEventListener('click', function(){
document.documentElement.classList.toggle('dark');
var d = document.documentElement.classList.contains('dark');
localStorage.setItem('chemistry8_theme', d?'dark':'light');
localStorage.setItem('theme', d?'dark':'light');
if (lab) lab.textContent = d?'Светлая':'Тёмная';
});
})();
/* sidebar */
(function(){
var side = document.getElementById('side');
Nav.forEach(function(it){
var a = document.createElement('a');
a.href = '#'+it.id; a.id = 'nav-'+it.id;
if (it.note) a.className = 'note';
a.innerHTML = '<span class="side-num">'+it.n+'</span><span>'+it.t+'</span>';
side.appendChild(a);
});
})();
/* progress / XP */
var PROG = {};
function popup(msg){ var p=document.getElementById('popup'); p.textContent=msg; p.classList.add('show'); setTimeout(function(){p.classList.remove('show');},2200); }
function loadLocal(){ try{ var s=localStorage.getItem('chemistry8_intro_read'); if(s) PROG=JSON.parse(s)||{}; }catch(e){} }
function saveLocal(){ try{ localStorage.setItem('chemistry8_intro_read', JSON.stringify(PROG)); }catch(e){} }
function getXp(){ return parseInt(localStorage.getItem('chemistry8_xp')||'0',10)||0; }
function addXp(n,src){
localStorage.setItem('chemistry8_xp', String(getXp()+n));
try{ if(window.LS&&LS.xp&&LS.xp.add) LS.xp.add(n,'chem8-intro-'+(src||'x')); }catch(e){}
refreshXp();
}
function refreshXp(){ var b=document.getElementById('xp-badge'); if(b) b.textContent=getXp()+' XP'; }
var _marked=new Set(), _pending=null, _timer=null;
function _flush(){ var body=_pending; _pending=null; if(!body) return; var 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(){}); }
function _queue(p){ _pending=Object.assign(_pending||{},p); if(_timer)clearTimeout(_timer); _timer=setTimeout(_flush,600); }
function markServerRead(id){ if(_marked.has(id))return; _marked.add(id); _queue({mark_read:id}); }
window.addEventListener('beforeunload',_flush);
function refreshProgress(){
var read = READ_IDS.filter(function(id){return PROG[id];}).length;
var pct = Math.round(read*100/READ_IDS.length);
var t=document.getElementById('prog-text'); if(t) t.textContent=read+' из '+READ_IDS.length+' параграфов · '+pct+'%';
var f=document.getElementById('prog-fill'); if(f) f.style.width=pct+'%';
Nav.forEach(function(it){ var a=document.getElementById('nav-'+it.id); if(a){ if(PROG[it.id]) a.classList.add('done'); else a.classList.remove('done'); } });
document.querySelectorAll('[data-mark]').forEach(function(m){ if(PROG[m.getAttribute('data-mark')]) m.classList.add('done'); });
}
function markRead(id){
if(PROG[id]) return;
PROG[id]=true; saveLocal();
if(READ_IDS.indexOf(id)!==-1) markServerRead(id);
addXp(5,id); refreshProgress(); popup('+5 XP · отмечено');
}
document.querySelectorAll('[data-mark]').forEach(function(m){
m.querySelector('.btn').addEventListener('click', function(){ markRead(m.getAttribute('data-mark')); });
});
function loadServer(){
var tok=(window.LS&&LS.getToken)?LS.getToken():''; if(!tok){ refreshProgress(); return; }
fetch('/api/textbooks/'+_TB_SLUG,{headers:{'Authorization':'Bearer '+tok}}).then(function(r){return r.ok?r.json():null;}).then(function(d){
if(d&&d.progress&&d.progress.read){ d.progress.read.forEach(function(k){ _marked.add(k); PROG[k]=true; }); saveLocal(); }
refreshProgress();
}).catch(function(){ refreshProgress(); });
}
/* scrollspy */
(function(){
var secs = Nav.map(function(it){return document.getElementById(it.id);}).filter(Boolean);
function onScroll(){
var y = window.scrollY + 90, cur = secs[0];
secs.forEach(function(s){ if(s.offsetTop<=y) cur=s; });
Nav.forEach(function(it){ var a=document.getElementById('nav-'+it.id); if(a) a.classList.toggle('active', cur && cur.id===it.id); });
}
window.addEventListener('scroll', onScroll, {passive:true}); onScroll();
})();
/* ===== WIDGETS ===== */
/* §1 element lookup */
var EL = {
H:[1,'Водород'],He:[2,'Гелий'],Li:[3,'Литий'],Be:[4,'Бериллий'],B:[5,'Бор'],C:[6,'Углерод'],
N:[7,'Азот'],O:[8,'Кислород'],F:[9,'Фтор'],Ne:[10,'Неон'],Na:[11,'Натрий'],Mg:[12,'Магний'],
Al:[13,'Алюминий'],Si:[14,'Кремний'],P:[15,'Фосфор'],S:[16,'Сера'],Cl:[17,'Хлор'],Ar:[18,'Аргон'],
K:[19,'Калий'],Ca:[20,'Кальций'],Fe:[26,'Железо'],Cu:[29,'Медь'],Zn:[30,'Цинк'],Ag:[47,'Серебро'],Ba:[56,'Барий']
};
(function(){
var grid=document.getElementById('el-grid'), info=document.getElementById('el-info'); if(!grid) return;
Object.keys(EL).forEach(function(s){
var ar = C.arOf?C.arOf(s):'';
var c=document.createElement('div'); c.className='el-cell'; c.dataset.s=s;
c.innerHTML='<span class="z">'+EL[s][0]+'</span><span class="s">'+s+'</span><span class="a">'+ar+'</span>';
c.addEventListener('click',function(){
grid.querySelectorAll('.el-cell').forEach(function(x){x.classList.remove('on');}); c.classList.add('on');
info.innerHTML='<b>'+EL[s][1]+'</b> ('+s+') · порядковый номер Z = '+EL[s][0]+' · A_r = '+ar;
});
grid.appendChild(c);
});
})();
function bindOpts(trId, okText){
var tr=document.querySelector('[data-tr="'+trId+'"]'); if(!tr) return;
var fb=tr.querySelector('.tr-fb');
tr.querySelectorAll('.opt').forEach(function(o){
o.addEventListener('click',function(){
var ok = o.hasAttribute('data-ok') || (okText && o.textContent.trim()===okText);
tr.querySelectorAll('.opt').forEach(function(x){x.style.pointerEvents='none';});
o.classList.add(ok?'ok':'bad'); fb.className='tr-fb show '+(ok?'ok':'bad');
fb.textContent = ok?'Верно! +3 XP':'Неверно.';
if(!ok){ tr.querySelectorAll('.opt').forEach(function(x){ if(x.hasAttribute('data-ok')||(okText&&x.textContent.trim()===okText)) x.classList.add('ok'); }); }
else addXp(3,trId);
});
});
}
bindOpts('t1','в 2 раза');
bindOpts('t4',null);
/* §2 Mr calculator */
(function(){
var inp=document.getElementById('mr-in'), out=document.getElementById('mr-out'), go=document.getElementById('mr-go'); if(!inp) return;
function calc(){
var f=inp.value.trim(); var cnt=C.elementCounts?C.elementCounts(f):null; var mr=C.molarMass?C.molarMass(f):NaN;
if(!cnt||isNaN(mr)){ out.className='out bad'; out.textContent='Не удалось разобрать формулу. Проверьте символы элементов.'; return; }
var terms=Object.keys(cnt).map(function(e){ return (C.arOf?C.arOf(e):'?')+'·'+cnt[e]; });
out.className='out ok';
out.innerHTML='<b>M_r('+f+') = '+C.fmt(mr)+'</b><br><span class="bd">'+
Object.keys(cnt).map(function(e){return e+': A_r='+(C.arOf?C.arOf(e):'?')+' × '+cnt[e];}).join(' &nbsp;|&nbsp; ')+
'<br>Σ = '+terms.join(' + ')+' = '+C.fmt(mr)+'</span>';
}
go.addEventListener('click',calc);
inp.addEventListener('keydown',function(e){ if(e.key==='Enter') calc(); });
document.querySelectorAll('.mr-ex').forEach(function(b){ b.addEventListener('click',function(){ inp.value=b.dataset.f; calc(); }); });
calc();
})();
/* §3 portion */
(function(){
var sub=document.getElementById('port-sub'), rng=document.getElementById('port-n'), nv=document.getElementById('port-nv'), out=document.getElementById('port-out'); if(!sub) return;
var M={H2O:18,O2:32,CO2:44,NaCl:58.5};
function rr(v){ return (Math.round(v*100)/100).toString().replace('.',','); }
function upd(){
var n=parseFloat(rng.value), s=sub.value, m=n*M[s], N=n*6.02;
nv.textContent=n.toFixed(1).replace('.',',');
out.innerHTML='<span class="bd">n = '+n.toFixed(1).replace('.',',')+' моль<br>'+
'm = n·M = '+n.toFixed(1).replace('.',',')+' · '+String(M[s]).replace('.',',')+' = <b>'+rr(m)+' г</b><br>'+
'N = n·N_A = '+rr(N)+'·10²³ ≈ <b>'+rr(N)+'·10²³ частиц</b></span>';
}
sub.addEventListener('change',upd); rng.addEventListener('input',upd); upd();
})();
/* §4 avogadro */
(function(){
var rng=document.getElementById('av-n'), nv=document.getElementById('av-nv'), out=document.getElementById('av-out'); if(!rng) return;
function upd(){ var n=parseFloat(rng.value), N=n*6.02; nv.textContent=n.toFixed(2).replace('.',',');
out.innerHTML='<span class="bd">N = n · N_A = '+n.toFixed(2).replace('.',',')+' · 6,02·10²³ = <b>'+(Math.round(N*100)/100).toString().replace('.',',')+'·10²³ частиц</b></span>';
}
rng.addEventListener('input',upd); upd();
})();
/* §5 M + gas volume */
(function(){
var inp=document.getElementById('m5-in'), out=document.getElementById('m5-out'), go=document.getElementById('m5-go'); if(!inp) return;
function calc(){ var f=inp.value.trim(), mr=C.molarMass?C.molarMass(f):NaN;
if(isNaN(mr)){ out.className='out bad'; out.textContent='Не удалось разобрать формулу.'; return; }
out.className='out ok';
out.innerHTML='<span class="bd">M('+f+') = <b>'+C.fmt(mr)+' г/моль</b><br>1 моль газа при н.у. занимает <b>22,4 л</b>.<br>Плотность газа ≈ M/22,4 = '+(Math.round(mr/22.4*1000)/1000).toString().replace('.',',')+' г/л</span>';
}
go.addEventListener('click',calc); inp.addEventListener('keydown',function(e){if(e.key==='Enter')calc();}); calc();
})();
/* §6 mole triangle */
(function(){
var mount=document.getElementById('mt-mount'), sub=document.getElementById('mt-sub'); if(!mount||!C.moleTriangle) return;
var api=C.moleTriangle(mount,{});
if(sub) sub.addEventListener('change',function(){
var f=sub.value; if(!f) return;
var m=C.molarMass(f); if(!isNaN(m)&&api&&api.set) api.set('M',m);
});
})();
/* §7 universal gas calc */
(function(){
var sub=document.getElementById('g7-sub'), key=document.getElementById('g7-key'), val=document.getElementById('g7-val'), go=document.getElementById('g7-go'), out=document.getElementById('g7-out'); if(!sub) return;
var Vm=22.4, NA=6.02;
function calc(){
var f=sub.value, M=C.molarMass(f), k=key.value, x=parseFloat(val.value.replace(',','.'));
if(isNaN(x)){ out.className='out bad'; out.textContent='Введите число.'; return; }
var n;
if(k==='n') n=x; else if(k==='m') n=x/M; else if(k==='V') n=x/Vm; else n=x/NA;
var m=n*M, V=n*Vm, N=n*NA;
function r(v){ return (Math.round(v*1000)/1000).toString().replace('.',','); }
out.className='out ok';
out.innerHTML='<span class="bd">M('+f+')='+M+' г/моль<br>n = <b>'+r(n)+' моль</b><br>m = <b>'+r(m)+' г</b><br>V(н.у.) = <b>'+r(V)+' л</b><br>N = <b>'+r(N)+'·10²³ частиц</b></span>';
}
go.addEventListener('click',calc); val.addEventListener('keydown',function(e){if(e.key==='Enter')calc();}); calc();
})();
/* §8 balancer */
(function(){
var pick=document.getElementById('bal-pick'), mount=document.getElementById('bal-mount'); if(!pick||!C.equationBalancer) return;
function build(){
var parts=pick.value.split('|'); var skel=parts[0]; var sol=parts[1].split(',').map(Number);
C.equationBalancer(mount,{skeleton:skel, solution:sol});
}
pick.addEventListener('change',build); build();
})();
/* §9 stoichiometry step solver */
var ST = [
{ eq:'2H₂ + O₂ → 2H₂O', given:'Дано: m(H₂) = 4 г. Найти m(H₂O).',
steps:['M(H₂)=2 г/моль, M(H₂O)=18 г/моль.','n(H₂) = m/M = 4/2 = 2 моль.','По уравнению n(H₂):n(H₂O) = 2:2 = 1:1 → n(H₂O)=2 моль.','m(H₂O) = n·M = 2·18 = 36 г. Ответ: 36 г.'] },
{ eq:'CaCO₃ → CaO + CO₂↑', given:'Дано: m(CaCO₃) = 100 г. Найти V(CO₂) при н.у.',
steps:['M(CaCO₃)=100 г/моль.','n(CaCO₃) = 100/100 = 1 моль.','n(CaCO₃):n(CO₂) = 1:1 → n(CO₂)=1 моль.','V(CO₂) = n·Vm = 1·22,4 = 22,4 л. Ответ: 22,4 л.'] },
{ eq:'Zn + 2HCl → ZnCl₂ + H₂↑', given:'Дано: n(Zn) = 0,5 моль. Найти V(H₂) при н.у.',
steps:['n(Zn):n(H₂) = 1:1 → n(H₂)=0,5 моль.','V(H₂) = n·Vm = 0,5·22,4 = 11,2 л. Ответ: 11,2 л.'] }
];
(function(){
var pick=document.getElementById('st-pick'), out=document.getElementById('st-out'), bStep=document.getElementById('st-step'), bAll=document.getElementById('st-all'); if(!pick) return;
ST.forEach(function(p,i){ var o=document.createElement('option'); o.value=i; o.textContent=p.eq; pick.appendChild(o); });
var cur=0, shown=0;
function render(){
var p=ST[cur];
var html='<b>'+p.eq+'</b><br><span style="color:var(--muted)">'+p.given+'</span><div style="margin-top:8px">';
for(var i=0;i<shown;i++) html+='<div class="exa" style="margin-top:6px"><div class="step">'+p.steps[i]+'</div></div>';
if(shown===0) html+='<span style="color:var(--muted)">Нажмите «Следующий шаг», чтобы решать пошагово.</span>';
html+='</div>'; out.className='out'; out.innerHTML=html;
if(shown>=p.steps.length){ out.className='out ok'; }
}
pick.addEventListener('change',function(){ cur=+pick.value; shown=0; render(); });
bStep.addEventListener('click',function(){ if(shown<ST[cur].steps.length){ shown++; render(); } });
bAll.addEventListener('click',function(){ shown=ST[cur].steps.length; render(); });
render();
})();
/* ===== BOSS ===== */
var BOSS = [
{ q:'Чему равна молярная масса серной кислоты $\\text{H}_2\\text{SO}_4$? (г/моль)', ans:98, tol:0.5, hint:'M = 2·1 + 32 + 4·16 = 98.' },
{ q:'Сколько моль вещества в $80$ г $\\text{NaOH}$ ($M=40$ г/моль)?', ans:2, tol:0.05, hint:'n = m/M = 80/40 = 2.' },
{ q:'Какой объём (л, н.у.) занимают $3$ моль кислорода $\\text{O}_2$?', ans:67.2, tol:0.3, hint:'V = n·Vm = 3·22,4 = 67,2.' },
{ q:'Сколько молекул в $2$ моль воды? Ответ в виде коэффициента при ·10²³.', ans:12.04, tol:0.1, hint:'N = n·N_A = 2·6,02 = 12,04 (·10²³).' }
];
var BKEY='chemistry8_intro_boss', BACH='chemistry8_intro_ach';
function bossState(){ try{ return JSON.parse(localStorage.getItem(BKEY)||'{}')||{}; }catch(e){ return {}; } }
function bossSave(s){ try{ localStorage.setItem(BKEY,JSON.stringify(s)); }catch(e){} }
(function(){
var cont=document.getElementById('boss-cont'); if(!cont) return;
var st=bossState();
BOSS.forEach(function(b,i){
var solved=!!st[i];
var d=document.createElement('div'); d.className='bcard'+(solved?' solved':''); d.id='b'+i;
d.innerHTML='<div class="q"><b>Задача '+(i+1)+'.</b> '+b.q+'</div>'+
'<div class="row"><input type="text" inputmode="decimal" placeholder="ответ" style="width:120px;font-family:var(--mono)"'+(solved?' value="'+b.ans+'" disabled':'')+'>'+
'<button class="btn primary go"'+(solved?' disabled':'')+'>Ответить</button><button class="btn hint">Подсказка</button></div>'+
'<div class="hintbox">'+b.hint+'</div>'+
'<div class="fb'+(solved?' show ok':'')+'">'+(solved?'Решено! +10 XP':'')+'</div>';
cont.appendChild(d);
var inp=d.querySelector('input'), go=d.querySelector('.go'), hint=d.querySelector('.hint'), hb=d.querySelector('.hintbox'), fb=d.querySelector('.fb');
hint.addEventListener('click',function(){ hb.classList.toggle('show'); });
if(!solved) go.addEventListener('click',function(){
var v=parseFloat((inp.value||'').replace(',','.'));
if(isNaN(v)){ fb.className='fb show bad'; fb.textContent='Введите число.'; return; }
if(Math.abs(v-b.ans)<=b.tol){
fb.className='fb show ok'; fb.textContent='Верно! +10 XP'; d.classList.add('solved'); go.disabled=true; inp.disabled=true;
var s=bossState(); if(!s[i]){ s[i]=true; bossSave(s); addXp(10,'boss'+i); updateBoss(s); }
} else { fb.className='fb show bad'; fb.textContent='Не то. Проверь расчёт и попробуй снова.'; }
});
inp.addEventListener('keydown',function(e){ if(e.key==='Enter'&&!go.disabled) go.click(); });
});
updateBoss(st);
if(window.renderMathInElement) try{renderMathInElement(cont);}catch(e){}
})();
function updateBoss(s){
var won=0; for(var k in s) if(s[k]) won++;
var lab=document.getElementById('boss-lab'), fill=document.getElementById('boss-fill');
if(lab) lab.textContent='Решено: '+won+' / '+BOSS.length;
if(fill) fill.style.width=Math.round(won*100/BOSS.length)+'%';
if(won>=BOSS.length && localStorage.getItem(BACH)!=='1'){
localStorage.setItem(BACH,'1'); addXp(30,'ach');
var cta=document.getElementById('boss-cta'); if(cta) cta.classList.add('show');
popup('Ачивка «Счёт в химии»! +30 XP');
try{ if(window.confetti) confetti({particleCount:180,spread:100,origin:{y:.6}}); }catch(e){}
} else if(won>=BOSS.length){ var c=document.getElementById('boss-cta'); if(c) c.classList.add('show'); }
}
/* init */
loadLocal(); refreshXp(); refreshProgress(); loadServer();
window.addEventListener('focus',loadServer);
}); /* end DOMContentLoaded */
</script>
</body>
</html>