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', + 'При действиях с величинами числа умножаются/делятся отдельно от единиц:' + + ''); + + /* 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]) => + '').join('') + + '
Объектм/скм/ч
' + nm + '' + ms + '' + kh + '
'); + + /* IV-2: главный калькулятор-конвертер */ + h += wgWrap('p6-iv2', 'КАЛЬК', 'Конвертер величин', 'Выбери, что переводишь — двигай slider.', + '
' + + '' + + '' + + '
' + + '
'); + + /* 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', + 'Правила:' + + '
    ' + + '
  1. Определи цену деления по двум соседним подписанным отметкам.
  2. ' + + '
  3. Смотри на шкалу перпендикулярно (избегая параллакса).
  4. ' + + '
  5. Если стрелка между делениями — округли до ближайшего.
  6. ' + + '
  7. Запиши с погрешностью $\\pm C/2$.
  8. ' + + '
'); + + /* IV-1: главный визуал — виртуальная линейка с переключаемой ценой деления */ + h += wgWrap('p7-iv1', 'СИМ', 'Виртуальная линейка', 'Меняй цену деления и положение риски — наблюдай, как меняется точность.', + '
' + + '' + + '' + + '
' + + '
' + + '' + + '
' + + '
'); + + /* IV-2: калькулятор цены деления */ + h += wgWrap('p7-iv2', 'КАЛЬК', 'Калькулятор цены деления', 'Введи две подписанные отметки и количество делений.', + '
' + + '' + + '' + + '' + + '
' + + '
' + + 'Цена деления: $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 + '
' + + '
' + + '' + + '' + + '' + + '
' + + '' + + '
' + (isSolved ? '✓ Босс повержен! +20 XP.' : '') + '
' + + '
'; + }); + + h += ''; + + 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 }; })();