// Физика 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
};

})();
