diff --git a/frontend/js/phys7_ch1_widgets.js b/frontend/js/phys7_ch1_widgets.js
index 13dbe37..d459ef3 100644
--- a/frontend/js/phys7_ch1_widgets.js
+++ b/frontend/js/phys7_ch1_widgets.js
@@ -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',
+ 'Складывать, вычитать и сравнивать можно только однородные величины (одного типа) '
+ + 'и только в одинаковых единицах . Нельзя сложить $2$ кг и $3$ м — это разные физические величины. '
+ + 'Нельзя сложить $5$ м и $2$ см без предварительного перевода в одну единицу: $5$ м $+ 0{,}02$ м $= 5{,}02$ м.');
+
+ h += makeCard('rule', 'Перевод между разными системами', '§ 6.2',
+ 'Скорость: $1$ км/ч $= \\dfrac{1000\\,\\text{м}}{3600\\,\\text{с}} \\approx 0{,}278$ м/с; '
+ + 'обратно: $1$ м/с $= 3{,}6$ км/ч. '
+ + 'Плотность: $1$ г/см³ $= 1000$ кг/м³ (умножаем на $1000$). '
+ + 'Мощность: $1$ л. с. $= 736$ Вт; $1$ кВт $= 1000$ Вт. '
+ + 'Энергия / работа: $1$ кДж $= 1000$ Дж, $1$ кВт·ч $= 3{,}6 \\cdot 10^6$ Дж.');
+
+ h += makeCard('example', 'Умножение / деление величин', '§ 6.3',
+ 'При действиях с величинами числа умножаются/делятся отдельно от единиц :'
+ + '
'
+ + '$v \\cdot t = 5\\,\\dfrac{\\text{м}}{\\text{с}} \\cdot 4\\,\\text{с} = 20\\,\\text{м}$ (секунды сократились — получили путь) '
+ + '$\\dfrac{m}{V} = \\dfrac{200\\,\\text{г}}{50\\,\\text{см}^3} = 4\\,\\dfrac{\\text{г}}{\\text{см}^3}$ '
+ + '$S = 2\\,\\text{м} \\cdot 3\\,\\text{м} = 6\\,\\text{м}^2$ (метры в квадрате) '
+ + ' ');
+
+ /* IV-1: визуал — таблица переводов скорости */
+ h += wgWrap('p6-iv1', 'СИМ', 'Скорости в природе', 'Сравни типичные скорости — одно и то же в разных единицах.',
+ ''
+ + 'Объект м/с км/ч '
+ + [['Улитка',0.001,0.0036],['Пешеход',1.4,5],['Велосипедист',5.5,20],['Автомобиль в городе',16.7,60],['Гепард',31,112],['Самолёт',250,900],['Звук в воздухе',340,1224]].map(([nm,ms,kh]) =>
+ '' + nm + ' ' + ms + ' ' + kh + ' ').join('')
+ + '
');
+
+ /* IV-2: главный калькулятор-конвертер */
+ h += wgWrap('p6-iv2', 'КАЛЬК', 'Конвертер величин', 'Выбери, что переводишь — двигай slider.',
+ ''
+ + 'Что переводим:Скорость Плотность Мощность Энергия Время '
+ + 'Значение: 60 '
+ + '
'
+ + '
');
+
+ /* 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 задач на действия с величинами.',
+ ''
+ + 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$ м.')
+ + '
');
+
+ 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 + '$ км/ч $= $ ' + (v / 3.6).toFixed(2) + ' м/с'
+ + ' $' + v + '$ м/с $= $ ' + (v * 3.6).toFixed(1) + ' км/ч';
+ } else if(kind === 'den'){
+ html = '$' + v + '$ г/см³ $= $ ' + (v * 1000).toLocaleString('ru-RU') + ' кг/м³';
+ } else if(kind === 'pow'){
+ html = '$' + v + '$ кВт $= $ ' + (v * 1000).toLocaleString('ru-RU') + ' Вт'
+ + ' $' + v + '$ л. с. $= $ ' + (v * 736).toLocaleString('ru-RU') + ' Вт';
+ } else if(kind === 'en'){
+ html = '$' + v + '$ кДж $= $ ' + (v * 1000).toLocaleString('ru-RU') + ' Дж'
+ + ' $' + v + '$ кВт·ч $= $ ' + (v * 3.6e6).toExponential(2) + ' Дж';
+ } else if(kind === 't'){
+ html = '$' + v + '$ мин $= $ ' + (v * 60).toLocaleString('ru-RU') + ' с'
+ + ' $' + v + '$ ч $= $ ' + (v * 3600).toLocaleString('ru-RU') + ' с';
+ }
+ 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',
+ 'Цена деления — это значение наименьшего деления на шкале прибора. Формула: '
+ + '$$C = \\dfrac{X_2 - X_1}{N}$$ где $X_1$, $X_2$ — два соседних подписанных значения, $N$ — число малых делений между ними. '
+ + 'Пример: на линейке между отметками $0$ и $1$ см нанесено $10$ делений. '
+ + '$C = (1 - 0)/10 = 0{,}1$ см $= 1$ мм.');
+
+ h += makeCard('rule', 'Погрешность измерения', '§ 7.2',
+ 'Любое измерение имеет погрешность $\\Delta X$. Обычно берут половину цены деления : '
+ + '$$\\Delta X = \\dfrac{C}{2}$$'
+ + 'Результат записывают как $X = X_0 \\pm \\Delta X$, например: $l = (15{,}3 \\pm 0{,}05)$ см. '
+ + 'Чем меньше цена деления , тем точнее прибор.');
+
+ h += makeCard('example', 'Как снять отсчёт', '§ 7.3',
+ 'Правила: '
+ + ''
+ + 'Определи цену деления по двум соседним подписанным отметкам. '
+ + 'Смотри на шкалу перпендикулярно (избегая параллакса). '
+ + 'Если стрелка между делениями — округли до ближайшего. '
+ + 'Запиши с погрешностью $\\pm C/2$. '
+ + ' ');
+
+ /* IV-1: главный визуал — виртуальная линейка с переключаемой ценой деления */
+ h += wgWrap('p7-iv1', 'СИМ', 'Виртуальная линейка', 'Меняй цену деления и положение риски — наблюдай, как меняется точность.',
+ ''
+ + 'Цена деления:10 мм (грубая) 5 мм 2 мм 1 мм (стандартная) '
+ + 'Положение риски, мм: 37 '
+ + '
'
+ + ''
+ + ' '
+ + '
'
+ + '
');
+
+ /* IV-2: калькулятор цены деления */
+ h += wgWrap('p7-iv2', 'КАЛЬК', 'Калькулятор цены деления', 'Введи две подписанные отметки и количество делений.',
+ ''
+ + '$X_1$: 0 '
+ + '$X_2$: 10 '
+ + '$N$ (делений): 10 '
+ + '
'
+ + ''
+ + 'Цена деления: $C = \\dfrac{X_2 - X_1}{N} = \\dfrac{$10 $ - $0 $}{$10 $} = $ 1 '
+ + ' Погрешность: $\\Delta X = C/2 = $ 0,5 '
+ + '
');
+
+ /* 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 задач на цену деления и погрешность.',
+ ''
+ + 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.')
+ + '
');
+
+ 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 += ' ';
+ for(let mm = 0; mm <= totalMm; mm += cd){
+ const x = leftPad + mm * pxPerMm;
+ const isCm = mm % 10 === 0;
+ const tickH = isCm ? 18 : 10;
+ s += ' ';
+ if(isCm) s += '' + (mm/10) + ' ';
+ }
+ s += 'см ';
+ // Риска
+ const rx = leftPad + pos * pxPerMm;
+ s += ' ';
+ s += ' ';
+ 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 =
+ 'Цена деления: ' + cd + ' мм '
+ + 'Отсчёт (округлён до деления): ' + rounded + ' мм '
+ + 'Запись с погрешностью: $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 += ''
+ + '
Финал главы 1: победи 5 боссов
'
+ + '
Реши все 5 задач — получишь ачивку «Юный физик» и +50 XP.
'
+ + '
'
+ + '
Побеждено: 0 / 5
'
+ + '
';
+
+ const bosses = [
+ { n:1, tag:'§4–6', 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 += ''
+ + '
'
+ + '' + b.tag + ' '
+ + 'Босс ' + b.n + '. ' + b.title + ' '
+ + '
'
+ + '
' + b.q + '
'
+ + '
'
+ + ' '
+ + 'Атаковать '
+ + 'Подсказка '
+ + '
'
+ + '
' + b.hint + '
'
+ + '
' + (isSolved ? '✓ Босс повержен! +20 XP.' : '') + '
'
+ + '
';
+ });
+
+ h += 'Ачивка «Юный физик» получена!
+50 XP · Глава 1 полностью пройдена.
';
+
+ 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 = '✓ Босс повержен! +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
};
})();