feat(phys7 ch1): Phase 1 Wave 3 — §6, §7, Финал главы 1 «Юный физик»

WIDGETS (+390 строк, теперь 1139 строк, экспорт p1..p7 + final1):

§6 «Действия над физическими величинами»:
- 3 карточки (однородные величины / переводы скорости/плотности/мощности/энергии /
  умножение единиц m·V → плотность)
- IV-1 СИМ: таблица типичных скоростей (улитка → звук в воздухе) в м/с и км/ч
- IV-2 КАЛЬК конвертер (главный визуал §6): 5 типов величин (скорость/плотность/
  мощность/энергия/время), slider значения → перевод во все связанные единицы
- IV-3 DnD: 8 эквивалентных пар (1 мин=60 с, 1 кВт=1000 Вт, и т.д.)
- IV-4 ТРН: 5 задач (км/ч↔м/с, г/см³→кг/м³, ч+мин→с, сумма в разных приставках)

§7 «Цена деления. Погрешность»:
- 3 карточки (C = (X2-X1)/N / ΔX = C/2, запись X±ΔX / правила снятия отсчёта)
- IV-1 СИМ (главный визуал §7): виртуальная линейка SVG со сменой цены деления
  (10/5/2/1 мм) и подвижной красной риской; авто-округление до ближайшего деления,
  запись результата с погрешностью в KaTeX
- IV-2 КАЛЬК: 3 slider'а X1/X2/N → формула C и ΔX
- IV-3 DnD: 6 приборов (линейка/штангенциркуль/микрометр/термометры/секундомер)
  → 6 типичных цен деления
- IV-4 ТРН: 5 задач на цену деления и погрешность

ФИНАЛ ГЛАВЫ 1 (5 боссов + ачивка «Юный физик» +50 XP):
- Боссы (синтез §4-§7):
  1. Площадь листа A4 в м² (перевод см→м + S=ab)
  2. Плотность бруска с m=135 г и V=50 см³ (алюминий)
  3. Скорость 90 км/ч в м/с
  4. Цена деления (5 см = 50 делений → 1 мм)
  5. Погрешность мензурки (C=2 мл → ΔV=1 мл) — Магистр-задача
- Прогресс-бар «Побеждено: N/5», localStorage-сохранение между сессиями,
  +20 XP за каждого босса, ачивка +50 XP при 5/5 победах.
- Все боссы с подсказками (toggle), Enter-submit, валидация числа.

