Files
Learn_System/frontend/js/phys7_ch3_widgets.js
T
Maxim Dolgolyov 98f955a85e fix(phys7): главный визуал курса работает + §22, §24 интерактивы улучшены
1. БАГ В HillSlideSim (phys.js):
   - При reset() начальное состояние x=0, h=hStart, v=0.
   - Первый step(): dropped=0 → v=0 → x не растёт → h не падает → тележка
     навсегда стоит на вершине (бесконечный нуль). Анимация ничего не показывала.
   - Фикс: reset() даёт начальный толчок (x = L*0.01) и v по энергии для
     этой малой высоты падения. step() теперь корректно ускоряет тележку.
   - Тест node: за 2.05 с тележка проходит 11.7 м, h падает с 4.9 м до 0.86 м,
     v растёт с 1.4 до 9.0 м/с. Е_полн ≈ const.

2. §22 «Сила тяжести» — новый IV-2 «Падение на 4 планетах»:
   - SVG 4-колоночная сцена, 4 шарика стартуют с одной высоты.
   - Slider высоты 2..20 м, кнопки «Уронить» / «Сброс».
   - Свободное падение по h(t) = h₀ − gt²/2 для каждой планеты (Земля 9.8,
     Луна 1.6, Марс 3.7, Юпитер 24.8).
   - Видно: Юпитер падает первым, Луна последней; для каждого сохраняется
     время падения √(2h/g) и итоговая v = g·t.
   - Live info: текущее t, статус каждого шарика (падает / упал за X с,
     v = Y м/с).

3. §24 «Вес тела» — переработан IV-1 «Лифт с динамометром»:
   - Было: 4 статичных схемы покой/падение/верх/вниз.
   - Стало: динамический симулятор. Кабина лифта со стрелкой ускорения
     снаружи, внутри — груз на пружинном динамометре с шкалой.
   - 2 slider'а: масса 0.5..10 кг, ускорение −10..+10 м/с².
   - 4 кнопки-пресета: Покой / Едет вверх / Едет вниз / Свободное падение.
   - Формула P = m(g + a) считается в реальном времени.
   - 4 режима с автоопределением: ПОКОЙ / НЕВЕСОМОСТЬ / ПЕРЕГРУЗКА /
     ПОНИЖЕННЫЙ ВЕС с разной цветовой индикацией.
   - Пружина динамометра реально растягивается/сжимается в зависимости
     от P; указатель и шкала тоже.

Parse OK, smoke (15 экспортов CH3) OK.
2026-05-30 12:14:48 +03:00

