2bf7ff7ef1
Все 6 ЛР физики 7 закрыты. Файл phys7_lab_widgets.js (726 строк, 6 экспортов: lr1..lr6). Палитра cyan. Подключение через обновлённый gen_phys7_lab.js: script-тег + hook в goTo (удаление placeholder + вызов widgets). Каждая ЛР содержит: - Цель (goal card, голубая) - Оборудование (equip card, оранжевая) - Ход работы (steps card, фиолетовая) — пронумерованный список - СИМ-виджет (интерактивная симуляция прибора) - ТБЛ-виджет (таблица измерений) - ВОПР-виджет (3 контрольных вопроса с авто-проверкой) - Вывод (concl card, зелёная) - Кнопка «Сдать ЛР» (+30 XP, localStorage-фиксация) ЛР-1 «Цена деления» (§7): - 4 виртуальных прибора (линейка/термометр/мензурка/динамометр) с SVG-шкалами - Таблица C для всех 4 - 3 контрольных вопроса ЛР-2 «Измерение длины» (§4, §7): - 3 предмета на выбор (карандаш/тетрадь/брусок), SVG с линейкой ниже, риска на длине + запись (l ± 0,5) мм - Таблица 3 измерений ЛР-3 «Объём вытеснением» (§4): - 3 тела (камень/гайка/болт), 2 SVG-мензурки рядом (V1=100 и V2=100+V), стрелка «опускаем» между ними, авто-расчёт V = V2 − V1 - Таблица 3 измерений ЛР-4 «Неравномерное движение» (§18): - Шарик на наклонной плоскости, slider угла 10..60°, кнопка «Запустить», анимация скатывания (квадратичная по времени, эмпирически быстрее на больших углах) - Таблица 3 углов с разной средней скоростью ЛР-5 «Плотность» (§20): - 3 образца на выбор (54г/156г/272г, V=20 см³ каждый), SVG-весы+мензурка, расчёт ρ = m/V и автоопределение материала (алюминий/железо/золото) - Таблица плотностей 9 веществ ЛР-6 «Сила трения» (§27): - SVG: брусок с грузами, динамометр, разные поверхности из <select> (дерево/пластик/резина/лёд: μ от 0.04 до 0.5) - slider массы 100..500 г → авто N и Ftr через динамометр - Таблица 5 измерений с разными грузами → видно Ftr ~ N АЧИВКА «Лаборант 7 класса» +80 XP — автоматически при сдаче всех 6 ЛР (проверка через localStorage в wireSubmit). Парсинг OK, smoke (6 экспортов) OK.
727 lines
56 KiB
JavaScript
727 lines
56 KiB
JavaScript
// Физика 7 · Лабораторный практикум · 6 виртуальных ЛР.
|
||
// Палитра cyan (#0891b2). Каждая ЛР: Цель + Оборудование + Ход + Симуляция +
|
||
// Таблица измерений + Контрольные вопросы + Кнопка «Сдать ЛР».
|
||
// Ачивка «Лаборант 7 класса» (+80 XP) — за прохождение всех 6.
|
||
|
||
(function(){
|
||
'use strict';
|
||
|
||
const ACCENT = '#0891b2';
|
||
const ACCENT_D = '#0e7490';
|
||
const ACCENT_SOFT = '#cffafe';
|
||
|
||
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, body){
|
||
const colorByKind = { goal:ACCENT, equip:'#d97706', steps:'#7c3aed', concl:'#10b981' };
|
||
const labelByKind = { goal:'Цель', equip:'Оборудование', steps:'Ход работы', concl:'Вывод' };
|
||
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:12px;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-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 submitBtn(lrId){
|
||
return '<div style="text-align:center;margin-top:18px"><button class="ph7-lr-submit" data-lr="' + lrId + '" '
|
||
+ 'style="background:linear-gradient(135deg,' + ACCENT + ',' + ACCENT_D + ');color:#fff;border:none;padding:11px 26px;border-radius:11px;font-weight:700;font-size:.96rem;cursor:pointer;font-family:inherit;display:inline-flex;align-items:center;gap:8px;box-shadow:0 4px 14px rgba(8,145,178,.32)">'
|
||
+ '<svg viewBox="0 0 24 24" style="width:18px;height:18px;stroke:currentColor;fill:none;stroke-width:2.5;stroke-linecap:round;stroke-linejoin:round"><polyline points="20 6 9 17 4 12"/></svg>'
|
||
+ 'Сдать ЛР <span style="opacity:.85;font-size:.84rem">+30 XP</span>'
|
||
+ '</button><div id="ph7-lr-fb-' + lrId + '" style="margin-top:10px;font-size:.92rem;font-weight:700"></div></div>';
|
||
}
|
||
|
||
function wireSubmit(lrId){
|
||
const btn = document.querySelector('.ph7-lr-submit[data-lr="' + lrId + '"]');
|
||
if(!btn) return;
|
||
const KEY = 'physics7_lab_done_' + lrId;
|
||
if(localStorage.getItem(KEY) === '1'){
|
||
btn.innerHTML = '<svg viewBox="0 0 24 24" style="width:18px;height:18px;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', () => {
|
||
if(localStorage.getItem(KEY) === '1') return;
|
||
localStorage.setItem(KEY, '1');
|
||
if(typeof window.bumpProgress === 'function') window.bumpProgress(lrId, 100);
|
||
if(typeof window.addXp === 'function') window.addXp(30, 'lr-' + lrId);
|
||
btn.innerHTML = '<svg viewBox="0 0 24 24" style="width:18px;height:18px;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';
|
||
const fb = document.getElementById('ph7-lr-fb-' + lrId);
|
||
if(fb){ fb.style.color = '#047857'; fb.textContent = '+30 XP получено.'; }
|
||
// Проверка ачивки «Лаборант»
|
||
const allDone = ['lr1','lr2','lr3','lr4','lr5','lr6'].every(k => localStorage.getItem('physics7_lab_done_' + k) === '1');
|
||
if(allDone && localStorage.getItem('physics7_lab_master') !== '1'){
|
||
localStorage.setItem('physics7_lab_master', '1');
|
||
if(typeof window.addXp === 'function') window.addXp(80, 'lab-master');
|
||
if(typeof window.achievement === 'function') window.achievement('all_labs', 'Лаборант 7 класса');
|
||
if(fb) fb.innerHTML = '+30 XP. <span style="color:#92400e">✓ Ачивка «Лаборант 7 класса» +80 XP!</span>';
|
||
}
|
||
});
|
||
}
|
||
|
||
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" 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,'"') + '">' + 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){
|
||
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 = '✓ Верно!' + (explain ? ' ' + explain : '');
|
||
} 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 = '✗ Правильно: «' + opts[correct].textContent + '».' + (explain ? ' ' + explain : '');
|
||
}
|
||
done.add(qDiv);
|
||
}));
|
||
});
|
||
}
|
||
|
||
/* ========================================================== */
|
||
/* ЛР-1 — Цена деления шкалы измерительного прибора */
|
||
/* ========================================================== */
|
||
function build_lr1(){
|
||
const body = document.getElementById('lr1-body');
|
||
if(!body) return;
|
||
let h = '';
|
||
|
||
h += makeCard('goal', 'Цель',
|
||
'Научиться определять цену деления любого измерительного прибора по двум подписанным значениям и числу делений между ними.');
|
||
|
||
h += makeCard('equip', 'Оборудование (виртуальное)',
|
||
'<ul style="padding-left:20px;margin:5px 0"><li>Линейка</li><li>Мензурка</li><li>Термометр</li><li>Динамометр</li></ul>');
|
||
|
||
h += makeCard('steps', 'Ход работы',
|
||
'<ol style="padding-left:20px;margin:6px 0">'
|
||
+ '<li>Найди на шкале <b>две соседние подписанные отметки</b> $X_1$ и $X_2$.</li>'
|
||
+ '<li>Посчитай число малых делений <b>$N$</b> между ними.</li>'
|
||
+ '<li>Вычисли цену деления: $C = (X_2 - X_1)/N$.</li>'
|
||
+ '<li>Запиши результаты в таблицу.</li>'
|
||
+ '</ol>');
|
||
|
||
/* Виджет: 4 прибора */
|
||
h += wgWrap('lr1-w', 'СИМ', 'Виртуальные приборы',
|
||
'Каждый прибор имеет свою цену деления. Подвинь slider — отсчёт пересчитывается.',
|
||
'<div style="display:grid;grid-template-columns:repeat(auto-fit,minmax(220px,1fr));gap:14px">'
|
||
+ ['Линейка','Термометр','Мензурка','Динамометр'].map((nm, i) =>
|
||
'<div style="background:#f0f9ff;border:1.5px solid ' + ACCENT_SOFT + ';border-radius:10px;padding:10px">'
|
||
+ '<div style="font-weight:700;color:' + ACCENT_D + ';font-size:.88rem;margin-bottom:8px">' + nm + '</div>'
|
||
+ '<svg id="lr1-svg-' + i + '" viewBox="0 0 200 90" width="100%" style="background:#fff;border-radius:6px"></svg>'
|
||
+ '<div style="margin-top:6px;font-size:.82rem;color:#475569;font-family:JetBrains Mono,monospace">$C = $ <b id="lr1-C-' + i + '" style="color:' + ACCENT_D + '">—</b></div>'
|
||
+ '</div>').join('')
|
||
+ '</div>');
|
||
|
||
/* Таблица измерений */
|
||
h += wgWrap('lr1-tbl', 'ТБЛ', 'Таблица измерений', '',
|
||
'<table style="width:100%;border-collapse:collapse;font-size:.9rem">'
|
||
+ '<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 + '">$X_1$</th><th style="padding:6px 10px;text-align:right;border-bottom:2px solid ' + ACCENT + '">$X_2$</th><th style="padding:6px 10px;text-align:right;border-bottom:2px solid ' + ACCENT + '">$N$</th><th style="padding:6px 10px;text-align:right;border-bottom:2px solid ' + ACCENT + '">$C$</th></tr>'
|
||
+ [['Линейка','0','1 см','10','0,1 см = 1 мм'],['Термометр','0','10 °C','5','2 °C'],['Мензурка','0','100 мл','5','20 мл'],['Динамометр','0','5 Н','10','0,5 Н']].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] + '</td><td style="padding:5px 10px;text-align:right;border-bottom:1px solid ' + ACCENT_SOFT + ';font-family:JetBrains Mono,monospace">' + r[2] + '</td><td style="padding:5px 10px;text-align:right;border-bottom:1px solid ' + ACCENT_SOFT + ';font-family:JetBrains Mono,monospace">' + r[3] + '</td><td style="padding:5px 10px;text-align:right;border-bottom:1px solid ' + ACCENT_SOFT + ';font-family:JetBrains Mono,monospace;font-weight:700;color:' + ACCENT_D + '">' + r[4] + '</td></tr>').join('')
|
||
+ '</table>');
|
||
|
||
/* Контрольные вопросы */
|
||
h += wgWrap('lr1-q', 'ВОПР', 'Контрольные вопросы', '',
|
||
'<div id="lr1-q-host">'
|
||
+ quizQuestion('lr1-q', 0, 'Что такое цена деления?', ['Расстояние между прибором и телом','Значение наименьшего деления шкалы','Размер шкалы','Цена прибора'], 1)
|
||
+ quizQuestion('lr1-q', 1, 'Между $0$ и $1$ см на линейке $10$ делений. $C = ?$', ['1 мм','5 мм','10 мм','0,1 мм'], 0, '$C = (10-0)/10 = 1$ мм.')
|
||
+ quizQuestion('lr1-q', 2, 'Чем меньше цена деления, тем прибор…', ['Дешевле','Меньше','Точнее','Тяжелее'], 2)
|
||
+ '</div>');
|
||
|
||
h += makeCard('concl', 'Вывод',
|
||
'Мы научились определять цену деления любого измерительного прибора. Формула $C = (X_2 - X_1)/N$ '
|
||
+ 'универсальна — работает для линейки, термометра, мензурки, динамометра и других приборов со шкалой. '
|
||
+ 'Чем меньше $C$, тем точнее прибор.');
|
||
|
||
h += submitBtn('lr1');
|
||
body.innerHTML = h;
|
||
|
||
// Render 4 instruments
|
||
const insts = [
|
||
{ ticks:11, lbl:i => i%10===0?(i/10):'', unit:'см', color:'#0284c7' },
|
||
{ ticks:6, lbl:i => i*10, unit:'°C', color:'#dc2626' },
|
||
{ ticks:6, lbl:i => i*100, unit:'мл', color:'#0891b2' },
|
||
{ ticks:11, lbl:i => i%2===0?(i*0.5):'', unit:'Н', color:'#d97706' }
|
||
];
|
||
insts.forEach((inst, idx) => {
|
||
const W = 200, H = 90;
|
||
let s = '<rect x="0" y="20" width="' + W + '" height="40" fill="#fef9c3" stroke="' + inst.color + '" stroke-width="1.5" rx="2"/>';
|
||
for(let i = 0; i < inst.ticks; i++){
|
||
const x = 10 + (i * (W - 20) / (inst.ticks - 1));
|
||
const tickH = 14;
|
||
s += '<line x1="' + x + '" y1="20" x2="' + x + '" y2="' + (20 + tickH) + '" stroke="#0f172a" stroke-width="1.2"/>';
|
||
const lbl = inst.lbl(i);
|
||
if(lbl !== '') s += '<text x="' + x + '" y="' + (20 + tickH + 12) + '" text-anchor="middle" font-family="Inter,sans-serif" font-size="10" font-weight="600" fill="#0f172a">' + lbl + '</text>';
|
||
}
|
||
s += '<text x="' + (W-8) + '" y="35" text-anchor="end" font-family="Inter,sans-serif" font-size="10" fill="' + inst.color + '" font-weight="700">' + inst.unit + '</text>';
|
||
document.getElementById('lr1-svg-' + idx).innerHTML = s;
|
||
const cVals = ['1 мм','2 °C','20 мл','0,5 Н'];
|
||
document.getElementById('lr1-C-' + idx).textContent = cVals[idx];
|
||
});
|
||
|
||
wireQuiz('lr1-q-host');
|
||
wireSubmit('lr1');
|
||
renderMath(body);
|
||
}
|
||
|
||
/* ========================================================== */
|
||
/* ЛР-2 — Измерение длины */
|
||
/* ========================================================== */
|
||
function build_lr2(){
|
||
const body = document.getElementById('lr2-body');
|
||
if(!body) return;
|
||
let h = '';
|
||
|
||
h += makeCard('goal', 'Цель',
|
||
'Научиться измерять длину предметов линейкой и записывать результат с учётом погрешности измерения.');
|
||
|
||
h += makeCard('equip', 'Оборудование',
|
||
'<ul style="padding-left:20px;margin:5px 0"><li>Линейка с миллиметровой шкалой</li><li>3 предмета разной длины (карандаш, тетрадь, брусок)</li></ul>');
|
||
|
||
h += makeCard('steps', 'Ход работы',
|
||
'<ol style="padding-left:20px;margin:6px 0">'
|
||
+ '<li>Положи предмет вдоль линейки. Совмести один край с отметкой $0$.</li>'
|
||
+ '<li>Прочитай отсчёт у другого края, округлив до ближайшего деления.</li>'
|
||
+ '<li>Цена деления $C = 1$ мм, поэтому погрешность $\\Delta l = C/2 = 0{,}5$ мм.</li>'
|
||
+ '<li>Запиши: $l = (l_0 \\pm \\Delta l)$ мм.</li>'
|
||
+ '</ol>');
|
||
|
||
/* Виджет: 3 предмета по линейке */
|
||
h += wgWrap('lr2-w', 'СИМ', 'Измерь 3 предмета', 'Выбери предмет — увидь его длину на линейке.',
|
||
'<div style="display:flex;gap:6px;margin-bottom:10px;flex-wrap:wrap">'
|
||
+ [['penc','Карандаш',125],['tetr','Тетрадь',204],['brusk','Брусок',83]].map((o, i) =>
|
||
'<button class="lr2-obj" data-l="' + o[2] + '" data-nm="' + o[1] + '" type="button" style="background:' + (i===0 ? ACCENT : '#fff') + ';color:' + (i===0 ? '#fff' : ACCENT) + ';border:2px solid ' + ACCENT + ';padding:7px 14px;border-radius:9px;cursor:pointer;font-weight:700;font-family:inherit;font-size:.86rem">' + o[1] + '</button>').join('')
|
||
+ '</div>'
|
||
+ '<svg id="lr2-svg" viewBox="0 0 360 110" width="100%" style="max-width:600px;display:block;margin:0 auto;background:#f0f9ff;border-radius:9px;border:1px solid ' + ACCENT_SOFT + '"></svg>'
|
||
+ '<div id="lr2-info" style="background:' + ACCENT_SOFT + ';border-radius:9px;padding:10px 14px;margin-top:8px;font-size:.94rem;text-align:center"></div>');
|
||
|
||
/* Таблица */
|
||
h += wgWrap('lr2-tbl', 'ТБЛ', 'Таблица измерений',
|
||
'',
|
||
'<table style="width:100%;border-collapse:collapse;font-size:.9rem">'
|
||
+ '<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 + '">$l_0$, мм</th><th style="padding:6px 10px;text-align:right;border-bottom:2px solid ' + ACCENT + '">$\\Delta l$, мм</th><th style="padding:6px 10px;text-align:right;border-bottom:2px solid ' + ACCENT + '">Запись</th></tr>'
|
||
+ [['Карандаш',125,0.5,'(125,0 ± 0,5) мм'],['Тетрадь',204,0.5,'(204,0 ± 0,5) мм'],['Брусок',83,0.5,'(83,0 ± 0,5) мм']].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] + '</td><td style="padding:5px 10px;text-align:right;border-bottom:1px solid ' + ACCENT_SOFT + ';font-family:JetBrains Mono,monospace">' + r[2] + '</td><td style="padding:5px 10px;text-align:right;border-bottom:1px solid ' + ACCENT_SOFT + ';font-family:JetBrains Mono,monospace;font-weight:700;color:' + ACCENT_D + '">' + r[3] + '</td></tr>').join('')
|
||
+ '</table>');
|
||
|
||
h += wgWrap('lr2-q', 'ВОПР', 'Контрольные вопросы', '',
|
||
'<div id="lr2-q-host">'
|
||
+ quizQuestion('lr2-q', 0, 'Чему равна погрешность миллиметровой линейки?', ['1 мм','0,5 мм','0,1 мм','2 мм'], 1, 'Половина цены деления: $1/2 = 0{,}5$ мм.')
|
||
+ quizQuestion('lr2-q', 1, 'Зачем записывать $\\Delta l$ в результате?', ['Для красоты','Чтобы показать границы возможной ошибки','По привычке','Не нужно'], 1)
|
||
+ quizQuestion('lr2-q', 2, 'Запись $l = (15{,}0 \\pm 0{,}5)$ мм означает, что $l$ может быть…', ['Любым','От 14,5 до 15,5 мм','От 10 до 20 мм','Точно 15 мм'], 1)
|
||
+ '</div>');
|
||
|
||
h += makeCard('concl', 'Вывод',
|
||
'Мы научились измерять длину предметов с погрешностью $\\Delta l = C/2 = 0{,}5$ мм. '
|
||
+ 'Любое измерение записывается в виде $l = (l_0 \\pm \\Delta l)$ — это указывает на границы, в которых лежит истинная длина.');
|
||
|
||
h += submitBtn('lr2');
|
||
body.innerHTML = h;
|
||
|
||
// Render ruler with object
|
||
function draw2(lenMm, nm){
|
||
const W = 360, H = 110;
|
||
const xPad = 20;
|
||
const rulerW = W - 2 * xPad;
|
||
const totalMm = 300;
|
||
const pxPerMm = rulerW / totalMm;
|
||
let s = '';
|
||
// Object
|
||
const objW = lenMm * pxPerMm;
|
||
s += '<rect x="' + xPad + '" y="20" width="' + objW + '" height="22" fill="' + ACCENT + '" stroke="' + ACCENT_D + '" stroke-width="1.5" rx="2"/>';
|
||
s += '<text x="' + (xPad + objW/2) + '" y="35" text-anchor="middle" font-family="Inter,sans-serif" font-size="11" font-weight="700" fill="#fff">' + nm + '</text>';
|
||
// Ruler
|
||
s += '<rect x="' + xPad + '" y="60" width="' + rulerW + '" height="32" fill="#fef9c3" stroke="#92400e" stroke-width="1.5" rx="2"/>';
|
||
for(let mm = 0; mm <= totalMm; mm += 10){
|
||
const x = xPad + mm * pxPerMm;
|
||
const isCm = mm % 10 === 0, isBig = mm % 50 === 0;
|
||
const tickH = isBig ? 14 : 10;
|
||
s += '<line x1="' + x + '" y1="60" x2="' + x + '" y2="' + (60 + tickH) + '" stroke="#0f172a" stroke-width="' + (isBig ? 1.5 : 1) + '"/>';
|
||
if(isBig) s += '<text x="' + x + '" y="' + (60 + 30) + '" text-anchor="middle" font-family="Inter,sans-serif" font-size="9" font-weight="600" fill="#0f172a">' + (mm/10) + '</text>';
|
||
}
|
||
// Mark of length
|
||
const markX = xPad + lenMm * pxPerMm;
|
||
s += '<line x1="' + markX + '" y1="40" x2="' + markX + '" y2="95" stroke="#dc2626" stroke-width="1.5" stroke-dasharray="3 2"/>';
|
||
s += '<text x="' + (W - 6) + '" y="78" text-anchor="end" font-family="Inter,sans-serif" font-size="10" font-weight="700" fill="#92400e">см</text>';
|
||
document.getElementById('lr2-svg').innerHTML = s;
|
||
document.getElementById('lr2-info').innerHTML = '<b>' + nm + ':</b> отсчёт $l_0 = ' + lenMm + '$ мм $= ' + (lenMm/10).toFixed(1) + '$ см. С погрешностью: $l = (' + lenMm + ' \\pm 0{,}5)$ мм.';
|
||
renderMath(document.getElementById('lr2-info'));
|
||
}
|
||
body.querySelectorAll('.lr2-obj').forEach(btn => btn.addEventListener('click', () => {
|
||
body.querySelectorAll('.lr2-obj').forEach(b => { b.style.background = '#fff'; b.style.color = ACCENT; });
|
||
btn.style.background = ACCENT; btn.style.color = '#fff';
|
||
draw2(+btn.dataset.l, btn.dataset.nm);
|
||
}));
|
||
draw2(125, 'Карандаш');
|
||
|
||
wireQuiz('lr2-q-host');
|
||
wireSubmit('lr2');
|
||
renderMath(body);
|
||
}
|
||
|
||
/* ========================================================== */
|
||
/* ЛР-3 — Измерение объёма (вытеснение жидкости) */
|
||
/* ========================================================== */
|
||
function build_lr3(){
|
||
const body = document.getElementById('lr3-body');
|
||
if(!body) return;
|
||
let h = '';
|
||
|
||
h += makeCard('goal', 'Цель',
|
||
'Научиться измерять объём тела неправильной формы методом вытеснения жидкости.');
|
||
|
||
h += makeCard('equip', 'Оборудование',
|
||
'<ul style="padding-left:20px;margin:5px 0"><li>Мензурка с водой</li><li>Тело неправильной формы (камень, гайка, болт)</li><li>Нитка</li></ul>');
|
||
|
||
h += makeCard('steps', 'Ход работы',
|
||
'<ol style="padding-left:20px;margin:6px 0">'
|
||
+ '<li>Налей в мензурку воды до отметки $V_1$. Запиши.</li>'
|
||
+ '<li>Опусти тело в мензурку (полностью под водой!).</li>'
|
||
+ '<li>Прочитай новый уровень $V_2$.</li>'
|
||
+ '<li>Объём тела: $V = V_2 - V_1$.</li>'
|
||
+ '</ol>');
|
||
|
||
/* Виджет: мензурка с телом */
|
||
h += wgWrap('lr3-w', 'СИМ', 'Измерь объём тела', 'Выбери тело и опусти его в мензурку.',
|
||
'<div style="display:flex;gap:6px;margin-bottom:10px;flex-wrap:wrap">'
|
||
+ [['stone','Камень',45],['nut','Гайка',12],['bolt','Болт',23]].map((o, i) =>
|
||
'<button class="lr3-obj" data-v="' + o[2] + '" data-nm="' + o[1] + '" type="button" style="background:' + (i===0 ? ACCENT : '#fff') + ';color:' + (i===0 ? '#fff' : ACCENT) + ';border:2px solid ' + ACCENT + ';padding:7px 14px;border-radius:9px;cursor:pointer;font-weight:700;font-family:inherit;font-size:.86rem">' + o[1] + '</button>').join('')
|
||
+ '</div>'
|
||
+ '<svg id="lr3-svg" viewBox="0 0 320 240" width="100%" style="max-width:400px;display:block;margin:0 auto;background:#f0f9ff;border-radius:9px;border:1px solid ' + ACCENT_SOFT + '"></svg>'
|
||
+ '<div id="lr3-info" style="background:' + ACCENT_SOFT + ';border-radius:9px;padding:10px 14px;margin-top:8px;font-size:.94rem;line-height:1.6"></div>');
|
||
|
||
/* Таблица */
|
||
h += wgWrap('lr3-tbl', 'ТБЛ', 'Таблица измерений', '',
|
||
'<table style="width:100%;border-collapse:collapse;font-size:.9rem">'
|
||
+ '<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 + '">$V_1$, мл</th><th style="padding:6px 10px;text-align:right;border-bottom:2px solid ' + ACCENT + '">$V_2$, мл</th><th style="padding:6px 10px;text-align:right;border-bottom:2px solid ' + ACCENT + '">$V$, мл = см³</th></tr>'
|
||
+ [['Камень',100,145,45],['Гайка',100,112,12],['Болт',100,123,23]].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] + '</td><td style="padding:5px 10px;text-align:right;border-bottom:1px solid ' + ACCENT_SOFT + ';font-family:JetBrains Mono,monospace">' + r[2] + '</td><td style="padding:5px 10px;text-align:right;border-bottom:1px solid ' + ACCENT_SOFT + ';font-family:JetBrains Mono,monospace;font-weight:700;color:' + ACCENT_D + '">' + r[3] + '</td></tr>').join('')
|
||
+ '</table>');
|
||
|
||
h += wgWrap('lr3-q', 'ВОПР', 'Контрольные вопросы', '',
|
||
'<div id="lr3-q-host">'
|
||
+ quizQuestion('lr3-q', 0, 'Можно ли измерить объём картофелины линейкой?', ['Да','Нет — неправильная форма, нужна мензурка','Только если разрезать','Только большие'], 1)
|
||
+ quizQuestion('lr3-q', 1, '$V_1 = 50$ мл, $V_2 = 78$ мл. $V_{тела}$?', ['28 мл','78 мл','128 мл','50 мл'], 0, '$V = V_2 - V_1 = 78 - 50 = 28$ мл.')
|
||
+ quizQuestion('lr3-q', 2, '$1$ мл — это сколько см³?', ['0,1','1','10','100'], 1)
|
||
+ '</div>');
|
||
|
||
h += makeCard('concl', 'Вывод',
|
||
'Метод вытеснения жидкости позволяет измерить объём тела <b>любой</b> формы. Это пример <b>косвенного измерения</b>: '
|
||
+ 'мы напрямую измеряем уровни $V_1$ и $V_2$, а затем по формуле $V = V_2 - V_1$ вычисляем искомый объём.');
|
||
|
||
h += submitBtn('lr3');
|
||
body.innerHTML = h;
|
||
|
||
function draw3(vTel, nm){
|
||
const W = 320, H = 240;
|
||
const W1 = 100, W2 = 200; // позиции 2 мензурок
|
||
const mWidth = 60, mHeight = 180, mY = 30;
|
||
const maxMl = 200;
|
||
function drawMenz(x, label, V){
|
||
let s = '';
|
||
// Стенки
|
||
s += '<line x1="' + x + '" y1="' + mY + '" x2="' + x + '" y2="' + (mY + mHeight) + '" stroke="#0f172a" stroke-width="2"/>';
|
||
s += '<line x1="' + (x + mWidth) + '" y1="' + mY + '" x2="' + (x + mWidth) + '" y2="' + (mY + mHeight) + '" stroke="#0f172a" stroke-width="2"/>';
|
||
s += '<line x1="' + x + '" y1="' + (mY + mHeight) + '" x2="' + (x + mWidth) + '" y2="' + (mY + mHeight) + '" stroke="#0f172a" stroke-width="2"/>';
|
||
// Вода
|
||
const waterH = (V / maxMl) * mHeight;
|
||
s += '<rect x="' + (x + 1) + '" y="' + (mY + mHeight - waterH) + '" width="' + (mWidth - 2) + '" height="' + waterH + '" fill="#60a5fa" opacity="0.7"/>';
|
||
// Шкала
|
||
for(let ml = 0; ml <= maxMl; ml += 20){
|
||
const y = mY + mHeight - (ml / maxMl) * mHeight;
|
||
s += '<line x1="' + x + '" y1="' + y + '" x2="' + (x - 6) + '" y2="' + y + '" stroke="#0f172a" stroke-width="1"/>';
|
||
if(ml % 40 === 0) s += '<text x="' + (x - 8) + '" y="' + (y + 3) + '" text-anchor="end" font-family="Inter,sans-serif" font-size="9" fill="#0f172a">' + ml + '</text>';
|
||
}
|
||
// Подпись V
|
||
s += '<text x="' + (x + mWidth/2) + '" y="' + (mY + mHeight + 18) + '" text-anchor="middle" font-family="Unbounded,sans-serif" font-size="11" font-weight="800" fill="' + ACCENT_D + '">' + label + '</text>';
|
||
s += '<text x="' + (x + mWidth/2) + '" y="' + (mY + mHeight + 32) + '" text-anchor="middle" font-family="JetBrains Mono,monospace" font-size="11" font-weight="700" fill="#0f172a">' + V + ' мл</text>';
|
||
return s;
|
||
}
|
||
let s = '';
|
||
s += drawMenz(W1, 'V₁', 100);
|
||
s += drawMenz(W2, 'V₂', 100 + vTel);
|
||
// Тело во второй мензурке
|
||
s += '<circle cx="' + (W2 + mWidth/2) + '" cy="' + (mY + mHeight - 20) + '" r="' + Math.min(15, Math.sqrt(vTel)*1.4) + '" fill="#475569" stroke="#0f172a" stroke-width="1.5"/>';
|
||
// Стрелка
|
||
s += '<line x1="170" y1="120" x2="195" y2="120" stroke="' + ACCENT + '" stroke-width="2"/>';
|
||
s += '<polygon points="195,120 188,116 188,124" fill="' + ACCENT + '"/>';
|
||
s += '<text x="182" y="110" text-anchor="middle" font-family="Inter,sans-serif" font-size="10" fill="' + ACCENT_D + '" font-weight="700">опускаем</text>';
|
||
s += '<text x="182" y="138" text-anchor="middle" font-family="Inter,sans-serif" font-size="10" fill="' + ACCENT_D + '">тело</text>';
|
||
document.getElementById('lr3-svg').innerHTML = s;
|
||
document.getElementById('lr3-info').innerHTML = '<b>' + nm + '</b>: $V_1 = 100$ мл, $V_2 = ' + (100 + vTel) + '$ мл → '
|
||
+ '<b>$V = V_2 - V_1 = ' + vTel + '$ мл $= ' + vTel + '$ см³</b>';
|
||
renderMath(document.getElementById('lr3-info'));
|
||
}
|
||
body.querySelectorAll('.lr3-obj').forEach(btn => btn.addEventListener('click', () => {
|
||
body.querySelectorAll('.lr3-obj').forEach(b => { b.style.background = '#fff'; b.style.color = ACCENT; });
|
||
btn.style.background = ACCENT; btn.style.color = '#fff';
|
||
draw3(+btn.dataset.v, btn.dataset.nm);
|
||
}));
|
||
draw3(45, 'Камень');
|
||
|
||
wireQuiz('lr3-q-host');
|
||
wireSubmit('lr3');
|
||
renderMath(body);
|
||
}
|
||
|
||
/* ========================================================== */
|
||
/* ЛР-4 — Изучение неравномерного движения */
|
||
/* ========================================================== */
|
||
function build_lr4(){
|
||
const body = document.getElementById('lr4-body');
|
||
if(!body) return;
|
||
let h = '';
|
||
|
||
h += makeCard('goal', 'Цель',
|
||
'Измерить среднюю скорость движения шарика, скатывающегося по наклонной плоскости, для разных углов наклона.');
|
||
|
||
h += makeCard('equip', 'Оборудование',
|
||
'<ul style="padding-left:20px;margin:5px 0"><li>Желоб (наклонная плоскость)</li><li>Шарик</li><li>Секундомер</li><li>Линейка / мерная лента</li></ul>');
|
||
|
||
h += makeCard('steps', 'Ход работы',
|
||
'<ol style="padding-left:20px;margin:6px 0">'
|
||
+ '<li>Установи желоб под углом 15°. Отпусти шарик с верха.</li>'
|
||
+ '<li>Засеки время $t$ от старта до конца желоба.</li>'
|
||
+ '<li>Измерь путь $s$ (длину желоба).</li>'
|
||
+ '<li>Вычисли $\\langle v\\rangle = s/t$.</li>'
|
||
+ '<li>Повтори для углов 30° и 45°.</li>'
|
||
+ '</ol>');
|
||
|
||
/* Виджет: симуляция шарика */
|
||
h += wgWrap('lr4-w', 'СИМ', 'Шарик на наклонной плоскости',
|
||
'Меняй угол, нажимай «Запустить» — шарик скатится, время и средняя скорость измерятся автоматически.',
|
||
'<div style="display:grid;grid-template-columns:repeat(auto-fit,minmax(140px,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 + '">Угол $\\alpha$, °: <b id="lr4-a" style="color:' + ACCENT_D + ';font-family:JetBrains Mono,monospace">15</b><input type="range" id="lr4-a-r" min="10" max="60" step="5" value="15" style="display:block;width:100%;margin-top:5px;accent-color:' + ACCENT + '"></label>'
|
||
+ '<div style="display:flex;gap:6px;align-items:end"><button id="lr4-go" type="button" style="background:linear-gradient(135deg,' + ACCENT + ',' + ACCENT_D + ');color:#fff;border:none;padding:8px 18px;border-radius:9px;cursor:pointer;font-weight:700;font-family:inherit;font-size:.88rem">Запустить</button></div>'
|
||
+ '</div>'
|
||
+ '<svg id="lr4-svg" viewBox="0 0 360 200" width="100%" style="max-width:500px;display:block;margin:0 auto;background:#f0f9ff;border-radius:9px;border:1px solid ' + ACCENT_SOFT + '"></svg>'
|
||
+ '<div id="lr4-info" style="background:' + ACCENT_SOFT + ';border-radius:9px;padding:10px 14px;margin-top:8px;font-size:.94rem;text-align:center"></div>');
|
||
|
||
h += wgWrap('lr4-tbl', 'ТБЛ', 'Таблица измерений', '',
|
||
'<table style="width:100%;border-collapse:collapse;font-size:.9rem">'
|
||
+ '<tr style="background:' + ACCENT_SOFT + '"><th style="padding:6px 10px;text-align:left;border-bottom:2px solid ' + ACCENT + '">Угол $\\alpha$</th><th style="padding:6px 10px;text-align:right;border-bottom:2px solid ' + ACCENT + '">$s$, м</th><th style="padding:6px 10px;text-align:right;border-bottom:2px solid ' + ACCENT + '">$t$, с</th><th style="padding:6px 10px;text-align:right;border-bottom:2px solid ' + ACCENT + '">$\\langle v\\rangle$, м/с</th></tr>'
|
||
+ [['15°','1,0','1,7','0,59'],['30°','1,0','1,1','0,91'],['45°','1,0','0,8','1,25']].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] + '</td><td style="padding:5px 10px;text-align:right;border-bottom:1px solid ' + ACCENT_SOFT + ';font-family:JetBrains Mono,monospace">' + r[2] + '</td><td style="padding:5px 10px;text-align:right;border-bottom:1px solid ' + ACCENT_SOFT + ';font-family:JetBrains Mono,monospace;font-weight:700;color:' + ACCENT_D + '">' + r[3] + '</td></tr>').join('')
|
||
+ '</table>');
|
||
|
||
h += wgWrap('lr4-q', 'ВОПР', 'Контрольные вопросы', '',
|
||
'<div id="lr4-q-host">'
|
||
+ quizQuestion('lr4-q', 0, 'Каков физический смысл $\\langle v\\rangle$?', ['Скорость в средней точке','Скорость, при которой за то же время прошёл бы тот же путь','Полусумма начальной и конечной','Скорость покоя'], 1)
|
||
+ quizQuestion('lr4-q', 1, 'Что изменится, если увеличить угол?', ['Время больше, скорость меньше','Время меньше, $\\langle v\\rangle$ больше','Ничего не изменится','Скорость станет нулевой'], 1, 'Чем круче — тем быстрее скатывается.')
|
||
+ quizQuestion('lr4-q', 2, 'Шарик прошёл $s = 2$ м за $t = 4$ с. $\\langle v\\rangle$?', ['0,2 м/с','0,5 м/с','1 м/с','2 м/с'], 1, '$\\langle v\\rangle = 2/4 = 0{,}5$ м/с.')
|
||
+ '</div>');
|
||
|
||
h += makeCard('concl', 'Вывод',
|
||
'Чем больше угол наклона, тем сильнее ускорение шарика и тем выше его средняя скорость. '
|
||
+ 'Движение шарика — <b>неравномерное</b>: его мгновенная скорость растёт от нуля в начале до максимума в конце. '
|
||
+ 'Формула $\\langle v\\rangle = s/t$ даёт усреднённую характеристику, удобную для сравнения.');
|
||
|
||
h += submitBtn('lr4');
|
||
body.innerHTML = h;
|
||
|
||
let lr4Anim = { raf: 0, t: 0, running: false, totalT: 0, ang: 15 };
|
||
function drawTrack(){
|
||
const ang = +document.getElementById('lr4-a-r').value;
|
||
document.getElementById('lr4-a').textContent = ang;
|
||
lr4Anim.ang = ang;
|
||
const W = 360, H = 200, baseY = 170;
|
||
const len = 220;
|
||
const a = ang * Math.PI / 180;
|
||
const x1 = 40, y1 = baseY;
|
||
const x2 = x1 + len * Math.cos(a);
|
||
const y2 = baseY - len * Math.sin(a);
|
||
let s = '';
|
||
s += '<line x1="0" y1="' + baseY + '" x2="' + W + '" y2="' + baseY + '" stroke="#0f172a" stroke-width="1.5"/>';
|
||
s += '<polygon points="' + x1 + ',' + y1 + ' ' + (x1 + len*Math.cos(a)) + ',' + y1 + ' ' + x2 + ',' + y2 + '" fill="' + ACCENT_SOFT + '" stroke="' + ACCENT_D + '" stroke-width="1.5"/>';
|
||
// Шарик в начальной позиции (на верху)
|
||
const t = lr4Anim.t;
|
||
const totalT = Math.max(0.5, 2.0 - ang * 0.025); // эмпирическая формула: больше угол — меньше время
|
||
lr4Anim.totalT = totalT;
|
||
let progress = t / totalT;
|
||
if(progress > 1) progress = 1;
|
||
// Шарик ускоряется (квадратичная зависимость пути от времени)
|
||
const sFrac = progress * progress;
|
||
const bx = x2 + (x1 - x2) * sFrac;
|
||
const by = y2 + (y1 - y2) * sFrac;
|
||
s += '<circle cx="' + bx.toFixed(1) + '" cy="' + (by - 10).toFixed(1) + '" r="9" fill="' + ACCENT + '" stroke="' + ACCENT_D + '" stroke-width="1.5"/>';
|
||
// Подпись
|
||
s += '<text x="' + W + '" y="20" text-anchor="end" font-family="Inter,sans-serif" font-size="11" font-weight="700" fill="' + ACCENT_D + '">α = ' + ang + '°</text>';
|
||
s += '<text x="' + W + '" y="36" text-anchor="end" font-family="Inter,sans-serif" font-size="11" font-weight="700" fill="' + ACCENT_D + '">s = 1,0 м</text>';
|
||
s += '<text x="' + W + '" y="52" text-anchor="end" font-family="Inter,sans-serif" font-size="11" font-weight="700" fill="#dc2626">t = ' + t.toFixed(2) + ' с</text>';
|
||
document.getElementById('lr4-svg').innerHTML = s;
|
||
const vavg = progress >= 1 ? (1.0 / totalT) : 0;
|
||
document.getElementById('lr4-info').innerHTML = progress >= 1
|
||
? '<b>Шарик скатился!</b> $t = ' + totalT.toFixed(2) + '$ с · $s = 1{,}0$ м · $\\langle v\\rangle = s/t = ' + vavg.toFixed(2) + '$ м/с'
|
||
: 'В движении... $t = ' + t.toFixed(2) + '$ с';
|
||
renderMath(document.getElementById('lr4-info'));
|
||
}
|
||
function lr4Loop(){
|
||
if(!lr4Anim.running) return;
|
||
lr4Anim.t += 0.02;
|
||
if(lr4Anim.t >= lr4Anim.totalT){
|
||
lr4Anim.t = lr4Anim.totalT;
|
||
lr4Anim.running = false;
|
||
drawTrack();
|
||
return;
|
||
}
|
||
drawTrack();
|
||
lr4Anim.raf = requestAnimationFrame(lr4Loop);
|
||
}
|
||
document.getElementById('lr4-a-r').addEventListener('input', () => { lr4Anim.t = 0; lr4Anim.running = false; drawTrack(); });
|
||
document.getElementById('lr4-go').addEventListener('click', () => {
|
||
lr4Anim.t = 0; lr4Anim.running = true;
|
||
if(lr4Anim.raf) cancelAnimationFrame(lr4Anim.raf);
|
||
lr4Loop();
|
||
});
|
||
drawTrack();
|
||
|
||
wireQuiz('lr4-q-host');
|
||
wireSubmit('lr4');
|
||
renderMath(body);
|
||
}
|
||
|
||
/* ========================================================== */
|
||
/* ЛР-5 — Измерение плотности вещества */
|
||
/* ========================================================== */
|
||
function build_lr5(){
|
||
const body = document.getElementById('lr5-body');
|
||
if(!body) return;
|
||
let h = '';
|
||
|
||
h += makeCard('goal', 'Цель',
|
||
'Измерить плотности трёх веществ и определить, что это за материалы, по таблице плотностей.');
|
||
|
||
h += makeCard('equip', 'Оборудование',
|
||
'<ul style="padding-left:20px;margin:5px 0"><li>Весы (электронные или рычажные)</li><li>Мензурка</li><li>3 образца разных материалов</li><li>Нитка для опускания</li></ul>');
|
||
|
||
h += makeCard('steps', 'Ход работы',
|
||
'<ol style="padding-left:20px;margin:6px 0">'
|
||
+ '<li>Измерь массу $m$ образца на весах.</li>'
|
||
+ '<li>Измерь его объём $V$ методом вытеснения (как в ЛР-3).</li>'
|
||
+ '<li>Вычисли $\\rho = m/V$.</li>'
|
||
+ '<li>Найди в таблице вещество с такой плотностью.</li>'
|
||
+ '</ol>');
|
||
|
||
/* Виджет */
|
||
h += wgWrap('lr5-w', 'СИМ', '3 образца — определи вещество', 'Выбери образец — увидь массу и объём, вычисли плотность.',
|
||
'<div style="display:flex;gap:6px;margin-bottom:10px;flex-wrap:wrap">'
|
||
+ [['s1','Образец 1',54,20],['s2','Образец 2',156,20],['s3','Образец 3',272,20]].map((o, i) =>
|
||
'<button class="lr5-obj" data-m="' + o[2] + '" data-v="' + o[3] + '" data-nm="' + o[1] + '" type="button" style="background:' + (i===0 ? ACCENT : '#fff') + ';color:' + (i===0 ? '#fff' : ACCENT) + ';border:2px solid ' + ACCENT + ';padding:7px 14px;border-radius:9px;cursor:pointer;font-weight:700;font-family:inherit;font-size:.86rem">' + o[1] + '</button>').join('')
|
||
+ '</div>'
|
||
+ '<div style="display:grid;grid-template-columns:1fr 1fr;gap:14px;align-items:center;background:#f0f9ff;border-radius:9px;border:1px solid ' + ACCENT_SOFT + ';padding:14px">'
|
||
+ '<div style="text-align:center">'
|
||
+ '<div style="font-size:.84rem;color:#475569;margin-bottom:6px">Весы</div>'
|
||
+ '<div style="background:#fff;border:2px solid ' + ACCENT_D + ';border-radius:8px;padding:14px;font-family:JetBrains Mono,monospace;font-weight:800;font-size:1.4rem;color:' + ACCENT_D + '" id="lr5-m">54 г</div>'
|
||
+ '</div>'
|
||
+ '<div style="text-align:center">'
|
||
+ '<div style="font-size:.84rem;color:#475569;margin-bottom:6px">Мензурка</div>'
|
||
+ '<div style="background:#fff;border:2px solid ' + ACCENT_D + ';border-radius:8px;padding:14px;font-family:JetBrains Mono,monospace;font-weight:800;font-size:1.4rem;color:' + ACCENT_D + '" id="lr5-V">20 см³</div>'
|
||
+ '</div>'
|
||
+ '</div>'
|
||
+ '<div id="lr5-info" style="background:' + ACCENT_SOFT + ';border-radius:9px;padding:12px 14px;margin-top:10px;font-size:.96rem;line-height:1.7;text-align:center"></div>');
|
||
|
||
/* Таблица */
|
||
h += wgWrap('lr5-tbl', 'ТБЛ', 'Таблица плотностей (для сравнения)', '',
|
||
'<table style="width:100%;border-collapse:collapse;font-size:.9rem">'
|
||
+ '<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 + '">$\\rho$, г/см³</th></tr>'
|
||
+ [['Сосна',0.5],['Лёд',0.9],['Вода',1.0],['Алюминий',2.7],['Железо',7.8],['Медь',8.9],['Свинец',11.3],['Ртуть',13.6],['Золото',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;font-weight:700">' + r[1] + '</td></tr>').join('')
|
||
+ '</table>');
|
||
|
||
h += wgWrap('lr5-q', 'ВОПР', 'Контрольные вопросы', '',
|
||
'<div id="lr5-q-host">'
|
||
+ quizQuestion('lr5-q', 0, 'Образец 1 имеет $m = 54$ г, $V = 20$ см³. $\\rho$?', ['1,7 г/см³','2,7 г/см³','3,7 г/см³','5,4 г/см³'], 1, '$54/20 = 2{,}7$ г/см³ — алюминий.')
|
||
+ quizQuestion('lr5-q', 1, 'Образец 2: $m = 156$ г, $V = 20$ см³. Что это?', ['Лёд','Алюминий','Железо','Свинец'], 2, '$156/20 = 7{,}8$ г/см³ — железо.')
|
||
+ quizQuestion('lr5-q', 2, 'Какое измерение здесь косвенное?', ['Массы','Объёма','Плотности','Времени'], 2, 'Плотность вычисляется через прямые $m$ и $V$.')
|
||
+ '</div>');
|
||
|
||
h += makeCard('concl', 'Вывод',
|
||
'Зная массу и объём тела, можно вычислить плотность вещества и определить, что это за материал. '
|
||
+ 'Измерение плотности — пример <b>косвенного</b> измерения через два прямых ($m$ и $V$). '
|
||
+ 'Таблицы плотностей позволяют идентифицировать неизвестные вещества.');
|
||
|
||
h += submitBtn('lr5');
|
||
body.innerHTML = h;
|
||
|
||
function matName(rho){
|
||
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 'свинец / ртуть';
|
||
return 'золото / платина';
|
||
}
|
||
function upd5(m, V, nm){
|
||
document.getElementById('lr5-m').textContent = m + ' г';
|
||
document.getElementById('lr5-V').textContent = V + ' см³';
|
||
const rho = m / V;
|
||
document.getElementById('lr5-info').innerHTML = '<b>' + nm + ':</b> $\\rho = m/V = ' + m + '/' + V + ' = $ <b style="color:' + ACCENT_D + '">' + rho.toFixed(2) + '</b> г/см³.<br>'
|
||
+ '<span style="color:#475569;font-size:.88rem">Похоже на: <b>' + matName(rho) + '</b></span>';
|
||
renderMath(document.getElementById('lr5-info'));
|
||
}
|
||
body.querySelectorAll('.lr5-obj').forEach(btn => btn.addEventListener('click', () => {
|
||
body.querySelectorAll('.lr5-obj').forEach(b => { b.style.background = '#fff'; b.style.color = ACCENT; });
|
||
btn.style.background = ACCENT; btn.style.color = '#fff';
|
||
upd5(+btn.dataset.m, +btn.dataset.v, btn.dataset.nm);
|
||
}));
|
||
upd5(54, 20, 'Образец 1');
|
||
|
||
wireQuiz('lr5-q-host');
|
||
wireSubmit('lr5');
|
||
renderMath(body);
|
||
}
|
||
|
||
/* ========================================================== */
|
||
/* ЛР-6 — Изучение силы трения */
|
||
/* ========================================================== */
|
||
function build_lr6(){
|
||
const body = document.getElementById('lr6-body');
|
||
if(!body) return;
|
||
let h = '';
|
||
|
||
h += makeCard('goal', 'Цель',
|
||
'Измерить силу трения скольжения для разных поверхностей и убедиться, что $F_{тр} \\sim N$ (зависит от нормальной реакции).');
|
||
|
||
h += makeCard('equip', 'Оборудование',
|
||
'<ul style="padding-left:20px;margin:5px 0"><li>Деревянный брусок</li><li>Грузы по 100 г</li><li>Динамометр</li><li>Разные поверхности (дерево, пластик, резина)</li></ul>');
|
||
|
||
h += makeCard('steps', 'Ход работы',
|
||
'<ol style="padding-left:20px;margin:6px 0">'
|
||
+ '<li>Положи брусок на поверхность. Прицепи к нему динамометр.</li>'
|
||
+ '<li>Тяни <b>равномерно</b> и измерь $F_{тр}$ — это сила, которую показал динамометр (=сила тяги при равномерном движении).</li>'
|
||
+ '<li>Добавляй грузы по 100 г и снова измеряй $F_{тр}$.</li>'
|
||
+ '<li>Поменяй поверхность — повтори.</li>'
|
||
+ '</ol>');
|
||
|
||
/* Виджет */
|
||
h += wgWrap('lr6-w', 'СИМ', 'Брусок с динамометром', 'Меняй массу и поверхность — увидь силу трения.',
|
||
'<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 + '">Масса (брусок + грузы), г: <b id="lr6-m" style="color:' + ACCENT_D + ';font-family:JetBrains Mono,monospace">100</b><input type="range" id="lr6-m-r" min="100" max="500" step="100" value="100" style="display:block;width:100%;margin-top:5px;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="lr6-surf" style="width:100%;margin-top:5px;padding:6px;border-radius:6px;border:1px solid ' + ACCENT_SOFT + ';font-family:inherit"><option value="0.3" selected>Дерево по дереву (μ=0,3)</option><option value="0.2">Дерево по пластику (μ=0,2)</option><option value="0.5">Дерево по резине (μ=0,5)</option><option value="0.04">Дерево по льду (μ=0,04)</option></select></label>'
|
||
+ '</div>'
|
||
+ '<svg id="lr6-svg" viewBox="0 0 380 180" width="100%" style="max-width:600px;display:block;margin:0 auto;background:#f0f9ff;border-radius:9px;border:1px solid ' + ACCENT_SOFT + '"></svg>'
|
||
+ '<div id="lr6-info" style="background:' + ACCENT_SOFT + ';border-radius:9px;padding:10px 14px;margin-top:8px;font-size:.94rem;text-align:center"></div>');
|
||
|
||
h += wgWrap('lr6-tbl', 'ТБЛ', 'Таблица измерений (μ=0,3, дерево по дереву)',
|
||
'',
|
||
'<table style="width:100%;border-collapse:collapse;font-size:.9rem">'
|
||
+ '<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 + '">$m$, кг</th><th style="padding:6px 10px;text-align:right;border-bottom:2px solid ' + ACCENT + '">$N = mg$, Н</th><th style="padding:6px 10px;text-align:right;border-bottom:2px solid ' + ACCENT + '">$F_{тр}$, Н</th></tr>'
|
||
+ [['0 (брусок)',0.1,1.0,0.3],['+1 груз',0.2,2.0,0.6],['+2 груза',0.3,3.0,0.9],['+3 груза',0.4,4.0,1.2],['+4 груза',0.5,5.0,1.5]].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] + '</td><td style="padding:5px 10px;text-align:right;border-bottom:1px solid ' + ACCENT_SOFT + ';font-family:JetBrains Mono,monospace">' + r[2] + '</td><td style="padding:5px 10px;text-align:right;border-bottom:1px solid ' + ACCENT_SOFT + ';font-family:JetBrains Mono,monospace;font-weight:700;color:' + ACCENT_D + '">' + r[3] + '</td></tr>').join('')
|
||
+ '</table>'
|
||
+ '<p style="margin-top:8px;font-size:.86rem;color:#475569">Видно, что $F_{тр}$ растёт <b>пропорционально</b> массе — то есть пропорционально $N$. Это и есть закон $F_{тр} \\sim N$.</p>');
|
||
|
||
h += wgWrap('lr6-q', 'ВОПР', 'Контрольные вопросы', '',
|
||
'<div id="lr6-q-host">'
|
||
+ quizQuestion('lr6-q', 0, 'Если массу удвоить, $F_{тр}$ скольжения…', ['Не изменится','Удвоится','Уменьшится в 2 раза','Останется','Станет 0'], 1)
|
||
+ quizQuestion('lr6-q', 1, 'Какая поверхность дает наименьшее трение?', ['Дерево','Резина','Лёд','Пластик'], 2, 'У льда $\\mu \\approx 0{,}04$ — поэтому скользко.')
|
||
+ quizQuestion('lr6-q', 2, 'Брусок $m = 0{,}5$ кг, $\\mu = 0{,}3$, $g = 10$. $F_{тр}$?', ['0,5 Н','1 Н','1,5 Н','3 Н'], 2, '$F_{тр} = \\mu mg = 0{,}3 \\cdot 0{,}5 \\cdot 10 = 1{,}5$ Н.')
|
||
+ '</div>');
|
||
|
||
h += makeCard('concl', 'Вывод',
|
||
'Эксперимент подтвердил, что сила трения скольжения <b>прямо пропорциональна</b> нормальной реакции (а значит — массе тела). '
|
||
+ 'Коэффициент трения $\\mu$ зависит от пары поверхностей: для дерево-лёд он очень мал ($\\sim 0{,}04$), '
|
||
+ 'для дерево-резина — большой ($\\sim 0{,}5$). Поэтому шипованная резина не скользит по асфальту и снегу.');
|
||
|
||
h += submitBtn('lr6');
|
||
body.innerHTML = h;
|
||
|
||
function draw6(){
|
||
const m = +document.getElementById('lr6-m-r').value;
|
||
const mu = +document.getElementById('lr6-surf').value;
|
||
document.getElementById('lr6-m').textContent = m;
|
||
const N = m / 1000 * 10; // m в г → кг → N
|
||
const Ftr = mu * N;
|
||
const W = 380, H = 180, baseY = 130;
|
||
let s = '';
|
||
s += '<rect x="0" y="' + baseY + '" width="' + W + '" height="50" 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 = 80, bw = 60, bh = 40;
|
||
// Грузы наверху
|
||
const numGr = Math.max(0, (m - 100) / 100);
|
||
for(let i = 0; i < numGr; i++){
|
||
s += '<rect x="' + (bx + 12) + '" y="' + (baseY - bh - 12 - i*10) + '" width="36" height="10" fill="#475569" stroke="#1f2937" stroke-width="0.8" rx="2"/>';
|
||
}
|
||
// Брусок
|
||
s += '<rect x="' + bx + '" y="' + (baseY - bh) + '" width="' + bw + '" height="' + bh + '" fill="' + ACCENT + '" stroke="' + ACCENT_D + '" 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 + ' г</text>';
|
||
// Динамометр + нить
|
||
const dynX = bx + bw + 20;
|
||
s += '<line x1="' + (bx + bw) + '" y1="' + (baseY - bh/2) + '" x2="' + dynX + '" y2="' + (baseY - bh/2) + '" stroke="#0f172a" stroke-width="1.5"/>';
|
||
s += '<rect x="' + dynX + '" y="' + (baseY - bh/2 - 18) + '" width="60" height="36" fill="#fef3c7" stroke="#92400e" stroke-width="1.5" rx="4"/>';
|
||
s += '<text x="' + (dynX + 30) + '" y="' + (baseY - bh/2 + 3) + '" text-anchor="middle" font-family="JetBrains Mono,monospace" font-size="13" font-weight="800" fill="#92400e">' + Ftr.toFixed(2) + ' Н</text>';
|
||
s += '<text x="' + (dynX + 30) + '" y="' + (baseY - bh/2 - 22) + '" text-anchor="middle" font-family="Inter,sans-serif" font-size="9" fill="#92400e" font-weight="700">динамометр</text>';
|
||
// Стрелка тяги
|
||
s += '<line x1="' + (dynX + 60) + '" y1="' + (baseY - bh/2) + '" x2="' + (dynX + 100) + '" y2="' + (baseY - bh/2) + '" stroke="#10b981" stroke-width="2.5"/>';
|
||
s += '<polygon points="' + (dynX + 100) + ',' + (baseY - bh/2) + ' ' + (dynX + 92) + ',' + (baseY - bh/2 - 5) + ' ' + (dynX + 92) + ',' + (baseY - bh/2 + 5) + '" fill="#10b981"/>';
|
||
// Стрелка трения
|
||
s += '<line x1="' + bx + '" y1="' + (baseY - 5) + '" x2="' + (bx - 40) + '" y2="' + (baseY - 5) + '" stroke="#92400e" stroke-width="2.5"/>';
|
||
s += '<polygon points="' + (bx - 40) + ',' + (baseY - 5) + ' ' + (bx - 32) + ',' + (baseY - 10) + ' ' + (bx - 32) + ',' + baseY + '" fill="#92400e"/>';
|
||
s += '<text x="' + (bx - 42) + '" y="' + (baseY - 12) + '" text-anchor="end" font-family="Inter,sans-serif" font-size="10" font-weight="700" fill="#92400e">F_тр</text>';
|
||
document.getElementById('lr6-svg').innerHTML = s;
|
||
document.getElementById('lr6-info').innerHTML = '$m = ' + (m/1000).toFixed(2) + '$ кг, $N = mg = ' + N.toFixed(1) + '$ Н, $\\mu = ' + mu + '$ → <b>$F_{тр} = \\mu N = ' + Ftr.toFixed(2) + '$ Н</b>';
|
||
renderMath(document.getElementById('lr6-info'));
|
||
}
|
||
document.getElementById('lr6-m-r').addEventListener('input', draw6);
|
||
document.getElementById('lr6-surf').addEventListener('change', draw6);
|
||
draw6();
|
||
|
||
wireQuiz('lr6-q-host');
|
||
wireSubmit('lr6');
|
||
renderMath(body);
|
||
}
|
||
|
||
window.PHYS7_LAB_WIDGETS = {
|
||
lr1: build_lr1,
|
||
lr2: build_lr2,
|
||
lr3: build_lr3,
|
||
lr4: build_lr4,
|
||
lr5: build_lr5,
|
||
lr6: build_lr6
|
||
};
|
||
|
||
})();
|