feat(phys10 ch1 wave1): §1 «МКТ» + §2 «Количество вещества» + симуляция броуновского движения

This commit is contained in:
Maxim Dolgolyov
2026-05-29 16:18:53 +03:00
parent d387018ee5
commit 3116f9d815
+597 -11
View File
@@ -670,34 +670,620 @@ function wireReadBtn(paraId){
function build_p1(){
const box = document.getElementById('p1-body');
let html = '';
/* THEORY 1 — положения МКТ */
html += makeCard('theory', "Основные положения МКТ", "§1", `
<p><b>Основные положения МКТ</b> — этот параграф в разработке (Phase 1+).</p>
<p>Здесь появятся: теория, формулы, разобранные примеры и 3–4 интерактива в стиле «алгебры 11» — таблицы, симуляции, ползунки, drag-and-drop и автопроверяемые тренажёры.</p>
<p style="margin-top:10px;padding:10px 14px;background:var(--sec-acc-soft);border-radius:9px;font-size:.92rem">
<b>Phase 0:</b> создан скелет учебника. <b>Phase 1+:</b> наполнение этого § содержанием по учебнику «Физика 10» (Беларусь, 2019).
</p>
<p><b>Молекулярно-кинетическая теория</b> (МКТ) объясняет строение и свойства веществ на основе движения и взаимодействия частиц (атомов, молекул, ионов).</p>
<p style="margin-top:10px"><b>Три основных положения МКТ:</b></p>
<ol style="margin:8px 0 8px 22px;line-height:1.75">
<li><b>Вещество состоит из частиц.</b> Молекулы и атомы имеют размер около $10^{-10}$ м.</li>
<li><b>Частицы движутся непрерывно и хаотически.</b> Это движение называется <b>тепловым</b>.</li>
<li><b>Частицы взаимодействуют между собой</b>: на малых расстояниях — отталкиваются, на больших — притягиваются.</li>
</ol>
<p>Все макроскопические свойства (температура, давление, плотность, теплопроводность и т. д.) объясняются микроскопическими процессами.</p>
`);
/* THEORY 2 — опытные подтверждения */
html += makeCard('rule', "Опытные подтверждения МКТ", "§1", `
<p><b>Броуновское движение</b> (Р. Броун, 1827): хаотическое движение мелких частиц (например, пыльцы в воде) под действием ударов молекул. Чем меньше частица и выше температура — тем интенсивнее движение.</p>
<p style="margin-top:8px"><b>Диффузия</b> — самопроизвольное перемешивание веществ за счёт движения молекул. Скорость диффузии возрастает с температурой.</p>
<p style="margin-top:8px"><b>Сжимаемость газов</b> и <b>тепловое расширение</b> тел — также прямое следствие движения и взаимодействия молекул.</p>
`);
/* THEORY 3 — размеры и числа */
html += makeCard('example', "Размеры и количество молекул", "§1", `
<ul style="margin:0 0 8px 22px;line-height:1.75">
<li>Размер молекулы: $\\sim 10^{-10}$ м (нанометр или ангстрем).</li>
<li>Размер атома водорода: $\\sim 0{,}5 \\cdot 10^{-10}$ м.</li>
<li>В $1 \\text{ см}^3$ воды примерно $3{,}3 \\cdot 10^{22}$ молекул.</li>
<li>В $1$ моль вещества — $N_A = 6{,}022 \\cdot 10^{23}$ молекул (число Авогадро).</li>
</ul>
<p><b>Пример:</b> если выпить стакан воды (200 мл) и подождать долгое время, в любом другом стакане воды через некоторое время будут несколько молекул из выпитого — настолько их много в любом веществе.</p>
`);
/* INTERACTIVE 1 — Симуляция броуновского движения */
html += `<div class="wg" id="p1-iv1">
<div class="wg-header"><span class="wg-badge">ИНТЕРАКТИВ 1</span><div class="wg-title">Симуляция броуновского движения</div></div>
<div class="wg-help">Оранжевая «броуновская» частица среди синих молекул газа. Меняй температуру — наблюдай хаотическое движение.</div>
<div class="sliders">
<label>Температура: <b id="p1-iv1-tL">300</b> К <input type="range" id="p1-iv1-T" min="100" max="500" value="300" step="10"></label>
</div>
<div style="background:#0f172a;border-radius:9px;padding:10px;text-align:center">
<svg id="p1-iv1-svg" viewBox="0 0 420 320" width="100%" style="max-width:420px;height:auto;background:#0f172a;border-radius:6px"></svg>
</div>
<div class="actions" style="justify-content:center">
<button class="btn primary" id="p1-iv1-pause">Пауза</button>
<button class="btn" id="p1-iv1-reset">Сброс</button>
</div>
<div style="margin-top:10px;padding:10px 14px;background:var(--sec-acc-soft);border-radius:9px;font-size:.88rem;line-height:1.5">
Чем выше $T$, тем сильнее удары молекул, тем хаотичнее движется броуновская частица. Жёлтый след показывает её траекторию.
</div>
</div>`;
/* INTERACTIVE 2 — DnD сортер: положение МКТ ↔ опыт */
html += `<div class="wg" id="p1-iv2">
<div class="wg-header"><span class="wg-badge">ИНТЕРАКТИВ 2</span><div class="wg-title">Положение МКТ ↔ опыт</div></div>
<div class="wg-help">Перетащи каждое явление в соответствующее положение МКТ.</div>
<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> 6 явлений — 3 положения</div>
<div id="p1-iv2-pool"></div>
<div style="display:grid;grid-template-columns:repeat(auto-fit,minmax(220px,1fr));gap:10px;margin-top:8px">
<div class="drop-box"><h5 data-cat="c1">1. Вещество из частиц</h5><div class="drop-items" data-cat="c1"></div></div>
<div class="drop-box"><h5 data-cat="c2">2. Частицы движутся</h5><div class="drop-items" data-cat="c2"></div></div>
<div class="drop-box"><h5 data-cat="c3">3. Частицы взаимодействуют</h5><div class="drop-items" data-cat="c3"></div></div>
</div>
<div class="actions"><button class="btn primary" id="p1-iv2-check">Проверить</button><button class="btn" id="p1-iv2-reset">Сначала</button></div>
<div class="feedback" id="p1-iv2-fb"></div>
</div>`;
/* INTERACTIVE 3 — Квикфайр: Верно/Неверно */
html += `<div class="wg" id="p1-iv3">
<div class="wg-header"><span class="wg-badge">ИНТЕРАКТИВ 3</span><div class="wg-title">Проверь знания МКТ</div></div>
<div class="wg-help">8 утверждений. Выбери «Верно» или «Неверно». Допуск ошибок — 1.</div>
<div class="score-display"><span>Задача <b id="p1-iv3-i">1</b> / 8</span><span>Очки: <b id="p1-iv3-s">0</b> / 8</span></div>
<div id="p1-iv3-q" style="padding:14px;background:var(--sec-acc-soft);border-radius:10px;font-size:1.05rem;margin-bottom:10px;text-align:center;min-height:54px"></div>
<div id="p1-iv3-opts" style="display:grid;grid-template-columns:1fr 1fr;gap:8px"></div>
<div class="feedback" id="p1-iv3-fb"></div>
<div class="actions"><button class="btn" id="p1-iv3-restart">Начать заново</button></div>
</div>`;
/* INTERACTIVE 4 — тренажёр чисел */
html += `<div class="wg" id="p1-iv4">
<div class="wg-header"><span class="wg-badge">ИНТЕРАКТИВ 4</span><div class="wg-title">Тренажёр: размеры и числа</div></div>
<div class="wg-help">5 задач на число Авогадро, размеры молекул, моли. Допуск $\\pm 5\\%$. Используй $N_A = 6 \\cdot 10^{23}$.</div>
<div class="score-display"><span>Задача <b id="p1-iv4-i">1</b> / 5</span><span>Очки: <b id="p1-iv4-s">0</b> / 5</span></div>
<div id="p1-iv4-q" style="padding:14px;background:var(--sec-acc-soft);border-radius:10px;font-size:1.05rem;margin-bottom:10px;text-align:center;min-height:54px"></div>
<div style="display:flex;gap:10px;align-items:center;flex-wrap:wrap;justify-content:center">
<span style="font-family:'JetBrains Mono',monospace">ответ =</span>
<input type="number" id="p1-iv4-ans" class="tinp" style="width:130px;text-align:center" step="any">
<button class="btn primary" id="p1-iv4-go">Проверить</button>
<button class="btn" id="p1-iv4-start">Заново</button>
</div>
<div class="feedback" id="p1-iv4-fb"></div>
</div>`;
html += secNav(null, 'p2');
html += readButton('p1');
box.innerHTML = html;
renderMath(box);
/* IV1 — Броуновское движение */
(function(){
const svg = document.getElementById('p1-iv1-svg');
const tInp = document.getElementById('p1-iv1-T');
const tLab = document.getElementById('p1-iv1-tL');
const btnPause = document.getElementById('p1-iv1-pause');
const btnReset = document.getElementById('p1-iv1-reset');
const W = 420, H = 320;
const baseSpeed = 80;
const tempChanges = new Set();
let _xpDone = false;
let raf = null, lastT = 0, paused = false;
let sim = null;
let brown = null;
let trail = [];
function makeSim(){
sim = PHYS.createGasSim({W, H, N: 50, speed: baseSpeed, r: 3});
brown = { x: W/2, y: H/2, vx: 0, vy: 0, r: 12 };
trail = [];
// initial temp
applyTemp(+tInp.value);
}
function applyTemp(T){
// baseSpeed соответствует T0=300 К; scale = sqrt(T/T0)
const targetScale = Math.sqrt(T/300);
// компенсируем текущую среднюю скорость
let curAvg = 0;
for(const p of sim.particles){ curAvg += Math.hypot(p.vx, p.vy); }
curAvg /= sim.particles.length;
const targetAvg = baseSpeed * targetScale;
if(curAvg > 0.01) sim.setSpeed(targetAvg / curAvg);
}
function frame(t){
raf = requestAnimationFrame(frame);
if(!lastT){ lastT = t; return; }
let dt = (t - lastT) / 1000;
lastT = t;
if(paused){ render(); return; }
if(dt > 0.06) dt = 0.06;
sim.step(dt);
// обновляем броуновскую частицу: суммируем импульс от ближних молекул
let fx = 0, fy = 0;
const R = brown.r + 4;
for(const p of sim.particles){
const dx = brown.x - p.x, dy = brown.y - p.y;
const d2 = dx*dx + dy*dy;
if(d2 < R*R && d2 > 1){
const d = Math.sqrt(d2);
const f = (R - d) / R;
fx += dx/d * f * 60;
fy += dy/d * f * 60;
}
}
// демпфирование + импульс
brown.vx = brown.vx * 0.88 + fx * dt;
brown.vy = brown.vy * 0.88 + fy * dt;
brown.x += brown.vx * dt;
brown.y += brown.vy * dt;
// отражение от стенок
if(brown.x < brown.r){ brown.x = brown.r; brown.vx = -brown.vx*0.6; }
if(brown.x > W - brown.r){ brown.x = W - brown.r; brown.vx = -brown.vx*0.6; }
if(brown.y < brown.r){ brown.y = brown.r; brown.vy = -brown.vy*0.6; }
if(brown.y > H - brown.r){ brown.y = H - brown.r; brown.vy = -brown.vy*0.6; }
trail.push({x: brown.x, y: brown.y});
if(trail.length > 60) trail.shift();
render();
}
function render(){
let g = '';
// фон тёмный (уже в SVG), молекулы синие
g += sim.render('#60a5fa');
// след броуновской — polyline с убывающей opacity
if(trail.length > 1){
for(let i = 1; i < trail.length; i++){
const op = i / trail.length;
g += `<line x1="${trail[i-1].x.toFixed(1)}" y1="${trail[i-1].y.toFixed(1)}" x2="${trail[i].x.toFixed(1)}" y2="${trail[i].y.toFixed(1)}" stroke="#fbbf24" stroke-width="2" stroke-linecap="round" opacity="${op.toFixed(2)}"/>`;
}
}
// броуновская частица — оранжевая
g += `<circle cx="${brown.x.toFixed(1)}" cy="${brown.y.toFixed(1)}" r="${brown.r}" fill="#f97316" stroke="#fff" stroke-width="2"/>`;
svg.innerHTML = g;
}
makeSim();
raf = requestAnimationFrame(frame);
tInp.addEventListener('input', () => {
const T = +tInp.value;
tLab.textContent = T;
applyTemp(T);
tempChanges.add(T);
if(!_xpDone && tempChanges.size >= 4){
_xpDone = true;
addXp(10, 'p1-iv1');
bumpProgress('p1', 15);
}
});
btnPause.addEventListener('click', () => {
paused = !paused;
btnPause.textContent = paused ? 'Продолжить' : 'Пауза';
});
btnReset.addEventListener('click', () => {
makeSim();
trail = [];
});
// cleanup при потере фокуса (минимизируем нагрузку)
document.addEventListener('visibilitychange', () => {
if(document.hidden && raf){ cancelAnimationFrame(raf); raf = null; lastT = 0; }
else if(!document.hidden && !raf){ raf = requestAnimationFrame(frame); }
});
})();
/* IV2 — DnD: положение ↔ опыт */
(function(){
const items = [
{ id:'q1', cat:'c2', html:'Запах духов распространяется в комнате' },
{ id:'q2', cat:'c2', html:'Пыльца хаотично движется в воде' },
{ id:'q3', cat:'c1', html:'Воздух сжимается в насосе' },
{ id:'q4', cat:'c3', html:'Капля воды растекается по стеклу' },
{ id:'q5', cat:'c1', html:'Стальной шарик падает в воде медленнее, чем в воздухе' },
{ id:'q6', cat:'c2', html:'Газ заполняет весь сосуд' },
];
const sorter = setupSorter({
poolId:'p1-iv2-pool',
scopeSelector:'#p1-iv2',
items: items,
cats:['c1','c2','c3'],
columnLayout:false,
});
document.getElementById('p1-iv2-check').addEventListener('click', ()=>{
const fb = document.getElementById('p1-iv2-fb');
const placedCount = items.filter(it => sorter.placed[it.id]).length;
const correct = items.filter(it => sorter.placed[it.id] === it.cat).length;
if(placedCount < items.length){ feedback(fb, false, '&#10007; Размести все 6 явлений.'); return; }
if(correct === items.length){ feedback(fb, true, '&#10003; Все 6 верно! +10 XP'); addXp(10,'p1-iv2'); bumpProgress('p1', 15); }
else feedback(fb, false, '&#10007; Правильно ' + correct + ' из 6. Попробуй ещё.');
});
document.getElementById('p1-iv2-reset').addEventListener('click', ()=>{ sorter.reset(); document.getElementById('p1-iv2-fb').style.display = 'none'; });
})();
/* IV3 — квикфайр Верно/Неверно */
(function(){
const Q = [
{ q:'Молекулы вещества движутся хаотически.', ans:true, why:'Тепловое движение — это хаотическое движение молекул.' },
{ q:'При нагревании молекулы движутся медленнее.', ans:false, why:'Наоборот — при нагревании движение усиливается.' },
{ q:'Атомы можно увидеть в обычный световой микроскоп.', ans:false, why:'Размер атома $\\sim 10^{-10}$ м — нужен электронный микроскоп.' },
{ q:'Между молекулами действуют только силы притяжения.', ans:false, why:'На малых расстояниях — отталкивание, на больших — притяжение.' },
{ q:'Размер молекулы порядка $10^{-10}$ м.', ans:true, why:'Это типичный размер молекулы (≈ 1 ангстрем).' },
{ q:'Броуновское движение объясняется ударами молекул жидкости.', ans:true, why:'Хаотические удары молекул заставляют двигаться броуновские частицы.' },
{ q:'Диффузия в газах происходит быстрее, чем в жидкостях.', ans:true, why:'В газах молекулы движутся свободнее и быстрее.' },
{ q:'В 1 моль любого вещества одинаковое число молекул — $N_A$.', ans:true, why:'$N_A = 6{,}022 \\cdot 10^{23}$ — число Авогадро.' },
];
let i = 0, score = 0;
const qEl = document.getElementById('p1-iv3-q');
const oEl = document.getElementById('p1-iv3-opts');
const fb = document.getElementById('p1-iv3-fb');
const iEl = document.getElementById('p1-iv3-i');
const sEl = document.getElementById('p1-iv3-s');
function show(){
if(i >= Q.length){
qEl.innerHTML = '<b>Готово!</b> Результат: ' + score + ' / ' + Q.length;
oEl.innerHTML = '';
if(score === Q.length){ addXp(15, 'p1-iv3'); bumpProgress('p1', 25); }
else if(score >= 5){ addXp(8, 'p1-iv3'); bumpProgress('p1', 15); }
return;
}
iEl.textContent = (i+1);
sEl.textContent = score;
const item = Q[i];
qEl.innerHTML = item.q;
oEl.innerHTML = '<button class="btn primary" data-v="1">Верно</button><button class="btn primary" data-v="0" style="background:#ef4444;border-color:#ef4444">Неверно</button>';
fb.style.display = 'none';
renderMath(qEl);
oEl.querySelectorAll('button').forEach(b => {
b.addEventListener('click', () => {
const v = b.dataset.v === '1';
if(v === item.ans){ score++; feedback(fb, true, '&#10003; Верно! ' + item.why + ' Дальше ▶'); }
else feedback(fb, false, '&#10007; Неверно. ' + item.why + ' Дальше ▶');
sEl.textContent = score;
oEl.querySelectorAll('button').forEach(x => x.disabled = true);
i++;
setTimeout(show, 1400);
});
});
}
document.getElementById('p1-iv3-restart').addEventListener('click', () => { i = 0; score = 0; show(); });
show();
})();
/* IV4 — тренажёр чисел */
(function(){
const Q = [
{ q:'В стакане воды $2{,}4 \\cdot 10^{24}$ молекул. Сколько это молей? ($N_A = 6 \\cdot 10^{23}$)', ans:4, hint:'$\\nu = N/N_A = 2{,}4 \\cdot 10^{24} / 6 \\cdot 10^{23} = 4$' },
{ q:'В 18 г воды ($M = 18$ г/моль) сколько моль?', ans:1, hint:'$\\nu = m/M = 18/18 = 1$ моль' },
{ q:'В 1 кг водорода ($M = 2$ г/моль) сколько моль?', ans:500, hint:'$\\nu = 1000/2 = 500$ моль' },
{ q:'В 3 моль газа сколько молекул? Введи число $X$ для ответа $X \\cdot 10^{23}$', ans:18, hint:'$N = 3 \\cdot 6 \\cdot 10^{23} = 18 \\cdot 10^{23}$' },
{ q:'Размер молекулы $\\sim 10^{-10}$ м. Сколько молекул в ряд поместится на $1$ мм? Введи порядок: $10^X$, $X = ?$', ans:7, hint:'$10^{-3}/10^{-10} = 10^{7}$' },
];
let i = 0, score = 0;
function show(){
if(i >= Q.length){
document.getElementById('p1-iv4-q').innerHTML = '<b>Готово!</b> Результат: ' + score + ' / ' + Q.length;
if(score === Q.length){ addXp(15, 'p1-iv4'); bumpProgress('p1', 25); }
else if(score >= 3){ addXp(8, 'p1-iv4'); bumpProgress('p1', 15); }
return;
}
document.getElementById('p1-iv4-i').textContent = (i+1);
document.getElementById('p1-iv4-s').textContent = score;
document.getElementById('p1-iv4-q').innerHTML = Q[i].q;
document.getElementById('p1-iv4-ans').value = '';
renderMath(document.getElementById('p1-iv4-q'));
document.getElementById('p1-iv4-fb').style.display = 'none';
}
function go(){
if(i >= Q.length) return;
const fb = document.getElementById('p1-iv4-fb');
const raw = document.getElementById('p1-iv4-ans').value.replace(',', '.');
const ans = parseFloat(raw);
if(isNaN(ans)){ feedback(fb, false, '&#10007; Введи число.'); return; }
const tol = Math.max(0.05 * Math.abs(Q[i].ans), 0.05);
if(Math.abs(ans - Q[i].ans) < tol){ score++; feedback(fb, true, '&#10003; Верно! '+Q[i].hint+'. Дальше ▶'); }
else feedback(fb, false, '&#10007; Неверно. Ответ: $'+Q[i].ans+'$. '+Q[i].hint+'. Дальше ▶');
document.getElementById('p1-iv4-s').textContent = score;
i++;
setTimeout(show, 1500);
}
document.getElementById('p1-iv4-go').addEventListener('click', go);
document.getElementById('p1-iv4-ans').addEventListener('keydown', e => { if(e.key === 'Enter') go(); });
document.getElementById('p1-iv4-start').addEventListener('click', () => { i = 0; score = 0; show(); });
show();
})();
wireReadBtn('p1');
}
function build_p2(){
const box = document.getElementById('p2-body');
let html = '';
html += makeCard('theory', "Масса и размеры молекул. Количество вещества", "§2", `
<p><b>Масса и размеры молекул. Количество вещества</b> — этот параграф в разработке (Phase 1+).</p>
<p>Здесь появятся: теория, формулы, разобранные примеры и 3–4 интерактива в стиле «алгебры 11» — таблицы, симуляции, ползунки, drag-and-drop и автопроверяемые тренажёры.</p>
<p style="margin-top:10px;padding:10px 14px;background:var(--sec-acc-soft);border-radius:9px;font-size:.92rem">
<b>Phase 0:</b> создан скелет учебника. <b>Phase 1+:</b> наполнение этого § содержанием по учебнику «Физика 10» (Беларусь, 2019).
</p>
/* THEORY 1 — количество вещества */
html += makeCard('theory', "Количество вещества и моль", "§2", `
<p><b>Количество вещества</b> $\\nu$ (греч. «ню») — физическая величина, равная числу частиц вещества, делённому на число Авогадро:</p>
<p style="text-align:center;margin:10px 0">$$\\nu = \\dfrac{N}{N_A}$$</p>
<p>Единица: <b>моль</b>. Один моль — количество вещества, содержащее столько же частиц, сколько атомов в $12$ г углерода-12.</p>
<p style="margin-top:8px"><b>Число Авогадро:</b> $N_A = 6{,}022 \\cdot 10^{23}$ моль$^{-1}$.</p>
`);
/* THEORY 2 — молярная масса */
html += makeCard('rule', "Молярная масса. Связь с массой", "§2", `
<p><b>Молярная масса</b> $M$ — масса одного моля вещества:</p>
<p style="text-align:center;margin:10px 0">$$M = m_0 \\cdot N_A$$</p>
<p>где $m_0$ — масса одной молекулы. Единица СИ — <b>кг/моль</b>, привычная — <b>г/моль</b>.</p>
<p style="margin-top:8px"><b>Связь количества вещества с массой:</b></p>
<p style="text-align:center;margin:10px 0">$$\\nu = \\dfrac{m}{M} = \\dfrac{N}{N_A}$$</p>
<p><b>Относительная молекулярная масса</b> $M_r$ — безразмерна: $M_r = M$ (в г/моль). Например, $M_r(\\text{H}_2\\text{O}) = 18$, $M(\\text{H}_2\\text{O}) = 18$ г/моль $= 0{,}018$ кг/моль.</p>
`);
/* THEORY 3 — масса молекулы */
html += makeCard('example', "Масса одной молекулы и концентрация", "§2", `
<p>Зная молярную массу и число Авогадро, найдём массу одной молекулы:</p>
<p style="text-align:center;margin:10px 0">$$m_0 = \\dfrac{M}{N_A}$$</p>
<p><b>Примеры:</b></p>
<ul style="margin:6px 0 8px 22px;line-height:1.75">
<li>Молекула воды ($M = 18$ г/моль): $m_0 = \\dfrac{18 \\cdot 10^{-3}}{6{,}022 \\cdot 10^{23}} \\approx 3 \\cdot 10^{-26}$ кг.</li>
<li>Атом водорода ($M = 1$ г/моль): $m_0 \\approx 1{,}66 \\cdot 10^{-27}$ кг (= 1 а.е.м.).</li>
<li>Атом углерода ($M = 12$ г/моль): $m_0 \\approx 2 \\cdot 10^{-26}$ кг.</li>
</ul>
<p><b>Концентрация молекул</b> $n$ — число молекул в единице объёма:</p>
<p style="text-align:center;margin:10px 0">$$n = \\dfrac{N}{V}$$</p>
<p>Единица: $\\text{м}^{-3}$.</p>
`);
/* INTERACTIVE 1 — калькулятор Авогадро */
html += `<div class="wg" id="p2-iv1">
<div class="wg-header"><span class="wg-badge">ИНТЕРАКТИВ 1</span><div class="wg-title">Калькулятор Авогадро</div></div>
<div class="wg-help">Введи массу $m$ (г) и молярную массу $M$ (г/моль) — получи количество вещества $\\nu$, число молекул $N$ и массу одной молекулы $m_0$.</div>
<div style="display:grid;grid-template-columns:repeat(auto-fit,minmax(180px,1fr));gap:10px;margin-bottom:10px">
<label style="display:block;font-size:.92rem;color:var(--muted);background:var(--card);padding:8px 12px;border-radius:8px;border:1px solid var(--border)">Масса $m$, г <input type="number" id="p2-iv1-m" class="tinp" style="width:100%;margin-top:6px" value="18" step="any"></label>
<label style="display:block;font-size:.92rem;color:var(--muted);background:var(--card);padding:8px 12px;border-radius:8px;border:1px solid var(--border)">Молярная масса $M$, г/моль <input type="number" id="p2-iv1-M" class="tinp" style="width:100%;margin-top:6px" value="18" step="any"></label>
</div>
<div style="margin-bottom:10px">
<span style="font-size:.84rem;color:var(--muted);margin-right:6px">Эталон:</span>
<select id="p2-iv1-preset" class="tinp" style="padding:6px 10px">
<option value="">— выбери вещество —</option>
<option value="18">Вода H₂O (M = 18)</option>
<option value="44">Углекислый газ CO₂ (M = 44)</option>
<option value="32">Кислород O₂ (M = 32)</option>
<option value="28">Азот N₂ (M = 28)</option>
<option value="2">Водород H₂ (M = 2)</option>
<option value="56">Железо Fe (M = 56)</option>
<option value="12">Углерод C (M = 12)</option>
</select>
</div>
<div class="actions" style="justify-content:center">
<button class="btn primary" id="p2-iv1-go">Вычислить</button>
</div>
<div id="p2-iv1-out" style="margin-top:10px;padding:12px 14px;background:var(--card);border-radius:9px;font-size:.94rem;min-height:80px;line-height:1.85"></div>
<div class="feedback" id="p2-iv1-fb"></div>
</div>`;
/* INTERACTIVE 2 — DnD сортер: вещество ↔ молярная масса */
html += `<div class="wg" id="p2-iv2">
<div class="wg-header"><span class="wg-badge">ИНТЕРАКТИВ 2</span><div class="wg-title">Сопоставь массу с веществом</div></div>
<div class="wg-help">Перетащи каждое вещество в ящик с его молярной массой.</div>
<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> 6 веществ — 6 значений</div>
<div id="p2-iv2-pool"></div>
<div style="display:grid;grid-template-columns:repeat(auto-fit,minmax(150px,1fr));gap:10px;margin-top:8px">
<div class="drop-box"><h5 data-cat="m2">2 г/моль</h5><div class="drop-items" data-cat="m2"></div></div>
<div class="drop-box"><h5 data-cat="m16">16 г/моль</h5><div class="drop-items" data-cat="m16"></div></div>
<div class="drop-box"><h5 data-cat="m18">18 г/моль</h5><div class="drop-items" data-cat="m18"></div></div>
<div class="drop-box"><h5 data-cat="m28">28 г/моль</h5><div class="drop-items" data-cat="m28"></div></div>
<div class="drop-box"><h5 data-cat="m32">32 г/моль</h5><div class="drop-items" data-cat="m32"></div></div>
<div class="drop-box"><h5 data-cat="m44">44 г/моль</h5><div class="drop-items" data-cat="m44"></div></div>
</div>
<div class="actions"><button class="btn primary" id="p2-iv2-check">Проверить</button><button class="btn" id="p2-iv2-reset">Сначала</button></div>
<div class="feedback" id="p2-iv2-fb"></div>
</div>`;
/* INTERACTIVE 3 — квикфайр «Что больше?» */
html += `<div class="wg" id="p2-iv3">
<div class="wg-header"><span class="wg-badge">ИНТЕРАКТИВ 3</span><div class="wg-title">Что больше?</div></div>
<div class="wg-help">6 вопросов «что больше» — выбери первое или второе.</div>
<div class="score-display"><span>Задача <b id="p2-iv3-i">1</b> / 6</span><span>Очки: <b id="p2-iv3-s">0</b> / 6</span></div>
<div id="p2-iv3-q" style="padding:14px;background:var(--sec-acc-soft);border-radius:10px;font-size:1.05rem;margin-bottom:10px;text-align:center;min-height:54px"></div>
<div id="p2-iv3-opts" style="display:grid;grid-template-columns:1fr 1fr;gap:8px"></div>
<div class="feedback" id="p2-iv3-fb"></div>
<div class="actions"><button class="btn" id="p2-iv3-restart">Начать заново</button></div>
</div>`;
/* INTERACTIVE 4 — тренажёр количества вещества */
html += `<div class="wg" id="p2-iv4">
<div class="wg-header"><span class="wg-badge">ИНТЕРАКТИВ 4</span><div class="wg-title">Тренажёр: количество вещества</div></div>
<div class="wg-help">6 задач на $\\nu = m/M$ и $N = \\nu N_A$. Допуск $\\pm 5\\%$. Используй $N_A = 6 \\cdot 10^{23}$.</div>
<div class="score-display"><span>Задача <b id="p2-iv4-i">1</b> / 6</span><span>Очки: <b id="p2-iv4-s">0</b> / 6</span></div>
<div id="p2-iv4-q" style="padding:14px;background:var(--sec-acc-soft);border-radius:10px;font-size:1.05rem;margin-bottom:10px;text-align:center;min-height:54px"></div>
<div style="display:flex;gap:10px;align-items:center;flex-wrap:wrap;justify-content:center">
<span style="font-family:'JetBrains Mono',monospace">ответ =</span>
<input type="number" id="p2-iv4-ans" class="tinp" style="width:130px;text-align:center" step="any">
<button class="btn primary" id="p2-iv4-go">Проверить</button>
<button class="btn" id="p2-iv4-start">Заново</button>
</div>
<div class="feedback" id="p2-iv4-fb"></div>
</div>`;
html += secNav('p1', 'p3');
html += readButton('p2');
box.innerHTML = html;
renderMath(box);
/* IV1 — калькулятор Авогадро */
(function(){
const mI = document.getElementById('p2-iv1-m');
const MI = document.getElementById('p2-iv1-M');
const sel = document.getElementById('p2-iv1-preset');
const out = document.getElementById('p2-iv1-out');
const fb = document.getElementById('p2-iv1-fb');
const go = document.getElementById('p2-iv1-go');
const used = new Set();
let _done = false;
const NA = 6e23;
function calc(){
const m = parseFloat(mI.value);
const M = parseFloat(MI.value);
if(!isFinite(m) || m <= 0){ feedback(fb, false, '&#10007; Введи положительную массу.'); return; }
if(!isFinite(M) || M <= 0){ feedback(fb, false, '&#10007; Введи положительную молярную массу.'); return; }
const nu = m / M;
const N = nu * NA;
// m0 в кг: m0 = M(кг/моль) / N_A = M*1e-3 / N_A
const m0 = (M * 1e-3) / NA;
const NstrExp = Math.floor(Math.log10(N));
const NstrCoef = N / Math.pow(10, NstrExp);
const m0Exp = Math.floor(Math.log10(m0));
const m0Coef = m0 / Math.pow(10, m0Exp);
out.innerHTML =
'<div style="margin-bottom:8px"><b>Количество вещества:</b> $\\nu = \\dfrac{m}{M} = \\dfrac{'+(+m.toFixed(3))+'}{'+(+M.toFixed(3))+'} \\approx '+(+nu.toFixed(4))+'$ моль</div>'
+ '<div style="margin-bottom:8px"><b>Число молекул:</b> $N = \\nu \\cdot N_A = '+(+nu.toFixed(4))+' \\cdot 6 \\cdot 10^{23} \\approx '+(+NstrCoef.toFixed(3))+' \\cdot 10^{'+NstrExp+'}$</div>'
+ '<div><b>Масса одной молекулы:</b> $m_0 = \\dfrac{M}{N_A} \\approx '+(+m0Coef.toFixed(3))+' \\cdot 10^{'+m0Exp+'}$ кг</div>';
renderMath(out);
feedback(fb, true, '&#10003; Вычислено.');
used.add((+M.toFixed(2))+':'+(+m.toFixed(2)));
if(!_done && used.size >= 4){ _done = true; addXp(10, 'p2-iv1'); bumpProgress('p2', 15); }
}
sel.addEventListener('change', () => {
if(sel.value){ MI.value = sel.value; }
});
go.addEventListener('click', calc);
mI.addEventListener('keydown', e => { if(e.key === 'Enter') calc(); });
MI.addEventListener('keydown', e => { if(e.key === 'Enter') calc(); });
})();
/* IV2 — DnD: вещество ↔ молярная масса */
(function(){
const items = [
{ id:'w1', cat:'m18', html:'Вода H$_2$O' },
{ id:'w2', cat:'m44', html:'Углекислый газ CO$_2$' },
{ id:'w3', cat:'m32', html:'Кислород O$_2$' },
{ id:'w4', cat:'m28', html:'Азот N$_2$' },
{ id:'w5', cat:'m2', html:'Водород H$_2$' },
{ id:'w6', cat:'m16', html:'Метан CH$_4$' },
];
const sorter = setupSorter({
poolId:'p2-iv2-pool',
scopeSelector:'#p2-iv2',
items: items,
cats:['m2','m16','m18','m28','m32','m44'],
columnLayout:false,
});
document.getElementById('p2-iv2-check').addEventListener('click', ()=>{
const fb = document.getElementById('p2-iv2-fb');
const placedCount = items.filter(it => sorter.placed[it.id]).length;
const correct = items.filter(it => sorter.placed[it.id] === it.cat).length;
if(placedCount < items.length){ feedback(fb, false, '&#10007; Размести все 6 веществ.'); return; }
if(correct === items.length){ feedback(fb, true, '&#10003; Все 6 верно! +10 XP'); addXp(10,'p2-iv2'); bumpProgress('p2', 15); }
else feedback(fb, false, '&#10007; Правильно ' + correct + ' из 6. Попробуй ещё.');
});
document.getElementById('p2-iv2-reset').addEventListener('click', ()=>{ sorter.reset(); document.getElementById('p2-iv2-fb').style.display = 'none'; });
})();
/* IV3 — квикфайр «Что больше?» */
(function(){
const Q = [
{ q:'Что весит больше: $1$ моль воды (M=18) или $1$ моль водорода (M=2)?', a:'1 моль воды (18 г)', b:'1 моль водорода (2 г)', ans:0, why:'Молярная масса воды 18 г/моль > 2 г/моль.' },
{ q:'Что больше по массе: $36$ г воды или $1$ моль воды (M=18)?', a:'36 г воды', b:'1 моль воды (18 г)', ans:0, why:'36 г = 2 моль воды > 1 моль (18 г).' },
{ q:'Что весит больше: $N_A$ молекул кислорода (M=32) или $2 N_A$ молекул водорода (M=2)?', a:'$N_A$ молекул O$_2$ (32 г)', b:'$2 N_A$ молекул H$_2$ (4 г)', ans:0, why:'32 г > 4 г.' },
{ q:'Что больше по массе: $1$ моль CO$_2$ (M=44) или $1$ кг воды?', a:'1 моль CO$_2$ (44 г)', b:'1 кг воды (1000 г)', ans:1, why:'1000 г > 44 г.' },
{ q:'Что больше по числу частиц: $10^{23}$ молекул железа или $10^{22}$ молекул свинца?', a:'$10^{23}$ молекул Fe', b:'$10^{22}$ молекул Pb', ans:0, why:'$10^{23} > 10^{22}$ в 10 раз.' },
{ q:'Что больше по массе: $5$ моль H$_2$ (M=2) или $1$ моль O$_2$ (M=32)?', a:'5 моль H$_2$ (10 г)', b:'1 моль O$_2$ (32 г)', ans:1, why:'$5 \\cdot 2 = 10$ г против $32$ г.' },
];
let i = 0, score = 0;
const qEl = document.getElementById('p2-iv3-q');
const oEl = document.getElementById('p2-iv3-opts');
const fb = document.getElementById('p2-iv3-fb');
const iEl = document.getElementById('p2-iv3-i');
const sEl = document.getElementById('p2-iv3-s');
function show(){
if(i >= Q.length){
qEl.innerHTML = '<b>Готово!</b> Результат: ' + score + ' / ' + Q.length;
oEl.innerHTML = '';
if(score === Q.length){ addXp(15, 'p2-iv3'); bumpProgress('p2', 25); }
else if(score >= 4){ addXp(8, 'p2-iv3'); bumpProgress('p2', 15); }
return;
}
iEl.textContent = (i+1);
sEl.textContent = score;
const item = Q[i];
qEl.innerHTML = item.q;
oEl.innerHTML = '<button class="btn primary" data-v="0">Первое: '+item.a+'</button><button class="btn primary" data-v="1">Второе: '+item.b+'</button>';
fb.style.display = 'none';
renderMath(qEl); renderMath(oEl);
oEl.querySelectorAll('button').forEach(b => {
b.addEventListener('click', () => {
const v = +b.dataset.v;
if(v === item.ans){ score++; feedback(fb, true, '&#10003; Верно! ' + item.why + ' Дальше ▶'); }
else feedback(fb, false, '&#10007; Неверно. ' + item.why + ' Дальше ▶');
sEl.textContent = score;
oEl.querySelectorAll('button').forEach(x => x.disabled = true);
i++;
setTimeout(show, 1500);
});
});
}
document.getElementById('p2-iv3-restart').addEventListener('click', () => { i = 0; score = 0; show(); });
show();
})();
/* IV4 — тренажёр количества вещества */
(function(){
const Q = [
{ q:'В 36 г воды (M=18 г/моль) сколько моль?', ans:2, hint:'$\\nu = m/M = 36/18 = 2$ моль' },
{ q:'В 88 г углекислого газа (M=44 г/моль) сколько моль?', ans:2, hint:'$\\nu = 88/44 = 2$ моль' },
{ q:'Сколько молекул в 1 моль вещества? Введи показатель $X$ для $6 \\cdot 10^X$', ans:23, hint:'$N_A = 6 \\cdot 10^{23}$' },
{ q:'M = 64 г/моль. Сколько моль в 32 г этого вещества?', ans:0.5, hint:'$\\nu = 32/64 = 0{,}5$ моль' },
{ q:'Сколько грамм в 3 моль воды (M=18 г/моль)?', ans:54, hint:'$m = \\nu M = 3 \\cdot 18 = 54$ г' },
{ q:'В 36 г воды сколько молекул? Введи $X$ для ответа $X \\cdot 10^{23}$', ans:12, hint:'$\\nu = 2$ моль, $N = 2 \\cdot 6 \\cdot 10^{23} = 12 \\cdot 10^{23}$' },
];
let i = 0, score = 0;
function show(){
if(i >= Q.length){
document.getElementById('p2-iv4-q').innerHTML = '<b>Готово!</b> Результат: ' + score + ' / ' + Q.length;
if(score === Q.length){ addXp(15, 'p2-iv4'); bumpProgress('p2', 25); }
else if(score >= 4){ addXp(8, 'p2-iv4'); bumpProgress('p2', 15); }
return;
}
document.getElementById('p2-iv4-i').textContent = (i+1);
document.getElementById('p2-iv4-s').textContent = score;
document.getElementById('p2-iv4-q').innerHTML = Q[i].q;
document.getElementById('p2-iv4-ans').value = '';
renderMath(document.getElementById('p2-iv4-q'));
document.getElementById('p2-iv4-fb').style.display = 'none';
}
function go(){
if(i >= Q.length) return;
const fb = document.getElementById('p2-iv4-fb');
const raw = document.getElementById('p2-iv4-ans').value.replace(',', '.');
const ans = parseFloat(raw);
if(isNaN(ans)){ feedback(fb, false, '&#10007; Введи число.'); return; }
const tol = Math.max(0.05 * Math.abs(Q[i].ans), 0.05);
if(Math.abs(ans - Q[i].ans) < tol){ score++; feedback(fb, true, '&#10003; Верно! '+Q[i].hint+'. Дальше ▶'); }
else feedback(fb, false, '&#10007; Неверно. Ответ: $'+Q[i].ans+'$. '+Q[i].hint+'. Дальше ▶');
document.getElementById('p2-iv4-s').textContent = score;
i++;
setTimeout(show, 1500);
}
document.getElementById('p2-iv4-go').addEventListener('click', go);
document.getElementById('p2-iv4-ans').addEventListener('keydown', e => { if(e.key === 'Enter') go(); });
document.getElementById('p2-iv4-start').addEventListener('click', () => { i = 0; score = 0; show(); });
show();
})();
wireReadBtn('p2');
}