Все 4 IV в §6 и §7 wireDnd/wireQuiz/калькуляторы привязаны. parse-check, smoke-test (8 экспортов) пройдены.
This commit is contained in:
Maxim Dolgolyov
2026-05-30 10:50:45 +03:00
parent 83aad34e8b
commit 903bc5cf42
+392 -2
View File
@@ -736,14 +736,404 @@ function add_p5(){
renderMath(body);
}
/* ========================================================== */
/* §6 — Действия над физическими величинами */
/* ========================================================== */
function add_p6(){
const body = document.getElementById('p6-body');
if(!body) return;
let h = '';
h += makeCard('theory', 'Правила сложения и сравнения', '§ 6.1',
'Складывать, вычитать и сравнивать можно <b>только однородные величины</b> (одного типа) '
+ 'и только в <b>одинаковых единицах</b>. Нельзя сложить $2$ кг и $3$ м — это разные физические величины. '
+ 'Нельзя сложить $5$ м и $2$ см без предварительного перевода в одну единицу: $5$ м $+ 0{,}02$ м $= 5{,}02$ м.');
h += makeCard('rule', 'Перевод между разными системами', '§ 6.2',
'<b>Скорость:</b> $1$ км/ч $= \\dfrac{1000\\,\\text{м}}{3600\\,\\text{с}} \\approx 0{,}278$ м/с;<br>'
+ 'обратно: $1$ м/с $= 3{,}6$ км/ч.<br><br>'
+ '<b>Плотность:</b> $1$ г/см³ $= 1000$ кг/м³ (умножаем на $1000$).<br><br>'
+ '<b>Мощность:</b> $1$ л. с. $= 736$ Вт; $1$ кВт $= 1000$ Вт.<br><br>'
+ '<b>Энергия / работа:</b> $1$ кДж $= 1000$ Дж, $1$ кВт·ч $= 3{,}6 \\cdot 10^6$ Дж.');
h += makeCard('example', 'Умножение / деление величин', '§ 6.3',
'При действиях с величинами <b>числа умножаются/делятся отдельно от единиц</b>:'
+ '<ul style="padding-left:20px;margin:6px 0">'
+ '<li>$v \\cdot t = 5\\,\\dfrac{\\text{м}}{\\text{с}} \\cdot 4\\,\\text{с} = 20\\,\\text{м}$ (секунды сократились — получили путь)</li>'
+ '<li>$\\dfrac{m}{V} = \\dfrac{200\\,\\text{г}}{50\\,\\text{см}^3} = 4\\,\\dfrac{\\text{г}}{\\text{см}^3}$</li>'
+ '<li>$S = 2\\,\\text{м} \\cdot 3\\,\\text{м} = 6\\,\\text{м}^2$ (метры в квадрате)</li>'
+ '</ul>');
/* IV-1: визуал — таблица переводов скорости */
h += wgWrap('p6-iv1', 'СИМ', 'Скорости в природе', 'Сравни типичные скорости — одно и то же в разных единицах.',
'<table style="width:100%;border-collapse:collapse;font-size:.9rem">'
+ '<tr style="background:#e0f2fe"><th style="padding:6px 10px;text-align:left;border-bottom:2px solid #0284c7">Объект</th><th style="padding:6px 10px;text-align:right;border-bottom:2px solid #0284c7">м/с</th><th style="padding:6px 10px;text-align:right;border-bottom:2px solid #0284c7">км/ч</th></tr>'
+ [['Улитка',0.001,0.0036],['Пешеход',1.4,5],['Велосипедист',5.5,20],['Автомобиль в городе',16.7,60],['Гепард',31,112],['Самолёт',250,900],['Звук в воздухе',340,1224]].map(([nm,ms,kh]) =>
'<tr><td style="padding:5px 10px;border-bottom:1px solid #e0f2fe">' + nm + '</td><td style="padding:5px 10px;text-align:right;border-bottom:1px solid #e0f2fe;font-family:JetBrains Mono,monospace">' + ms + '</td><td style="padding:5px 10px;text-align:right;border-bottom:1px solid #e0f2fe;font-family:JetBrains Mono,monospace">' + kh + '</td></tr>').join('')
+ '</table>');
/* IV-2: главный калькулятор-конвертер */
h += wgWrap('p6-iv2', 'КАЛЬК', 'Конвертер величин', 'Выбери, что переводишь — двигай slider.',
'<div style="display:grid;grid-template-columns:1fr 1fr;gap:8px;margin-bottom:12px">'
+ '<label style="display:block;font-size:.86rem;color:#475569;background:#fff;padding:8px 12px;border-radius:8px;border:1px solid #bae6fd">Что переводим:<select id="p6-kind" style="width:100%;margin-top:5px;padding:6px;border-radius:6px;border:1px solid #bae6fd;font-family:inherit"><option value="vel" selected>Скорость</option><option value="den">Плотность</option><option value="pow">Мощность</option><option value="en">Энергия</option><option value="t">Время</option></select></label>'
+ '<label style="display:block;font-size:.86rem;color:#475569;background:#fff;padding:8px 12px;border-radius:8px;border:1px solid #bae6fd">Значение: <b id="p6-v" style="color:#0c4a6e;font-family:JetBrains Mono,monospace">60</b><input type="range" id="p6-v-r" min="1" max="1000" step="1" value="60" style="display:block;width:100%;margin-top:6px;accent-color:#0284c7"></label>'
+ '</div>'
+ '<div id="p6-out" style="background:#e0f2fe;border-radius:9px;padding:12px 14px;font-size:.94rem;line-height:1.7"></div>');
/* IV-3: DnD равные величины */
h += wgWrap('p6-iv3', 'DnD', 'Найди равные пары', 'Соедини 8 эквивалентных записей.',
dndPool('p6-dnd', [
{ id:'a1', cat:'60', html:'$1$ мин' },
{ id:'a2', cat:'1000', html:'$1$ кВт' },
{ id:'a3', cat:'10', html:'$10$ см' },
{ id:'a4', cat:'500', html:'$\\dfrac{1}{2}$ кг' },
{ id:'a5', cat:'60', html:'$60$ с' },
{ id:'a6', cat:'1000', html:'$1000$ Вт' },
{ id:'a7', cat:'10', html:'$0{,}1$ м' },
{ id:'a8', cat:'500', html:'$500$ г' }
], [
{ cat:'60', label:'1 мин = 60 с' },
{ cat:'1000', label:'1 кВт = 1000 Вт' },
{ cat:'10', label:'10 см = 0,1 м' },
{ cat:'500', label:'0,5 кг = 500 г' }
]));
/* IV-4: тренажёр */
h += wgWrap('p6-iv4', 'ТРН', 'Тренажёр §6', '5 задач на действия с величинами.',
'<div id="p6-tr-host">'
+ quizQuestion('p6-tr', 0, '$72$ км/ч $= ?$ м/с', ['10','15','20','25'], 2, '$72 / 3{,}6 = 20$ м/с.')
+ quizQuestion('p6-tr', 1, '$5$ м/с $= ?$ км/ч', ['15','18','20','25'], 1, '$5 \\cdot 3{,}6 = 18$ км/ч.')
+ quizQuestion('p6-tr', 2, '$2{,}5$ г/см³ $= ?$ кг/м³', ['25','250','2500','25000'], 2, '$2{,}5 \\cdot 1000 = 2500$ кг/м³.')
+ quizQuestion('p6-tr', 3, '$3$ ч $20$ мин $= ?$ с', ['12 000','11 200','12 200','13 200'], 0, '$3 \\cdot 3600 + 20 \\cdot 60 = 10\\,800 + 1\\,200 = 12\\,000$ с.')
+ quizQuestion('p6-tr', 4, 'Сложить: $5$ м $+ 40$ см $+ 10$ мм $= ?$ м', ['5,41','5,5','5,401','5,41'], 0, '$5 + 0{,}40 + 0{,}010 = 5{,}41$ м.')
+ '</div>');
h += readButton('p6');
body.innerHTML = h;
// Wire converter §6 IV-2
const updConv6 = () => {
const kind = document.getElementById('p6-kind').value;
const v = +document.getElementById('p6-v-r').value;
document.getElementById('p6-v').textContent = v;
const out = document.getElementById('p6-out');
let html = '';
if(kind === 'vel'){
html = '$' + v + '$ км/ч $= $ <b style="color:#0c4a6e;font-family:JetBrains Mono,monospace">' + (v / 3.6).toFixed(2) + '</b> м/с'
+ '<br>$' + v + '$ м/с $= $ <b style="color:#0c4a6e;font-family:JetBrains Mono,monospace">' + (v * 3.6).toFixed(1) + '</b> км/ч';
} else if(kind === 'den'){
html = '$' + v + '$ г/см³ $= $ <b style="color:#0c4a6e;font-family:JetBrains Mono,monospace">' + (v * 1000).toLocaleString('ru-RU') + '</b> кг/м³';
} else if(kind === 'pow'){
html = '$' + v + '$ кВт $= $ <b style="color:#0c4a6e;font-family:JetBrains Mono,monospace">' + (v * 1000).toLocaleString('ru-RU') + '</b> Вт'
+ '<br>$' + v + '$ л. с. $= $ <b style="color:#0c4a6e;font-family:JetBrains Mono,monospace">' + (v * 736).toLocaleString('ru-RU') + '</b> Вт';
} else if(kind === 'en'){
html = '$' + v + '$ кДж $= $ <b style="color:#0c4a6e;font-family:JetBrains Mono,monospace">' + (v * 1000).toLocaleString('ru-RU') + '</b> Дж'
+ '<br>$' + v + '$ кВт·ч $= $ <b style="color:#0c4a6e;font-family:JetBrains Mono,monospace">' + (v * 3.6e6).toExponential(2) + '</b> Дж';
} else if(kind === 't'){
html = '$' + v + '$ мин $= $ <b style="color:#0c4a6e;font-family:JetBrains Mono,monospace">' + (v * 60).toLocaleString('ru-RU') + '</b> с'
+ '<br>$' + v + '$ ч $= $ <b style="color:#0c4a6e;font-family:JetBrains Mono,monospace">' + (v * 3600).toLocaleString('ru-RU') + '</b> с';
}
out.innerHTML = html;
renderMath(out);
};
document.getElementById('p6-kind').addEventListener('change', updConv6);
document.getElementById('p6-v-r').addEventListener('input', updConv6);
updConv6();
wireDnd('p6-dnd', [
{ id:'a1', cat:'60' },{ id:'a2', cat:'1000' },{ id:'a3', cat:'10' },{ id:'a4', cat:'500' },
{ id:'a5', cat:'60' },{ id:'a6', cat:'1000' },{ id:'a7', cat:'10' },{ id:'a8', cat:'500' }
], []);
wireQuiz('p6-tr-host', () => { if(window.addXp) window.addXp(15, 'tr-p6'); });
wireReadBtn('p6');
renderMath(body);
}
/* ========================================================== */
/* §7 — Измерительные приборы. Цена деления. Погрешность */
/* ========================================================== */
function add_p7(){
const body = document.getElementById('p7-body');
if(!body) return;
let h = '';
h += makeCard('theory', 'Цена деления шкалы', '§ 7.1',
'<b>Цена деления</b> — это значение наименьшего деления на шкале прибора. Формула:<br>'
+ '$$C = \\dfrac{X_2 - X_1}{N}$$ где $X_1$, $X_2$ — два соседних подписанных значения, $N$ — число малых делений между ними.<br><br>'
+ '<b>Пример:</b> на линейке между отметками $0$ и $1$ см нанесено $10$ делений. '
+ '$C = (1 - 0)/10 = 0{,}1$ см $= 1$ мм.');
h += makeCard('rule', 'Погрешность измерения', '§ 7.2',
'Любое измерение имеет <b>погрешность</b> $\\Delta X$. Обычно берут <b>половину цены деления</b>:<br>'
+ '$$\\Delta X = \\dfrac{C}{2}$$'
+ 'Результат записывают как $X = X_0 \\pm \\Delta X$, например: $l = (15{,}3 \\pm 0{,}05)$ см.<br><br>'
+ 'Чем <b>меньше цена деления</b>, тем <b>точнее</b> прибор.');
h += makeCard('example', 'Как снять отсчёт', '§ 7.3',
'<b>Правила:</b>'
+ '<ol style="padding-left:20px;margin:6px 0">'
+ '<li>Определи цену деления по двум соседним подписанным отметкам.</li>'
+ '<li>Смотри на шкалу <b>перпендикулярно</b> (избегая параллакса).</li>'
+ '<li>Если стрелка между делениями — округли до ближайшего.</li>'
+ '<li>Запиши с погрешностью $\\pm C/2$.</li>'
+ '</ol>');
/* IV-1: главный визуал — виртуальная линейка с переключаемой ценой деления */
h += wgWrap('p7-iv1', 'СИМ', 'Виртуальная линейка', 'Меняй цену деления и положение риски — наблюдай, как меняется точность.',
'<div style="display:grid;grid-template-columns:1fr 1fr;gap:8px;margin-bottom:14px">'
+ '<label style="display:block;font-size:.86rem;color:#475569;background:#fff;padding:8px 12px;border-radius:8px;border:1px solid #bae6fd">Цена деления:<select id="p7-cd" style="width:100%;margin-top:5px;padding:6px;border-radius:6px;border:1px solid #bae6fd;font-family:inherit"><option value="10">10 мм (грубая)</option><option value="5">5 мм</option><option value="2">2 мм</option><option value="1" selected>1 мм (стандартная)</option></select></label>'
+ '<label style="display:block;font-size:.86rem;color:#475569;background:#fff;padding:8px 12px;border-radius:8px;border:1px solid #bae6fd">Положение риски, мм: <b id="p7-pos" style="color:#0c4a6e;font-family:JetBrains Mono,monospace">37</b><input type="range" id="p7-pos-r" min="0" max="100" step="1" value="37" style="display:block;width:100%;margin-top:6px;accent-color:#0284c7"></label>'
+ '</div>'
+ '<div style="background:#fff;border:1.5px solid #bae6fd;border-radius:10px;padding:14px;text-align:center;overflow-x:auto">'
+ '<svg id="p7-svg" viewBox="0 0 360 80" width="100%" style="max-width:600px;display:block;margin:0 auto"></svg>'
+ '</div>'
+ '<div id="p7-info" style="background:#e0f2fe;border-radius:9px;padding:10px 14px;margin-top:10px;font-size:.94rem;line-height:1.7"></div>');
/* IV-2: калькулятор цены деления */
h += wgWrap('p7-iv2', 'КАЛЬК', 'Калькулятор цены деления', 'Введи две подписанные отметки и количество делений.',
'<div style="display:grid;grid-template-columns:repeat(3,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 #bae6fd">$X_1$: <b id="p7-x1" style="color:#0c4a6e;font-family:JetBrains Mono,monospace">0</b><input type="range" id="p7-x1-r" min="0" max="50" step="1" value="0" style="display:block;width:100%;margin-top:6px;accent-color:#0284c7"></label>'
+ '<label style="display:block;font-size:.86rem;color:#475569;background:#fff;padding:8px 12px;border-radius:8px;border:1px solid #bae6fd">$X_2$: <b id="p7-x2" style="color:#0c4a6e;font-family:JetBrains Mono,monospace">10</b><input type="range" id="p7-x2-r" min="5" max="100" step="1" value="10" style="display:block;width:100%;margin-top:6px;accent-color:#0284c7"></label>'
+ '<label style="display:block;font-size:.86rem;color:#475569;background:#fff;padding:8px 12px;border-radius:8px;border:1px solid #bae6fd">$N$ (делений): <b id="p7-N" style="color:#0c4a6e;font-family:JetBrains Mono,monospace">10</b><input type="range" id="p7-N-r" min="2" max="50" step="1" value="10" style="display:block;width:100%;margin-top:6px;accent-color:#0284c7"></label>'
+ '</div>'
+ '<div style="background:#e0f2fe;border-radius:9px;padding:12px 14px;font-size:.94rem;line-height:1.7">'
+ 'Цена деления: $C = \\dfrac{X_2 - X_1}{N} = \\dfrac{$<span id="p7-cd2-x2">10</span>$ - $<span id="p7-cd2-x1">0</span>$}{$<span id="p7-cd2-N">10</span>$} = $ <b id="p7-cd2" style="color:#0c4a6e;font-family:JetBrains Mono,monospace">1</b>'
+ '<br>Погрешность: $\\Delta X = C/2 = $ <b id="p7-dx" style="color:#dc2626;font-family:JetBrains Mono,monospace">0,5</b>'
+ '</div>');
/* IV-3: DnD — соедини прибор с подходящей ценой деления */
h += wgWrap('p7-iv3', 'DnD', 'Прибор и его точность', 'Соотнеси приборы и их типичную цену деления.',
dndPool('p7-dnd', [
{ id:'a1', cat:'mm', html:'Школьная линейка' },
{ id:'a2', cat:'01mm', html:'Штангенциркуль' },
{ id:'a3', cat:'001mm', html:'Микрометр' },
{ id:'a4', cat:'1c', html:'Бытовой термометр' },
{ id:'a5', cat:'01c', html:'Лабораторный термометр' },
{ id:'a6', cat:'01s', html:'Школьный секундомер' }
], [
{ cat:'mm', label:'1 мм' },
{ cat:'01mm', label:'0,1 мм' },
{ cat:'001mm', label:'0,01 мм' },
{ cat:'1c', label:'1 °C' },
{ cat:'01c', label:'0,1 °C' },
{ cat:'01s', label:'0,1 с' }
]));
/* IV-4: тренажёр */
h += wgWrap('p7-iv4', 'ТРН', 'Тренажёр §7', '5 задач на цену деления и погрешность.',
'<div id="p7-tr-host">'
+ quizQuestion('p7-tr', 0, 'Между отметками $0$ и $1$ см на линейке $10$ маленьких делений. Цена деления?', ['0,1 мм','1 мм','5 мм','10 мм'], 1, '$C = (10-0)/10 = 1$ мм.')
+ quizQuestion('p7-tr', 1, 'Цена деления $C = 0{,}5$ см. Погрешность?', ['0,25 мм','0,25 см','0,5 см','5 мм'], 1, '$\\Delta X = C/2 = 0{,}25$ см.')
+ quizQuestion('p7-tr', 2, 'Какой прибор точнее: с $C = 1$ мм или с $C = 0{,}1$ мм?', ['Первый','Второй','Они одинаково точны','Зависит от величины'], 1, 'Чем меньше цена деления, тем точнее прибор.')
+ quizQuestion('p7-tr', 3, 'Длина $l = (12{,}3 \\pm 0{,}05)$ см. Какова цена деления линейки?', ['0,05 см','0,1 см','0,5 см','1 см'], 1, '$C = 2 \\Delta X = 2 \\cdot 0{,}05 = 0{,}1$ см.')
+ quizQuestion('p7-tr', 4, 'Стрелка термометра стоит между $20$ и $21$ °C, ближе к $20$. Цена деления $1$ °C. Запиши температуру.', ['20 °C','20,5 °C','21 °C','20,3 °C'], 0, 'Округляем до ближайшего деления — $20$ °C; запись с погрешностью: $(20 \\pm 0{,}5)$ °C.')
+ '</div>');
h += readButton('p7');
body.innerHTML = h;
// §7 IV-1 wire: SVG ruler
function drawRuler(){
const cd = +document.getElementById('p7-cd').value;
const pos = +document.getElementById('p7-pos-r').value;
const W = 360, leftPad = 14, scaleW = W - 2 * leftPad;
const totalMm = 100;
const pxPerMm = scaleW / totalMm;
let s = '';
s += '<rect x="0" y="20" width="' + W + '" height="40" fill="#fef3c7" stroke="#92400e" stroke-width="1.5" rx="2"/>';
for(let mm = 0; mm <= totalMm; mm += cd){
const x = leftPad + mm * pxPerMm;
const isCm = mm % 10 === 0;
const tickH = isCm ? 18 : 10;
s += '<line x1="' + x.toFixed(1) + '" y1="20" x2="' + x.toFixed(1) + '" y2="' + (20 + tickH) + '" stroke="#0f172a" stroke-width="' + (isCm ? 1.5 : 1) + '"/>';
if(isCm) s += '<text x="' + x.toFixed(1) + '" y="56" text-anchor="middle" font-family="Inter,sans-serif" font-size="9" font-weight="600" fill="#0f172a">' + (mm/10) + '</text>';
}
s += '<text x="' + (W - 6) + '" y="34" text-anchor="end" font-family="Inter,sans-serif" font-size="10" font-weight="700" fill="#92400e">см</text>';
// Риска
const rx = leftPad + pos * pxPerMm;
s += '<polygon points="' + (rx-6) + ',8 ' + (rx+6) + ',8 ' + rx + ',22" fill="#dc2626"/>';
s += '<line x1="' + rx + '" y1="22" x2="' + rx + '" y2="64" stroke="#dc2626" stroke-width="2" stroke-dasharray="3 2"/>';
document.getElementById('p7-svg').innerHTML = s;
// Info
document.getElementById('p7-pos').textContent = pos;
// Округление до ближайшей цены деления
const rounded = Math.round(pos / cd) * cd;
const dx = cd / 2;
document.getElementById('p7-info').innerHTML =
'Цена деления: <b style="font-family:JetBrains Mono,monospace;color:#0c4a6e">' + cd + ' мм</b><br>'
+ 'Отсчёт (округлён до деления): <b style="font-family:JetBrains Mono,monospace;color:#0c4a6e">' + rounded + ' мм</b><br>'
+ 'Запись с погрешностью: $l = (' + rounded + ' \\pm ' + dx + ')$ мм';
renderMath(document.getElementById('p7-info'));
}
document.getElementById('p7-cd').addEventListener('change', drawRuler);
document.getElementById('p7-pos-r').addEventListener('input', drawRuler);
drawRuler();
// §7 IV-2 wire: calc
const updCalc = () => {
let x1 = +document.getElementById('p7-x1-r').value;
let x2 = +document.getElementById('p7-x2-r').value;
const N = +document.getElementById('p7-N-r').value;
if(x2 <= x1){ x2 = x1 + 1; document.getElementById('p7-x2-r').value = x2; }
document.getElementById('p7-x1').textContent = x1;
document.getElementById('p7-x2').textContent = x2;
document.getElementById('p7-N').textContent = N;
const C = (x2 - x1) / N;
document.getElementById('p7-cd2-x1').textContent = x1;
document.getElementById('p7-cd2-x2').textContent = x2;
document.getElementById('p7-cd2-N').textContent = N;
document.getElementById('p7-cd2').textContent = (+C.toPrecision(4)).toString().replace('.', ',');
document.getElementById('p7-dx').textContent = (+(C/2).toPrecision(4)).toString().replace('.', ',');
};
['p7-x1-r','p7-x2-r','p7-N-r'].forEach(id => document.getElementById(id).addEventListener('input', updCalc));
updCalc();
wireDnd('p7-dnd', [
{ id:'a1', cat:'mm' },{ id:'a2', cat:'01mm' },{ id:'a3', cat:'001mm' },
{ id:'a4', cat:'1c' },{ id:'a5', cat:'01c' },{ id:'a6', cat:'01s' }
], []);
wireQuiz('p7-tr-host', () => { if(window.addXp) window.addXp(15, 'tr-p7'); });
wireReadBtn('p7');
renderMath(body);
}
/* ========================================================== */
/* Финал главы 1 — 5 боссов + ачивка «Юный физик» (+50 XP) */
/* ========================================================== */
function add_final1(){
const body = document.getElementById('final1-body');
if(!body) return;
let h = '';
h += '<div style="background:linear-gradient(135deg,#e0e7ff,#ede9fe);border:1.5px solid #4f46e5;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:#312e81">Финал главы 1: победи 5 боссов</div>'
+ '<div style="font-size:.88rem;color:#475569;margin-top:5px">Реши все 5 задач — получишь ачивку «Юный физик» и +50 XP.</div>'
+ '<div style="height:10px;background:#fff;border-radius:6px;overflow:hidden;margin-top:12px;border:1px solid #c7d2fe"><div id="ch1-fin-fill" style="height:100%;background:linear-gradient(90deg,#4f46e5,#7c3aed);width:0%;transition:width .4s"></div></div>'
+ '<div id="ch1-fin-lab" style="font-size:.84rem;color:#475569;margin-top:6px">Побеждено: 0 / 5</div>'
+ '</div>';
const bosses = [
{ n:1, tag:'§46', title:'Перевод и площадь',
q:'Прямоугольный лист бумаги: $a = 21$ см, $b = 30$ см. Найди площадь в м².',
hint:'$a = 0{,}21$ м, $b = 0{,}30$ м. $S = a b = 0{,}21 \\cdot 0{,}30 = 0{,}063$ м².',
ans:0.063, tol:0.001, step:'0.001' },
{ n:2, tag:'§4 + §6', title:'Плотность бруска',
q:'Брусок $a = 4$ см, $b = 5$ см, $c = 2{,}5$ см, $m = 135$ г. Найди $\\rho$ в г/см³.',
hint:'$V = 4 \\cdot 5 \\cdot 2{,}5 = 50$ см³. $\\rho = m/V = 135/50 = 2{,}7$ г/см³ (алюминий).',
ans:2.7, tol:0.05, step:'0.01' },
{ n:3, tag:'§6', title:'Скорость в м/с',
q:'Автомобиль едет $v = 90$ км/ч. Найди $v$ в м/с.',
hint:'$v = 90/3{,}6 = 25$ м/с.',
ans:25, tol:0.2, step:'0.1' },
{ n:4, tag:'§7', title:'Цена деления',
q:'Между отметками $10$ см и $15$ см на линейке $50$ малых делений. Какова цена деления в мм?',
hint:'$X_2 - X_1 = 5$ см $= 50$ мм. $C = 50/50 = 1$ мм.',
ans:1, tol:0.05, step:'0.1' },
{ n:5, tag:'§7', title:'Юный физик (погрешность)',
q:'Цена деления мензурки $C = 2$ мл. Отсчёт $V = 84$ мл. Запиши погрешность $\\Delta V$ в мл.',
hint:'$\\Delta V = C/2 = 2/2 = 1$ мл. (Запись: $V = (84 \\pm 1)$ мл.)',
ans:1, tol:0.05, step:'0.1' }
];
const STATE_KEY = 'physics7_ch1_final_bosses';
let solved = {};
try{ solved = JSON.parse(localStorage.getItem(STATE_KEY) || '{}') || {}; }catch(e){}
bosses.forEach(b => {
const isSolved = !!solved[b.n];
h += '<div class="boss-card" data-boss="' + b.n + '" style="background:#fff;border:2px solid ' + (isSolved ? '#10b981' : '#bae6fd') + ';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:#4f46e5;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:#f0f9ff;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 #bae6fd;border-radius:8px;width:130px;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,#4f46e5,#7c3aed);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 #bae6fd;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="ch1-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; Глава 1 полностью пройдена.</div></div></div>';
body.innerHTML = h;
renderMath(body);
function updateBar(){
const cnt = bosses.filter(b => solved[b.n]).length;
document.getElementById('ch1-fin-fill').style.width = (cnt * 100 / bosses.length) + '%';
document.getElementById('ch1-fin-lab').textContent = 'Побеждено: ' + cnt + ' / ' + bosses.length;
if(cnt === bosses.length){
document.getElementById('ch1-mastered').style.display = 'flex';
const ACH_KEY = 'physics7_ch1_yphys';
if(localStorage.getItem(ACH_KEY) !== '1'){
localStorage.setItem(ACH_KEY, '1');
if(window.addXp) window.addXp(50, 'ach-ch1-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('.boss-card[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-ch1-' + 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_CH1_WIDGETS = {
p1: add_p1,
p2: add_p2,
p3: add_p3,
p4: add_p4,
p5: add_p5
// p6, p7, final1 — в Wave 3
p5: add_p5,
p6: add_p6,
p7: add_p7,
final1: add_final1
};
})();