2227 lines
171 KiB
JavaScript
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.
// Физика 7 · Глава 3 «Движение и силы» · виджеты §§14–20 (часть 1: кинематика).
// Силы §§21–27 + финал — в Phase 4 (phys7_ch3b_widgets.js или этот же файл, расширим).
// Палитра главы — red (#dc2626).
(function(){
'use strict';
const ACCENT = '#dc2626';
const ACCENT_D = '#991b1b';
const ACCENT_SOFT = '#fee2e2';
function renderMath(root){
if(window.renderMathInElement){
try{
window.renderMathInElement(root, {
delimiters: [{left:'$$',right:'$$',display:true},{left:'$',right:'$',display:false}],
throwOnError: false
});
}catch(e){}
}
}
function makeCard(kind, title, badge, body){
const colorByKind = { theory:ACCENT, rule:'#0284c7', example:'#10b981' };
const labelByKind = { theory:'Теория', rule:'Правило', example:'Пример' };
const c = colorByKind[kind] || ACCENT;
return '<div style="background:#fff;border:1.5px solid ' + ACCENT_SOFT + ';border-left:5px solid ' + c + ';border-radius:11px;padding:14px 16px;margin-bottom:14px;box-shadow:0 2px 8px rgba(0,0,0,.05)">'
+ '<div style="display:flex;align-items:center;gap:10px;margin-bottom:8px;flex-wrap:wrap">'
+ '<span style="background:' + c + ';color:#fff;padding:3px 10px;border-radius:99px;font-size:.7rem;font-weight:800;text-transform:uppercase;letter-spacing:.05em">' + labelByKind[kind] + '</span>'
+ '<span style="font-size:.72rem;font-weight:700;color:#64748b;letter-spacing:.04em">' + (badge||'') + '</span>'
+ '<span style="font-family:Unbounded,sans-serif;font-weight:800;font-size:.96rem;color:#0f172a;flex:1;min-width:0">' + title + '</span>'
+ '</div>'
+ '<div style="font-size:.94rem;line-height:1.6;color:#0f172a">' + body + '</div>'
+ '</div>';
}
function wgWrap(id, badge, title, hint, body){
return '<div id="' + id + '" style="background:#fff;border:1.5px solid ' + ACCENT_SOFT + ';border-radius:12px;padding:14px 16px;margin-bottom:14px;box-shadow:0 2px 8px rgba(0,0,0,.05)">'
+ '<div style="display:flex;align-items:center;gap:10px;margin-bottom:8px;flex-wrap:wrap">'
+ '<span style="background:' + ACCENT + ';color:#fff;padding:3px 10px;border-radius:99px;font-size:.7rem;font-weight:800;letter-spacing:.05em">' + badge + '</span>'
+ '<span style="font-family:Unbounded,sans-serif;font-weight:800;font-size:.92rem;color:#0f172a">' + title + '</span>'
+ '</div>'
+ (hint ? '<div style="font-size:.84rem;color:#64748b;background:' + ACCENT_SOFT + ';border-left:3px solid ' + ACCENT + ';border-radius:6px;padding:8px 12px;margin-bottom:10px;line-height:1.5">' + hint + '</div>' : '')
+ body
+ '</div>';
}
function readButton(pid){
return '<div style="text-align:center;margin-top:18px"><button class="ph7-read-btn" data-pid="' + pid + '" '
+ 'style="background:linear-gradient(135deg,#10b981,#047857);color:#fff;border:none;padding:11px 22px;border-radius:11px;font-weight:700;font-size:.92rem;cursor:pointer;font-family:inherit;display:inline-flex;align-items:center;gap:8px;box-shadow:0 4px 14px rgba(16,185,129,.32)">'
+ '<svg viewBox="0 0 24 24" style="width:16px;height:16px;stroke:currentColor;fill:none;stroke-width:2.5;stroke-linecap:round;stroke-linejoin:round"><polyline points="20 6 9 17 4 12"/></svg>'
+ 'Я прочитал § &nbsp;<span style="opacity:.85;font-size:.86rem">+10 XP</span>'
+ '</button></div>';
}
function wireReadBtn(pid){
const btn = document.querySelector('.ph7-read-btn[data-pid="' + pid + '"]');
if(!btn) return;
const KEY = 'physics7_ch3_read_' + pid;
if(localStorage.getItem(KEY) === '1'){
btn.innerHTML = '<svg viewBox="0 0 24 24" style="width:16px;height:16px;stroke:currentColor;fill:none;stroke-width:2.5;stroke-linecap:round;stroke-linejoin:round"><polyline points="20 6 9 17 4 12"/></svg> Прочитано';
btn.disabled = true; btn.style.background = '#94a3b8'; btn.style.cursor = 'default';
return;
}
btn.addEventListener('click', function(){
if(localStorage.getItem(KEY) === '1') return;
localStorage.setItem(KEY, '1');
if(typeof window.bumpProgress === 'function') window.bumpProgress(pid, 30);
if(typeof window.addXp === 'function') window.addXp(10, 'read-' + pid);
btn.innerHTML = '<svg viewBox="0 0 24 24" style="width:16px;height:16px;stroke:currentColor;fill:none;stroke-width:2.5;stroke-linecap:round;stroke-linejoin:round"><polyline points="20 6 9 17 4 12"/></svg> Прочитано';
btn.disabled = true; btn.style.background = '#94a3b8'; btn.style.cursor = 'default';
});
}
function wireDnd(host, items){
const root = document.getElementById(host);
if(!root) return;
const checkBtn = root.querySelector('.dnd-check');
const fb = root.querySelector('.dnd-fb');
let placed = {}, armed = null;
root.querySelectorAll('.dnd-chip').forEach(chip => chip.addEventListener('click', () => {
if(armed === chip){ armed.classList.remove('armed'); armed.style.boxShadow = ''; armed = null; return; }
if(armed){ armed.classList.remove('armed'); armed.style.boxShadow = ''; }
armed = chip;
chip.classList.add('armed');
chip.style.boxShadow = '0 0 0 3px rgba(220,38,38,.3)';
}));
root.querySelectorAll('.drop-box').forEach(box => box.addEventListener('click', () => {
if(!armed) return;
const cat = box.dataset.cat;
const id = armed.dataset.id;
placed[id] = cat;
const clone = armed.cloneNode(true);
clone.classList.remove('armed'); clone.classList.add('placed');
clone.style.background = ACCENT_SOFT; clone.style.borderColor = ACCENT; clone.style.boxShadow = '';
clone.addEventListener('click', e => {
e.stopPropagation();
delete placed[id];
clone.remove();
armed = null;
const orig = root.querySelector('.dnd-chip[data-id="' + id + '"]:not(.placed)');
if(orig) orig.style.display = '';
});
box.querySelector('.drop-items').appendChild(clone);
armed.style.display = 'none';
armed.classList.remove('armed'); armed.style.boxShadow = '';
armed = null;
}));
if(checkBtn) checkBtn.addEventListener('click', () => {
const total = items.length;
let correct = 0;
items.forEach(it => { if(placed[it.id] === it.cat) correct++; });
fb.style.display = 'block';
if(correct === total){
fb.style.background = '#d1fae5'; fb.style.color = '#065f46'; fb.style.borderLeft = '4px solid #10b981';
fb.innerHTML = '&#10003; Идеально! ' + total + '/' + total + '.';
if(typeof window.addXp === 'function') window.addXp(15, 'dnd-' + host);
} else {
fb.style.background = '#fee2e2'; fb.style.color = '#7f1d1d'; fb.style.borderLeft = '4px solid #dc2626';
fb.innerHTML = '&#10007; Правильно: ' + correct + '/' + total + '. Попробуй ещё раз.';
}
});
}
function dndPool(host, items, cats){
const chips = items.map(it => '<button class="dnd-chip" data-id="' + it.id + '" type="button" style="background:#fff;border:1.5px solid ' + ACCENT_SOFT + ';border-radius:10px;padding:7px 13px;cursor:pointer;font-size:.9rem;font-family:inherit;transition:all .15s">' + it.html + '</button>').join('');
const boxes = cats.map(c => '<div class="drop-box" data-cat="' + c.cat + '" style="background:#fff;border:1.5px dashed ' + ACCENT_SOFT + ';border-radius:10px;padding:10px;min-height:80px"><h5 style="font-family:Unbounded,sans-serif;font-size:.76rem;color:' + ACCENT_D + ';margin-bottom:8px;text-transform:uppercase;letter-spacing:.04em">' + c.label + '</h5><div class="drop-items" style="display:flex;flex-wrap:wrap;gap:6px;min-height:32px"></div></div>').join('');
return '<div id="' + host + '" class="dnd-host">'
+ '<div style="font-size:.86rem;color:#475569;margin-bottom:10px">Кликни по карточке, потом по корзине. Чтобы вернуть — кликни в корзине.</div>'
+ '<div style="display:flex;flex-wrap:wrap;gap:8px;margin-bottom:14px;padding:10px;border:1.5px dashed ' + ACCENT_SOFT + ';border-radius:10px;background:#fef2f2">' + chips + '</div>'
+ '<div style="display:grid;grid-template-columns:repeat(auto-fit,minmax(170px,1fr));gap:10px;margin-bottom:12px">' + boxes + '</div>'
+ '<div style="display:flex;gap:8px"><button class="dnd-check" type="button" style="background:linear-gradient(135deg,' + ACCENT + ',' + ACCENT_D + ');color:#fff;border:none;padding:9px 18px;border-radius:9px;font-weight:700;font-size:.86rem;cursor:pointer;font-family:inherit">Проверить</button></div>'
+ '<div class="dnd-fb" style="padding:10px 14px;border-radius:9px;font-weight:600;font-size:.88rem;margin-top:8px;display:none;line-height:1.45"></div>'
+ '</div>';
}
function quizQuestion(host, idx, q, opts, correctIdx, explain){
const optsHtml = opts.map((o,i) => '<button class="qz-opt" data-i="' + i + '" type="button" style="background:#fff;border:1.5px solid ' + ACCENT_SOFT + ';border-radius:9px;padding:9px 14px;cursor:pointer;font-size:.92rem;font-family:inherit;text-align:left;width:100%;margin-bottom:6px">' + o + '</button>').join('');
return '<div class="qz-q" data-idx="' + idx + '" style="background:' + ACCENT_SOFT + ';border:1.5px solid ' + ACCENT_SOFT + ';border-radius:10px;padding:12px 14px;margin-bottom:10px">'
+ '<div style="font-weight:700;margin-bottom:8px;font-size:.94rem">' + (idx+1) + '. ' + q + '</div>'
+ '<div class="qz-opts" data-correct="' + correctIdx + '" data-explain="' + (explain||'').replace(/"/g,'&quot;') + '">' + optsHtml + '</div>'
+ '<div class="qz-fb" style="padding:9px 12px;border-radius:8px;font-size:.86rem;margin-top:6px;display:none;line-height:1.45"></div>'
+ '</div>';
}
function wireQuiz(host, onAllCorrect){
const root = document.getElementById(host);
if(!root) return;
const all = root.querySelectorAll('.qz-q');
const done = new Set();
all.forEach(qDiv => {
const opts = qDiv.querySelectorAll('.qz-opt');
const correct = +qDiv.querySelector('.qz-opts').dataset.correct;
const explain = qDiv.querySelector('.qz-opts').dataset.explain;
const fb = qDiv.querySelector('.qz-fb');
opts.forEach(o => o.addEventListener('click', () => {
if(done.has(qDiv)) return;
const i = +o.dataset.i;
opts.forEach(x => x.disabled = true);
if(i === correct){
o.style.background = '#d1fae5'; o.style.borderColor = '#10b981'; o.style.color = '#065f46';
fb.style.display = 'block'; fb.style.background = '#d1fae5'; fb.style.color = '#065f46'; fb.style.borderLeft = '4px solid #10b981';
fb.innerHTML = '&#10003; Верно!' + (explain ? ' ' + explain : '');
done.add(qDiv);
if(done.size === all.length && typeof onAllCorrect === 'function') onAllCorrect();
} else {
o.style.background = '#fee2e2'; o.style.borderColor = '#dc2626'; o.style.color = '#7f1d1d';
opts[correct].style.background = '#d1fae5'; opts[correct].style.borderColor = '#10b981'; opts[correct].style.color = '#065f46';
fb.style.display = 'block'; fb.style.background = '#fee2e2'; fb.style.color = '#7f1d1d'; fb.style.borderLeft = '4px solid #dc2626';
fb.innerHTML = '&#10007; Неверно. Правильно: «' + opts[correct].textContent + '».' + (explain ? ' ' + explain : '');
done.add(qDiv);
}
}));
});
}
/* ========================================================== */
/* §14 — Механическое движение. Относительность */
/* ========================================================== */
function add_p14(){
const body = document.getElementById('p14-body');
if(!body) return;
let h = '';
h += makeCard('theory', 'Что такое механическое движение', '§ 14.1',
'<b>Механическое движение</b> — это изменение положения тела относительно других тел с течением времени.<br><br>'
+ 'Чтобы описать движение, нужно выбрать <b>тело отсчёта</b> — то, относительно которого смотрим. '
+ 'Тело отсчёта + связанные с ним оси координат + часы = <b>система отсчёта (СО)</b>.');
h += makeCard('rule', 'Относительность движения и покоя', '§ 14.2',
'Одно и то же тело может <b>одновременно</b> и покоиться, и двигаться — в зависимости от выбранной СО.<br><br>'
+ 'Пример: пассажир в вагоне сидит на месте — относительно вагона он <b>покоится</b>; '
+ 'относительно земли он <b>движется</b> вместе с поездом со скоростью $v$.<br><br>'
+ 'Часто за тело отсчёта удобно брать Землю.');
h += makeCard('example', 'Облако и самолёт', '§ 14.3',
'Из иллюминатора самолёта вы видите облако.<br>'
+ '<b>Относительно земли</b>: самолёт летит со скоростью $900$ км/ч, облако почти неподвижно.<br>'
+ '<b>Относительно самолёта</b>: самолёт «стоит на месте» — а облако «летит» назад со скоростью $\\approx 900$ км/ч.<br>'
+ 'И то, и другое — правильно. Просто <b>СО разные</b>.');
/* IV-1 СИМ: 2 СО — поезд + пассажир + станция */
h += wgWrap('p14-iv1', 'СИМ', 'Откуда смотришь — то и видишь', 'Переключай систему отсчёта и наблюдай, кто движется, а кто стоит.',
'<div style="display:flex;gap:6px;margin-bottom:10px">'
+ '<button class="p14-co" data-co="ground" type="button" style="background:#dc2626;color:#fff;border:none;padding:8px 16px;border-radius:9px;cursor:pointer;font-weight:700;font-family:inherit">СО: Земля</button>'
+ '<button class="p14-co" data-co="train" type="button" style="background:#fff;color:#dc2626;border:2px solid #dc2626;padding:8px 16px;border-radius:9px;cursor:pointer;font-weight:700;font-family:inherit">СО: Поезд</button>'
+ '</div>'
+ '<svg id="p14-svg" viewBox="0 0 380 160" width="100%" style="max-width:600px;display:block;margin:0 auto;background:#fef2f2;border-radius:9px;border:1px solid ' + ACCENT_SOFT + '"></svg>'
+ '<div id="p14-info" style="background:' + ACCENT_SOFT + ';border-radius:9px;padding:10px 14px;margin-top:8px;font-size:.92rem;line-height:1.55"></div>');
/* IV-2 КВИЗ */
h += wgWrap('p14-iv2', 'КВИЗ', 'Покой или движение?', '',
'<div id="p14-q-host">'
+ quizQuestion('p14-q', 0, 'Пассажир сидит в едущем автобусе. Относительно сидящих рядом пассажиров он:', ['Движется','Покоится','И то, и другое','Невозможно определить'], 1)
+ quizQuestion('p14-q', 1, 'Тот же пассажир относительно дороги:', ['Движется','Покоится','Невозможно сказать','Только при остановке'], 0)
+ quizQuestion('p14-q', 2, 'Дерево у дороги относительно проезжающей машины:', ['Покоится','Движется в сторону, противоположную машине','Движется в ту же сторону','Зависит от скорости'], 1, 'Если за СО взять машину, дерево «летит» назад со скоростью машины.')
+ quizQuestion('p14-q', 3, 'Что такое система отсчёта?', ['Только тело отсчёта','Тело отсчёта + оси координат + часы','Только часы','Любая точка'], 1)
+ '</div>');
/* IV-3 DnD */
h += wgWrap('p14-iv3', 'DnD', 'Что движется относительно чего', '',
dndPool('p14-dnd', [
{ id:'a1', cat:'mov', html:'Луна относительно Земли' },
{ id:'a2', cat:'mov', html:'Машина относительно дороги' },
{ id:'a3', cat:'rest', html:'Светильник относительно потолка' },
{ id:'a4', cat:'rest', html:'Пассажир относительно вагона' },
{ id:'a5', cat:'mov', html:'Стрелка часов относительно циферблата' },
{ id:'a6', cat:'rest', html:'Книга на столе относительно стола' }
], [
{ cat:'mov', label:'Движется' },
{ cat:'rest', label:'Покоится' }
]));
/* IV-4 ТРН */
h += wgWrap('p14-iv4', 'ТРН', 'Тренажёр §14', '',
'<div id="p14-tr-host">'
+ quizQuestion('p14-tr', 0, 'Что НЕ входит в систему отсчёта?', ['Тело отсчёта','Оси координат','Часы','Скорость движения'], 3)
+ quizQuestion('p14-tr', 1, 'Покой или движение — это:', ['Абсолютные понятия','Относительные понятия (зависят от СО)','Существует только покой','Существует только движение'], 1)
+ quizQuestion('p14-tr', 2, 'Если за тело отсчёта взять движущийся поезд, то платформа:', ['Покоится','Движется','Не существует','Не имеет значения'], 1)
+ quizQuestion('p14-tr', 3, 'Можно ли утверждать, что Земля покоится?', ['Да, всегда','Нет, в одной СО — да, в другой — нет','Нет, никогда','Только ночью'], 1, 'Относительно Солнца Земля движется. Относительно дома — обычно её удобно считать покоящейся.')
+ '</div>');
h += readButton('p14');
body.innerHTML = h;
// §14 IV-1 wire
let frame14 = 0, raf14 = 0;
function draw14(){
const co = body.querySelector('.p14-co[style*="background: rgb(220, 38, 38)"], .p14-co[style*="background:#dc2626"]');
const isGround = !co || co.dataset.co === 'ground';
const W = 380, H = 160;
let trainX, treeX;
if(isGround){
trainX = 20 + (frame14 % 320);
treeX = 250;
} else {
trainX = 100; // поезд стоит
treeX = 380 - (frame14 % 320);
}
let s = '';
// Небо
s += '<rect x="0" y="0" width="' + W + '" height="' + (H - 30) + '" fill="#dbeafe"/>';
// Земля
s += '<rect x="0" y="' + (H - 30) + '" width="' + W + '" height="30" fill="#86efac"/>';
// Рельсы
s += '<line x1="0" y1="' + (H - 22) + '" x2="' + W + '" y2="' + (H - 22) + '" stroke="#374151" stroke-width="1.5"/>';
s += '<line x1="0" y1="' + (H - 18) + '" x2="' + W + '" y2="' + (H - 18) + '" stroke="#374151" stroke-width="1.5"/>';
// Дерево
s += '<rect x="' + treeX + '" y="' + (H - 60) + '" width="6" height="30" fill="#92400e"/>';
s += '<circle cx="' + (treeX + 3) + '" cy="' + (H - 65) + '" r="15" fill="#16a34a"/>';
// Поезд
s += '<rect x="' + trainX + '" y="' + (H - 70) + '" width="80" height="40" fill="#dc2626" stroke="#7f1d1d" stroke-width="1.5" rx="4"/>';
s += '<rect x="' + (trainX + 10) + '" y="' + (H - 60) + '" width="20" height="14" fill="#bfdbfe" stroke="#0c4a6e" stroke-width="1"/>';
s += '<rect x="' + (trainX + 40) + '" y="' + (H - 60) + '" width="20" height="14" fill="#bfdbfe" stroke="#0c4a6e" stroke-width="1"/>';
s += '<circle cx="' + (trainX + 18) + '" cy="' + (H - 28) + '" r="8" fill="#1f2937"/>';
s += '<circle cx="' + (trainX + 62) + '" cy="' + (H - 28) + '" r="8" fill="#1f2937"/>';
// Пассажир в окне
s += '<circle cx="' + (trainX + 20) + '" cy="' + (H - 56) + '" r="4" fill="#fbbf24"/>';
// Стрелка «направление обзора»
if(isGround){
s += '<text x="10" y="20" font-family="Inter,sans-serif" font-size="11" font-weight="700" fill="#0c4a6e">Наблюдатель на земле</text>';
} else {
s += '<text x="10" y="20" font-family="Inter,sans-serif" font-size="11" font-weight="700" fill="#0c4a6e">Наблюдатель в поезде</text>';
}
document.getElementById('p14-svg').innerHTML = s;
const info = document.getElementById('p14-info');
if(isGround){
info.innerHTML = '<b>СО: Земля.</b> Поезд <span style="color:#dc2626;font-weight:700">движется</span> направо. Дерево <span style="color:#10b981;font-weight:700">покоится</span>.';
} else {
info.innerHTML = '<b>СО: Поезд.</b> Поезд <span style="color:#10b981;font-weight:700">покоится</span>. Дерево <span style="color:#dc2626;font-weight:700">«движется»</span> назад с той же скоростью.';
}
}
function loop14(){
frame14 += 1;
if(!document.getElementById('p14-svg')){ cancelAnimationFrame(raf14); return; }
draw14();
raf14 = requestAnimationFrame(loop14);
}
body.querySelectorAll('.p14-co').forEach(btn => btn.addEventListener('click', () => {
body.querySelectorAll('.p14-co').forEach(b => { b.style.background = '#fff'; b.style.color = '#dc2626'; b.style.border = '2px solid #dc2626'; });
btn.style.background = '#dc2626'; btn.style.color = '#fff'; btn.style.border = 'none';
btn.style.padding = '8px 16px'; // компенсировать border
frame14 = 0;
draw14();
}));
raf14 = requestAnimationFrame(loop14);
wireDnd('p14-dnd', [
{ id:'a1', cat:'mov' },{ id:'a2', cat:'mov' },{ id:'a3', cat:'rest' },
{ id:'a4', cat:'rest' },{ id:'a5', cat:'mov' },{ id:'a6', cat:'rest' }
]);
wireQuiz('p14-q-host', () => { if(window.addXp) window.addXp(10, 'q-p14'); });
wireQuiz('p14-tr-host', () => { if(window.addXp) window.addXp(15, 'tr-p14'); });
wireReadBtn('p14');
renderMath(body);
}
/* ========================================================== */
/* §15 — Траектория, путь, время */
/* ========================================================== */
function add_p15(){
const body = document.getElementById('p15-body');
if(!body) return;
let h = '';
h += makeCard('theory', 'Три ключевых понятия', '§ 15.1',
'<b>Траектория</b> — это линия, по которой движется тело.<br>'
+ '<b>Путь</b> $s$ — длина этой линии. $[s] = $ м (метр).<br>'
+ '<b>Время</b> $t$ — длительность движения. $[t] = $ с (секунда).');
h += makeCard('rule', 'Виды траекторий', '§ 15.2',
'<ul style="padding-left:20px;margin:6px 0">'
+ '<li><b>Прямолинейная</b> — лифт, шарик в трубке.</li>'
+ '<li><b>Криволинейная</b> — поворот машины, спутник вокруг Земли.</li>'
+ '<li><b>Замкнутая</b> — бегун на круговой дорожке возвращается в старт; путь $\\neq 0$, а вот «перемещение» (вектор) равно $0$.</li>'
+ '</ul>'
+ 'Длина траектории <b>не зависит от направления</b> — это всегда положительное число.');
h += makeCard('example', 'Школьник идёт в школу', '§ 15.3',
'<b>Сценарий:</b> школьник прошёл $200$ м прямо, потом свернул и прошёл ещё $150$ м, через $5$ мин был в школе.<br>'
+ '<b>Траектория:</b> ломаная из двух прямых.<br>'
+ '<b>Путь:</b> $s = 200 + 150 = 350$ м.<br>'
+ '<b>Время:</b> $t = 5$ мин $= 300$ с.');
/* IV-1 СИМ: интерактивная траектория — нажимай по квадратам, считаем путь */
h += wgWrap('p15-iv1', 'СИМ', 'Считаем путь по траектории', 'Кликай по точкам сетки, чтобы построить ломаную траекторию. Путь будет суммироваться.',
'<div style="margin-bottom:8px;display:flex;gap:6px;flex-wrap:wrap;align-items:center">'
+ '<button id="p15-reset" type="button" style="background:#fff;border:1.5px solid ' + ACCENT_SOFT + ';padding:6px 12px;border-radius:8px;cursor:pointer;font-family:inherit;font-weight:600;font-size:.84rem">Сброс</button>'
+ '<div style="font-size:.84rem;color:#475569">Каждая клетка — $\\textbf{1}$ м.</div>'
+ '</div>'
+ '<svg id="p15-svg" viewBox="0 0 360 220" width="100%" style="max-width:600px;display:block;margin:0 auto;background:#fef2f2;border-radius:9px;border:1px solid ' + ACCENT_SOFT + ';cursor:crosshair"></svg>'
+ '<div id="p15-info" style="background:' + ACCENT_SOFT + ';border-radius:9px;padding:10px 14px;margin-top:8px;font-size:.94rem;text-align:center">Кликай по клеткам, чтобы построить путь.</div>');
/* IV-2 КВИЗ */
h += wgWrap('p15-iv2', 'КВИЗ', 'Что есть что', '',
'<div id="p15-q-host">'
+ quizQuestion('p15-q', 0, 'Длина траектории — это:', ['Время','Скорость','Путь','Перемещение'], 2)
+ quizQuestion('p15-q', 1, 'Бегун пробежал круг $400$ м и вернулся в старт. Путь равен:', ['0 м','200 м','400 м','800 м'], 2, 'Путь = длина траектории, не зависит от того, что бегун вернулся.')
+ quizQuestion('p15-q', 2, 'Единица пути в СИ:', ['Секунда','Метр','Километр','Минута'], 1)
+ '</div>');
/* IV-3 DnD */
h += wgWrap('p15-iv3', 'DnD', 'Тип траектории', '',
dndPool('p15-dnd', [
{ id:'a1', cat:'st', html:'Лифт' },
{ id:'a2', cat:'st', html:'Падающий мяч (вертикально)' },
{ id:'a3', cat:'cv', html:'Поворот машины' },
{ id:'a4', cat:'cv', html:'Спутник вокруг Земли' },
{ id:'a5', cat:'cl', html:'Бегун по круговой дорожке' },
{ id:'a6', cat:'cl', html:'Стрелка часов' }
], [
{ cat:'st', label:'Прямолинейная' },
{ cat:'cv', label:'Криволинейная' },
{ cat:'cl', label:'Замкнутая' }
]));
/* IV-4 ТРН */
h += wgWrap('p15-iv4', 'ТРН', 'Тренажёр §15', '',
'<div id="p15-tr-host">'
+ quizQuestion('p15-tr', 0, 'Велосипедист проехал $2$ км и вернулся обратно. Какой путь он проехал?', ['0 км','1 км','2 км','4 км'], 3, 'Путь — сумма длин всех участков: $2 + 2 = 4$ км.')
+ quizQuestion('p15-tr', 1, '$5$ мин $= ?$ с', ['50','300','500','3000'], 1)
+ quizQuestion('p15-tr', 2, '$1{,}5$ км $= ?$ м', ['15','150','1 500','15 000'], 2)
+ quizQuestion('p15-tr', 3, 'Может ли путь быть отрицательным?', ['Да','Нет — это длина, всегда $\\ge 0$','Только в нестандартных СО','Зависит от направления'], 1)
+ quizQuestion('p15-tr', 4, 'Что такое траектория?', ['Скорость движения','Линия, по которой движется тело','Длина пути','Время в пути'], 1)
+ '</div>');
h += readButton('p15');
body.innerHTML = h;
// §15 IV-1: clickable path
const W = 360, H = 220, cell = 20;
const points = [];
function draw15(){
let s = '';
// Сетка
for(let x = 0; x <= W; x += cell) s += '<line x1="' + x + '" y1="0" x2="' + x + '" y2="' + H + '" stroke="#fecaca" stroke-width="0.5"/>';
for(let y = 0; y <= H; y += cell) s += '<line x1="0" y1="' + y + '" x2="' + W + '" y2="' + y + '" stroke="#fecaca" stroke-width="0.5"/>';
// Линии между точками
if(points.length > 1){
let d = 'M ' + points[0].x + ' ' + points[0].y;
for(let i = 1; i < points.length; i++) d += ' L ' + points[i].x + ' ' + points[i].y;
s += '<path d="' + d + '" fill="none" stroke="#dc2626" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"/>';
}
// Точки
points.forEach((p, i) => {
s += '<circle cx="' + p.x + '" cy="' + p.y + '" r="6" fill="#dc2626" stroke="#fff" stroke-width="2"/>';
if(i === 0) s += '<text x="' + (p.x + 10) + '" y="' + (p.y - 6) + '" font-family="Inter,sans-serif" font-size="10" font-weight="700" fill="#0f172a">старт</text>';
else if(i === points.length - 1) s += '<text x="' + (p.x + 10) + '" y="' + (p.y - 6) + '" font-family="Inter,sans-serif" font-size="10" font-weight="700" fill="#0f172a">' + (i+1) + '</text>';
});
document.getElementById('p15-svg').innerHTML = s;
// Считаем путь
let total = 0;
for(let i = 1; i < points.length; i++){
total += Math.hypot(points[i].x - points[i-1].x, points[i].y - points[i-1].y);
}
const meters = (total / cell);
document.getElementById('p15-info').innerHTML = points.length < 2
? 'Кликай по клеткам, чтобы построить путь. (Кликнуто: ' + points.length + ')'
: 'Точек: <b>' + points.length + '</b> &nbsp;&middot;&nbsp; Путь $s = $ <b>' + meters.toFixed(1) + '</b> м';
renderMath(document.getElementById('p15-info'));
}
document.getElementById('p15-svg').addEventListener('click', e => {
const rect = e.target.getBoundingClientRect();
const svgW = rect.width, svgH = rect.height;
const rawX = (e.clientX - rect.left) * (W / svgW);
const rawY = (e.clientY - rect.top) * (H / svgH);
const x = Math.round(rawX / cell) * cell;
const y = Math.round(rawY / cell) * cell;
points.push({ x, y });
draw15();
});
document.getElementById('p15-reset').addEventListener('click', () => { points.length = 0; draw15(); });
draw15();
wireDnd('p15-dnd', [
{ id:'a1', cat:'st' },{ id:'a2', cat:'st' },{ id:'a3', cat:'cv' },
{ id:'a4', cat:'cv' },{ id:'a5', cat:'cl' },{ id:'a6', cat:'cl' }
]);
wireQuiz('p15-q-host', () => { if(window.addXp) window.addXp(10, 'q-p15'); });
wireQuiz('p15-tr-host', () => { if(window.addXp) window.addXp(15, 'tr-p15'); });
wireReadBtn('p15');
renderMath(body);
}
/* ========================================================== */
/* §16 — Равномерное движение. Скорость */
/* ========================================================== */
function add_p16(){
const body = document.getElementById('p16-body');
if(!body) return;
let h = '';
h += makeCard('theory', 'Равномерное прямолинейное движение', '§ 16.1',
'<b>Равномерное</b> — это движение, при котором за <i>любые равные промежутки времени</i> тело проходит <i>равные</i> расстояния.<br><br>'
+ 'Скорость такого движения:<br>'
+ '$$v = \\dfrac{s}{t}$$<br>'
+ '$[v] = $ м/с (или км/ч). Это <b>векторная</b> величина — у неё есть направление, но в 7-м классе мы пишем как скаляр (без минуса).');
h += makeCard('rule', 'Единицы скорости', '§ 16.2',
'<table style="width:100%;border-collapse:collapse;margin-top:4px;font-size:.92rem">'
+ '<tr style="background:' + ACCENT_SOFT + '"><th style="padding:6px 10px;text-align:left;border-bottom:2px solid ' + ACCENT + '">Единица</th><th style="padding:6px 10px;text-align:left;border-bottom:2px solid ' + ACCENT + '">Перевод</th><th style="padding:6px 10px;text-align:left;border-bottom:2px solid ' + ACCENT + '">Где встречается</th></tr>'
+ [['м/с','1 м/с = 3,6 км/ч','физика, наука'],['км/ч','1 км/ч ≈ 0,28 м/с','транспорт, спидометры'],['см/с','100 см/с = 1 м/с','биология, мелкие объекты'],['км/с','1 км/с = 1000 м/с','космос, ракеты']].map(r =>
'<tr><td style="padding:5px 10px;border-bottom:1px solid ' + ACCENT_SOFT + ';font-family:JetBrains Mono,monospace;font-weight:700">' + r[0] + '</td><td style="padding:5px 10px;border-bottom:1px solid ' + ACCENT_SOFT + ';font-family:JetBrains Mono,monospace">' + r[1] + '</td><td style="padding:5px 10px;border-bottom:1px solid ' + ACCENT_SOFT + '">' + r[2] + '</td></tr>').join('')
+ '</table>');
h += makeCard('example', 'Простой расчёт', '§ 16.3',
'Поезд за $t = 2$ ч проехал $s = 180$ км равномерно.<br>'
+ '$v = s/t = 180\\,\\text{км}/2\\,\\text{ч} = 90$ км/ч $= 25$ м/с.<br><br>'
+ 'Из формулы $v = s/t$ можно выразить и другие: $s = v t$, $t = s/v$.');
/* IV-1 СИМ: автомобиль с slider v */
h += wgWrap('p16-iv1', 'СИМ', 'Автомобиль со скоростью v', 'Меняй скорость — наблюдай, как меняется путь за одно и то же время.',
'<div style="margin-bottom:10px"><label style="display:block;font-size:.86rem;color:#475569;background:#fff;padding:8px 12px;border-radius:8px;border:1px solid ' + ACCENT_SOFT + '">Скорость $v$, м/с: <b id="p16-v" style="color:' + ACCENT_D + ';font-family:JetBrains Mono,monospace">10</b><input type="range" id="p16-v-r" min="1" max="30" step="1" value="10" style="display:block;width:100%;margin-top:6px;accent-color:' + ACCENT + '"></label></div>'
+ '<svg id="p16-svg" viewBox="0 0 380 110" width="100%" style="max-width:600px;display:block;margin:0 auto;background:#fef2f2;border-radius:9px;border:1px solid ' + ACCENT_SOFT + '"></svg>'
+ '<div id="p16-info" style="background:' + ACCENT_SOFT + ';border-radius:9px;padding:10px 14px;margin-top:8px;font-size:.92rem;text-align:center"></div>');
/* IV-2 КАЛЬК: v = s/t */
h += wgWrap('p16-iv2', 'КАЛЬК', 'Калькулятор $v = s/t$', '',
'<div style="display:grid;grid-template-columns:1fr 1fr;gap:8px;margin-bottom:10px">'
+ '<label style="display:block;font-size:.86rem;color:#475569;background:#fff;padding:8px 12px;border-radius:8px;border:1px solid ' + ACCENT_SOFT + '">Путь $s$, м: <b id="p16c-s" style="color:' + ACCENT_D + ';font-family:JetBrains Mono,monospace">100</b><input type="range" id="p16c-s-r" min="10" max="10000" step="10" value="100" style="display:block;width:100%;margin-top:6px;accent-color:' + ACCENT + '"></label>'
+ '<label style="display:block;font-size:.86rem;color:#475569;background:#fff;padding:8px 12px;border-radius:8px;border:1px solid ' + ACCENT_SOFT + '">Время $t$, с: <b id="p16c-t" style="color:' + ACCENT_D + ';font-family:JetBrains Mono,monospace">20</b><input type="range" id="p16c-t-r" min="1" max="3600" step="1" value="20" style="display:block;width:100%;margin-top:6px;accent-color:' + ACCENT + '"></label>'
+ '</div>'
+ '<div style="background:' + ACCENT_SOFT + ';border-radius:9px;padding:12px 14px;font-size:.96rem;line-height:1.7">'
+ '$v = s/t = $ <b id="p16c-v" style="color:' + ACCENT_D + ';font-family:JetBrains Mono,monospace">5</b> м/с $= $ <b id="p16c-vkmh" style="color:' + ACCENT_D + ';font-family:JetBrains Mono,monospace">18</b> км/ч'
+ '</div>');
/* IV-3 DnD: скорости из жизни */
h += wgWrap('p16-iv3', 'DnD', 'Скорости в жизни', 'Сопоставь объекты с их скоростями.',
dndPool('p16-dnd', [
{ id:'a1', cat:'low', html:'$1$ м/с' },
{ id:'a2', cat:'low', html:'$5$ км/ч' },
{ id:'a3', cat:'mid', html:'$60$ км/ч' },
{ id:'a4', cat:'mid', html:'$20$ м/с' },
{ id:'a5', cat:'high', html:'$900$ км/ч' },
{ id:'a6', cat:'high', html:'$340$ м/с' }
], [
{ cat:'low', label:'Пешеход' },
{ cat:'mid', label:'Машина / велосипедист' },
{ cat:'high', label:'Самолёт / звук' }
]));
/* IV-4 ТРН */
h += wgWrap('p16-iv4', 'ТРН', 'Тренажёр §16', '',
'<div id="p16-tr-host">'
+ quizQuestion('p16-tr', 0, 'Велосипедист за $30$ с проехал $150$ м. Скорость?', ['3 м/с','5 м/с','10 м/с','15 м/с'], 1, '$v = s/t = 150/30 = 5$ м/с.')
+ quizQuestion('p16-tr', 1, 'Автобус едет $54$ км/ч. Какой это путь за $10$ с?', ['90 м','120 м','150 м','180 м'], 2, '$v = 54/3{,}6 = 15$ м/с; $s = vt = 15 \\cdot 10 = 150$ м.')
+ quizQuestion('p16-tr', 2, 'Самолёт пролетает $720$ км за $1$ ч $30$ мин. Скорость в км/ч?', ['360','480','540','720'], 1, '$1$ ч $30$ мин $= 1{,}5$ ч. $v = 720/1{,}5 = 480$ км/ч.')
+ quizQuestion('p16-tr', 3, 'За какое время поезд проедет $300$ км со скоростью $100$ км/ч?', ['1 ч','2 ч','3 ч','4 ч'], 2, '$t = s/v = 300/100 = 3$ ч.')
+ quizQuestion('p16-tr', 4, 'Звук в воздухе $340$ м/с. Сколько это км/ч?', ['1024 км/ч','1224 км/ч','1340 км/ч','1500 км/ч'], 1, '$340 \\cdot 3{,}6 = 1224$ км/ч.')
+ '</div>');
h += readButton('p16');
body.innerHTML = h;
// §16 IV-1: car sim
let frame16 = 0, raf16 = 0;
function draw16(){
const v = +document.getElementById('p16-v-r').value;
document.getElementById('p16-v').textContent = v;
const W = 380, H = 110;
const t = frame16 / 60; // секунды (60 fps)
const pxPerM = 5;
let pos = (v * t * pxPerM) % (W - 60);
let s = '';
s += '<rect x="0" y="0" width="' + W + '" height="' + (H - 20) + '" fill="#dbeafe"/>';
s += '<rect x="0" y="' + (H - 20) + '" width="' + W + '" height="20" fill="#94a3b8"/>';
// Разметка
for(let x = 0; x < W; x += 20){
s += '<rect x="' + x + '" y="' + (H - 11) + '" width="10" height="2" fill="#fff"/>';
}
// Машина
s += '<rect x="' + pos + '" y="' + (H - 45) + '" width="50" height="22" fill="#dc2626" stroke="#7f1d1d" stroke-width="1.5" rx="3"/>';
s += '<rect x="' + (pos + 8) + '" y="' + (H - 41) + '" width="14" height="10" fill="#bfdbfe" stroke="#0c4a6e" stroke-width="0.5"/>';
s += '<rect x="' + (pos + 28) + '" y="' + (H - 41) + '" width="14" height="10" fill="#bfdbfe" stroke="#0c4a6e" stroke-width="0.5"/>';
s += '<circle cx="' + (pos + 12) + '" cy="' + (H - 20) + '" r="5" fill="#1f2937"/>';
s += '<circle cx="' + (pos + 38) + '" cy="' + (H - 20) + '" r="5" fill="#1f2937"/>';
document.getElementById('p16-svg').innerHTML = s;
const traveled = (v * t).toFixed(1);
document.getElementById('p16-info').innerHTML = '$v = ' + v + '$ м/с &middot; время $t = ' + t.toFixed(1) + '$ с &middot; путь $s = vt = $ <b>' + traveled + '</b> м';
renderMath(document.getElementById('p16-info'));
}
function loop16(){
frame16 += 1;
if(!document.getElementById('p16-svg')){ cancelAnimationFrame(raf16); return; }
draw16();
raf16 = requestAnimationFrame(loop16);
}
document.getElementById('p16-v-r').addEventListener('input', () => { frame16 = 0; draw16(); });
raf16 = requestAnimationFrame(loop16);
// §16 IV-2: calc v=s/t
const upd16c = () => {
const s = +document.getElementById('p16c-s-r').value;
const t = +document.getElementById('p16c-t-r').value;
document.getElementById('p16c-s').textContent = s;
document.getElementById('p16c-t').textContent = t;
const v = s / t;
document.getElementById('p16c-v').textContent = v.toFixed(2);
document.getElementById('p16c-vkmh').textContent = (v * 3.6).toFixed(1);
};
['p16c-s-r','p16c-t-r'].forEach(id => document.getElementById(id).addEventListener('input', upd16c));
upd16c();
wireDnd('p16-dnd', [
{ id:'a1', cat:'low' },{ id:'a2', cat:'low' },{ id:'a3', cat:'mid' },
{ id:'a4', cat:'mid' },{ id:'a5', cat:'high' },{ id:'a6', cat:'high' }
]);
wireQuiz('p16-tr-host', () => { if(window.addXp) window.addXp(15, 'tr-p16'); });
wireReadBtn('p16');
renderMath(body);
}
/* ========================================================== */
/* §17 — Графики пути и скорости (ГЛАВНЫЙ ВИЗУАЛ КИНЕМАТИКИ) */
/* ========================================================== */
function add_p17(){
const body = document.getElementById('p17-body');
if(!body) return;
let h = '';
h += makeCard('theory', 'График $s(t)$', '§ 17.1',
'При равномерном движении путь растёт <b>линейно</b> со временем: $s = v t$.<br>'
+ 'На графике «путь от времени» — это <b>прямая линия</b>, проходящая через начало координат '
+ '(если в момент $t = 0$ тело было в стартовой точке).<br><br>'
+ '<b>Наклон</b> прямой равен скорости $v$. Чем круче линия — тем быстрее движется тело.');
h += makeCard('rule', 'График $v(t)$', '§ 17.2',
'При равномерном движении скорость <b>постоянна</b>: $v(t) = $ const.<br>'
+ 'На графике «скорость от времени» — это <b>горизонтальная линия</b>.<br><br>'
+ '<b>Площадь под графиком</b> $v(t)$ равна <b>пройденному пути</b>: $s = v \\cdot t$ (площадь прямоугольника).');
h += makeCard('example', 'Два тела на одном графике', '§ 17.3',
'Если на $s(t)$ две прямые — та, у которой <b>больше наклон</b>, движется быстрее. '
+ 'Если линии пересекаются — в эту точку оба тела пришли одновременно. '
+ 'Параллельные прямые → одинаковые скорости.');
/* IV-1 ГЛАВНЫЙ ВИЗУАЛ: интерактивные графики */
h += wgWrap('p17-iv1', 'СИМ', 'Главный визуал: графики s(t) и v(t)', 'Меняй $v_1$ и $v_2$ — наблюдай два тела одновременно.',
'<div style="display:grid;grid-template-columns:1fr 1fr;gap:8px;margin-bottom:10px">'
+ '<label style="display:block;font-size:.86rem;color:#475569;background:#fff;padding:8px 12px;border-radius:8px;border:1px solid ' + ACCENT_SOFT + '">Тело 1: $v_1$, м/с: <b id="p17-v1" style="color:#dc2626;font-family:JetBrains Mono,monospace">3</b><input type="range" id="p17-v1-r" min="1" max="10" step="1" value="3" style="display:block;width:100%;margin-top:6px;accent-color:#dc2626"></label>'
+ '<label style="display:block;font-size:.86rem;color:#475569;background:#fff;padding:8px 12px;border-radius:8px;border:1px solid ' + ACCENT_SOFT + '">Тело 2: $v_2$, м/с: <b id="p17-v2" style="color:#0284c7;font-family:JetBrains Mono,monospace">6</b><input type="range" id="p17-v2-r" min="1" max="10" step="1" value="6" style="display:block;width:100%;margin-top:6px;accent-color:#0284c7"></label>'
+ '</div>'
+ '<div style="display:grid;grid-template-columns:1fr 1fr;gap:12px">'
+ '<div><div style="text-align:center;font-weight:700;font-size:.88rem;color:#0c4a6e;margin-bottom:4px">График $s(t)$</div><svg id="p17-svg-s" viewBox="0 0 220 170" width="100%" style="background:#fef2f2;border-radius:9px;border:1px solid ' + ACCENT_SOFT + '"></svg></div>'
+ '<div><div style="text-align:center;font-weight:700;font-size:.88rem;color:#0c4a6e;margin-bottom:4px">График $v(t)$</div><svg id="p17-svg-v" viewBox="0 0 220 170" width="100%" style="background:#fef2f2;border-radius:9px;border:1px solid ' + ACCENT_SOFT + '"></svg></div>'
+ '</div>'
+ '<div id="p17-info" style="background:' + ACCENT_SOFT + ';border-radius:9px;padding:10px 14px;margin-top:10px;font-size:.92rem;line-height:1.6"></div>');
/* IV-2 КВИЗ */
h += wgWrap('p17-iv2', 'КВИЗ', 'Чтение графиков', '',
'<div id="p17-q-host">'
+ quizQuestion('p17-q', 0, 'На графике $s(t)$ прямая поднимается круче — что это значит?', ['Тело тяжелее','Тело движется быстрее','Тело покоится','Тело останавливается'], 1, 'Чем круче наклон, тем больше $v = s/t$.')
+ quizQuestion('p17-q', 1, 'На графике $v(t)$ — горизонтальная линия. Что движется?', ['Тело ускоряется','Тело движется равномерно','Тело покоится','Невозможно сказать'], 1)
+ quizQuestion('p17-q', 2, 'Что равно площадь под графиком $v(t)$ (прямоугольник)?', ['Скорости','Времени','Пути','Массе'], 2, '$s = v \\cdot t$ — это произведение, оно же площадь прямоугольника.')
+ quizQuestion('p17-q', 3, 'Две параллельные прямые на $s(t)$ — что это значит?', ['Тела имеют одинаковую скорость','Тела покоятся','Тела движутся в разных направлениях','Тела разной массы'], 0)
+ '</div>');
/* IV-3 DnD */
h += wgWrap('p17-iv3', 'DnD', 'Опиши график', '',
dndPool('p17-dnd', [
{ id:'a1', cat:'st_rest', html:'$s(t)$ — горизонтальная' },
{ id:'a2', cat:'st_move', html:'$s(t)$ — наклонная вверх' },
{ id:'a3', cat:'v_rest', html:'$v(t)$ — лежит на оси $t$' },
{ id:'a4', cat:'v_move', html:'$v(t)$ — горизонтальная не на 0' },
{ id:'a5', cat:'st_move', html:'$s = 3t$' },
{ id:'a6', cat:'v_move', html:'$v = 5$ м/с (const)' }
], [
{ cat:'st_rest', label:'Тело покоится' },
{ cat:'st_move', label:'Равномерное движение' },
{ cat:'v_rest', label:'$v = 0$ (покой)' },
{ cat:'v_move', label:'$v \\ne 0$ (движется)' }
]));
/* IV-4 ТРН */
h += wgWrap('p17-iv4', 'ТРН', 'Тренажёр §17', '',
'<div id="p17-tr-host">'
+ quizQuestion('p17-tr', 0, 'Тело прошло $40$ м за $8$ с равномерно. Скорость $v$?', ['3 м/с','4 м/с','5 м/с','8 м/с'], 2)
+ quizQuestion('p17-tr', 1, 'На графике $v(t)$ — линия $v = 4$ м/с. Какой путь за $6$ с?', ['10 м','18 м','24 м','30 м'], 2, '$s = vt = 4 \\cdot 6 = 24$ м (площадь прямоугольника).')
+ quizQuestion('p17-tr', 2, 'Два тела. На $s(t)$ первое — прямая $s = 2t$, второе — $s = 5t$. Кто быстрее?', ['Первое','Второе','Одинаково','Нельзя определить'], 1, '$v_2 = 5$ м/с $> v_1 = 2$ м/с.')
+ quizQuestion('p17-tr', 3, 'Тело покоится. Как выглядит $s(t)$?', ['Прямая через 0','Горизонтальная линия','Линия под углом 45°','Парабола'], 1)
+ '</div>');
h += readButton('p17');
body.innerHTML = h;
// §17 IV-1 graphs
function draw17(){
const v1 = +document.getElementById('p17-v1-r').value;
const v2 = +document.getElementById('p17-v2-r').value;
document.getElementById('p17-v1').textContent = v1;
document.getElementById('p17-v2').textContent = v2;
const W = 220, H = 170, pad = 30;
const tMax = 10, vMax = 12, sMax = 100;
// s(t)
function toXs(t){ return pad + (W - 2*pad) * t / tMax; }
function toYs(s){ return H - pad - (H - 2*pad) * s / sMax; }
let ss = '';
ss += '<line x1="' + pad + '" y1="' + (H - pad) + '" x2="' + (W - pad + 4) + '" y2="' + (H - pad) + '" stroke="#0f172a" stroke-width="1.5"/>';
ss += '<line x1="' + pad + '" y1="' + (pad - 4) + '" x2="' + pad + '" y2="' + (H - pad) + '" stroke="#0f172a" stroke-width="1.5"/>';
ss += '<text x="' + (W - pad + 6) + '" y="' + (H - pad + 4) + '" font-family="Inter,sans-serif" font-size="10" fill="#0f172a">t, с</text>';
ss += '<text x="' + (pad - 4) + '" y="' + (pad - 6) + '" text-anchor="end" font-family="Inter,sans-serif" font-size="10" fill="#0f172a">s, м</text>';
// ticks
for(let t = 0; t <= tMax; t += 2){
const x = toXs(t);
ss += '<line x1="' + x + '" y1="' + (H - pad) + '" x2="' + x + '" y2="' + (H - pad + 3) + '" stroke="#0f172a" stroke-width="1"/>';
if(t > 0) ss += '<text x="' + x + '" y="' + (H - pad + 13) + '" text-anchor="middle" font-family="Inter,sans-serif" font-size="8" fill="#0f172a">' + t + '</text>';
}
for(let s = 0; s <= sMax; s += 20){
const y = toYs(s);
ss += '<line x1="' + pad + '" y1="' + y + '" x2="' + (pad - 3) + '" y2="' + y + '" stroke="#0f172a" stroke-width="1"/>';
if(s > 0) ss += '<text x="' + (pad - 5) + '" y="' + (y + 3) + '" text-anchor="end" font-family="Inter,sans-serif" font-size="8" fill="#0f172a">' + s + '</text>';
}
// Lines
ss += '<line x1="' + toXs(0) + '" y1="' + toYs(0) + '" x2="' + toXs(tMax) + '" y2="' + toYs(Math.min(sMax, v1 * tMax)) + '" stroke="#dc2626" stroke-width="2.5"/>';
ss += '<line x1="' + toXs(0) + '" y1="' + toYs(0) + '" x2="' + toXs(tMax) + '" y2="' + toYs(Math.min(sMax, v2 * tMax)) + '" stroke="#0284c7" stroke-width="2.5"/>';
ss += '<text x="' + (W - 60) + '" y="20" font-family="Inter,sans-serif" font-size="10" font-weight="700" fill="#dc2626">тело 1: v=' + v1 + '</text>';
ss += '<text x="' + (W - 60) + '" y="34" font-family="Inter,sans-serif" font-size="10" font-weight="700" fill="#0284c7">тело 2: v=' + v2 + '</text>';
document.getElementById('p17-svg-s').innerHTML = ss;
// v(t)
function toYv(v){ return H - pad - (H - 2*pad) * v / vMax; }
let sv = '';
sv += '<line x1="' + pad + '" y1="' + (H - pad) + '" x2="' + (W - pad + 4) + '" y2="' + (H - pad) + '" stroke="#0f172a" stroke-width="1.5"/>';
sv += '<line x1="' + pad + '" y1="' + (pad - 4) + '" x2="' + pad + '" y2="' + (H - pad) + '" stroke="#0f172a" stroke-width="1.5"/>';
sv += '<text x="' + (W - pad + 6) + '" y="' + (H - pad + 4) + '" font-family="Inter,sans-serif" font-size="10" fill="#0f172a">t, с</text>';
sv += '<text x="' + (pad - 4) + '" y="' + (pad - 6) + '" text-anchor="end" font-family="Inter,sans-serif" font-size="10" fill="#0f172a">v, м/с</text>';
for(let t = 0; t <= tMax; t += 2){
const x = toXs(t);
sv += '<line x1="' + x + '" y1="' + (H - pad) + '" x2="' + x + '" y2="' + (H - pad + 3) + '" stroke="#0f172a" stroke-width="1"/>';
if(t > 0) sv += '<text x="' + x + '" y="' + (H - pad + 13) + '" text-anchor="middle" font-family="Inter,sans-serif" font-size="8" fill="#0f172a">' + t + '</text>';
}
for(let v = 0; v <= vMax; v += 2){
const y = toYv(v);
sv += '<line x1="' + pad + '" y1="' + y + '" x2="' + (pad - 3) + '" y2="' + y + '" stroke="#0f172a" stroke-width="1"/>';
if(v > 0) sv += '<text x="' + (pad - 5) + '" y="' + (y + 3) + '" text-anchor="end" font-family="Inter,sans-serif" font-size="8" fill="#0f172a">' + v + '</text>';
}
// Площадь под v1 (заливка)
sv += '<rect x="' + toXs(0) + '" y="' + toYv(v1) + '" width="' + (toXs(tMax) - toXs(0)) + '" height="' + (toYv(0) - toYv(v1)) + '" fill="#dc2626" opacity="0.18"/>';
sv += '<line x1="' + toXs(0) + '" y1="' + toYv(v1) + '" x2="' + toXs(tMax) + '" y2="' + toYv(v1) + '" stroke="#dc2626" stroke-width="2.5"/>';
sv += '<line x1="' + toXs(0) + '" y1="' + toYv(v2) + '" x2="' + toXs(tMax) + '" y2="' + toYv(v2) + '" stroke="#0284c7" stroke-width="2.5"/>';
sv += '<text x="' + (toXs(5)) + '" y="' + (toYv(v1/2)) + '" text-anchor="middle" font-family="Inter,sans-serif" font-size="9" font-weight="700" fill="#dc2626">s₁ = v₁·t</text>';
document.getElementById('p17-svg-v').innerHTML = sv;
// Info
document.getElementById('p17-info').innerHTML = 'За $t = 10$ с тело 1 пройдёт $s_1 = v_1 t = ' + (v1 * 10) + '$ м, тело 2 — $s_2 = v_2 t = ' + (v2 * 10) + '$ м. ' + (v1 === v2 ? 'Скорости равны — графики $s(t)$ параллельны.' : 'Скорости разные → разный наклон.');
renderMath(document.getElementById('p17-info'));
}
['p17-v1-r','p17-v2-r'].forEach(id => document.getElementById(id).addEventListener('input', draw17));
draw17();
wireDnd('p17-dnd', [
{ id:'a1', cat:'st_rest' },{ id:'a2', cat:'st_move' },{ id:'a3', cat:'v_rest' },
{ id:'a4', cat:'v_move' },{ id:'a5', cat:'st_move' },{ id:'a6', cat:'v_move' }
]);
wireQuiz('p17-q-host', () => { if(window.addXp) window.addXp(10, 'q-p17'); });
wireQuiz('p17-tr-host', () => { if(window.addXp) window.addXp(15, 'tr-p17'); });
wireReadBtn('p17');
renderMath(body);
}
/* ========================================================== */
/* §18 — Неравномерное движение. Средняя скорость */
/* ========================================================== */
function add_p18(){
const body = document.getElementById('p18-body');
if(!body) return;
let h = '';
h += makeCard('theory', 'Неравномерное движение', '§ 18.1',
'В реальности тела почти никогда не движутся <b>идеально равномерно</b>: машина в городе разгоняется, тормозит у светофоров, '
+ 'снова разгоняется. Скорость <b>меняется</b> со временем — это <b>неравномерное (переменное) движение</b>.<br><br>'
+ 'Чтобы охарактеризовать его «в среднем», используют <b>среднюю скорость</b>.');
h += makeCard('rule', 'Формула средней скорости', '§ 18.2',
'$$\\langle v\\rangle = \\dfrac{s_{полн}}{t_{полн}}$$<br>'
+ '«Средняя скорость = весь путь, делённый на всё время».<br><br>'
+ '<b>Внимание!</b> Часто путают: $\\langle v\\rangle \\ne (v_1 + v_2)/2$ — среднеарифметическое <b>не работает</b>, '
+ 'если на участках разное <b>время</b>. Среднее по пути считается через массы (произведение $v \\cdot t$ на каждом участке).');
h += makeCard('example', 'Пешеход и метро', '§ 18.3',
'Девочка прошла пешком $0{,}5$ км за $10$ мин, потом проехала $5$ км на метро за $10$ мин.<br>'
+ '$s_{полн} = 0{,}5 + 5 = 5{,}5$ км, $t_{полн} = 20$ мин $= 1/3$ ч.<br>'
+ '$\\langle v\\rangle = 5{,}5 / (1/3) = 16{,}5$ км/ч.<br><br>'
+ 'Хотя метро ехало $30$ км/ч, а пешком — $3$ км/ч, средняя — <b>не</b> $(30+3)/2 = 16{,}5$. Совпало здесь только потому, что время оказалось равным!');
/* IV-1 КАЛЬК */
h += wgWrap('p18-iv1', 'КАЛЬК', 'Средняя на двух участках', 'Меняй $v_1, t_1, v_2, t_2$ — сравнивай среднюю и среднеарифметическое.',
'<div style="display:grid;grid-template-columns:repeat(auto-fit,minmax(150px,1fr));gap:8px;margin-bottom:10px">'
+ '<label style="display:block;font-size:.86rem;color:#475569;background:#fff;padding:8px 12px;border-radius:8px;border:1px solid ' + ACCENT_SOFT + '">$v_1$, м/с: <b id="p18-v1" style="color:' + ACCENT_D + ';font-family:JetBrains Mono,monospace">20</b><input type="range" id="p18-v1-r" min="1" max="50" step="1" value="20" style="display:block;width:100%;margin-top:6px;accent-color:' + ACCENT + '"></label>'
+ '<label style="display:block;font-size:.86rem;color:#475569;background:#fff;padding:8px 12px;border-radius:8px;border:1px solid ' + ACCENT_SOFT + '">$t_1$, с: <b id="p18-t1" style="color:' + ACCENT_D + ';font-family:JetBrains Mono,monospace">60</b><input type="range" id="p18-t1-r" min="5" max="300" step="5" value="60" style="display:block;width:100%;margin-top:6px;accent-color:' + ACCENT + '"></label>'
+ '<label style="display:block;font-size:.86rem;color:#475569;background:#fff;padding:8px 12px;border-radius:8px;border:1px solid ' + ACCENT_SOFT + '">$v_2$, м/с: <b id="p18-v2" style="color:' + ACCENT_D + ';font-family:JetBrains Mono,monospace">10</b><input type="range" id="p18-v2-r" min="1" max="50" step="1" value="10" style="display:block;width:100%;margin-top:6px;accent-color:' + ACCENT + '"></label>'
+ '<label style="display:block;font-size:.86rem;color:#475569;background:#fff;padding:8px 12px;border-radius:8px;border:1px solid ' + ACCENT_SOFT + '">$t_2$, с: <b id="p18-t2" style="color:' + ACCENT_D + ';font-family:JetBrains Mono,monospace">120</b><input type="range" id="p18-t2-r" min="5" max="300" step="5" value="120" style="display:block;width:100%;margin-top:6px;accent-color:' + ACCENT + '"></label>'
+ '</div>'
+ '<div style="background:' + ACCENT_SOFT + ';border-radius:9px;padding:12px 14px;display:flex;flex-direction:column;gap:6px;font-size:.94rem">'
+ '<div>$\\langle v\\rangle = (v_1 t_1 + v_2 t_2)/(t_1+t_2) = $ <b id="p18-vavg" style="color:' + ACCENT_D + ';font-family:JetBrains Mono,monospace">13.33</b> м/с</div>'
+ '<div style="font-size:.86rem;color:#475569">Ловушка: $(v_1+v_2)/2 = $ <b id="p18-trap" style="color:#dc2626;font-family:JetBrains Mono,monospace">15.00</b> м/с — <span id="p18-trap-lbl" style="font-weight:700;color:#dc2626">НЕВЕРНО</span></div>'
+ '</div>');
/* IV-2 КВИЗ */
h += wgWrap('p18-iv2', 'КВИЗ', 'Среднеарифметическое и средняя', '',
'<div id="p18-q-host">'
+ quizQuestion('p18-q', 0, 'Когда $\\langle v\\rangle$ равна среднему арифметическому?', ['Всегда','Когда время на участках одинаковое','Когда путь одинаковый','Никогда'], 1, 'Среднее арифметическое — это $\\langle v\\rangle$ только если $t_1 = t_2$.')
+ quizQuestion('p18-q', 1, 'Половину пути тело шло $5$ м/с, вторую половину — $20$ м/с. $\\langle v\\rangle$ ближе к…', ['5 м/с','12,5 м/с','20 м/с','8 м/с'], 3, 'Если одинаковые пути, то $\\langle v\\rangle = 2 v_1 v_2 / (v_1 + v_2) = 8$ м/с (ближе к меньшей).')
+ quizQuestion('p18-q', 2, 'Если тело часть времени стояло (т.е. $v = 0$), $\\langle v\\rangle$ всего пути…', ['Стала больше','Уменьшилась','Не изменилась','Стала равна нулю'], 1)
+ '</div>');
/* IV-3 DnD */
h += wgWrap('p18-iv3', 'DnD', 'Какое движение?', '',
dndPool('p18-dnd', [
{ id:'a1', cat:'eq', html:'Лифт между этажами на постоянной скорости' },
{ id:'a2', cat:'eq', html:'Шарик в воде, опускающийся равномерно' },
{ id:'a3', cat:'neq', html:'Машина в городе' },
{ id:'a4', cat:'neq', html:'Падающий камень (в воздухе)' },
{ id:'a5', cat:'neq', html:'Поезд от станции до станции' },
{ id:'a6', cat:'eq', html:'Звук в воздухе на короткой дистанции' }
], [
{ cat:'eq', label:'Равномерное' },
{ cat:'neq', label:'Неравномерное' }
]));
/* IV-4 ТРН */
h += wgWrap('p18-iv4', 'ТРН', 'Тренажёр §18', '',
'<div id="p18-tr-host">'
+ quizQuestion('p18-tr', 0, 'Тело прошло $60$ м за $4$ с и ещё $80$ м за $6$ с. $\\langle v\\rangle$?', ['10 м/с','12 м/с','14 м/с','15 м/с'], 2, '$\\langle v\\rangle = (60+80)/(4+6) = 140/10 = 14$ м/с.')
+ quizQuestion('p18-tr', 1, 'Машина $1$ ч ехала $60$ км/ч, потом $2$ ч — $90$ км/ч. $\\langle v\\rangle$ в км/ч?', ['70','75','80','85'], 2, '$(60 \\cdot 1 + 90 \\cdot 2)/(1+2) = 240/3 = 80$ км/ч.')
+ quizQuestion('p18-tr', 2, 'Поезд проехал $300$ км за $5$ ч, при этом $1$ ч стоял на станции. Средняя скорость движения?', ['50 км/ч','60 км/ч','75 км/ч','100 км/ч'], 2, '«Средняя скорость движения» — без учёта стоянки: $300 / (5-1) = 75$ км/ч.')
+ quizQuestion('p18-tr', 3, 'Велосипедист первые $5$ км ехал $20$ мин, ещё $5$ км — $40$ мин. $\\langle v\\rangle$?', ['7,5 км/ч','10 км/ч','12 км/ч','15 км/ч'], 1, '$s = 10$ км, $t = 1$ ч. $\\langle v\\rangle = 10$ км/ч.')
+ '</div>');
h += readButton('p18');
body.innerHTML = h;
// §18 IV-1
const upd18 = () => {
const v1 = +document.getElementById('p18-v1-r').value;
const t1 = +document.getElementById('p18-t1-r').value;
const v2 = +document.getElementById('p18-v2-r').value;
const t2 = +document.getElementById('p18-t2-r').value;
document.getElementById('p18-v1').textContent = v1;
document.getElementById('p18-t1').textContent = t1;
document.getElementById('p18-v2').textContent = v2;
document.getElementById('p18-t2').textContent = t2;
const vavg = (v1*t1 + v2*t2)/(t1+t2);
const arith = (v1+v2)/2;
document.getElementById('p18-vavg').textContent = vavg.toFixed(2);
document.getElementById('p18-trap').textContent = arith.toFixed(2);
const same = Math.abs(vavg - arith) < 0.01;
document.getElementById('p18-trap-lbl').textContent = same ? 'СОВПАЛО (t₁ = t₂)' : 'НЕВЕРНО';
document.getElementById('p18-trap-lbl').style.color = same ? '#10b981' : '#dc2626';
};
['p18-v1-r','p18-t1-r','p18-v2-r','p18-t2-r'].forEach(id => document.getElementById(id).addEventListener('input', upd18));
upd18();
wireDnd('p18-dnd', [
{ id:'a1', cat:'eq' },{ id:'a2', cat:'eq' },{ id:'a3', cat:'neq' },
{ id:'a4', cat:'neq' },{ id:'a5', cat:'neq' },{ id:'a6', cat:'eq' }
]);
wireQuiz('p18-q-host', () => { if(window.addXp) window.addXp(10, 'q-p18'); });
wireQuiz('p18-tr-host', () => { if(window.addXp) window.addXp(15, 'tr-p18'); });
wireReadBtn('p18');
renderMath(body);
}
/* ========================================================== */
/* §19 — Инерция */
/* ========================================================== */
function add_p19(){
const body = document.getElementById('p19-body');
if(!body) return;
let h = '';
h += makeCard('theory', 'Закон инерции Галилея', '§ 19.1',
'Тело сохраняет состояние <b>покоя</b> или <b>равномерного прямолинейного движения</b>, '
+ 'пока на него <b>не действуют другие тела</b> (или действия уравновешены).<br><br>'
+ 'Это явление называется <b>инерцией</b>. Оно открыто Галилеем в начале XVII века.');
h += makeCard('rule', 'Что меняет скорость', '§ 19.2',
'Чтобы изменить скорость тела (величину или направление), нужно <b>воздействие другого тела</b>:'
+ '<ul style="padding-left:20px;margin:5px 0">'
+ '<li>Толкнули — мяч полетел.</li>'
+ '<li>Поймали — мяч остановился.</li>'
+ '<li>Если на тело ничего не действует, оно <b>само собой</b> двигаться/останавливаться <b>не может</b>.</li>'
+ '</ul>'
+ 'Тело сопротивляется изменению скорости. <b>Мера этой инертности — масса</b>: тяжёлое тело сложнее разогнать и сложнее остановить.');
h += makeCard('example', 'Пассажиры в автобусе', '§ 19.3',
'<b>Автобус резко тормозит.</b> Пассажиры (которые ещё не получили воздействие от спинки сиденья) '
+ 'по инерции продолжают двигаться вперёд — поэтому их «бросает» вперёд.<br>'
+ '<b>Автобус резко трогается.</b> Пассажиры по инерции пока остаются на месте, а автобус «уезжает из-под них» — их откидывает назад.<br>'
+ 'Поэтому в транспорте важно держаться или пристёгиваться.');
/* IV-1 СИМ: шарик на гладком столе с переключателем «удар»/«трение» */
h += wgWrap('p19-iv1', 'СИМ', 'Шарик: с трением и без', 'Запусти шарик и сравни: с трением он остановится, без трения — будет двигаться вечно.',
'<div style="display:flex;gap:6px;margin-bottom:10px;flex-wrap:wrap">'
+ '<button id="p19-launch" type="button" style="background:#dc2626;color:#fff;border:none;padding:8px 16px;border-radius:9px;cursor:pointer;font-weight:700;font-family:inherit">Запустить</button>'
+ '<button id="p19-toggle" type="button" style="background:#fff;color:#dc2626;border:1.5px solid #dc2626;padding:8px 16px;border-radius:9px;cursor:pointer;font-weight:700;font-family:inherit">Трение: ВКЛ</button>'
+ '</div>'
+ '<svg id="p19-svg" viewBox="0 0 380 130" width="100%" style="max-width:600px;display:block;margin:0 auto;background:#fef2f2;border-radius:9px;border:1px solid ' + ACCENT_SOFT + '"></svg>'
+ '<div id="p19-info" style="background:' + ACCENT_SOFT + ';border-radius:9px;padding:10px 14px;margin-top:8px;font-size:.92rem;text-align:center"></div>');
/* IV-2 КВИЗ */
h += wgWrap('p19-iv2', 'КВИЗ', 'Инерция в жизни', '',
'<div id="p19-q-host">'
+ quizQuestion('p19-q', 0, 'Автобус резко тормозит. Куда «летят» пассажиры?', ['Вверх','Вниз','Вперёд','Назад'], 2, 'По инерции тело сохраняет движение вперёд.')
+ quizQuestion('p19-q', 1, 'Без действия других тел тело будет…', ['Останавливаться','Сохранять скорость или покой','Самопроизвольно ускоряться','Разваливаться'], 1)
+ quizQuestion('p19-q', 2, 'Что является мерой инертности тела?', ['Размер','Цвет','Масса','Объём'], 2)
+ quizQuestion('p19-q', 3, 'Кто сформулировал закон инерции?', ['Архимед','Галилей','Ньютон','Эйнштейн'], 1)
+ '</div>');
/* IV-3 DnD */
h += wgWrap('p19-iv3', 'DnD', 'Кого тяжелее остановить?', '',
dndPool('p19-dnd', [
{ id:'a1', cat:'easy', html:'Пушинка' },
{ id:'a2', cat:'easy', html:'Теннисный мячик' },
{ id:'a3', cat:'mid', html:'Велосипедист' },
{ id:'a4', cat:'mid', html:'Школьник на роликах' },
{ id:'a5', cat:'hard', html:'Грузовик с песком' },
{ id:'a6', cat:'hard', html:'Корабль' }
], [
{ cat:'easy', label:'Легко (малая масса)' },
{ cat:'mid', label:'Средне' },
{ cat:'hard', label:'Тяжело (большая масса)' }
]));
/* IV-4 ТРН */
h += wgWrap('p19-iv4', 'ТРН', 'Тренажёр §19', '',
'<div id="p19-tr-host">'
+ quizQuestion('p19-tr', 0, 'На тело не действуют другие тела. Что произойдёт?', ['Оно остановится','Оно ускорится','Оно сохранит скорость и направление','Невозможно сказать'], 2)
+ quizQuestion('p19-tr', 1, 'Почему машина после выключения мотора всё-таки тормозит?', ['Из-за инерции','Из-за силы трения о дорогу и сопротивления воздуха','Сама по себе','Из-за гравитации'], 1)
+ quizQuestion('p19-tr', 2, 'У какого тела инертность больше: у $1$ кг или $10$ кг?', ['1 кг','10 кг','Одинакова','Зависит от формы'], 1)
+ quizQuestion('p19-tr', 3, 'Зачем нужны ремни безопасности в машине?', ['Чтобы держать форму сиденья','Чтобы при резком торможении не «улететь» по инерции вперёд','Для красоты','Чтобы не сидеть прямо'], 1)
+ '</div>');
h += readButton('p19');
body.innerHTML = h;
// §19 IV-1 sim
let p19 = { x: 30, v: 0, friction: true, raf: 0 };
function draw19(){
const svg = document.getElementById('p19-svg');
if(!svg){ cancelAnimationFrame(p19.raf); return; }
if(p19.friction && p19.v > 0){
p19.v -= 0.04;
if(p19.v < 0) p19.v = 0;
}
p19.x += p19.v;
if(p19.x > 350){ p19.x = 350; p19.v = 0; }
let s = '';
s += '<rect x="0" y="0" width="380" height="100" fill="#dbeafe"/>';
s += '<rect x="0" y="100" width="380" height="30" fill="#94a3b8"/>';
if(!p19.friction){
s += '<text x="190" y="20" text-anchor="middle" font-family="Inter,sans-serif" font-size="10" font-weight="700" fill="#10b981">идеально гладкая поверхность</text>';
} else {
// Зубчатая текстура трения
for(let i = 0; i < 18; i++) s += '<line x1="' + (i*22+5) + '" y1="100" x2="' + (i*22+15) + '" y2="105" stroke="#374151" stroke-width="1"/>';
}
s += '<circle cx="' + p19.x + '" cy="92" r="10" fill="#dc2626" stroke="#7f1d1d" stroke-width="1.5"/>';
svg.innerHTML = s;
document.getElementById('p19-info').innerHTML = 'Скорость шарика: <b>' + p19.v.toFixed(2) + '</b> у. е. ' + (p19.friction ? '(с трением — тормозит)' : '(без трения — будет двигаться вечно)');
if(p19.v > 0 || !p19.friction) p19.raf = requestAnimationFrame(draw19);
}
document.getElementById('p19-launch').addEventListener('click', () => { p19.x = 30; p19.v = 3.5; if(p19.raf) cancelAnimationFrame(p19.raf); draw19(); });
document.getElementById('p19-toggle').addEventListener('click', () => {
p19.friction = !p19.friction;
const btn = document.getElementById('p19-toggle');
btn.textContent = 'Трение: ' + (p19.friction ? 'ВКЛ' : 'ВЫКЛ');
btn.style.background = p19.friction ? '#fff' : '#10b981';
btn.style.color = p19.friction ? '#dc2626' : '#fff';
btn.style.borderColor = p19.friction ? '#dc2626' : '#10b981';
if(!p19.friction && p19.v === 0){ p19.v = 1.5; if(p19.raf) cancelAnimationFrame(p19.raf); draw19(); }
});
draw19();
wireDnd('p19-dnd', [
{ id:'a1', cat:'easy' },{ id:'a2', cat:'easy' },{ id:'a3', cat:'mid' },
{ id:'a4', cat:'mid' },{ id:'a5', cat:'hard' },{ id:'a6', cat:'hard' }
]);
wireQuiz('p19-q-host', () => { if(window.addXp) window.addXp(10, 'q-p19'); });
wireQuiz('p19-tr-host', () => { if(window.addXp) window.addXp(15, 'tr-p19'); });
wireReadBtn('p19');
renderMath(body);
}
/* ========================================================== */
/* §20 — Масса тела. Плотность вещества */
/* ========================================================== */
function add_p20(){
const body = document.getElementById('p20-body');
if(!body) return;
let h = '';
h += makeCard('theory', 'Что такое масса', '§ 20.1',
'Масса $m$ — физическая величина, характеризующая <b>количество вещества</b> в теле '
+ 'и <b>инертность</b> (то, как трудно изменить скорость тела).<br><br>'
+ '$[m] = $ кг (килограмм) — основная единица СИ. Кратные: $1$ т = $1000$ кг; '
+ 'дольные: $1$ г = $0{,}001$ кг.');
h += makeCard('rule', 'Плотность вещества', '§ 20.2',
'<b>Плотность</b> $\\rho$ показывает, какая масса вещества содержится в единице объёма:<br>'
+ '$$\\rho = \\dfrac{m}{V}$$<br>'
+ '$[\\rho] = $ кг/м³. Также используют г/см³: $1$ г/см³ $= 1000$ кг/м³.<br><br>'
+ 'Из формулы $\\rho = m/V$ можно выразить и другие: $m = \\rho V$, $V = m/\\rho$.');
h += makeCard('example', 'Таблица плотностей', '§ 20.3',
'<table style="width:100%;border-collapse:collapse;margin-top:4px;font-size:.92rem">'
+ '<tr style="background:' + ACCENT_SOFT + '"><th style="padding:6px 10px;text-align:left;border-bottom:2px solid ' + ACCENT + '">Вещество</th><th style="padding:6px 10px;text-align:right;border-bottom:2px solid ' + ACCENT + '">кг/м³</th><th style="padding:6px 10px;text-align:right;border-bottom:2px solid ' + ACCENT + '">г/см³</th></tr>'
+ [['Воздух',1.29,0.00129],['Пенопласт',40,0.04],['Сосна',520,0.52],['Лёд',900,0.9],['Вода',1000,1.0],['Алюминий',2700,2.7],['Железо',7800,7.8],['Медь',8900,8.9],['Свинец',11300,11.3],['Ртуть',13600,13.6],['Золото',19300,19.3]].map(r =>
'<tr><td style="padding:5px 10px;border-bottom:1px solid ' + ACCENT_SOFT + '">' + r[0] + '</td><td style="padding:5px 10px;text-align:right;border-bottom:1px solid ' + ACCENT_SOFT + ';font-family:JetBrains Mono,monospace">' + r[1].toLocaleString('ru-RU') + '</td><td style="padding:5px 10px;text-align:right;border-bottom:1px solid ' + ACCENT_SOFT + ';font-family:JetBrains Mono,monospace">' + r[2] + '</td></tr>').join('')
+ '</table>');
/* IV-1 КАЛЬК: rho = m/V */
h += wgWrap('p20-iv1', 'КАЛЬК', 'Калькулятор $\\rho = m/V$', 'Меняй массу и объём — увидь плотность и узнай вещество.',
'<div style="display:grid;grid-template-columns:1fr 1fr;gap:8px;margin-bottom:10px">'
+ '<label style="display:block;font-size:.86rem;color:#475569;background:#fff;padding:8px 12px;border-radius:8px;border:1px solid ' + ACCENT_SOFT + '">$m$, г: <b id="p20-m" style="color:' + ACCENT_D + ';font-family:JetBrains Mono,monospace">270</b><input type="range" id="p20-m-r" min="1" max="20000" step="1" value="270" style="display:block;width:100%;margin-top:6px;accent-color:' + ACCENT + '"></label>'
+ '<label style="display:block;font-size:.86rem;color:#475569;background:#fff;padding:8px 12px;border-radius:8px;border:1px solid ' + ACCENT_SOFT + '">$V$, см³: <b id="p20-V" style="color:' + ACCENT_D + ';font-family:JetBrains Mono,monospace">100</b><input type="range" id="p20-V-r" min="1" max="2000" step="1" value="100" style="display:block;width:100%;margin-top:6px;accent-color:' + ACCENT + '"></label>'
+ '</div>'
+ '<div style="background:' + ACCENT_SOFT + ';border-radius:9px;padding:12px 14px;font-size:.94rem;line-height:1.7">'
+ '$\\rho = m/V = $ <b id="p20-rho" style="color:' + ACCENT_D + ';font-family:JetBrains Mono,monospace">2.70</b> г/см³ $= $ <b id="p20-rho-si" style="color:' + ACCENT_D + ';font-family:JetBrains Mono,monospace">2700</b> кг/м³'
+ '<div id="p20-mat" style="font-size:.86rem;color:#475569;margin-top:4px"></div>'
+ '</div>');
/* IV-2 СИМ: куб 1 дм³ из разных материалов */
h += wgWrap('p20-iv2', 'СИМ', 'Куб 1 дм³: какой массы?', 'Выбери вещество — увидь массу для куба объёмом $1$ дм³ $= 1$ литр.',
'<div style="display:flex;flex-wrap:wrap;gap:6px;margin-bottom:10px">'
+ [['Пенопласт',40,'#fde68a'],['Сосна',520,'#92400e'],['Вода',1000,'#0284c7'],['Алюминий',2700,'#94a3b8'],['Железо',7800,'#374151'],['Свинец',11300,'#1f2937'],['Ртуть',13600,'#475569'],['Золото',19300,'#fbbf24']].map(([nm, rho, col]) =>
'<button class="p20-mat" data-rho="' + rho + '" data-nm="' + nm + '" data-col="' + col + '" type="button" style="background:#fff;border:1.5px solid ' + ACCENT_SOFT + ';border-radius:8px;padding:6px 12px;cursor:pointer;font-family:inherit;font-size:.85rem">' + nm + '</button>').join('')
+ '</div>'
+ '<div style="display:grid;grid-template-columns:120px 1fr;gap:14px;align-items:center">'
+ '<svg id="p20-cube" viewBox="0 0 120 120" width="120"><polygon points="20,40 60,20 100,40 100,90 60,110 20,90" fill="#0284c7" stroke="#0c4a6e" stroke-width="2"/><polyline points="20,40 60,60 100,40" fill="none" stroke="#0c4a6e" stroke-width="1.5"/><line x1="60" y1="60" x2="60" y2="110" stroke="#0c4a6e" stroke-width="1.5"/></svg>'
+ '<div style="font-size:.94rem;line-height:1.7">'
+ '<b id="p20-mat-nm" style="color:#0c4a6e">Вода</b><br>'
+ 'Объём: $V = 1$ дм³ $= 1$ л $= 1000$ см³<br>'
+ '$m = \\rho V = $ <b id="p20-mat-m" style="color:' + ACCENT_D + ';font-family:JetBrains Mono,monospace">1</b> кг'
+ '</div>'
+ '</div>');
/* IV-3 DnD */
h += wgWrap('p20-iv3', 'DnD', 'Какое вещество?', '',
dndPool('p20-dnd', [
{ id:'a1', cat:'light', html:'$\\rho = 0{,}5$ г/см³' },
{ id:'a2', cat:'light', html:'$\\rho = 0{,}9$ г/см³' },
{ id:'a3', cat:'mid', html:'$\\rho = 2{,}7$ г/см³' },
{ id:'a4', cat:'mid', html:'$\\rho = 7{,}8$ г/см³' },
{ id:'a5', cat:'heavy', html:'$\\rho = 11{,}3$ г/см³' },
{ id:'a6', cat:'heavy', html:'$\\rho = 19{,}3$ г/см³' }
], [
{ cat:'light', label:'Лёгкий: дерево / лёд' },
{ cat:'mid', label:'Средний: алюминий / железо' },
{ cat:'heavy', label:'Тяжёлый: свинец / золото' }
]));
/* IV-4 ТРН */
h += wgWrap('p20-iv4', 'ТРН', 'Тренажёр §20', '',
'<div id="p20-tr-host">'
+ quizQuestion('p20-tr', 0, 'Брусок $m = 540$ г, $V = 200$ см³. $\\rho$?', ['2,7 г/см³','3,5 г/см³','5,4 г/см³','27 г/см³'], 0, '$\\rho = 540/200 = 2{,}7$ — алюминий.')
+ quizQuestion('p20-tr', 1, 'Какой объём занимает $7{,}8$ кг железа? ($\\rho = 7800$ кг/м³)', ['10 см³','100 см³','1000 см³','10 л'], 2, '$V = m/\\rho = 7{,}8/7800 = 0{,}001$ м³ $= 1000$ см³.')
+ quizQuestion('p20-tr', 2, 'Масса куба $V = 1$ м³ воды равна:', ['100 кг','500 кг','1000 кг','1 т'], 2, '$m = \\rho V = 1000 \\cdot 1 = 1000$ кг $= 1$ т. Оба ответа верны, но «1000 кг» — самый точный.')
+ quizQuestion('p20-tr', 3, '$\\rho = 13{,}6$ г/см³. Какое это вещество?', ['Железо','Свинец','Ртуть','Золото'], 2)
+ quizQuestion('p20-tr', 4, 'Из бруска $\\rho_1 = 1000$ кг/м³ и $\\rho_2 = 2000$ кг/м³ при одинаковом объёме легче будет:', ['Первый','Второй','Одинаково','Зависит от формы'], 0, 'При $V = $ const, $m \\sim \\rho$. Меньшая плотность → меньшая масса.')
+ '</div>');
h += readButton('p20');
body.innerHTML = h;
// §20 IV-1
const matName = (rho) => {
if(rho < 0.2) return 'газ / лёгкий пористый материал';
if(rho < 0.6) return 'пенопласт / лёгкое дерево';
if(rho < 0.95) return 'дерево / лёд';
if(rho < 1.1) return 'вода';
if(rho < 3) return 'алюминий / стекло';
if(rho < 9) return 'железо / медь';
if(rho < 14) return 'свинец / ртуть';
if(rho < 22) return 'золото / платина';
return 'плотнее любого металла на Земле';
};
const upd20 = () => {
const m = +document.getElementById('p20-m-r').value;
const V = +document.getElementById('p20-V-r').value;
document.getElementById('p20-m').textContent = m;
document.getElementById('p20-V').textContent = V;
const rho = m / V;
document.getElementById('p20-rho').textContent = rho.toFixed(2);
document.getElementById('p20-rho-si').textContent = (rho * 1000).toFixed(0);
document.getElementById('p20-mat').textContent = 'Похоже на: ' + matName(rho);
};
['p20-m-r','p20-V-r'].forEach(id => document.getElementById(id).addEventListener('input', upd20));
upd20();
// §20 IV-2 cube
body.querySelectorAll('.p20-mat').forEach(btn => btn.addEventListener('click', () => {
body.querySelectorAll('.p20-mat').forEach(b => { b.style.background = '#fff'; b.style.color = '#0f172a'; });
btn.style.background = btn.dataset.col; btn.style.color = '#fff';
const rho = +btn.dataset.rho; // кг/м³
const m = rho * 0.001; // V = 1 дм³ = 0.001 м³
document.getElementById('p20-mat-nm').textContent = btn.dataset.nm;
document.getElementById('p20-mat-m').textContent = m.toFixed(2);
document.getElementById('p20-cube').querySelector('polygon').setAttribute('fill', btn.dataset.col);
}));
// Default selected — water
const waterBtn = body.querySelector('.p20-mat[data-nm="Вода"]');
if(waterBtn){ waterBtn.style.background = waterBtn.dataset.col; waterBtn.style.color = '#fff'; }
wireDnd('p20-dnd', [
{ id:'a1', cat:'light' },{ id:'a2', cat:'light' },{ id:'a3', cat:'mid' },
{ id:'a4', cat:'mid' },{ id:'a5', cat:'heavy' },{ id:'a6', cat:'heavy' }
]);
wireQuiz('p20-tr-host', () => { if(window.addXp) window.addXp(15, 'tr-p20'); });
wireReadBtn('p20');
renderMath(body);
}
/* ========================================================== */
/* §21 — Сила */
/* ========================================================== */
function add_p21(){
const body = document.getElementById('p21-body');
if(!body) return;
let h = '';
h += makeCard('theory', 'Что такое сила', '§ 21.1',
'<b>Сила</b> $\\vec F$ — физическая величина, которая характеризует <b>взаимодействие</b> двух тел. '
+ 'Сила всегда возникает в паре: одно тело действует на другое.<br><br>'
+ '$[F] = $ Н (ньютон) — в честь Исаака Ньютона.');
h += makeCard('rule', 'Что значит «изобразить силу»', '§ 21.2',
'У силы три характеристики (поэтому её рисуют <b>стрелкой</b> — вектором):'
+ '<ul style="padding-left:20px;margin:6px 0">'
+ '<li><b>Модуль</b> (длина стрелки) — численное значение в ньютонах.</li>'
+ '<li><b>Направление</b> — куда направлена стрелка.</li>'
+ '<li><b>Точка приложения</b> — где начало стрелки (где сила «приложена» к телу).</li>'
+ '</ul>');
h += makeCard('example', 'Четыре силы из жизни', '§ 21.3',
'<table style="width:100%;border-collapse:collapse;margin-top:4px;font-size:.92rem">'
+ '<tr style="background:' + ACCENT_SOFT + '"><th style="padding:6px 10px;text-align:left;border-bottom:2px solid ' + ACCENT + '">Сила</th><th style="padding:6px 10px;text-align:left;border-bottom:2px solid ' + ACCENT + '">Когда возникает</th></tr>'
+ [
['<b>$\\vec F_т$</b> — сила тяжести', 'Земля притягивает тело'],
['<b>$\\vec F_{упр}$</b> — упругости', 'Тело деформировано (сжато, растянуто)'],
['<b>$\\vec F_{тр}$</b> — трения', 'Тело движется (или пытается) по другому'],
['<b>$\\vec N$</b> — нормальная реакция', 'Тело давит на опору; опора отвечает']
].map(r => '<tr><td style="padding:5px 10px;border-bottom:1px solid ' + ACCENT_SOFT + '">' + r[0] + '</td><td style="padding:5px 10px;border-bottom:1px solid ' + ACCENT_SOFT + '">' + r[1] + '</td></tr>').join('')
+ '</table>');
/* IV-1 СИМ: визуал силы со стрелкой */
h += wgWrap('p21-iv1', 'СИМ', 'Сила-стрелка', 'Меняй модуль и направление — посмотри, как меняется стрелка.',
'<div style="display:grid;grid-template-columns:1fr 1fr;gap:8px;margin-bottom:10px">'
+ '<label style="display:block;font-size:.86rem;color:#475569;background:#fff;padding:8px 12px;border-radius:8px;border:1px solid ' + ACCENT_SOFT + '">Модуль $F$, Н: <b id="p21-F" style="color:' + ACCENT_D + ';font-family:JetBrains Mono,monospace">10</b><input type="range" id="p21-F-r" min="1" max="50" step="1" value="10" style="display:block;width:100%;margin-top:6px;accent-color:' + ACCENT + '"></label>'
+ '<label style="display:block;font-size:.86rem;color:#475569;background:#fff;padding:8px 12px;border-radius:8px;border:1px solid ' + ACCENT_SOFT + '">Угол, °: <b id="p21-ang" style="color:' + ACCENT_D + ';font-family:JetBrains Mono,monospace">0</b><input type="range" id="p21-ang-r" min="0" max="360" step="15" value="0" style="display:block;width:100%;margin-top:6px;accent-color:' + ACCENT + '"></label>'
+ '</div>'
+ '<svg id="p21-svg" viewBox="0 0 300 220" width="100%" style="max-width:400px;display:block;margin:0 auto;background:#fef2f2;border-radius:9px;border:1px solid ' + ACCENT_SOFT + '"></svg>');
/* IV-2 КВИЗ */
h += wgWrap('p21-iv2', 'КВИЗ', 'Что такое сила', '',
'<div id="p21-q-host">'
+ quizQuestion('p21-q', 0, 'В каких единицах измеряют силу?', ['Килограммы','Метры','Ньютоны','Джоули'], 2)
+ quizQuestion('p21-q', 1, 'Сила — это…', ['Скалярная величина','Векторная величина','Время','Длина'], 1)
+ quizQuestion('p21-q', 2, 'Что НЕ относится к характеристикам силы?', ['Модуль','Направление','Точка приложения','Цвет'], 3)
+ quizQuestion('p21-q', 3, 'Что произойдёт с покоящимся телом, если на него действует одна сила?', ['Ничего','Тело изменит скорость','Тело пропадёт','Не меняется'], 1, 'Если силы не уравновешены — тело меняет скорость.')
+ '</div>');
/* IV-3 DnD */
h += wgWrap('p21-iv3', 'DnD', 'Какая сила?', '',
dndPool('p21-dnd', [
{ id:'a1', cat:'grav', html:'Яблоко падает с дерева' },
{ id:'a2', cat:'grav', html:'Снежинки летят на землю' },
{ id:'a3', cat:'el', html:'Сжатая пружина возвращается' },
{ id:'a4', cat:'el', html:'Растянутая резинка' },
{ id:'a5', cat:'fr', html:'Машина тормозит' },
{ id:'a6', cat:'fr', html:'Книга по столу не скользит' },
{ id:'a7', cat:'norm', html:'Стол держит лежащую книгу' },
{ id:'a8', cat:'norm', html:'Пол не пускает человека провалиться' }
], [
{ cat:'grav', label:'$\\vec F_т$ (тяжести)' },
{ cat:'el', label:'$\\vec F_{упр}$ (упругости)' },
{ cat:'fr', label:'$\\vec F_{тр}$ (трения)' },
{ cat:'norm', label:'$\\vec N$ (нормальная реакция)' }
]));
/* IV-4 ТРН */
h += wgWrap('p21-iv4', 'ТРН', 'Тренажёр §21', '',
'<div id="p21-tr-host">'
+ quizQuestion('p21-tr', 0, 'Силу обозначают…', ['$s$','$v$','$F$','$t$'], 2)
+ quizQuestion('p21-tr', 1, 'Сила — мера…', ['Длины','Времени','Взаимодействия тел','Объёма'], 2)
+ quizQuestion('p21-tr', 2, 'В каких единицах СИ выражают силу?', ['Кг','Н (ньютоны)','Дж','м/с²'], 1)
+ quizQuestion('p21-tr', 3, 'Что нужно для того, чтобы изобразить силу?', ['Только число','Только направление','Стрелка с заданной длиной, направлением и точкой приложения','Только подпись'], 2)
+ '</div>');
h += readButton('p21');
body.innerHTML = h;
function draw21(){
const F = +document.getElementById('p21-F-r').value;
const ang = +document.getElementById('p21-ang-r').value;
document.getElementById('p21-F').textContent = F;
document.getElementById('p21-ang').textContent = ang;
const cx = 150, cy = 110;
const len = F * 2.5;
const a = -ang * Math.PI / 180;
const x2 = cx + len * Math.cos(a);
const y2 = cy + len * Math.sin(a);
let s = '';
// Тело — кружок
s += '<circle cx="' + cx + '" cy="' + cy + '" r="14" fill="#dc2626" stroke="#7f1d1d" stroke-width="1.5"/>';
// Стрелка силы
s += '<line x1="' + cx + '" y1="' + cy + '" x2="' + x2.toFixed(1) + '" y2="' + y2.toFixed(1) + '" stroke="#0c4a6e" stroke-width="3"/>';
// Наконечник
const ah = 10;
const ax1 = x2 - ah * Math.cos(a - 0.4);
const ay1 = y2 - ah * Math.sin(a - 0.4);
const ax2 = x2 - ah * Math.cos(a + 0.4);
const ay2 = y2 - ah * Math.sin(a + 0.4);
s += '<polygon points="' + x2.toFixed(1) + ',' + y2.toFixed(1) + ' ' + ax1.toFixed(1) + ',' + ay1.toFixed(1) + ' ' + ax2.toFixed(1) + ',' + ay2.toFixed(1) + '" fill="#0c4a6e"/>';
// Подпись
s += '<text x="' + (x2 + 8 * Math.cos(a)) + '" y="' + (y2 + 8 * Math.sin(a) + 5) + '" font-family="Inter,sans-serif" font-size="13" font-weight="700" fill="#0c4a6e">F = ' + F + ' Н</text>';
document.getElementById('p21-svg').innerHTML = s;
}
['p21-F-r','p21-ang-r'].forEach(id => document.getElementById(id).addEventListener('input', draw21));
draw21();
wireDnd('p21-dnd', [
{ id:'a1', cat:'grav' },{ id:'a2', cat:'grav' },{ id:'a3', cat:'el' },{ id:'a4', cat:'el' },
{ id:'a5', cat:'fr' },{ id:'a6', cat:'fr' },{ id:'a7', cat:'norm' },{ id:'a8', cat:'norm' }
]);
wireQuiz('p21-q-host', () => { if(window.addXp) window.addXp(10, 'q-p21'); });
wireQuiz('p21-tr-host', () => { if(window.addXp) window.addXp(15, 'tr-p21'); });
wireReadBtn('p21');
renderMath(body);
}
/* ========================================================== */
/* §22 — Сила тяжести */
/* ========================================================== */
function add_p22(){
const body = document.getElementById('p22-body');
if(!body) return;
let h = '';
h += makeCard('theory', 'Притяжение к Земле', '§ 22.1',
'Земля притягивает все тела возле своей поверхности. Сила, с которой Земля действует на тело, '
+ 'называется <b>силой тяжести</b> $\\vec F_т$. Она направлена <b>вертикально вниз</b> '
+ '(точнее — к центру Земли).');
h += makeCard('rule', 'Формула силы тяжести', '§ 22.2',
'$$F_т = m g$$<br>'
+ 'где $m$ — масса тела (кг), $g$ — ускорение свободного падения.<br><br>'
+ '<b>На Земле:</b> $g = 9{,}8$ Н/кг (часто округляют до $g \\approx 10$ Н/кг).<br>'
+ '<b>На Луне:</b> $g_Л \\approx 1{,}6$ Н/кг (в 6 раз меньше).<br>'
+ '<b>На Марсе:</b> $g_М \\approx 3{,}7$ Н/кг.<br>'
+ '<b>На Юпитере:</b> $g_Ю \\approx 24{,}8$ Н/кг.');
h += makeCard('example', 'Сколько весит яблоко?', '§ 22.3',
'Масса среднего яблока $m = 100$ г $= 0{,}1$ кг. На Земле:<br>'
+ '$F_т = mg = 0{,}1 \\cdot 9{,}8 \\approx 1$ Н.<br><br>'
+ 'Поэтому 1 ньютон — это «сила, с которой Земля притягивает 100 г».');
/* IV-1 КАЛЬК: F_т = mg, выбор планеты */
h += wgWrap('p22-iv1', 'КАЛЬК', 'Сила тяжести на разных планетах', 'Выбери планету и тело — узнай, как меняется $F_т$.',
'<div style="display:flex;gap:6px;margin-bottom:10px;flex-wrap:wrap">'
+ [['earth','Земля',9.8],['moon','Луна',1.6],['mars','Марс',3.7],['jupiter','Юпитер',24.8]].map((p, i) =>
'<button class="p22-pl" data-g="' + p[2] + '" data-nm="' + p[1] + '" type="button" style="background:' + (i===0 ? '#dc2626' : '#fff') + ';color:' + (i===0 ? '#fff' : '#dc2626') + ';border:2px solid #dc2626;padding:8px 14px;border-radius:9px;cursor:pointer;font-weight:700;font-family:inherit;font-size:.86rem">' + p[1] + '</button>').join('')
+ '</div>'
+ '<label style="display:block;font-size:.86rem;color:#475569;background:#fff;padding:8px 12px;border-radius:8px;border:1px solid ' + ACCENT_SOFT + ';margin-bottom:10px">Масса $m$, кг: <b id="p22-m" style="color:' + ACCENT_D + ';font-family:JetBrains Mono,monospace">0.1</b><input type="range" id="p22-m-r" min="1" max="1000" step="1" value="100" style="display:block;width:100%;margin-top:6px;accent-color:' + ACCENT + '"></label>'
+ '<div style="background:' + ACCENT_SOFT + ';border-radius:9px;padding:12px 14px;font-size:.94rem;line-height:1.7">'
+ '$F_т = m g = $ <b id="p22-Ft" style="color:' + ACCENT_D + ';font-family:JetBrains Mono,monospace">0.98</b> Н (на <span id="p22-pl-nm" style="font-weight:700">Земле</span>, $g = $ <span id="p22-g-val" style="font-family:JetBrains Mono,monospace;font-weight:700">9.8</span> Н/кг)'
+ '</div>');
/* IV-2 СИМ — Падение тела на 4 планетах */
h += wgWrap('p22-iv2', 'СИМ', 'Падение с одной высоты на 4 планетах', 'Нажми «Уронить» — увидь, кто упадёт быстрее. Время и скорость в реальном времени.',
'<div style="display:flex;gap:6px;margin-bottom:10px;flex-wrap:wrap">'
+ '<button id="p22-drop" type="button" style="background:#dc2626;color:#fff;border:none;padding:9px 18px;border-radius:9px;cursor:pointer;font-weight:700;font-family:inherit;font-size:.9rem">Уронить</button>'
+ '<button id="p22-drop-reset" type="button" style="background:#fff;color:#dc2626;border:1.5px solid #dc2626;padding:9px 14px;border-radius:9px;cursor:pointer;font-weight:700;font-family:inherit;font-size:.88rem">Сброс</button>'
+ '<label style="display:inline-flex;align-items:center;gap:6px;background:#fff;border:1px solid ' + ACCENT_SOFT + ';padding:6px 12px;border-radius:8px;font-size:.86rem;color:#475569">Высота $h$, м: <b id="p22-drop-h" style="color:#dc2626;font-family:JetBrains Mono,monospace">10</b><input type="range" id="p22-drop-h-r" min="2" max="20" step="1" value="10" style="width:120px;accent-color:#dc2626"></label>'
+ '</div>'
+ '<svg id="p22-drop-svg" viewBox="0 0 380 200" width="100%" style="max-width:600px;display:block;margin:0 auto;background:#fef2f2;border-radius:9px;border:1px solid ' + ACCENT_SOFT + '"></svg>'
+ '<div id="p22-drop-info" style="background:' + ACCENT_SOFT + ';border-radius:9px;padding:10px 14px;margin-top:8px;font-size:.92rem;line-height:1.55"></div>');
/* IV-3 КВИЗ (был IV-2) */
h += wgWrap('p22-iv3', 'КВИЗ', 'Сила тяжести', '',
'<div id="p22-q-host">'
+ quizQuestion('p22-q', 0, 'Куда направлена сила тяжести на поверхности Земли?', ['Вверх','Вертикально вниз (к центру Земли)','В сторону Солнца','В произвольном направлении'], 1)
+ quizQuestion('p22-q', 1, '$g$ на Земле примерно равно…', ['1 Н/кг','9,8 Н/кг','100 Н/кг','1000 Н/кг'], 1)
+ quizQuestion('p22-q', 2, 'На Луне $g_Л$ примерно в 6 раз меньше земного. Что произойдёт с $F_т$ камня?', ['Не изменится','Увеличится в 6 раз','Уменьшится в 6 раз','Станет нулевой'], 2)
+ quizQuestion('p22-q', 3, 'Масса остаётся постоянной на Земле и Луне, а сила тяжести?', ['Тоже постоянна','Уменьшается на Луне','Увеличивается на Луне','Зависит от формы тела'], 1, 'Масса = «количество вещества», она не меняется. А $F_т = mg$ меняется через $g$.')
+ '</div>');
/* IV-3 DnD */
h += wgWrap('p22-iv3', 'DnD', 'Сопоставь массу и силу тяжести (на Земле)', '',
dndPool('p22-dnd', [
{ id:'a1', cat:'1n', html:'$m = 100$ г' },
{ id:'a2', cat:'10n', html:'$m = 1$ кг' },
{ id:'a3', cat:'100n', html:'$m = 10$ кг' },
{ id:'a4', cat:'1n', html:'$m = 0{,}1$ кг' },
{ id:'a5', cat:'10n', html:'$m = 1000$ г' },
{ id:'a6', cat:'100n', html:'$m = 10\\,000$ г' }
], [
{ cat:'1n', label:'$F_т \\approx 1$ Н' },
{ cat:'10n', label:'$F_т \\approx 10$ Н' },
{ cat:'100n', label:'$F_т \\approx 100$ Н' }
]));
/* IV-4 ТРН */
h += wgWrap('p22-iv4', 'ТРН', 'Тренажёр §22', '',
'<div id="p22-tr-host">'
+ quizQuestion('p22-tr', 0, '$m = 5$ кг. $F_т$ на Земле ($g = 10$ Н/кг)?', ['5 Н','15 Н','50 Н','500 Н'], 2, '$F_т = mg = 5 \\cdot 10 = 50$ Н.')
+ quizQuestion('p22-tr', 1, '$m = 200$ г. $F_т$ на Земле ($g = 9{,}8$ Н/кг) ≈?', ['0,98 Н','1,96 Н','19,6 Н','2 Н'], 1, '$F_т = 0{,}2 \\cdot 9{,}8 = 1{,}96$ Н.')
+ quizQuestion('p22-tr', 2, 'Космонавт $m = 80$ кг. $F_т$ на Луне ($g_Л = 1{,}6$ Н/кг)?', ['80 Н','128 Н','480 Н','800 Н'], 1, '$F_т = 80 \\cdot 1{,}6 = 128$ Н.')
+ quizQuestion('p22-tr', 3, 'Где $F_т$ одной и той же массы больше: на Юпитере или Луне?', ['На Луне','На Юпитере','Одинаково','Зависит от вещества'], 1, 'У Юпитера $g$ намного больше.')
+ quizQuestion('p22-tr', 4, 'Чтобы $F_т$ на Земле была $30$ Н, нужна масса…', ['1 кг','2 кг','3 кг','30 кг'], 2, 'При $g \\approx 10$ Н/кг: $m = F_т/g = 30/10 = 3$ кг.')
+ '</div>');
h += readButton('p22');
body.innerHTML = h;
let g22 = 9.8;
let plName = 'Земле';
function upd22(){
const m = +document.getElementById('p22-m-r').value / 1000; // ползунок в граммах
document.getElementById('p22-m').textContent = (m).toFixed(2);
const Ft = m * g22;
document.getElementById('p22-Ft').textContent = Ft.toFixed(2);
document.getElementById('p22-pl-nm').textContent = plName;
document.getElementById('p22-g-val').textContent = g22.toString().replace('.', ',');
}
body.querySelectorAll('.p22-pl').forEach(btn => btn.addEventListener('click', () => {
body.querySelectorAll('.p22-pl').forEach(b => { b.style.background = '#fff'; b.style.color = '#dc2626'; });
btn.style.background = '#dc2626'; btn.style.color = '#fff';
g22 = +btn.dataset.g;
plName = btn.dataset.nm;
upd22();
}));
document.getElementById('p22-m-r').addEventListener('input', upd22);
upd22();
// §22 IV-2 — Падение тел на 4 планетах
const planets22 = [
{ nm:'Земля', g:9.8, col:'#0284c7' },
{ nm:'Луна', g:1.6, col:'#94a3b8' },
{ nm:'Марс', g:3.7, col:'#dc2626' },
{ nm:'Юпитер', g:24.8, col:'#d97706' }
];
let drop22 = { t:0, raf:0, running:false, h0:10 };
function drawDrop22(){
const svg = document.getElementById('p22-drop-svg');
if(!svg) return;
const W = 380, H = 200, baseY = 175;
const pxPerM = (baseY - 25) / drop22.h0;
const colW = W / planets22.length;
let s = '';
s += '<line x1="0" y1="' + baseY + '" x2="' + W + '" y2="' + baseY + '" stroke="#0f172a" stroke-width="1.5"/>';
planets22.forEach((p, i) => {
const cx = colW * i + colW/2;
const startY = baseY - drop22.h0 * pxPerM;
// Падение: h(t) = h0 - g*t²/2 (свободное падение)
const fall = 0.5 * p.g * drop22.t * drop22.t;
const hNow = Math.max(0, drop22.h0 - fall);
const cy = baseY - hNow * pxPerM - 6;
const v = p.g * drop22.t;
const fellTime = Math.sqrt(2 * drop22.h0 / p.g);
const onGround = drop22.t >= fellTime;
// Опорная линия высоты
s += '<line x1="' + (cx - colW/2 + 10) + '" y1="' + startY + '" x2="' + (cx + colW/2 - 10) + '" y2="' + startY + '" stroke="#cbd5e1" stroke-width="1" stroke-dasharray="3 2"/>';
// Шарик
s += '<circle cx="' + cx.toFixed(1) + '" cy="' + (onGround ? baseY - 6 : cy).toFixed(1) + '" r="9" fill="' + p.col + '" stroke="#0f172a" stroke-width="1.5"/>';
// Подпись планеты
s += '<text x="' + cx + '" y="' + (baseY + 16) + '" text-anchor="middle" font-family="Inter,sans-serif" font-size="11" font-weight="700" fill="' + p.col + '">' + p.nm + '</text>';
s += '<text x="' + cx + '" y="' + 18 + '" text-anchor="middle" font-family="Inter,sans-serif" font-size="9" fill="#475569">g=' + p.g + '</text>';
if(onGround) s += '<text x="' + cx + '" y="' + (baseY - 14) + '" text-anchor="middle" font-family="JetBrains Mono,monospace" font-size="10" font-weight="700" fill="#10b981">' + fellTime.toFixed(2) + ' с</text>';
});
svg.innerHTML = s;
// Info
const status = planets22.map(p => {
const fellTime = Math.sqrt(2 * drop22.h0 / p.g);
const onGround = drop22.t >= fellTime;
const vNow = onGround ? p.g * fellTime : p.g * drop22.t;
return '<b style="color:' + p.col + '">' + p.nm + '</b>: ' + (onGround ? 'упал за ' + fellTime.toFixed(2) + ' с, $v = ' + vNow.toFixed(1) + '$ м/с' : 'падает, $v = ' + vNow.toFixed(1) + '$ м/с');
}).join(' &middot; ');
document.getElementById('p22-drop-info').innerHTML = '$t = ' + drop22.t.toFixed(2) + '$ с &middot; ' + status;
renderMath(document.getElementById('p22-drop-info'));
}
function loop22(){
if(!drop22.running) return;
drop22.t += 0.04;
drawDrop22();
// Останавливаем когда даже самая медленная (Луна) упала
const maxT = Math.sqrt(2 * drop22.h0 / 1.6);
if(drop22.t >= maxT + 0.5){ drop22.running = false; return; }
drop22.raf = requestAnimationFrame(loop22);
}
document.getElementById('p22-drop-h-r').addEventListener('input', () => {
drop22.h0 = +document.getElementById('p22-drop-h-r').value;
document.getElementById('p22-drop-h').textContent = drop22.h0;
drop22.t = 0; drop22.running = false;
if(drop22.raf) cancelAnimationFrame(drop22.raf);
drawDrop22();
});
document.getElementById('p22-drop').addEventListener('click', () => {
drop22.t = 0; drop22.running = true;
if(drop22.raf) cancelAnimationFrame(drop22.raf);
loop22();
});
document.getElementById('p22-drop-reset').addEventListener('click', () => {
drop22.t = 0; drop22.running = false;
if(drop22.raf) cancelAnimationFrame(drop22.raf);
drawDrop22();
});
drawDrop22();
wireDnd('p22-dnd', [
{ id:'a1', cat:'1n' },{ id:'a2', cat:'10n' },{ id:'a3', cat:'100n' },
{ id:'a4', cat:'1n' },{ id:'a5', cat:'10n' },{ id:'a6', cat:'100n' }
]);
wireQuiz('p22-q-host', () => { if(window.addXp) window.addXp(10, 'q-p22'); });
wireQuiz('p22-tr-host', () => { if(window.addXp) window.addXp(15, 'tr-p22'); });
wireReadBtn('p22');
renderMath(body);
}
/* ========================================================== */
/* §23 — Сила упругости */
/* ========================================================== */
function add_p23(){
const body = document.getElementById('p23-body');
if(!body) return;
let h = '';
h += makeCard('theory', 'Когда возникает упругая сила', '§ 23.1',
'Если тело <b>деформировано</b> (сжато, растянуто, изогнуто), внутри него возникает '
+ '<b>сила упругости</b> $\\vec F_{упр}$, которая <b>стремится вернуть тело в исходную форму</b>.<br><br>'
+ 'Природа этой силы — взаимодействие молекул, изученное в §10.');
h += makeCard('rule', 'Закон Гука (качественно)', '§ 23.2',
'$$F_{упр} \\sim \\Delta l$$<br>'
+ 'Сила упругости <b>пропорциональна удлинению</b> $\\Delta l$. '
+ 'То есть в 2 раза больше растянули → в 2 раза больше сила.<br><br>'
+ '<b>Внимание:</b> закон работает только пока деформация мала. Если перерастянуть пружину — '
+ 'она <b>не вернётся</b> в исходное положение (пластическая деформация).');
h += makeCard('example', 'Где встречается', '§ 23.3',
'<ul style="padding-left:20px;margin:6px 0">'
+ '<li><b>Пружина</b> матраса, ручки, амортизатора.</li>'
+ '<li><b>Резинка</b> для волос, рогатка.</li>'
+ '<li><b>Лук</b>: тетива растянута → возвращает стрелу.</li>'
+ '<li><b>Опора</b> прогибается под весом и отвечает реакцией $\\vec N$ — это тоже упругая сила.</li>'
+ '</ul>');
/* IV-1 СИМ: пружина с slider */
h += wgWrap('p23-iv1', 'СИМ', 'Пружина: растяжение и сила', 'Меняй удлинение — увидь, как растёт сила упругости.',
'<div style="margin-bottom:10px"><label style="display:block;font-size:.86rem;color:#475569;background:#fff;padding:8px 12px;border-radius:8px;border:1px solid ' + ACCENT_SOFT + '">Удлинение $\\Delta l$, см: <b id="p23-dl" style="color:' + ACCENT_D + ';font-family:JetBrains Mono,monospace">5</b><input type="range" id="p23-dl-r" min="0" max="20" step="1" value="5" style="display:block;width:100%;margin-top:6px;accent-color:' + ACCENT + '"></label></div>'
+ '<svg id="p23-svg" viewBox="0 0 380 160" width="100%" style="max-width:600px;display:block;margin:0 auto;background:#fef2f2;border-radius:9px;border:1px solid ' + ACCENT_SOFT + '"></svg>'
+ '<div id="p23-info" style="background:' + ACCENT_SOFT + ';border-radius:9px;padding:10px 14px;margin-top:8px;font-size:.94rem;text-align:center"></div>');
/* IV-2 КВИЗ */
h += wgWrap('p23-iv2', 'КВИЗ', 'Упругость', '',
'<div id="p23-q-host">'
+ quizQuestion('p23-q', 0, 'Когда возникает сила упругости?', ['Всегда','Только в жидкостях','При деформации тела','При движении'], 2)
+ quizQuestion('p23-q', 1, 'Куда направлена $\\vec F_{упр}$?', ['В сторону деформации','Против деформации (возвращает в исходное)','Вертикально вниз','Зависит от тела'], 1)
+ quizQuestion('p23-q', 2, 'Если растянуть пружину в 3 раза сильнее, $F_{упр}$ изменится…', ['Не изменится','Увеличится в 3 раза','Уменьшится в 3 раза','Увеличится в 9 раз'], 1, 'Это закон Гука: $F \\sim \\Delta l$.')
+ '</div>');
/* IV-3 DnD */
h += wgWrap('p23-iv3', 'DnD', 'Деформация или нет?', '',
dndPool('p23-dnd', [
{ id:'a1', cat:'def', html:'Сжатая пружина матраса' },
{ id:'a2', cat:'def', html:'Растянутая резинка' },
{ id:'a3', cat:'def', html:'Прогнутая доска под весом' },
{ id:'a4', cat:'def', html:'Натянутая тетива лука' },
{ id:'a5', cat:'no', html:'Лежащий камень (нет видимой деформации)' },
{ id:'a6', cat:'no', html:'Висящая на крючке шапка' }
], [
{ cat:'def', label:'Есть деформация → есть $F_{упр}$' },
{ cat:'no', label:'Нет видимой деформации' }
]));
/* IV-4 ТРН */
h += wgWrap('p23-iv4', 'ТРН', 'Тренажёр §23', '',
'<div id="p23-tr-host">'
+ quizQuestion('p23-tr', 0, 'Природа силы упругости — это…', ['Гравитация','Взаимодействие молекул','Магнетизм','Электричество'], 1)
+ quizQuestion('p23-tr', 1, 'Если пружину растянуть слишком сильно, она…', ['Останется упругой','Может остаться растянутой (пластическая деформация)','Станет тяжелее','Не изменится'], 1)
+ quizQuestion('p23-tr', 2, 'Что является примером упругой деформации?', ['Растяжение пружины и возврат','Лепка из пластилина','Раскалывание камня','Резка хлеба'], 0)
+ quizQuestion('p23-tr', 3, 'Какая из сил из жизни — упругая?', ['Сила тяжести','Сила трения','Реакция опоры $\\vec N$','Лунное притяжение'], 2, 'Опора прогибается и «отвечает» упругой силой.')
+ '</div>');
h += readButton('p23');
body.innerHTML = h;
function draw23(){
const dl = +document.getElementById('p23-dl-r').value;
document.getElementById('p23-dl').textContent = dl;
const F = dl * 2; // условный коэф. жёсткости 2 Н/см
const W = 380, H = 160;
const topY = 20, hookX = 80;
const naturalLen = 60;
const totalLen = naturalLen + dl * 3.5; // визуально усилено
let s = '';
// Потолок
s += '<rect x="20" y="10" width="120" height="10" fill="#475569" stroke="#0f172a" stroke-width="1"/>';
for(let i = 0; i < 6; i++) s += '<line x1="' + (20 + i*20) + '" y1="20" x2="' + (10 + i*20) + '" y2="30" stroke="#0f172a" stroke-width="0.8"/>';
// Подвес
s += '<line x1="' + hookX + '" y1="20" x2="' + hookX + '" y2="' + topY + '" stroke="#374151" stroke-width="1.5"/>';
// Пружина (зигзаг)
const coils = 8;
let path = 'M ' + hookX + ' ' + topY;
for(let i = 0; i < coils; i++){
const y = topY + (i + 0.5) * (totalLen / coils);
path += ' L ' + (hookX + (i%2 ? 18 : -18)) + ' ' + y;
}
path += ' L ' + hookX + ' ' + (topY + totalLen);
s += '<path d="' + path + '" fill="none" stroke="#dc2626" stroke-width="2.5"/>';
// Груз
s += '<rect x="' + (hookX - 22) + '" y="' + (topY + totalLen) + '" width="44" height="28" fill="#7c3aed" stroke="#5b21b6" stroke-width="1.5" rx="3"/>';
s += '<text x="' + hookX + '" y="' + (topY + totalLen + 19) + '" text-anchor="middle" font-family="Inter,sans-serif" font-size="11" font-weight="700" fill="#fff">груз</text>';
// Стрелка F_упр (вверх) и F_т (вниз) если dl>0
if(dl > 0){
const arrY = topY + totalLen + 50;
// F_упр вверх
s += '<line x1="200" y1="' + arrY + '" x2="200" y2="' + (arrY - 35) + '" stroke="#10b981" stroke-width="2.5"/>';
s += '<polygon points="200,' + (arrY - 35) + ' 195,' + (arrY - 28) + ' 205,' + (arrY - 28) + '" fill="#10b981"/>';
s += '<text x="210" y="' + (arrY - 28) + '" font-family="Inter,sans-serif" font-size="12" font-weight="700" fill="#10b981">F_упр = ' + F + ' Н ↑</text>';
// F_т вниз
s += '<line x1="260" y1="' + arrY + '" x2="260" y2="' + (arrY + 30) + '" stroke="#7c3aed" stroke-width="2.5"/>';
s += '<polygon points="260,' + (arrY + 30) + ' 255,' + (arrY + 23) + ' 265,' + (arrY + 23) + '" fill="#7c3aed"/>';
s += '<text x="270" y="' + (arrY + 25) + '" font-family="Inter,sans-serif" font-size="12" font-weight="700" fill="#7c3aed">F_т ↓</text>';
}
document.getElementById('p23-svg').innerHTML = s;
document.getElementById('p23-info').innerHTML = 'Удлинение $\\Delta l = ' + dl + '$ см &middot; $F_{упр} \\approx $ <b>' + F + '</b> Н (при коэф. жёсткости $k = 2$ Н/см)';
renderMath(document.getElementById('p23-info'));
}
document.getElementById('p23-dl-r').addEventListener('input', draw23);
draw23();
wireDnd('p23-dnd', [
{ id:'a1', cat:'def' },{ id:'a2', cat:'def' },{ id:'a3', cat:'def' },
{ id:'a4', cat:'def' },{ id:'a5', cat:'no' },{ id:'a6', cat:'no' }
]);
wireQuiz('p23-q-host', () => { if(window.addXp) window.addXp(10, 'q-p23'); });
wireQuiz('p23-tr-host', () => { if(window.addXp) window.addXp(15, 'tr-p23'); });
wireReadBtn('p23');
renderMath(body);
}
/* ========================================================== */
/* §24 — Вес тела */
/* ========================================================== */
function add_p24(){
const body = document.getElementById('p24-body');
if(!body) return;
let h = '';
h += makeCard('theory', 'Что такое вес', '§ 24.1',
'<b>Вес</b> $\\vec P$ — это сила, с которой тело <b>давит на опору</b> (или растягивает подвес) '
+ 'вследствие притяжения к Земле.<br><br>'
+ 'Это <b>не</b> то же самое, что сила тяжести! Хоть на покоящемся теле они равны по модулю, '
+ 'они приложены к <b>разным телам</b>:'
+ '<ul style="padding-left:20px;margin:5px 0">'
+ '<li>$\\vec F_т$ — действует на <b>само тело</b> от Земли;</li>'
+ '<li>$\\vec P$ — действует на <b>опору или подвес</b> от тела.</li>'
+ '</ul>');
h += makeCard('rule', 'Когда $P = F_т$, а когда нет', '§ 24.2',
'<b>Покоится</b> на горизонтальной опоре или висит на нити: $P = F_т = mg$.<br><br>'
+ '<b>Невесомость</b> (свободное падение): $P = 0$ — тело не давит на опору. '
+ 'Космонавт на МКС в невесомости — у него масса есть, $F_т$ от Земли тоже есть, '
+ 'а вот вес $P = 0$, потому что станция падает на Землю вместе с ним.<br><br>'
+ '<b>Перегрузка</b>: в ускоряющемся лифте вверх вес <b>больше</b> $mg$. В лифте вниз — <b>меньше</b>.');
h += makeCard('example', 'Как взвешивают тело', '§ 24.3',
'На <b>чашечных весах</b> сравнивают вес тела и вес гирь. '
+ 'Когда чашечки уравновешены — массы равны (поскольку $g$ одинаков, и $P = mg$).<br><br>'
+ 'На <b>пружинных весах (динамометре)</b> измеряют силу, с которой тело растягивает пружину. '
+ 'Это и есть вес тела в Ньютонах.');
/* IV-1 СИМ: динамический лифт с динамометром */
h += wgWrap('p24-iv1', 'СИМ', 'Лифт с динамометром: вес меняется при ускорении', 'Подвинь slider ускорения — увидь, как меняется показание динамометра (вес).',
'<div style="display:grid;grid-template-columns:repeat(auto-fit,minmax(150px,1fr));gap:8px;margin-bottom:10px">'
+ '<label style="display:block;font-size:.86rem;color:#475569;background:#fff;padding:7px 11px;border-radius:8px;border:1px solid ' + ACCENT_SOFT + '">Масса груза $m$, кг: <b id="p24-m" style="color:#dc2626;font-family:JetBrains Mono,monospace">2</b><input type="range" id="p24-m-r" min="0.5" max="10" step="0.5" value="2" style="display:block;width:100%;margin-top:5px;accent-color:#dc2626"></label>'
+ '<label style="display:block;font-size:.86rem;color:#475569;background:#fff;padding:7px 11px;border-radius:8px;border:1px solid ' + ACCENT_SOFT + '">Ускорение лифта $a$, м/с²: <b id="p24-a" style="color:#dc2626;font-family:JetBrains Mono,monospace">0</b><input type="range" id="p24-a-r" min="-10" max="10" step="0.5" value="0" style="display:block;width:100%;margin-top:5px;accent-color:#dc2626"></label>'
+ '</div>'
+ '<div style="display:flex;gap:6px;margin-bottom:10px;flex-wrap:wrap">'
+ [['0','Покой'],['2','Едет вверх с ускорением'],['-2','Едет вниз с ускорением'],['-10','Свободное падение']].map(p =>
'<button class="p24-preset" data-a="' + p[0] + '" type="button" style="background:#fff;color:#dc2626;border:1.5px solid #dc2626;padding:6px 12px;border-radius:8px;cursor:pointer;font-weight:600;font-family:inherit;font-size:.82rem">' + p[1] + '</button>').join('')
+ '</div>'
+ '<svg id="p24-svg" viewBox="0 0 380 260" width="100%" style="max-width:560px;display:block;margin:0 auto;background:#fef2f2;border-radius:9px;border:1px solid ' + ACCENT_SOFT + '"></svg>'
+ '<div id="p24-info" style="background:' + ACCENT_SOFT + ';border-radius:9px;padding:10px 14px;margin-top:8px;font-size:.94rem;line-height:1.65"></div>');
/* IV-2 КВИЗ */
h += wgWrap('p24-iv2', 'КВИЗ', 'Вес vs сила тяжести', '',
'<div id="p24-q-host">'
+ quizQuestion('p24-q', 0, 'К чему приложена сила тяжести $\\vec F_т$?', ['К опоре','К самому телу','К центру Земли','К воздуху'], 1)
+ quizQuestion('p24-q', 1, 'К чему приложен вес $\\vec P$?', ['К телу','К опоре или подвесу','К атмосфере','К центру Земли'], 1)
+ quizQuestion('p24-q', 2, 'Космонавт в МКС находится в…', ['Состоянии покоя','Невесомости (P = 0)','Перегрузке','Постоянной силе'], 1)
+ quizQuestion('p24-q', 3, 'Тело $m = 2$ кг лежит на столе. Чему равен вес? ($g = 10$ Н/кг)', ['2 Н','10 Н','20 Н','200 Н'], 2, '$P = mg = 2 \\cdot 10 = 20$ Н.')
+ '</div>');
/* IV-3 DnD */
h += wgWrap('p24-iv3', 'DnD', 'Какая сила куда приложена?', '',
dndPool('p24-dnd', [
{ id:'a1', cat:'ft', html:'Земля тянет яблоко вниз' },
{ id:'a2', cat:'ft', html:'Земля тянет космонавта на орбите' },
{ id:'a3', cat:'p', html:'Книга давит на стол' },
{ id:'a4', cat:'p', html:'Гирька растягивает пружину' },
{ id:'a5', cat:'zero', html:'Космонавт в невесомости (P = 0)' },
{ id:'a6', cat:'zero', html:'Шарик в свободном падении' }
], [
{ cat:'ft', label:'$\\vec F_т$ (на тело)' },
{ cat:'p', label:'$\\vec P$ (на опору/подвес)' },
{ cat:'zero', label:'$P = 0$ (невесомость)' }
]));
/* IV-4 ТРН */
h += wgWrap('p24-iv4', 'ТРН', 'Тренажёр §24', '',
'<div id="p24-tr-host">'
+ quizQuestion('p24-tr', 0, '$m = 0{,}5$ кг лежит на столе. Вес (при $g = 10$ Н/кг)?', ['0,5 Н','5 Н','50 Н','500 Н'], 1)
+ quizQuestion('p24-tr', 1, 'Гиря $m = 100$ г висит на нити. Сила, с которой нить растягивается, равна:', ['1 Н','10 Н','100 Н','0,1 Н'], 0, '$P = mg = 0{,}1 \\cdot 10 = 1$ Н.')
+ quizQuestion('p24-tr', 2, 'В свободно падающем лифте чувствует ли пассажир свой вес?', ['Да, как всегда','Нет, наступает невесомость','В 2 раза больше','В 2 раза меньше'], 1)
+ quizQuestion('p24-tr', 3, 'На Луне ($g_Л = 1{,}6$ Н/кг) тело массой $5$ кг весит:', ['5 Н','8 Н','16 Н','50 Н'], 1, '$P = 5 \\cdot 1{,}6 = 8$ Н.')
+ '</div>');
h += readButton('p24');
body.innerHTML = h;
function draw24(){
const svg = document.getElementById('p24-svg');
if(!svg) return;
const m = +document.getElementById('p24-m-r').value;
const a = +document.getElementById('p24-a-r').value; // положительное = вверх
const g = 10;
// Вес на пружине: P = m(g + a). a > 0 (лифт ускоряется вверх) → P > mg.
// Если лифт в свободном падении (a = -g), P = 0.
const P = Math.max(0, m * (g + a));
const W = 380, H = 260;
// Кабина лифта 160×220, центрирована
const cabX = 110, cabY = 20, cabW = 160, cabH = 220;
let s = '';
// Шахта (стенки)
s += '<line x1="' + (cabX - 10) + '" y1="0" x2="' + (cabX - 10) + '" y2="' + H + '" stroke="#94a3b8" stroke-width="2" stroke-dasharray="6 4"/>';
s += '<line x1="' + (cabX + cabW + 10) + '" y1="0" x2="' + (cabX + cabW + 10) + '" y2="' + H + '" stroke="#94a3b8" stroke-width="2" stroke-dasharray="6 4"/>';
// Кабина
s += '<rect x="' + cabX + '" y="' + cabY + '" width="' + cabW + '" height="' + cabH + '" fill="#fff" stroke="#0f172a" stroke-width="2" rx="3"/>';
// Стрелка ускорения слева снаружи
if(Math.abs(a) > 0.3){
const aY = cabY + cabH/2;
const arrLen = Math.min(50, Math.abs(a) * 4);
const dir = a > 0 ? -1 : 1; // вверх = -y, вниз = +y
s += '<line x1="60" y1="' + aY + '" x2="60" y2="' + (aY + arrLen * dir) + '" stroke="#10b981" stroke-width="3"/>';
s += '<polygon points="' + 55 + ',' + (aY + arrLen * dir - 8 * (-dir)) + ' ' + 65 + ',' + (aY + arrLen * dir - 8 * (-dir)) + ' ' + 60 + ',' + (aY + arrLen * dir) + '" fill="#10b981"/>';
s += '<text x="60" y="' + (a > 0 ? aY - arrLen - 8 : aY + arrLen + 18) + '" text-anchor="middle" font-family="Inter,sans-serif" font-size="11" font-weight="700" fill="#10b981">a = ' + (a > 0 ? '+' : '') + a + '</text>';
}
// Динамометр (вертикальный): корпус, пружина, груз
const dynX = cabX + cabW/2, dynTop = cabY + 30;
s += '<line x1="' + dynX + '" y1="' + cabY + '" x2="' + dynX + '" y2="' + dynTop + '" stroke="#475569" stroke-width="2"/>';
// Корпус
s += '<rect x="' + (dynX - 15) + '" y="' + dynTop + '" width="30" height="120" fill="#fef3c7" stroke="#92400e" stroke-width="1.5" rx="3"/>';
// Пружина — длина пропорц. P
const maxStretch = 80;
const Pmax = m * (g + 10); // P при a = +10
const stretch = Math.min(maxStretch, (P / Pmax) * maxStretch);
const sprBot = dynTop + 5 + stretch;
const coils = 6;
let path = 'M ' + dynX + ' ' + (dynTop + 5);
for(let i = 0; i < coils; i++){
path += ' L ' + (dynX + (i%2 ? 9 : -9)) + ' ' + (dynTop + 5 + (i + 0.5) * stretch/coils);
}
path += ' L ' + dynX + ' ' + sprBot;
s += '<path d="' + path + '" fill="none" stroke="#92400e" stroke-width="1.5"/>';
// Шкала справа
for(let i = 0; i <= 10; i++){
const ty = dynTop + 5 + (i / 10) * maxStretch;
s += '<line x1="' + (dynX + 15) + '" y1="' + ty + '" x2="' + (dynX + 22) + '" y2="' + ty + '" stroke="#92400e" stroke-width="1"/>';
if(i % 2 === 0) s += '<text x="' + (dynX + 26) + '" y="' + (ty + 3) + '" font-family="Inter,sans-serif" font-size="8" fill="#92400e">' + ((Pmax * i / 10).toFixed(0)) + '</text>';
}
// Указатель
s += '<line x1="' + (dynX - 10) + '" y1="' + sprBot + '" x2="' + (dynX + 12) + '" y2="' + sprBot + '" stroke="#dc2626" stroke-width="2.5"/>';
// Груз — кружок снизу пружины
s += '<circle cx="' + dynX + '" cy="' + (sprBot + 18) + '" r="14" fill="#dc2626" stroke="#7f1d1d" stroke-width="1.5"/>';
s += '<text x="' + dynX + '" y="' + (sprBot + 22) + '" text-anchor="middle" font-family="Inter,sans-serif" font-size="9" font-weight="700" fill="#fff">' + m + ' кг</text>';
// Показание динамометра в нижней части кабины
s += '<text x="' + dynX + '" y="' + (cabY + cabH - 12) + '" text-anchor="middle" font-family="Unbounded,sans-serif" font-size="13" font-weight="800" fill="' + (P < 0.1 ? '#10b981' : (P > m*g ? '#dc2626' : (P < m*g ? '#0284c7' : '#475569'))) + '">P = ' + P.toFixed(1) + ' Н</text>';
// Подпись режима
let mode, modeCol;
if(Math.abs(P - m*g) < 0.01){ mode = 'ПОКОЙ или равномерно: P = mg'; modeCol = '#475569'; }
else if(P < 0.1){ mode = 'НЕВЕСОМОСТЬ: P = 0'; modeCol = '#10b981'; }
else if(P > m*g){ mode = 'ПЕРЕГРУЗКА: P > mg'; modeCol = '#dc2626'; }
else { mode = 'ПОНИЖЕННЫЙ ВЕС: P < mg'; modeCol = '#0284c7'; }
s += '<text x="' + (cabX + cabW/2) + '" y="14" text-anchor="middle" font-family="Inter,sans-serif" font-size="11" font-weight="800" fill="' + modeCol + '">' + mode + '</text>';
svg.innerHTML = s;
document.getElementById('p24-m').textContent = m;
document.getElementById('p24-a').textContent = a > 0 ? '+' + a : a;
const Ft = m * g;
document.getElementById('p24-info').innerHTML =
'<b>$F_т = mg = ' + Ft.toFixed(1) + '$ Н</b> — сила тяжести на тело (не меняется при ускорении).<br>'
+ '<b>$P = m(g + a) = ' + m + ' \\cdot (' + g + (a >= 0 ? ' + ' : ' ') + Math.abs(a) + ') = ' + P.toFixed(1) + '$ Н</b> — показание динамометра (вес).<br>'
+ '<span style="color:' + modeCol + ';font-weight:700">' + mode + '</span>';
renderMath(document.getElementById('p24-info'));
}
body.querySelectorAll('.p24-preset').forEach(btn => btn.addEventListener('click', () => {
document.getElementById('p24-a-r').value = btn.dataset.a;
draw24();
}));
['p24-m-r','p24-a-r'].forEach(id => document.getElementById(id).addEventListener('input', draw24));
draw24();
wireDnd('p24-dnd', [
{ id:'a1', cat:'ft' },{ id:'a2', cat:'ft' },{ id:'a3', cat:'p' },
{ id:'a4', cat:'p' },{ id:'a5', cat:'zero' },{ id:'a6', cat:'zero' }
]);
wireQuiz('p24-q-host', () => { if(window.addXp) window.addXp(10, 'q-p24'); });
wireQuiz('p24-tr-host', () => { if(window.addXp) window.addXp(15, 'tr-p24'); });
wireReadBtn('p24');
renderMath(body);
}
/* ========================================================== */
/* §25 — Динамометр */
/* ========================================================== */
function add_p25(){
const body = document.getElementById('p25-body');
if(!body) return;
let h = '';
h += makeCard('theory', 'Принцип работы', '§ 25.1',
'<b>Динамометр</b> (от греч. <i>динамис</i> — сила) — прибор для измерения силы.<br><br>'
+ 'В основе — пружина: чем сильнее её растягивают, тем больше она удлиняется. '
+ 'Шкала проградуирована так, чтобы каждому положению указателя соответствовала сила в <b>ньютонах</b>.');
h += makeCard('rule', 'Виды динамометров', '§ 25.2',
'<ul style="padding-left:20px;margin:6px 0">'
+ '<li><b>Школьный лабораторный</b> — обычно $0..4$ Н или $0..10$ Н, цена деления $0{,}1$ Н.</li>'
+ '<li><b>Медицинский (для силы кисти)</b> — до $600$ Н.</li>'
+ '<li><b>Промышленный</b> — для измерения тяги машин, до тысяч Н и больше.</li>'
+ '</ul>'
+ '<b>Правила:</b> прибор калибруют в вертикальном положении; не превышай предел шкалы (иначе пружина испортится).');
h += makeCard('example', 'Связь с массой', '§ 25.3',
'Динамометр измеряет <b>силу</b> (например, вес висящего груза). Если знаешь $g$, можно найти массу:<br>'
+ '$$m = \\dfrac{F}{g}$$<br>'
+ 'Например, динамометр показал $P = 5$ Н. Тогда $m = 5 / 10 = 0{,}5$ кг $= 500$ г.');
/* IV-1 СИМ: виртуальный динамометр */
h += wgWrap('p25-iv1', 'СИМ', 'Виртуальный динамометр', 'Меняй силу — наблюдай растяжение пружины и положение указателя.',
'<div style="display:grid;grid-template-columns:1fr 1fr;gap:8px;margin-bottom:10px">'
+ '<label style="display:block;font-size:.86rem;color:#475569;background:#fff;padding:8px 12px;border-radius:8px;border:1px solid ' + ACCENT_SOFT + '">Сила $F$, Н: <b id="p25-F" style="color:' + ACCENT_D + ';font-family:JetBrains Mono,monospace">2</b><input type="range" id="p25-F-r" min="0" max="10" step="0.1" value="2" style="display:block;width:100%;margin-top:6px;accent-color:' + ACCENT + '"></label>'
+ '<label style="display:block;font-size:.86rem;color:#475569;background:#fff;padding:8px 12px;border-radius:8px;border:1px solid ' + ACCENT_SOFT + '">Предел $F_{max}$, Н: <b id="p25-Fmax" style="color:' + ACCENT_D + ';font-family:JetBrains Mono,monospace">10</b><input type="range" id="p25-Fmax-r" min="2" max="50" step="1" value="10" style="display:block;width:100%;margin-top:6px;accent-color:' + ACCENT + '"></label>'
+ '</div>'
+ '<svg id="p25-svg" viewBox="0 0 200 240" width="100%" style="max-width:280px;display:block;margin:0 auto"></svg>'
+ '<div id="p25-info" style="background:' + ACCENT_SOFT + ';border-radius:9px;padding:10px 14px;margin-top:8px;font-size:.92rem;text-align:center"></div>');
/* IV-2 КАЛЬК: F = mg обратное */
h += wgWrap('p25-iv2', 'КАЛЬК', 'Динамометр → масса', 'По показанию динамометра найди массу тела.',
'<div style="display:grid;grid-template-columns:1fr 1fr;gap:8px;margin-bottom:10px">'
+ '<label style="display:block;font-size:.86rem;color:#475569;background:#fff;padding:8px 12px;border-radius:8px;border:1px solid ' + ACCENT_SOFT + '">Показание $F$, Н: <b id="p25c-F" style="color:' + ACCENT_D + ';font-family:JetBrains Mono,monospace">5</b><input type="range" id="p25c-F-r" min="0.1" max="100" step="0.1" value="5" style="display:block;width:100%;margin-top:6px;accent-color:' + ACCENT + '"></label>'
+ '<label style="display:block;font-size:.86rem;color:#475569;background:#fff;padding:8px 12px;border-radius:8px;border:1px solid ' + ACCENT_SOFT + '">$g$, Н/кг: <b id="p25c-g" style="color:' + ACCENT_D + ';font-family:JetBrains Mono,monospace">9.8</b><select id="p25c-g-s" style="width:100%;margin-top:6px;padding:6px;border-radius:6px;border:1px solid ' + ACCENT_SOFT + ';font-family:inherit"><option value="9.8" selected>9,8 (Земля)</option><option value="1.6">1,6 (Луна)</option><option value="3.7">3,7 (Марс)</option><option value="24.8">24,8 (Юпитер)</option></select></label>'
+ '</div>'
+ '<div style="background:' + ACCENT_SOFT + ';border-radius:9px;padding:12px 14px;font-size:.94rem">$m = F/g = $ <b id="p25c-m" style="color:' + ACCENT_D + ';font-family:JetBrains Mono,monospace">0.51</b> кг $= $ <b id="p25c-mg" style="color:' + ACCENT_D + ';font-family:JetBrains Mono,monospace">510</b> г</div>');
/* IV-3 DnD */
h += wgWrap('p25-iv3', 'DnD', 'Выбери подходящий динамометр', '',
dndPool('p25-dnd', [
{ id:'a1', cat:'school', html:'Измерить вес гирьки 200 г' },
{ id:'a2', cat:'school', html:'Найти силу упругости школьной пружины' },
{ id:'a3', cat:'med', html:'Измерить силу кисти руки спортсмена' },
{ id:'a4', cat:'med', html:'Тест на здоровье в поликлинике' },
{ id:'a5', cat:'ind', html:'Тяга трактора, тащащего прицеп' },
{ id:'a6', cat:'ind', html:'Сила, с которой буксир тянет баржу' }
], [
{ cat:'school', label:'Школьный (0..10 Н)' },
{ cat:'med', label:'Медицинский (до 600 Н)' },
{ cat:'ind', label:'Промышленный (тысячи Н)' }
]));
/* IV-4 ТРН */
h += wgWrap('p25-iv4', 'ТРН', 'Тренажёр §25', '',
'<div id="p25-tr-host">'
+ quizQuestion('p25-tr', 0, 'Динамометр показал $F = 4$ Н на Земле ($g = 10$). Масса тела?', ['0,4 кг','4 кг','40 кг','0,04 кг'], 0)
+ quizQuestion('p25-tr', 1, 'Какова цена деления школьного динамометра 0..4 Н с 40 малыми делениями?', ['0,01 Н','0,1 Н','0,4 Н','1 Н'], 1, '$C = 4/40 = 0{,}1$ Н.')
+ quizQuestion('p25-tr', 2, 'Принцип работы динамометра основан на…', ['Силе тяжести','Растяжении пружины (упругая сила)','Магнитном поле','Электричестве'], 1)
+ quizQuestion('p25-tr', 3, 'Что произойдёт, если превысить предел шкалы?', ['Ничего','Пружина испортится','Прибор сам сломается','Подаст звуковой сигнал'], 1)
+ '</div>');
h += readButton('p25');
body.innerHTML = h;
function draw25(){
const F = +document.getElementById('p25-F-r').value;
const Fmax = +document.getElementById('p25-Fmax-r').value;
document.getElementById('p25-F').textContent = F.toFixed(1);
document.getElementById('p25-Fmax').textContent = Fmax;
if(window.PHYS && window.PHYS.dynamometer){
let svgInner = window.PHYS.dynamometer(80, 20, 160, Fmax, Math.min(F, Fmax));
// Цвет фона для подложки
document.getElementById('p25-svg').innerHTML = '<rect x="0" y="0" width="200" height="240" fill="#fef2f2" rx="8"/>' + svgInner;
}
document.getElementById('p25-info').innerHTML = F > Fmax
? '<b style="color:#dc2626">Превышение предела!</b> Реальная сила $F = ' + F.toFixed(1) + '$ Н > $F_{max} = ' + Fmax + '$ Н. Пружина может испортиться.'
: 'Показание: $F = ' + F.toFixed(1) + '$ Н из $' + Fmax + '$ Н. Цена деления $\\approx ' + (Fmax/20).toFixed(2) + '$ Н.';
renderMath(document.getElementById('p25-info'));
}
['p25-F-r','p25-Fmax-r'].forEach(id => document.getElementById(id).addEventListener('input', draw25));
draw25();
const upd25c = () => {
const F = +document.getElementById('p25c-F-r').value;
const g = +document.getElementById('p25c-g-s').value;
document.getElementById('p25c-F').textContent = F.toFixed(1);
document.getElementById('p25c-g').textContent = g.toString().replace('.', ',');
const m = F / g;
document.getElementById('p25c-m').textContent = m.toFixed(3);
document.getElementById('p25c-mg').textContent = (m * 1000).toFixed(0);
};
document.getElementById('p25c-F-r').addEventListener('input', upd25c);
document.getElementById('p25c-g-s').addEventListener('change', upd25c);
upd25c();
wireDnd('p25-dnd', [
{ id:'a1', cat:'school' },{ id:'a2', cat:'school' },{ id:'a3', cat:'med' },
{ id:'a4', cat:'med' },{ id:'a5', cat:'ind' },{ id:'a6', cat:'ind' }
]);
wireQuiz('p25-tr-host', () => { if(window.addXp) window.addXp(15, 'tr-p25'); });
wireReadBtn('p25');
renderMath(body);
}
/* ========================================================== */
/* §26 — Сложение сил. Равнодействующая (ГЛАВНЫЙ ВИЗУАЛ ГЛАВЫ 3) */
/* ========================================================== */
function add_p26(){
const body = document.getElementById('p26-body');
if(!body) return;
let h = '';
h += makeCard('theory', 'Что такое равнодействующая', '§ 26.1',
'Если на тело действует <b>несколько сил</b>, их совместное действие можно заменить <b>одной силой</b> — '
+ '<b>равнодействующей</b> $\\vec R$.<br><br>'
+ 'Тело ведёт себя так, словно на него действует только $\\vec R$. Например, если $R = 0$ — тело покоится '
+ 'или движется равномерно (силы уравновешены).');
h += makeCard('rule', 'Силы на одной прямой', '§ 26.2',
'<b>Сонаправленные силы</b> (в одну сторону): $$R = F_1 + F_2$$ направление — то же.<br><br>'
+ '<b>Противоположные силы</b>: $$R = |F_1 - F_2|$$ направление — в сторону <b>большей</b> силы.<br><br>'
+ 'Если $F_1 = F_2$ и они противоположны — $R = 0$, тело в равновесии.');
h += makeCard('example', 'Перетягивание каната', '§ 26.3',
'Команда А тянет канат с силой $F_A = 600$ Н вправо. Команда Б — $F_Б = 500$ Н влево.<br>'
+ '$R = 600 - 500 = 100$ Н вправо. Канат смещается в сторону команды А.<br><br>'
+ 'Если бы $F_A = F_Б = 600$ Н, было бы равновесие — канат стоял бы на месте.');
/* IV-1 ГЛАВНЫЙ ВИЗУАЛ: Конструктор сил на теле */
h += wgWrap('p26-iv1', 'СИМ', 'Конструктор сил на теле — главный визуал', 'Двигай ползунки 4 сил → видишь, как меняется равнодействующая и поведение тела.',
'<div style="display:grid;grid-template-columns:repeat(auto-fit,minmax(150px,1fr));gap:8px;margin-bottom:12px">'
+ '<label style="display:block;font-size:.84rem;color:#475569;background:#fff;padding:7px 11px;border-radius:8px;border:1px solid ' + ACCENT_SOFT + '">$F_т$ (вниз), Н: <b id="p26-ft" style="color:#7c3aed;font-family:JetBrains Mono,monospace">10</b><input type="range" id="p26-ft-r" min="0" max="30" step="1" value="10" style="display:block;width:100%;margin-top:6px;accent-color:#7c3aed"></label>'
+ '<label style="display:block;font-size:.84rem;color:#475569;background:#fff;padding:7px 11px;border-radius:8px;border:1px solid ' + ACCENT_SOFT + '">$N$ (вверх), Н: <b id="p26-N" style="color:#475569;font-family:JetBrains Mono,monospace">10</b><input type="range" id="p26-N-r" min="0" max="30" step="1" value="10" style="display:block;width:100%;margin-top:6px;accent-color:#475569"></label>'
+ '<label style="display:block;font-size:.84rem;color:#475569;background:#fff;padding:7px 11px;border-radius:8px;border:1px solid ' + ACCENT_SOFT + '">$F_{тяги}$ (вправо), Н: <b id="p26-Ft2" style="color:#10b981;font-family:JetBrains Mono,monospace">8</b><input type="range" id="p26-Ft2-r" min="0" max="30" step="1" value="8" style="display:block;width:100%;margin-top:6px;accent-color:#10b981"></label>'
+ '<label style="display:block;font-size:.84rem;color:#475569;background:#fff;padding:7px 11px;border-radius:8px;border:1px solid ' + ACCENT_SOFT + '">$F_{тр}$ (влево), Н: <b id="p26-Ftr" style="color:#92400e;font-family:JetBrains Mono,monospace">3</b><input type="range" id="p26-Ftr-r" min="0" max="30" step="1" value="3" style="display:block;width:100%;margin-top:6px;accent-color:#92400e"></label>'
+ '</div>'
+ '<svg id="p26-svg" viewBox="0 0 400 240" width="100%" style="max-width:600px;display:block;margin:0 auto;background:#fef2f2;border-radius:9px;border:1px solid ' + ACCENT_SOFT + '"></svg>'
+ '<div id="p26-info" style="background:' + ACCENT_SOFT + ';border-radius:9px;padding:10px 14px;margin-top:8px;font-size:.94rem;line-height:1.65"></div>');
/* IV-2 КАЛЬК */
h += wgWrap('p26-iv2', 'КАЛЬК', 'Сложение двух сил на одной прямой', '',
'<div style="display:grid;grid-template-columns:1fr 1fr 1fr;gap:8px;margin-bottom:10px">'
+ '<label style="display:block;font-size:.86rem;color:#475569;background:#fff;padding:8px 12px;border-radius:8px;border:1px solid ' + ACCENT_SOFT + '">$F_1$, Н: <b id="p26c-F1" style="color:' + ACCENT_D + ';font-family:JetBrains Mono,monospace">5</b><input type="range" id="p26c-F1-r" min="0" max="30" step="1" value="5" style="display:block;width:100%;margin-top:6px;accent-color:' + ACCENT + '"></label>'
+ '<label style="display:block;font-size:.86rem;color:#475569;background:#fff;padding:8px 12px;border-radius:8px;border:1px solid ' + ACCENT_SOFT + '">$F_2$, Н: <b id="p26c-F2" style="color:' + ACCENT_D + ';font-family:JetBrains Mono,monospace">3</b><input type="range" id="p26c-F2-r" min="0" max="30" step="1" value="3" style="display:block;width:100%;margin-top:6px;accent-color:' + ACCENT + '"></label>'
+ '<label style="display:block;font-size:.86rem;color:#475569;background:#fff;padding:8px 12px;border-radius:8px;border:1px solid ' + ACCENT_SOFT + '">Направления:<select id="p26c-dir" style="width:100%;margin-top:6px;padding:6px;border-radius:6px;border:1px solid ' + ACCENT_SOFT + ';font-family:inherit"><option value="same" selected>Сонаправлены</option><option value="opp">Противоположны</option></select></label>'
+ '</div>'
+ '<div id="p26c-out" style="background:' + ACCENT_SOFT + ';border-radius:9px;padding:12px 14px;font-size:.96rem;line-height:1.7"></div>');
/* IV-3 DnD */
h += wgWrap('p26-iv3', 'DnD', 'Куда направлена R?', '',
dndPool('p26-dnd', [
{ id:'a1', cat:'right', html:'$F_1 = 10$ Н →, $F_2 = 4$ Н →' },
{ id:'a2', cat:'right', html:'$F_1 = 8$ Н →, $F_2 = 5$ Н ←' },
{ id:'a3', cat:'left', html:'$F_1 = 3$ Н →, $F_2 = 9$ Н ←' },
{ id:'a4', cat:'left', html:'$F_1 = 5$ Н ←, $F_2 = 5$ Н ←' },
{ id:'a5', cat:'zero', html:'$F_1 = 7$ Н →, $F_2 = 7$ Н ←' },
{ id:'a6', cat:'zero', html:'Только $F_т$ и $N$ равны на горизонтальной опоре' }
], [
{ cat:'right', label:'R направлена →' },
{ cat:'left', label:'R направлена ←' },
{ cat:'zero', label:'R = 0' }
]));
/* IV-4 ТРН */
h += wgWrap('p26-iv4', 'ТРН', 'Тренажёр §26', '',
'<div id="p26-tr-host">'
+ quizQuestion('p26-tr', 0, '$F_1 = 6$ Н, $F_2 = 4$ Н, сонаправлены. R?', ['2 Н','4 Н','10 Н','24 Н'], 2)
+ quizQuestion('p26-tr', 1, '$F_1 = 10$ Н, $F_2 = 6$ Н, противоположны. Модуль R?', ['4 Н','6 Н','10 Н','16 Н'], 0)
+ quizQuestion('p26-tr', 2, 'Книга лежит на столе. Действуют $F_т$ и $N$. Чему равна R?', ['$mg$','$2mg$','$0$','$N - F_т$'], 2, 'Тело в покое → силы уравновешены, $R = 0$.')
+ quizQuestion('p26-tr', 3, 'На брусок действуют $F_{тяги} = 12$ Н → и $F_{тр} = 5$ Н ←. Куда движется?', ['Покоится','Влево','Вправо','Невозможно сказать'], 2, '$R = 12-5 = 7$ Н → — вправо.')
+ quizQuestion('p26-tr', 4, '$F_1 = F_2 = 8$ Н, противоположны. R?', ['0','8 Н','16 Н','Зависит от массы'], 0)
+ '</div>');
h += readButton('p26');
body.innerHTML = h;
function draw26(){
const Ft = +document.getElementById('p26-ft-r').value;
const N = +document.getElementById('p26-N-r').value;
const Ftt = +document.getElementById('p26-Ft2-r').value;
const Ftr = +document.getElementById('p26-Ftr-r').value;
document.getElementById('p26-ft').textContent = Ft;
document.getElementById('p26-N').textContent = N;
document.getElementById('p26-Ft2').textContent = Ftt;
document.getElementById('p26-Ftr').textContent = Ftr;
const Rx = Ftt - Ftr;
const Ry = Ft - N; // положительное Ry — вниз
const R = Math.hypot(Rx, Ry);
const cx = 200, cy = 130;
const sc = 3.5; // px/Н
let s = '';
// Земля
s += '<rect x="40" y="200" width="320" height="10" fill="#475569"/>';
for(let i = 0; i < 14; i++) s += '<line x1="' + (50 + i*22) + '" y1="210" x2="' + (44 + i*22) + '" y2="220" stroke="#0f172a" stroke-width="0.8"/>';
// Тело — куб
s += '<rect x="' + (cx-22) + '" y="' + (cy-22) + '" width="44" height="44" fill="#dc2626" stroke="#7f1d1d" stroke-width="1.5" rx="3"/>';
// Стрелки сил
function arrow(x1,y1,x2,y2,col,label){
const len = Math.hypot(x2-x1, y2-y1);
if(len < 4) return '';
const a = Math.atan2(y2-y1, x2-x1);
const ah = 8;
const ax1 = x2 - ah * Math.cos(a - 0.45);
const ay1 = y2 - ah * Math.sin(a - 0.45);
const ax2 = x2 - ah * Math.cos(a + 0.45);
const ay2 = y2 - ah * Math.sin(a + 0.45);
return '<line x1="' + x1 + '" y1="' + y1 + '" x2="' + x2.toFixed(1) + '" y2="' + y2.toFixed(1) + '" stroke="' + col + '" stroke-width="2.5"/>'
+ '<polygon points="' + x2.toFixed(1) + ',' + y2.toFixed(1) + ' ' + ax1.toFixed(1) + ',' + ay1.toFixed(1) + ' ' + ax2.toFixed(1) + ',' + ay2.toFixed(1) + '" fill="' + col + '"/>'
+ '<text x="' + (x2 + 6*Math.cos(a)).toFixed(1) + '" y="' + (y2 + 6*Math.sin(a) + 4).toFixed(1) + '" font-family="Inter,sans-serif" font-size="11" font-weight="700" fill="' + col + '">' + label + '</text>';
}
s += arrow(cx, cy, cx, cy + Ft * sc, '#7c3aed', 'F_т=' + Ft); // вниз
s += arrow(cx, cy, cx, cy - N * sc, '#475569', 'N=' + N); // вверх
s += arrow(cx, cy, cx + Ftt * sc, cy, '#10b981', 'F_тяги=' + Ftt); // вправо
s += arrow(cx, cy, cx - Ftr * sc, cy, '#92400e', 'F_тр=' + Ftr); // влево
// Равнодействующая
if(R > 0.1){
s += arrow(cx, cy, cx + Rx * sc, cy + Ry * sc, '#dc2626', 'R=' + R.toFixed(1));
}
document.getElementById('p26-svg').innerHTML = s;
// Info
let verdict;
if(R < 0.1) verdict = '<b style="color:#10b981">Силы уравновешены. R = 0</b> — тело в покое или движется равномерно.';
else if(Math.abs(Ry) < 0.1 && Rx > 0) verdict = '<b style="color:#dc2626">R = ' + Rx + ' Н вправо</b> — тело ускоряется вправо.';
else if(Math.abs(Ry) < 0.1 && Rx < 0) verdict = '<b style="color:#dc2626">R = ' + (-Rx) + ' Н влево</b> — тело ускоряется влево.';
else if(Math.abs(Rx) < 0.1 && Ry > 0) verdict = '<b style="color:#dc2626">R = ' + Ry + ' Н вниз</b> — тело падает / провалится.';
else if(Math.abs(Rx) < 0.1 && Ry < 0) verdict = '<b style="color:#dc2626">R = ' + (-Ry) + ' Н вверх</b> — тело подпрыгнет.';
else verdict = '<b style="color:#dc2626">R ≈ ' + R.toFixed(1) + ' Н</b> — тело ускоряется под углом.';
document.getElementById('p26-info').innerHTML = verdict + '<br><span style="font-size:.84rem;color:#475569">$R_x = F_{тяги} - F_{тр} = ' + Rx + '$, $R_y = F_т - N = ' + Ry + '$</span>';
renderMath(document.getElementById('p26-info'));
}
['p26-ft-r','p26-N-r','p26-Ft2-r','p26-Ftr-r'].forEach(id => document.getElementById(id).addEventListener('input', draw26));
draw26();
const upd26c = () => {
const F1 = +document.getElementById('p26c-F1-r').value;
const F2 = +document.getElementById('p26c-F2-r').value;
const dir = document.getElementById('p26c-dir').value;
document.getElementById('p26c-F1').textContent = F1;
document.getElementById('p26c-F2').textContent = F2;
let R;
if(dir === 'same') R = F1 + F2;
else R = Math.abs(F1 - F2);
let arrow1 = '→', arrow2 = '→';
if(dir === 'opp') arrow2 = '←';
let result;
if(dir === 'same') result = '$R = F_1 + F_2 = ' + F1 + ' + ' + F2 + ' = $ <b>' + R + '</b> Н (→)';
else {
const dir_arrow = F1 > F2 ? '→' : (F2 > F1 ? '←' : '');
result = '$R = |F_1 - F_2| = |' + F1 + ' - ' + F2 + '| = $ <b>' + R + '</b> Н ' + (R === 0 ? '<b style="color:#10b981">(равновесие)</b>' : '(' + dir_arrow + ')');
}
document.getElementById('p26c-out').innerHTML = result;
renderMath(document.getElementById('p26c-out'));
};
['p26c-F1-r','p26c-F2-r'].forEach(id => document.getElementById(id).addEventListener('input', upd26c));
document.getElementById('p26c-dir').addEventListener('change', upd26c);
upd26c();
wireDnd('p26-dnd', [
{ id:'a1', cat:'right' },{ id:'a2', cat:'right' },{ id:'a3', cat:'left' },
{ id:'a4', cat:'left' },{ id:'a5', cat:'zero' },{ id:'a6', cat:'zero' }
]);
wireQuiz('p26-tr-host', () => { if(window.addXp) window.addXp(15, 'tr-p26'); });
wireReadBtn('p26');
renderMath(body);
}
/* ========================================================== */
/* §27 — Трение. Сила трения */
/* ========================================================== */
function add_p27(){
const body = document.getElementById('p27-body');
if(!body) return;
let h = '';
h += makeCard('theory', 'Откуда берётся трение', '§ 27.1',
'При движении (или попытке движения) одного тела по поверхности другого возникает сила, '
+ 'мешающая движению — <b>сила трения</b> $\\vec F_{тр}$. Она направлена <b>против движения</b>.<br><br>'
+ 'Причины: <b>шероховатость</b> поверхностей и <b>притяжение молекул</b> в местах контакта.');
h += makeCard('rule', 'Виды трения', '§ 27.2',
'<ul style="padding-left:20px;margin:6px 0">'
+ '<li><b>Трение покоя</b> — мешает сдвинуть тело с места. Бывает максимальным, после чего тело начинает скользить.</li>'
+ '<li><b>Трение скольжения</b> — действует на скользящее тело. $F_{тр} \\approx \\mu N$, где $\\mu$ — коэффициент трения, $N$ — нормальная реакция (≈ вес).</li>'
+ '<li><b>Трение качения</b> — катит колесо. Намного меньше скольжения. Поэтому колесо — гениальное изобретение.</li>'
+ '</ul>'
+ '$F_{тр}$ <b>не зависит</b> (почти) от площади контакта, но зависит от $N$ и от шероховатости.');
h += makeCard('example', 'Где трение мешает, а где помогает', '§ 27.3',
'<b>Полезно:</b> идти и ехать (без трения — гололёд!), тормоза, гвозди в дереве, держать карандаш.<br>'
+ '<b>Мешает:</b> греет и изнашивает детали машин, замедляет движение. Чтобы уменьшить — смазывают маслом, ставят подшипники.');
/* IV-1 СИМ: брусок на поверхности с slider массы, μ, тяги */
h += wgWrap('p27-iv1', 'СИМ', 'Симулятор трения', 'Меняй массу, материал и силу тяги — посмотри, едет брусок или стоит.',
'<div style="display:grid;grid-template-columns:repeat(auto-fit,minmax(150px,1fr));gap:8px;margin-bottom:10px">'
+ '<label style="display:block;font-size:.86rem;color:#475569;background:#fff;padding:7px 11px;border-radius:8px;border:1px solid ' + ACCENT_SOFT + '">Масса $m$, кг: <b id="p27-m" style="color:' + ACCENT_D + ';font-family:JetBrains Mono,monospace">5</b><input type="range" id="p27-m-r" min="0.5" max="20" step="0.5" value="5" style="display:block;width:100%;margin-top:6px;accent-color:' + ACCENT + '"></label>'
+ '<label style="display:block;font-size:.86rem;color:#475569;background:#fff;padding:7px 11px;border-radius:8px;border:1px solid ' + ACCENT_SOFT + '">Сила тяги $F$, Н: <b id="p27-F" style="color:' + ACCENT_D + ';font-family:JetBrains Mono,monospace">15</b><input type="range" id="p27-F-r" min="0" max="100" step="1" value="15" style="display:block;width:100%;margin-top:6px;accent-color:' + ACCENT + '"></label>'
+ '<label style="display:block;font-size:.86rem;color:#475569;background:#fff;padding:7px 11px;border-radius:8px;border:1px solid ' + ACCENT_SOFT + '">Поверхность:<select id="p27-mu" style="width:100%;margin-top:6px;padding:6px;border-radius:6px;border:1px solid ' + ACCENT_SOFT + ';font-family:inherit"><option value="0.04">Лёд ($\\mu = 0{,}04$)</option><option value="0.2" selected>Сталь по стали ($0{,}2$)</option><option value="0.4">Дерево по дереву ($0{,}4$)</option><option value="0.7">Резина по асфальту ($0{,}7$)</option></select></label>'
+ '</div>'
+ '<svg id="p27-svg" viewBox="0 0 380 150" width="100%" style="max-width:600px;display:block;margin:0 auto;background:#fef2f2;border-radius:9px;border:1px solid ' + ACCENT_SOFT + '"></svg>'
+ '<div id="p27-info" style="background:' + ACCENT_SOFT + ';border-radius:9px;padding:10px 14px;margin-top:8px;font-size:.94rem;line-height:1.65"></div>');
/* IV-2 КВИЗ */
h += wgWrap('p27-iv2', 'КВИЗ', 'Трение', '',
'<div id="p27-q-host">'
+ quizQuestion('p27-q', 0, 'Куда направлена сила трения скольжения?', ['Перпендикулярно движению','Против движения','Вверх','Куда угодно'], 1)
+ quizQuestion('p27-q', 1, 'Какое трение наименьшее (при прочих равных)?', ['Покоя','Скольжения','Качения','Все одинаковы'], 2)
+ quizQuestion('p27-q', 2, 'Если на тело давит больший вес, $F_{тр}$ при скольжении…', ['Не меняется','Увеличивается','Уменьшается','Становится 0'], 1, '$F_{тр} \\sim N$, а $N$ зависит от веса.')
+ quizQuestion('p27-q', 3, 'Гололёд опасен, потому что коэф. трения…', ['Очень большой','Очень маленький','Не существует','Зависит от веса'], 1)
+ '</div>');
/* IV-3 DnD */
h += wgWrap('p27-iv3', 'DnD', 'Трение полезно или вредно?', '',
dndPool('p27-dnd', [
{ id:'a1', cat:'good', html:'Можно идти (нога не скользит)' },
{ id:'a2', cat:'good', html:'Тормоза машины' },
{ id:'a3', cat:'good', html:'Гвоздь держится в доске' },
{ id:'a4', cat:'bad', html:'Шестерёнки изнашиваются' },
{ id:'a5', cat:'bad', html:'Двигатель греется и теряет КПД' },
{ id:'a6', cat:'bad', html:'Лыжи скользят медленнее без смазки' }
], [
{ cat:'good', label:'Полезно' },
{ cat:'bad', label:'Мешает' }
]));
/* IV-4 ТРН */
h += wgWrap('p27-iv4', 'ТРН', 'Тренажёр §27', '',
'<div id="p27-tr-host">'
+ quizQuestion('p27-tr', 0, 'Брусок $m = 2$ кг, $\\mu = 0{,}3$. $F_{тр}$ при скольжении (g=10)?', ['3 Н','6 Н','10 Н','20 Н'], 1, '$N = mg = 20$ Н. $F_{тр} = \\mu N = 0{,}3 \\cdot 20 = 6$ Н.')
+ quizQuestion('p27-tr', 1, 'Чтобы брусок $m = 5$ кг сдвинуть, надо приложить $F > F_{тр\\,пок}$. Если $\\mu_{пок} = 0{,}4$ и $g = 10$, минимальная $F$?', ['10 Н','15 Н','20 Н','25 Н'], 2, '$F_{тр\\,пок} = \\mu N = 0{,}4 \\cdot 50 = 20$ Н.')
+ quizQuestion('p27-tr', 2, 'Подшипники нужны, чтобы…', ['Увеличить трение','Заменить трение скольжения трением качения','Прибавить веса','Соединить детали'], 1)
+ quizQuestion('p27-tr', 3, 'Если смазать ось маслом, $F_{тр}$ …', ['Увеличится','Уменьшится','Не изменится','Станет нулевой'], 1)
+ quizQuestion('p27-tr', 4, 'Лыжи покрывают воском, чтобы…', ['Защитить от воды','Уменьшить трение','Увеличить массу','Покрасить'], 1)
+ '</div>');
h += readButton('p27');
body.innerHTML = h;
function draw27(){
const m = +document.getElementById('p27-m-r').value;
const F = +document.getElementById('p27-F-r').value;
const mu = +document.getElementById('p27-mu').value;
document.getElementById('p27-m').textContent = m;
document.getElementById('p27-F').textContent = F;
const g = 10;
const N = m * g;
const FtrMax = mu * N;
const moving = F > FtrMax;
const Ftr = moving ? FtrMax : F;
const R = moving ? (F - FtrMax) : 0;
const W = 380, H = 150, baseY = 110;
let s = '';
s += '<rect x="0" y="' + baseY + '" width="' + W + '" height="30" fill="#94a3b8"/>';
for(let i = 0; i < 18; i++) s += '<line x1="' + (i*22+5) + '" y1="' + baseY + '" x2="' + (i*22+15) + '" y2="' + (baseY+8) + '" stroke="#374151" stroke-width="0.8"/>';
// Брусок
const bx = 130, bw = 80, bh = 40;
s += '<rect x="' + bx + '" y="' + (baseY - bh) + '" width="' + bw + '" height="' + bh + '" fill="#dc2626" stroke="#7f1d1d" stroke-width="1.5" rx="3"/>';
s += '<text x="' + (bx + bw/2) + '" y="' + (baseY - bh/2 + 5) + '" text-anchor="middle" font-family="Inter,sans-serif" font-size="11" font-weight="700" fill="#fff">m=' + m + ' кг</text>';
// F тяги (вправо)
const fLen = Math.min(80, F * 1.5);
s += '<line x1="' + (bx + bw) + '" y1="' + (baseY - bh/2) + '" x2="' + (bx + bw + fLen) + '" y2="' + (baseY - bh/2) + '" stroke="#10b981" stroke-width="3"/>';
s += '<polygon points="' + (bx + bw + fLen) + ',' + (baseY - bh/2) + ' ' + (bx + bw + fLen - 8) + ',' + (baseY - bh/2 - 5) + ' ' + (bx + bw + fLen - 8) + ',' + (baseY - bh/2 + 5) + '" fill="#10b981"/>';
s += '<text x="' + (bx + bw + fLen + 5) + '" y="' + (baseY - bh/2 - 4) + '" font-family="Inter,sans-serif" font-size="11" font-weight="700" fill="#10b981">F=' + F + 'Н</text>';
// F_тр (влево, от низа бруска)
const trLen = Math.min(80, Ftr * 1.5);
if(trLen > 2){
s += '<line x1="' + bx + '" y1="' + (baseY - 6) + '" x2="' + (bx - trLen) + '" y2="' + (baseY - 6) + '" stroke="#92400e" stroke-width="3"/>';
s += '<polygon points="' + (bx - trLen) + ',' + (baseY - 6) + ' ' + (bx - trLen + 8) + ',' + (baseY - 11) + ' ' + (bx - trLen + 8) + ',' + (baseY - 1) + '" fill="#92400e"/>';
s += '<text x="' + (bx - trLen - 5) + '" y="' + (baseY - 12) + '" text-anchor="end" font-family="Inter,sans-serif" font-size="11" font-weight="700" fill="#92400e">F_тр=' + Ftr.toFixed(1) + 'Н</text>';
}
// Verdict
s += '<text x="' + (W/2) + '" y="20" text-anchor="middle" font-family="Unbounded,sans-serif" font-size="13" font-weight="800" fill="' + (moving ? '#dc2626' : '#10b981') + '">' + (moving ? 'ЕДЕТ →' : 'ПОКОИТСЯ') + '</text>';
document.getElementById('p27-svg').innerHTML = s;
document.getElementById('p27-info').innerHTML =
'$N = mg = ' + N + '$ Н, &nbsp; $F_{тр\\,max} = \\mu N = ' + FtrMax.toFixed(1) + '$ Н<br>'
+ (moving
? '$F$ > $F_{тр}$ → тело ускоряется, $R = F - F_{тр} = ' + R.toFixed(1) + '$ Н вправо.'
: '$F \\le F_{тр\\,max}$ → трение покоя уравновешивает силу тяги, $R = 0$. Тело <b>не сдвинется</b>.');
renderMath(document.getElementById('p27-info'));
}
['p27-m-r','p27-F-r'].forEach(id => document.getElementById(id).addEventListener('input', draw27));
document.getElementById('p27-mu').addEventListener('change', draw27);
draw27();
wireDnd('p27-dnd', [
{ id:'a1', cat:'good' },{ id:'a2', cat:'good' },{ id:'a3', cat:'good' },
{ id:'a4', cat:'bad' },{ id:'a5', cat:'bad' },{ id:'a6', cat:'bad' }
]);
wireQuiz('p27-q-host', () => { if(window.addXp) window.addXp(10, 'q-p27'); });
wireQuiz('p27-tr-host', () => { if(window.addXp) window.addXp(15, 'tr-p27'); });
wireReadBtn('p27');
renderMath(body);
}
/* ========================================================== */
/* Финал главы 3 — 10 боссов + ачивка «Мастер движения» */
/* ========================================================== */
function add_final3(){
const body = document.getElementById('final3-body');
if(!body) return;
let h = '';
h += '<div style="background:linear-gradient(135deg,' + ACCENT_SOFT + ',#fff1f2);border:1.5px solid ' + ACCENT + ';border-radius:14px;padding:16px;margin-bottom:14px;text-align:center">'
+ '<div style="font-family:Unbounded,sans-serif;font-weight:800;font-size:1.12rem;color:' + ACCENT_D + '">Финал главы 3: победи 10 боссов</div>'
+ '<div style="font-size:.88rem;color:#475569;margin-top:5px">Реши все 10 задач — получишь ачивку «Мастер движения» и +50 XP.</div>'
+ '<div style="height:10px;background:#fff;border-radius:6px;overflow:hidden;margin-top:12px;border:1px solid ' + ACCENT_SOFT + '"><div id="ch3-fin-fill" style="height:100%;background:linear-gradient(90deg,' + ACCENT + ',' + ACCENT_D + ');width:0%;transition:width .4s"></div></div>'
+ '<div id="ch3-fin-lab" style="font-size:.84rem;color:#475569;margin-top:6px">Побеждено: 0 / 10</div>'
+ '</div>';
const bosses = [
{ n:1, tag:'§16', title:'Скорость',
q:'Автомобиль за $t = 4$ с проехал $s = 80$ м равномерно. Найди $v$ в м/с.',
hint:'$v = s/t = 80/4 = 20$ м/с.',
ans:20, tol:0.2, step:'0.1' },
{ n:2, tag:'§18', title:'Средняя скорость',
q:'Велосипедист: $v_1 = 10$ м/с за $t_1 = 60$ с, потом $v_2 = 4$ м/с за $t_2 = 60$ с. $\\langle v\\rangle$ в м/с?',
hint:'$\\langle v\\rangle = (10\\cdot 60 + 4\\cdot 60)/(60+60) = 840/120 = 7$ м/с.',
ans:7, tol:0.2, step:'0.1' },
{ n:3, tag:'§20', title:'Плотность',
q:'Брусок $V = 50$ см³, $m = 390$ г. Плотность $\\rho$ в г/см³? Что за вещество?',
hint:'$\\rho = 390/50 = 7{,}8$ г/см³ — железо.',
ans:7.8, tol:0.1, step:'0.1' },
{ n:4, tag:'§22', title:'Сила тяжести на Земле',
q:'$m = 4$ кг, $g = 9{,}8$ Н/кг. Найди $F_т$ в Н (округли до десятых).',
hint:'$F_т = mg = 4 \\cdot 9{,}8 = 39{,}2$ Н.',
ans:39.2, tol:0.2, step:'0.1' },
{ n:5, tag:'§22', title:'Сила тяжести на Луне',
q:'Тот же камень $m = 4$ кг на Луне ($g_Л = 1{,}6$ Н/кг). $F_т$ в Н?',
hint:'$F_т = 4 \\cdot 1{,}6 = 6{,}4$ Н.',
ans:6.4, tol:0.1, step:'0.1' },
{ n:6, tag:'§25', title:'Динамометр',
q:'Динамометр показал $P = 7{,}5$ Н на Земле ($g = 10$ Н/кг). Масса тела в граммах?',
hint:'$m = P/g = 0{,}75$ кг $= 750$ г.',
ans:750, tol:5, step:'1' },
{ n:7, tag:'§26', title:'Равнодействующая 2 сил',
q:'$F_1 = 30$ Н →, $F_2 = 18$ Н ←. Модуль R в Н?',
hint:'$R = |30 - 18| = 12$ Н вправо.',
ans:12, tol:0.5, step:'1' },
{ n:8, tag:'§26', title:'Равнодействующая 3 сил',
q:'$F_1 = 25$ Н →, $F_2 = 15$ Н →, $F_3 = 30$ Н ←. Модуль R в Н?',
hint:'Вправо: $25 + 15 = 40$. Влево: $30$. $R = |40 - 30| = 10$ Н вправо.',
ans:10, tol:0.5, step:'1' },
{ n:9, tag:'§27', title:'Сила трения',
q:'Брусок $m = 3$ кг скользит по столу с $\\mu = 0{,}2$ ($g = 10$ Н/кг). $F_{тр}$ в Н?',
hint:'$N = mg = 30$ Н. $F_{тр} = \\mu N = 0{,}2 \\cdot 30 = 6$ Н.',
ans:6, tol:0.2, step:'0.1' },
{ n:10, tag:'синтез', title:'Мастер движения',
q:'Брусок $m = 2$ кг с силой тяги $F = 8$ Н вправо. Коэф. трения $\\mu = 0{,}3$, $g = 10$ Н/кг. Найди равнодействующую R в Н (со знаком: + если вправо, − если влево, 0 если покой).',
hint:'$F_{тр\\,max} = \\mu mg = 0{,}3 \\cdot 2 \\cdot 10 = 6$ Н. $F = 8$ Н > $F_{тр\\,max} = 6$ Н — едет. $R = 8 - 6 = 2$ Н (вправо).',
ans:2, tol:0.2, step:'0.1' }
];
const STATE_KEY = 'physics7_ch3_final_bosses';
let solved = {};
try{ solved = JSON.parse(localStorage.getItem(STATE_KEY) || '{}') || {}; }catch(e){}
bosses.forEach(b => {
const isSolved = !!solved[b.n];
h += '<div data-boss="' + b.n + '" style="background:#fff;border:2px solid ' + (isSolved ? '#10b981' : ACCENT_SOFT) + ';border-radius:12px;padding:14px 16px;margin-bottom:12px;box-shadow:' + (isSolved ? '0 0 0 3px rgba(16,185,129,.16)' : '0 2px 8px rgba(0,0,0,.05)') + '">'
+ '<div style="display:flex;align-items:center;gap:10px;margin-bottom:8px;flex-wrap:wrap">'
+ '<span style="background:' + ACCENT + ';color:#fff;padding:3px 10px;border-radius:99px;font-size:.7rem;font-weight:800;letter-spacing:.04em">' + b.tag + '</span>'
+ '<span style="font-family:Unbounded,sans-serif;font-weight:800;font-size:.96rem;color:#0f172a">Босс ' + b.n + '. ' + b.title + '</span>'
+ '</div>'
+ '<div style="padding:10px 12px;background:' + ACCENT_SOFT + ';border-radius:8px;margin-bottom:10px;font-size:.94rem;line-height:1.55">' + b.q + '</div>'
+ '<div style="display:flex;gap:8px;flex-wrap:wrap;align-items:center">'
+ '<input type="number" step="' + b.step + '" class="boss-inp" data-n="' + b.n + '" placeholder="число" style="padding:8px 12px;border:1.5px solid ' + ACCENT_SOFT + ';border-radius:8px;width:140px;text-align:center;font-family:JetBrains Mono,monospace;font-size:.95rem"' + (isSolved ? ' value="' + b.ans + '" disabled' : '') + '>'
+ '<button class="boss-go" data-n="' + b.n + '" type="button" style="background:linear-gradient(135deg,' + ACCENT + ',' + ACCENT_D + ');color:#fff;border:none;padding:8px 16px;border-radius:9px;font-weight:700;font-size:.88rem;cursor:pointer;font-family:inherit"' + (isSolved ? ' disabled' : '') + '>Атаковать</button>'
+ '<button class="boss-hint" data-n="' + b.n + '" type="button" style="background:#fff;color:#475569;border:1.5px solid ' + ACCENT_SOFT + ';padding:8px 14px;border-radius:9px;font-weight:600;font-size:.86rem;cursor:pointer;font-family:inherit">Подсказка</button>'
+ '</div>'
+ '<div class="boss-hint-txt" data-n="' + b.n + '" style="margin-top:8px;padding:9px 13px;background:#fef3c7;border-left:3px solid #f59e0b;border-radius:6px;font-size:.86rem;line-height:1.5;display:none">' + b.hint + '</div>'
+ '<div class="boss-fb" data-n="' + b.n + '" style="margin-top:8px;padding:9px 13px;border-radius:8px;font-weight:600;font-size:.88rem;line-height:1.45;' + (isSolved ? 'background:#d1fae5;color:#065f46;border-left:4px solid #10b981' : 'display:none') + '">' + (isSolved ? '&#10003; Босс повержен! +20 XP.' : '') + '</div>'
+ '</div>';
});
h += '<div id="ch3-mastered" style="margin-top:14px;padding:14px 18px;border-radius:12px;background:linear-gradient(135deg,#fef3c7,#fde68a);border:1.5px solid #f59e0b;display:none;align-items:center;gap:12px"><svg style="width:32px;height:32px;stroke:#92400e;fill:none;stroke-width:2;stroke-linecap:round;stroke-linejoin:round" viewBox="0 0 24 24"><polygon points="12,2 15,9 22,9.3 17,14 18.5,21 12,17 5.5,21 7,14 2,9.3 9,9"/></svg><div style="flex:1"><div style="font-weight:800;color:#92400e;font-family:Unbounded,sans-serif">Ачивка «Мастер движения» получена!</div><div style="font-size:.86rem;color:#78350f;margin-top:2px">+50 XP &middot; Глава 3 полностью пройдена.</div></div></div>';
body.innerHTML = h;
renderMath(body);
function updateBar(){
const cnt = bosses.filter(b => solved[b.n]).length;
document.getElementById('ch3-fin-fill').style.width = (cnt * 100 / bosses.length) + '%';
document.getElementById('ch3-fin-lab').textContent = 'Побеждено: ' + cnt + ' / ' + bosses.length;
if(cnt === bosses.length){
document.getElementById('ch3-mastered').style.display = 'flex';
const ACH_KEY = 'physics7_ch3_master';
if(localStorage.getItem(ACH_KEY) !== '1'){
localStorage.setItem(ACH_KEY, '1');
if(window.addXp) window.addXp(50, 'ach-ch3-master');
if(window.achievement) window.achievement('ch_done', 'Мастер движения');
}
}
}
updateBar();
body.querySelectorAll('.boss-hint').forEach(btn => btn.addEventListener('click', () => {
const n = btn.dataset.n;
const txt = body.querySelector('.boss-hint-txt[data-n="' + n + '"]');
if(txt) txt.style.display = txt.style.display === 'none' ? 'block' : 'none';
}));
body.querySelectorAll('.boss-go').forEach(btn => btn.addEventListener('click', () => {
const n = +btn.dataset.n;
const b = bosses.find(x => x.n === n);
const inp = body.querySelector('.boss-inp[data-n="' + n + '"]');
const fb = body.querySelector('.boss-fb[data-n="' + n + '"]');
const card = body.querySelector('[data-boss="' + n + '"]');
const v = parseFloat((inp.value || '').replace(',', '.'));
if(isNaN(v)){
fb.style.display = 'block';
fb.style.background = '#fee2e2'; fb.style.color = '#7f1d1d'; fb.style.borderLeft = '4px solid #dc2626';
fb.textContent = 'Введи число.';
return;
}
if(Math.abs(v - b.ans) < b.tol){
fb.style.display = 'block';
fb.style.background = '#d1fae5'; fb.style.color = '#065f46'; fb.style.borderLeft = '4px solid #10b981';
fb.innerHTML = '&#10003; Босс повержен! +20 XP.';
card.style.border = '2px solid #10b981';
card.style.boxShadow = '0 0 0 3px rgba(16,185,129,.16)';
btn.disabled = true; inp.disabled = true;
if(!solved[n]){
solved[n] = true;
try{ localStorage.setItem(STATE_KEY, JSON.stringify(solved)); }catch(e){}
if(window.addXp) window.addXp(20, 'boss-ch3-' + n);
updateBar();
}
} else {
fb.style.display = 'block';
fb.style.background = '#fee2e2'; fb.style.color = '#7f1d1d'; fb.style.borderLeft = '4px solid #dc2626';
fb.textContent = 'Не то. Перепроверь решение.';
}
}));
body.querySelectorAll('.boss-inp').forEach(inp => inp.addEventListener('keydown', e => {
if(e.key === 'Enter'){ e.preventDefault(); body.querySelector('.boss-go[data-n="' + inp.dataset.n + '"]').click(); }
}));
}
window.PHYS7_CH3_WIDGETS = {
p14: add_p14, p15: add_p15, p16: add_p16, p17: add_p17,
p18: add_p18, p19: add_p19, p20: add_p20,
p21: add_p21, p22: add_p22, p23: add_p23,
p24: add_p24, p25: add_p25,
p26: add_p26, p27: add_p27,
final3: add_final3
};
})();