Files
Learn_System/frontend/js/phys7_ch3_widgets.js
T
Maxim Dolgolyov 96a2097e70 feat(phys7 ch3): Phase 3 — кинематика, §§14-20
Глава 3 «Движение и силы», часть 1 (без сил — Phase 4).
Файл: phys7_ch3_widgets.js (1082 строк, экспорт p14..p20).

§14 Мех. движение, относительность:
- 3 карточки (СО / относительность / самолёт-облако)
- IV-1 СИМ: переключатель СО (Земля/Поезд) с анимацией поезда vs дерева
- IV-2 КВИЗ 4 / IV-3 DnD 6→2 / IV-4 ТРН 4

§15 Траектория, путь, время:
- 3 карточки + IV-1 СИМ: интерактивная сетка SVG, клик → точка → построение
  ломаной траектории + автоподсчёт пути (1 клетка = 1 м), кнопка «Сброс»
- DnD 6→3 (прямолин/криволин/замкн) + квиз 3 + тренажёр 5

§16 Равномерное движение, скорость:
- 3 карточки (определение / 4 единицы скорости / расчёт)
- IV-1 СИМ: автомобиль на дороге, slider v=1..30 м/с, анимация движения с
  пересчётом пройденного пути в реальном времени
- IV-2 КАЛЬК v=s/t, slider s и t, вывод в м/с и км/ч
- DnD 6→3 (пешеход/машина/самолёт) + тренажёр 5

§17 Графики s(t) и v(t) — ГЛАВНЫЙ ВИЗУАЛ КИНЕМАТИКИ:
- 3 карточки (наклон=v / горизонталь / параллельные = равные v)
- IV-1 СИМ: рядом 2 графика SVG — s(t) с двумя прямыми (v1, v2) и v(t) с
  двумя горизонтальными + заливка площади под v1 как «s = v·t»; slider v1, v2
- КВИЗ 4 / DnD 6 → 4 типа линий / ТРН 4

§18 Средняя скорость:
- 3 карточки (формула / ловушка ≠ среднеарифм. / пешеход + метро)
- IV-1 КАЛЬК: 4 slider'а v1/t1/v2/t2, средневзвешенная vs ловушка с авто-
  индикатором «СОВПАЛО (t1=t2) / НЕВЕРНО»
- КВИЗ 3 / DnD 6→2 / ТРН 4

§19 Инерция:
- 3 карточки (закон Галилея / масса как мера инертности / пассажиры в автобусе)
- IV-1 СИМ: шарик на поверхности SVG с кнопкой «Запустить» и переключателем
  «Трение ВКЛ/ВЫКЛ»: с трением — тормозит и останавливается; без — катится вечно
- КВИЗ 4 / DnD 6→3 (легко/средне/тяжело) / ТРН 4

§20 Масса. Плотность:
- 3 карточки (масса / формула ρ=m/V / таблица 11 веществ)
- IV-1 КАЛЬК ρ=m/V: slider m=1..20000 г и V=1..2000 см³, вывод в г/см³ и кг/м³
  + автоопределение вещества (газ/пенопласт/дерево/вода/.../золото)
- IV-2 СИМ: 8 кнопок материалов → SVG-куб 1 дм³ меняет цвет и подпись массы
- DnD 6→3 (лёгкий/средний/тяжёлый) / ТРН 5

Парсинг OK, smoke-test (7 экспортов) OK.

Phase 3 — 7 из 14 § главы 3. Силы (§§21-27) + финал «Мастер движения» — Phase 4.
2026-05-30 11:10:48 +03:00

1083 lines
82 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);
}
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..p27 + final3 — в Phase 4 (силы)
};
})();