diff --git a/frontend/css/phys9-flagships.css b/frontend/css/phys9-flagships.css new file mode 100644 index 0000000..d8658e5 --- /dev/null +++ b/frontend/css/phys9-flagships.css @@ -0,0 +1,193 @@ +/* phys9-flagships.css — стили для крупных интерактивов Физики 9 (флагманы F1-F19). */ + +.flag-card{ + background: linear-gradient(135deg, var(--card,#fff), var(--sec-acc-soft,#dbeafe)); + border: 2px solid var(--sec-acc,#2563eb); + border-radius: 16px; + padding: 18px 20px; + margin: 18px 0; + box-shadow: 0 4px 14px rgba(15,23,42,.08); + position: relative; + overflow: hidden; +} +.flag-card::before{ + content: '★ ФЛАГМАН'; + position: absolute; top: 10px; right: 14px; + font-family: 'Unbounded', sans-serif; + font-size: .68rem; font-weight: 800; + color: #fff; + background: linear-gradient(135deg, #fbbf24, #f59e0b); + padding: 4px 10px; + border-radius: 99px; + letter-spacing: .06em; +} +.flag-title{ + font-family: 'Unbounded', sans-serif; + font-size: 1.18rem; + font-weight: 800; + color: var(--sec-acc-d, #1d4ed8); + margin-bottom: 4px; + padding-right: 90px; +} +.flag-desc{ + font-size: .92rem; + color: var(--text); + opacity: .85; + margin-bottom: 14px; + line-height: 1.5; +} +.flag-canvas{ + display: block; + width: 100%; + max-width: 720px; + height: auto; + background: var(--bg-subtle, #f8fafc); + border: 1.5px solid var(--border, #e2e8f0); + border-radius: 12px; + margin-bottom: 12px; + touch-action: none; +} +.flag-svg{ + display: block; + width: 100%; + max-width: 720px; + height: auto; + background: var(--bg-subtle, #f8fafc); + border: 1.5px solid var(--border, #e2e8f0); + border-radius: 12px; + margin-bottom: 12px; +} +.flag-controls{ + display: flex; + gap: 8px; + flex-wrap: wrap; + margin: 10px 0; + align-items: center; +} +.flag-btn{ + padding: 8px 16px; + border-radius: 9px; + border: 1.5px solid var(--sec-acc, #2563eb); + background: var(--card, #fff); + color: var(--sec-acc-d, #1d4ed8); + font-weight: 700; + font-size: .88rem; + cursor: pointer; + transition: transform .1s, background .15s; + font-family: inherit; + display: inline-flex; + align-items: center; + gap: 6px; +} +.flag-btn:hover{ background: var(--sec-acc-soft, #dbeafe); } +.flag-btn:active{ transform: scale(.96); } +.flag-btn:disabled{ opacity: .5; cursor: not-allowed; } +.flag-btn.primary{ + background: linear-gradient(135deg, var(--sec-acc,#2563eb), var(--sec-acc-d,#1d4ed8)); + color: #fff; + border-color: transparent; +} +.flag-btn.primary:hover{ filter: brightness(1.1); } +.flag-btn.success{ + background: linear-gradient(135deg, #10b981, #059669); + color: #fff; + border-color: transparent; +} +.flag-btn.danger{ + background: var(--card,#fff); + color: var(--fail,#dc2626); + border-color: var(--fail,#dc2626); +} +.flag-stats{ + display: grid; + grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); + gap: 10px; + padding: 12px 14px; + background: var(--sec-acc-soft, #dbeafe); + border-radius: 11px; + margin: 10px 0; +} +.flag-stat{ + font-size: .85rem; + color: var(--text); + display: flex; + flex-direction: column; + gap: 2px; +} +.flag-stat .lbl{ + font-size: .72rem; + font-weight: 700; + color: var(--muted); + text-transform: uppercase; + letter-spacing: .05em; +} +.flag-stat .val{ + font-family: 'JetBrains Mono', monospace; + font-size: 1.05rem; + font-weight: 800; + color: var(--sec-acc-d, #1d4ed8); +} +.flag-sliders{ + display: grid; + grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); + gap: 10px; + margin: 10px 0; +} +.flag-sliders label{ + display: block; + font-size: .88rem; + color: var(--muted); + background: var(--card, #fff); + padding: 8px 12px; + border-radius: 8px; + border: 1px solid var(--border, #e2e8f0); +} +.flag-sliders label b{ + font-family: 'JetBrains Mono', monospace; + color: var(--sec-acc-d, #1d4ed8); + margin-left: 4px; +} +.flag-sliders input[type="range"]{ + display: block; + width: 100%; + margin-top: 6px; + accent-color: var(--sec-acc, #2563eb); +} +.flag-feedback{ + padding: 10px 14px; + border-radius: 9px; + font-weight: 600; + font-size: .9rem; + margin-top: 10px; + display: none; +} +.flag-feedback.show{ display: block; } +.flag-feedback.ok{ + background: var(--ok-bg, #d1fae5); + color: #065f46; + border-left: 4px solid var(--ok, #10b981); +} +.flag-feedback.warn{ + background: var(--warn-bg, #fef3c7); + color: #78350f; + border-left: 4px solid var(--warn, #f59e0b); +} +.flag-feedback.fail{ + background: var(--fail-bg, #fee2e2); + color: #7f1d1d; + border-left: 4px solid var(--fail, #dc2626); +} +.flag-medal{ + display: inline-block; + padding: 4px 11px; + background: linear-gradient(135deg, #fbbf24, #f59e0b); + color: #fff; + border-radius: 99px; + font-family: 'Unbounded', sans-serif; + font-size: .76rem; + font-weight: 800; + letter-spacing: .04em; + text-transform: uppercase; +} + +html.dark .flag-canvas, html.dark .flag-svg{ background: #1a1f2e; border-color: #374151; } diff --git a/frontend/js/flagships/phys9_flag_F1_trajectory.js b/frontend/js/flagships/phys9_flag_F1_trajectory.js new file mode 100644 index 0000000..e47b13f --- /dev/null +++ b/frontend/js/flagships/phys9_flag_F1_trajectory.js @@ -0,0 +1,186 @@ +// F1. Конструктор траектории (§5) — рисуй мышкой, видишь s vs |Δr|. +(function(){ +'use strict'; + +const B = () => window.PHYS9_FLAG_BASE; +const C = () => window.PHYS9_COLORS || {}; + +function init(secId){ + if (!B()) return false; + const body = '' + + '' + + '
' + + '' + + '' + + '' + + '' + + '' + + '
' + + '
' + + '
Путь $s$0
' + + '
r| перемещение0
' + + '
Отношение $s/|Δ r|$
' + + '
' + + '
'; + + const card = B().makeCard(secId, + 'F1. Конструктор траектории', + 'Нарисуй мышкой/пальцем кривую — посмотри, как соотносятся путь $s$ и перемещение $|Δ\\vec r|$. Они равны только для прямой.', + body); + if (!card) return false; + + const cv = document.getElementById('F1-cv'); + const ctx = cv.getContext('2d'); + let points = []; /* [{x, y}] */ + let drawing = false; + + function getPos(e){ + const rect = cv.getBoundingClientRect(); + const sx = cv.width / rect.width; + const sy = cv.height / rect.height; + const tx = (e.touches ? e.touches[0].clientX : e.clientX) - rect.left; + const ty = (e.touches ? e.touches[0].clientY : e.clientY) - rect.top; + return { x: tx * sx, y: ty * sy }; + } + + function calc(){ + let s = 0; + for (let i = 1; i < points.length; i++){ + s += Math.hypot(points[i].x - points[i-1].x, points[i].y - points[i-1].y); + } + let dr = 0; + if (points.length >= 2) { + const a = points[0], b = points[points.length-1]; + dr = Math.hypot(b.x - a.x, b.y - a.y); + } + /* px → м: считаем что canvas 600px = 6 м */ + const m_per_px = 6 / 600; + return { s: s * m_per_px, dr: dr * m_per_px }; + } + + function draw(){ + ctx.clearRect(0, 0, cv.width, cv.height); + /* сетка */ + const col = C(); + ctx.strokeStyle = col.grid || '#e5e7eb'; + ctx.lineWidth = 1; + for (let x = 0; x < cv.width; x += 50){ ctx.beginPath(); ctx.moveTo(x, 0); ctx.lineTo(x, cv.height); ctx.stroke(); } + for (let y = 0; y < cv.height; y += 50){ ctx.beginPath(); ctx.moveTo(0, y); ctx.lineTo(cv.width, y); ctx.stroke(); } + if (points.length === 0){ + ctx.fillStyle = col.textMuted || '#64748b'; + ctx.font = '15px Inter,sans-serif'; + ctx.fillText('Нажми и проведи курсором/пальцем — нарисуй траекторию', 60, cv.height/2); + return; + } + /* траектория */ + ctx.strokeStyle = col.velocity || '#0891b2'; + ctx.lineWidth = 3; + ctx.lineCap = 'round'; + ctx.lineJoin = 'round'; + ctx.beginPath(); + ctx.moveTo(points[0].x, points[0].y); + for (let i = 1; i < points.length; i++) ctx.lineTo(points[i].x, points[i].y); + ctx.stroke(); + /* перемещение */ + if (points.length >= 2){ + const a = points[0], b = points[points.length-1]; + ctx.strokeStyle = col.displacement || '#2563eb'; + ctx.lineWidth = 2.5; + ctx.setLineDash([8, 5]); + ctx.beginPath(); ctx.moveTo(a.x, a.y); ctx.lineTo(b.x, b.y); ctx.stroke(); + ctx.setLineDash([]); + /* стрелка перемещения */ + B().arrow(ctx, a.x, a.y, b.x, b.y, col.displacement || '#2563eb', 2.5); + /* точки start/end */ + ctx.fillStyle = '#10b981'; + ctx.beginPath(); ctx.arc(a.x, a.y, 7, 0, Math.PI*2); ctx.fill(); + ctx.fillStyle = '#dc2626'; + ctx.beginPath(); ctx.arc(b.x, b.y, 7, 0, Math.PI*2); ctx.fill(); + ctx.fillStyle = col.text || '#0f172a'; + ctx.font = 'bold 13px Inter,sans-serif'; + ctx.fillText('старт', a.x + 10, a.y - 8); + ctx.fillText('конец', b.x + 10, b.y - 8); + } + /* обновить статы */ + const r = calc(); + document.getElementById('F1-s').textContent = r.s.toFixed(2) + ' м'; + document.getElementById('F1-dr').textContent = r.dr.toFixed(2) + ' м'; + const ratio = r.dr > 0.01 ? (r.s / r.dr).toFixed(2) : '∞'; + document.getElementById('F1-ratio').textContent = ratio; + /* feedback */ + const fb = document.getElementById('F1-fb'); + if (r.dr > 0.01 && Math.abs(r.s/r.dr - 1) < 0.05){ + fb.className = 'flag-feedback ok show'; + fb.innerHTML = '✓ Прямая траектория: $s = |Δ\\vec r|$ — это единственный случай равенства!'; + } else if (r.dr < 0.05 && r.s > 0.1){ + fb.className = 'flag-feedback warn show'; + fb.innerHTML = 'Замкнутая или почти замкнутая кривая: $|Δ\\vec r| \\to 0$, но путь $s$ остался.'; + } else if (r.s > 0.1) { + fb.className = 'flag-feedback ok show'; + fb.innerHTML = 'Криволинейное движение: $s > |Δ\\vec r|$ всегда (в '+ratio+' раз больше).'; + } else { + fb.className = 'flag-feedback'; + } + try { if(window.renderMathInElement) window.renderMathInElement(card, { delimiters:[{left:'$',right:'$',display:false}], throwOnError:false }); } catch(e){} + } + + function start(e){ + drawing = true; + points = [getPos(e)]; + draw(); + e.preventDefault(); + } + function move(e){ + if (!drawing) return; + const p = getPos(e); + const last = points[points.length-1]; + if (Math.hypot(p.x - last.x, p.y - last.y) > 3) { + points.push(p); + draw(); + } + e.preventDefault(); + } + function end(){ drawing = false; } + + cv.addEventListener('mousedown', start); + cv.addEventListener('mousemove', move); + cv.addEventListener('mouseup', end); + cv.addEventListener('mouseleave', end); + cv.addEventListener('touchstart', start, {passive:false}); + cv.addEventListener('touchmove', move, {passive:false}); + cv.addEventListener('touchend', end); + + document.getElementById('F1-clear').addEventListener('click', ()=>{ points = []; draw(); }); + document.getElementById('F1-close').addEventListener('click', ()=>{ + if (points.length < 2) return; + points.push(Object.assign({}, points[0])); draw(); + }); + document.getElementById('F1-line').addEventListener('click', ()=>{ + points = [{x:80, y:160}, {x:520, y:160}]; draw(); + }); + document.getElementById('F1-arc').addEventListener('click', ()=>{ + points = []; + const cx = 300, cy = 250, r = 180; + for (let a = Math.PI; a >= 0; a -= 0.05) points.push({ x: cx + r*Math.cos(a), y: cy - r*Math.sin(a)*0.6 }); + draw(); + }); + document.getElementById('F1-circle').addEventListener('click', ()=>{ + points = []; + const cx = 300, cy = 160, r = 110; + for (let a = 0; a <= 2*Math.PI + 0.05; a += 0.05) points.push({ x: cx + r*Math.cos(a), y: cy + r*Math.sin(a)*0.7 }); + draw(); + }); + + draw(); + return true; +} + +if (window.PHYS9_FLAG_BASE) window.PHYS9_FLAG_BASE.register('F1', { init: init, cleanup: function(){} }); +else { + /* base ещё не загружен — отложить регистрацию */ + document.addEventListener('DOMContentLoaded', ()=>{ + if (window.PHYS9_FLAG_BASE) window.PHYS9_FLAG_BASE.register('F1', { init: init, cleanup: function(){} }); + }); +} + +})(); diff --git a/frontend/js/flagships/phys9_flag_F2_race.js b/frontend/js/flagships/phys9_flag_F2_race.js new file mode 100644 index 0000000..e500b3b --- /dev/null +++ b/frontend/js/flagships/phys9_flag_F2_race.js @@ -0,0 +1,284 @@ +// F2. Гонка двух тел (§9) — симуляция + real-time графики x₁(t), x₂(t). +(function(){ +'use strict'; + +const B = () => window.PHYS9_FLAG_BASE; +const C = () => window.PHYS9_COLORS || {}; + +function init(secId){ + if (!B()) return false; + const body = '' + + '
' + + '' + + '' + + '' + + '' + + '' + + '
' + + '' + + '
' + + '' + + '' + + '' + + '
' + + '
' + + '
Время0 с
' + + '
$x_1$ ($v_1$)0 м (10 м/с)
' + + '
$x_2$ ($v_2$)80 м (−3 м/с)
' + + '
Встреча
' + + '
' + + '
'; + + const card = B().makeCard(secId, + 'F2. Гонка двух тел', + 'Меняй параметры до старта. Слева — реальная гонка, справа — графики $x(t)$. Пересечение графиков = встреча. Кнопка «Случайный сценарий» — для тренировки.', + body); + if (!card) return false; + + const cv = document.getElementById('F2-cv'); + const ctx = cv.getContext('2d'); + /* layout: левая половина — гонка, правая — графики */ + const W = cv.width, H = cv.height; + const RACE_W = Math.floor(W * 0.5); + const GRAPH_X = RACE_W + 10; + const GRAPH_W = W - GRAPH_X - 10; + + /* state */ + let st = { x1: 0, x2: 80, v1: 10, v2: -3, a1: 0, a2: 0, t: 0, running: false, met: false, tMet: -1, xMet: -1, history: [] }; + + function readSliders(){ + st.v1 = +document.getElementById('F2-v1').value; + st.v2 = +document.getElementById('F2-v2').value; + st.a1 = +document.getElementById('F2-a1').value; + st.a2 = +document.getElementById('F2-a2').value; + st.x2 = +document.getElementById('F2-x2').value; + document.getElementById('F2-v1v').textContent = st.v1; + document.getElementById('F2-a1v').textContent = st.a1; + document.getElementById('F2-x2v').textContent = st.x2; + document.getElementById('F2-v2v').textContent = st.v2; + document.getElementById('F2-a2v').textContent = st.a2; + /* теоретическая точка встречи (при текущих параметрах) */ + /* x1(t) = v1*t + 0.5*a1*t² , x2(t) = x2_0 + v2*t + 0.5*a2*t² */ + /* Δ = 0.5*(a1-a2)*t² + (v1-v2)*t - x2_0 = 0 */ + const A = 0.5*(st.a1 - st.a2); + const Bcoeff = st.v1 - st.v2; + const Ccoeff = -st.x2; + let tMeet = -1; + if (Math.abs(A) < 1e-6){ + if (Math.abs(Bcoeff) > 1e-6) tMeet = -Ccoeff / Bcoeff; + } else { + const D = Bcoeff*Bcoeff - 4*A*Ccoeff; + if (D >= 0){ + const t1 = (-Bcoeff + Math.sqrt(D)) / (2*A); + const t2 = (-Bcoeff - Math.sqrt(D)) / (2*A); + const tCand = [t1, t2].filter(x => x > 0.01).sort((a,b)=>a-b); + if (tCand.length) tMeet = tCand[0]; + } + } + document.getElementById('F2-meet').textContent = (tMeet > 0 && tMeet < 60) ? tMeet.toFixed(2)+' с' : '—'; + } + + function reset(){ + st.t = 0; st.x1 = 0; st.met = false; st.tMet = -1; st.xMet = -1; st.history = []; + readSliders(); + st.running = false; + document.getElementById('F2-go').textContent = 'Старт'; + document.getElementById('F2-fb').className = 'flag-feedback'; + } + + function tick(dt){ + if (!st.running) { draw(); return; } + /* Эйлер по dt */ + const N = 4; + const ddt = dt/N; + for (let i = 0; i < N; i++){ + st.v1 += st.a1 * ddt; + st.v2 += st.a2 * ddt; + st.x1 += st.v1 * ddt; + st.x2 += st.v2 * ddt; + st.t += ddt; + if (!st.met && Math.abs(st.x1 - st.x2) < 0.6){ + st.met = true; st.tMet = st.t; st.xMet = st.x1; + st.running = false; + document.getElementById('F2-go').textContent = 'Старт'; + const fb = document.getElementById('F2-fb'); + fb.className = 'flag-feedback ok show'; + fb.innerHTML = '✓ Встреча! $t = $ '+st.tMet.toFixed(2)+' с, $x = $ '+st.xMet.toFixed(1)+' м.'; + break; + } + st.history.push({ t: st.t, x1: st.x1, x2: st.x2 }); + if (st.history.length > 600) st.history.shift(); + } + if (st.t > 60) { st.running = false; document.getElementById('F2-go').textContent='Старт'; } + draw(); + } + + function draw(){ + const col = C(); + ctx.fillStyle = col.bg || '#fafafa'; + ctx.fillRect(0, 0, W, H); + /* === ЛЕВАЯ ПОЛОВИНА: гонка === */ + /* трасса */ + const trackY = 80; + const trackH = 100; + ctx.fillStyle = col.surface || '#a16207'; + ctx.fillRect(0, trackY, RACE_W, trackH); + /* разметка */ + ctx.strokeStyle = '#fff'; + ctx.setLineDash([12, 8]); + ctx.lineWidth = 2; + ctx.beginPath(); + ctx.moveTo(0, trackY + trackH/2); + ctx.lineTo(RACE_W, trackY + trackH/2); + ctx.stroke(); + ctx.setLineDash([]); + /* шкала на трассе: 0..200 м → 0..RACE_W */ + const x2px = x => (x / 200) * RACE_W; + /* отметки 0, 50, 100, 150, 200 */ + ctx.fillStyle = col.text || '#0f172a'; + ctx.font = '11px Inter,sans-serif'; + for (let k = 0; k <= 200; k += 50){ + const px = x2px(k); + ctx.fillRect(px-1, trackY + trackH, 2, 8); + ctx.fillText(k+' м', px - 12, trackY + trackH + 22); + } + /* машины */ + const car1x = Math.max(8, Math.min(RACE_W - 32, x2px(st.x1))); + const car2x = Math.max(8, Math.min(RACE_W - 32, x2px(st.x2))); + /* car 1 — голубая */ + ctx.fillStyle = col.velocity || '#0891b2'; + ctx.fillRect(car1x - 12, trackY + 18, 24, 14); + ctx.fillRect(car1x - 8, trackY + 13, 16, 7); + ctx.fillStyle = '#fff'; + ctx.font = 'bold 11px Inter,sans-serif'; + ctx.fillText('1', car1x - 3, trackY + 28); + /* car 2 — красная */ + ctx.fillStyle = col.fail || '#dc2626'; + ctx.fillRect(car2x - 12, trackY + 68, 24, 14); + ctx.fillRect(car2x - 8, trackY + 63, 16, 7); + ctx.fillStyle = '#fff'; + ctx.fillText('2', car2x - 3, trackY + 78); + /* подпись времени */ + ctx.fillStyle = col.text || '#0f172a'; + ctx.font = 'bold 14px Inter,sans-serif'; + ctx.fillText('t = ' + st.t.toFixed(1) + ' с', 12, 30); + ctx.font = '12px Inter,sans-serif'; + /* === ПРАВАЯ ПОЛОВИНА: графики x(t) === */ + const gx = GRAPH_X, gy = 30, gw = GRAPH_W, gh = H - 70; + /* фон */ + ctx.fillStyle = col.bgSubtle || '#f8fafc'; + ctx.fillRect(gx, gy, gw, gh); + /* оси */ + ctx.strokeStyle = col.axis || '#1e293b'; + ctx.lineWidth = 1.5; + ctx.beginPath(); + ctx.moveTo(gx + 30, gy); ctx.lineTo(gx + 30, gy + gh); + ctx.lineTo(gx + gw - 5, gy + gh); + ctx.stroke(); + /* шкала: x от 0..210 м, t от 0..max(20, текущ.) */ + const tMax = Math.max(20, st.t + 2); + const xMax = 220; + const toGx = t => gx + 30 + (t / tMax) * (gw - 40); + const toGy = xx => gy + gh - (xx / xMax) * (gh - 10); + /* сетка */ + ctx.strokeStyle = col.grid || '#e5e7eb'; + ctx.lineWidth = 1; + for (let k = 0; k <= tMax; k += Math.max(2, Math.floor(tMax/8))){ + const px = toGx(k); + ctx.beginPath(); ctx.moveTo(px, gy); ctx.lineTo(px, gy+gh); ctx.stroke(); + } + for (let k = 0; k <= xMax; k += 50){ + const py = toGy(k); + ctx.beginPath(); ctx.moveTo(gx+30, py); ctx.lineTo(gx+gw-5, py); ctx.stroke(); + } + /* подписи осей */ + ctx.fillStyle = col.textMuted || '#64748b'; + ctx.font = '10px Inter,sans-serif'; + ctx.fillText('t, с', gx + gw - 22, gy + gh - 4); + ctx.fillText('x, м', gx + 4, gy + 10); + for (let k = 0; k <= tMax; k += Math.max(5, Math.floor(tMax/5))){ + const px = toGx(k); + ctx.fillText(k.toFixed(0), px - 6, gy + gh + 14); + } + for (let k = 0; k <= xMax; k += 50){ + const py = toGy(k); + ctx.fillText(k, gx + 6, py + 3); + } + /* линия x1(t) — голубая */ + if (st.history.length > 1){ + ctx.strokeStyle = col.velocity || '#0891b2'; + ctx.lineWidth = 2.5; + ctx.beginPath(); + for (let i = 0; i < st.history.length; i++){ + const p = st.history[i]; + const px = toGx(p.t), py = toGy(Math.max(0, Math.min(xMax, p.x1))); + if (i === 0) ctx.moveTo(px, py); else ctx.lineTo(px, py); + } + ctx.stroke(); + /* линия x2(t) — красная */ + ctx.strokeStyle = col.fail || '#dc2626'; + ctx.beginPath(); + for (let i = 0; i < st.history.length; i++){ + const p = st.history[i]; + const px = toGx(p.t), py = toGy(Math.max(0, Math.min(xMax, p.x2))); + if (i === 0) ctx.moveTo(px, py); else ctx.lineTo(px, py); + } + ctx.stroke(); + } + /* момент встречи */ + if (st.tMet > 0){ + const px = toGx(st.tMet), py = toGy(st.xMet); + ctx.fillStyle = '#fbbf24'; + ctx.beginPath(); ctx.arc(px, py, 7, 0, Math.PI*2); ctx.fill(); + ctx.strokeStyle = '#0f172a'; ctx.lineWidth = 1.5; ctx.stroke(); + ctx.fillStyle = col.text || '#0f172a'; + ctx.font = 'bold 11px Inter,sans-serif'; + ctx.fillText('★ встреча', px + 10, py - 6); + } + /* легенда */ + ctx.font = '11px Inter,sans-serif'; + ctx.fillStyle = col.velocity || '#0891b2'; + ctx.fillRect(gx + 36, gy + 4, 12, 4); + ctx.fillStyle = col.text || '#0f172a'; + ctx.fillText('тело 1', gx + 52, gy + 10); + ctx.fillStyle = col.fail || '#dc2626'; + ctx.fillRect(gx + 90, gy + 4, 12, 4); + ctx.fillStyle = col.text || '#0f172a'; + ctx.fillText('тело 2', gx + 106, gy + 10); + /* обновить статы текстом */ + document.getElementById('F2-t').textContent = st.t.toFixed(1) + ' с'; + document.getElementById('F2-x1').textContent = st.x1.toFixed(1) + ' м ('+st.v1.toFixed(1)+' м/с)'; + document.getElementById('F2-x2disp').textContent = st.x2.toFixed(1) + ' м ('+st.v2.toFixed(1)+' м/с)'; + } + + document.getElementById('F2-go').addEventListener('click', ()=>{ + if (st.met) reset(); + if (st.t === 0) readSliders(); + st.running = !st.running; + document.getElementById('F2-go').textContent = st.running ? 'Пауза' : 'Старт'; + }); + document.getElementById('F2-reset').addEventListener('click', reset); + document.getElementById('F2-random').addEventListener('click', ()=>{ + document.getElementById('F2-v1').value = Math.round(5 + Math.random()*15); + document.getElementById('F2-a1').value = (Math.round((Math.random()-0.3)*8))/2; + document.getElementById('F2-x2').value = 60 + Math.round(Math.random()*120); + document.getElementById('F2-v2').value = Math.round(-8 + Math.random()*10); + document.getElementById('F2-a2').value = (Math.round((Math.random()-0.5)*6))/2; + reset(); + }); + ['F2-v1','F2-a1','F2-x2','F2-v2','F2-a2'].forEach(id => + document.getElementById(id).addEventListener('input', ()=>{ if (!st.running) reset(); else readSliders(); }) + ); + + readSliders(); + draw(); + B().startLoop('F2', cv, tick); + return true; +} + +if (window.PHYS9_FLAG_BASE) window.PHYS9_FLAG_BASE.register('F2', { init: init, cleanup: function(){} }); +else document.addEventListener('DOMContentLoaded', ()=>{ + if (window.PHYS9_FLAG_BASE) window.PHYS9_FLAG_BASE.register('F2', { init: init, cleanup: function(){} }); +}); + +})(); diff --git a/frontend/js/flagships/phys9_flag_base.js b/frontend/js/flagships/phys9_flag_base.js new file mode 100644 index 0000000..6b90721 --- /dev/null +++ b/frontend/js/flagships/phys9_flag_base.js @@ -0,0 +1,148 @@ +// phys9_flag_base.js — общая инфраструктура для всех флагман-интерактивов Физики 9. +// Экспорт: window.PHYS9_FLAG_BASE = { register, unmount, ... }. +(function(){ +'use strict'; + +const C = () => window.PHYS9_COLORS || {}; +const _flags = {}; /* id → { init, cleanup, _raf, _mounted, _io } */ + +/* === Регистрация флагмана === */ +function register(id, def){ + _flags[id] = Object.assign({ _raf: 0, _mounted: false, _io: null }, def); +} + +/* === Размонтировать (вызывается при goTo другого §) === */ +function unmount(id){ + const f = _flags[id]; if (!f) return; + if (f._raf) { cancelAnimationFrame(f._raf); f._raf = 0; } + if (f._io) { try { f._io.disconnect(); } catch(e){} f._io = null; } + if (f.cleanup) try { f.cleanup(); } catch(e){} + f._mounted = false; +} + +/* === Размонтировать все === */ +function unmountAll(){ + for (const id in _flags) unmount(id); +} + +/* === Загрузка флагмана для секции pN === */ +function mount(id, secId){ + const f = _flags[id]; if (!f) return false; + if (f._mounted) return true; + const ok = f.init(secId); + if (ok !== false) f._mounted = true; + return ok !== false; +} + +/* === Обёртка SVG/canvas вставки в pN-body === */ +function makeCard(secId, title, desc, body){ + const flagBox = document.createElement('div'); + flagBox.className = 'flag-card phys9-flag-' + secId; + flagBox.innerHTML = + '
' + title + '
' + + '
' + desc + '
' + + body; + const host = document.getElementById(secId + '-body'); + if (!host) return null; + if (host.querySelector('.phys9-flag-' + secId)) return null; + host.appendChild(flagBox); + try { if(window.renderMathInElement) window.renderMathInElement(flagBox, { delimiters: [{left:'$$',right:'$$',display:true},{left:'$',right:'$',display:false}], throwOnError:false }); } catch(e){} + return flagBox; +} + +/* === Анимационный цикл с IntersectionObserver для авто-паузы === */ +function startLoop(id, canvas, tick){ + const f = _flags[id]; if (!f) return; + let visible = true; + /* IntersectionObserver — если canvas вне экрана, паузим */ + try { + f._io = new IntersectionObserver(entries => { + visible = entries[0].isIntersecting; + }, { threshold: 0.05 }); + f._io.observe(canvas); + } catch(e){} + + let lastT = performance.now(); + function loop(now){ + const dt = Math.min(50, now - lastT) / 1000; + lastT = now; + if (visible) { + try { tick(dt); } catch(e){ console.warn('phys9 flag tick:', e.message); } + } + f._raf = requestAnimationFrame(loop); + } + f._raf = requestAnimationFrame(loop); +} + +/* === Высокий-DPI canvas init === */ +function initCanvas(id){ + const cv = document.getElementById(id); + if (!cv) return null; + const dpr = window.devicePixelRatio || 1; + const W = cv.offsetWidth || 600; + const H = cv.offsetHeight || 400; + cv.width = Math.round(W * dpr); + cv.height = Math.round(H * dpr); + const ctx = cv.getContext('2d'); + ctx.setTransform(dpr, 0, 0, dpr, 0, 0); + return { cv, ctx, W, H }; +} + +/* === Стрелка на canvas === */ +function arrow(ctx, x1, y1, x2, y2, color, width){ + ctx.strokeStyle = color; + ctx.fillStyle = color; + ctx.lineWidth = width || 2.5; + ctx.lineCap = 'round'; + const dx = x2 - x1, dy = y2 - y1, len = Math.hypot(dx, dy); + if (len < 1e-3) return; + const ux = dx/len, uy = dy/len, h = 10, hw = 6; + const bx = x2 - ux*h, by = y2 - uy*h; + ctx.beginPath(); + ctx.moveTo(x1, y1); ctx.lineTo(bx, by); + ctx.stroke(); + ctx.beginPath(); + ctx.moveTo(x2, y2); + ctx.lineTo(bx - uy*hw, by + ux*hw); + ctx.lineTo(bx + uy*hw, by - ux*hw); + ctx.closePath(); + ctx.fill(); +} + +/* === Сохранение рекорда === */ +function saveRecord(key, value){ + try { + const cur = +(localStorage.getItem('phys9_record_' + key) || -Infinity); + if (value > cur) localStorage.setItem('phys9_record_' + key, String(value)); + return Math.max(cur, value); + } catch(e){ return value; } +} +function getRecord(key, def){ + try { return +(localStorage.getItem('phys9_record_' + key) || (def || 0)); } + catch(e){ return def || 0; } +} + +window.PHYS9_FLAG_BASE = { + register: register, + mount: mount, + unmount: unmount, + unmountAll: unmountAll, + makeCard: makeCard, + initCanvas: initCanvas, + startLoop: startLoop, + arrow: arrow, + saveRecord: saveRecord, + getRecord: getRecord, + C: C +}; + +/* === Хук на goTo: отменять анимации при переключении секций === */ +const _origGoTo = window.goTo; +if (typeof _origGoTo === 'function') { + window.goTo = function(id){ + unmountAll(); + return _origGoTo.apply(this, arguments); + }; +} + +})(); diff --git a/frontend/textbooks/physics_8_ch1.html b/frontend/textbooks/physics_8_ch1.html index 6a4c581..f402dfe 100644 --- a/frontend/textbooks/physics_8_ch1.html +++ b/frontend/textbooks/physics_8_ch1.html @@ -32,335 +32,6 @@ --ok:#10b981; --ok-bg:#d1fae5; --warn:#f59e0b; --warn-bg:#fef3c7; --bad:#ef4444; --fail:#dc2626; --fail-bg:#fee2e2; } - -function _initP8_iv6(){ - const sb = document.getElementById('p8-iv6-sandbox'); - if (!sb || !window.P8Helpers || !window.P8Anim) return; - const W = 560, H = 280; - const svg = P8Helpers.svg.create(W, H); - svg.setAttribute('width','100%'); svg.setAttribute('height','100%'); svg.style.display='block'; - sb.appendChild(svg); - /* Sim state */ - const m = 0.5; // kg ice - const c_ice = 2100; - const c_water = 4200; - const lambda = 330000; - const r_vap = 2300000; - let power = 500; - let energyAccumulated = 0; - let running = false; - let raf = null; - let points = [{ t: 0, T: -20 }]; - /* Axes */ - const pad = { l: 50, r: 18, t: 22, b: 32 }; - const plotW = W - pad.l - pad.r; - const plotH = H - pad.t - pad.b; - /* Background */ - svg.appendChild(P8Helpers.svg.el('rect', { x: pad.l, y: pad.t, width: plotW, height: plotH, fill: '#fafafa', stroke: '#e5e7eb' })); - /* Y axis: -20 to 120 °C */ - const yMin = -20, yMax = 120; - function yToPx(T) { return pad.t + plotH * (1 - (T - yMin) / (yMax - yMin)); } - function tToPx(t) { return pad.l + plotW * Math.min(1, t / 300); } // 300 s scale - /* Y ticks */ - [-20, 0, 20, 40, 60, 80, 100, 120].forEach(t => { - const y = yToPx(t); - svg.appendChild(P8Helpers.svg.el('line', { x1: pad.l, y1: y, x2: pad.l + plotW, y2: y, stroke: '#e5e7eb' })); - svg.appendChild(P8Helpers.svg.el('text', { x: pad.l - 6, y: y + 3, 'font-family':"'JetBrains Mono',monospace", 'font-size': 10, fill: 'var(--p8-muted,#64748b)', 'text-anchor':'end', text: t+'°' })); - }); - /* Phase regions overlays (transparent) */ - const phaseRegions = [ - { from: -20, to: 0, fill: '#bfdbfe', name: 'лёд' }, - { from: 0, to: 100, fill: '#7dd3fc', name: 'вода' }, - { from: 100, to: 120, fill: '#fde68a', name: 'пар' } - ]; - phaseRegions.forEach(r => { - const y1 = yToPx(r.from), y2 = yToPx(r.to); - svg.appendChild(P8Helpers.svg.el('rect', { x: pad.l, y: y2, width: plotW, height: y1 - y2, fill: r.fill, opacity: 0.18 })); - svg.appendChild(P8Helpers.svg.el('text', { x: pad.l + plotW - 6, y: (y1 + y2) / 2 + 3, 'font-family':"'Inter',sans-serif", 'font-size': 10, 'font-weight': 700, fill: 'var(--p8-text)', 'text-anchor': 'end', text: r.name })); - }); - /* Phase lines (0 and 100) */ - svg.appendChild(P8Helpers.svg.el('line', { x1: pad.l, y1: yToPx(0), x2: pad.l + plotW, y2: yToPx(0), stroke: '#0f172a', 'stroke-width': 1, 'stroke-dasharray': '3 3' })); - svg.appendChild(P8Helpers.svg.el('line', { x1: pad.l, y1: yToPx(100), x2: pad.l + plotW, y2: yToPx(100), stroke: '#0f172a', 'stroke-width': 1, 'stroke-dasharray': '3 3' })); - /* X axis */ - svg.appendChild(P8Helpers.svg.el('line', { x1: pad.l, y1: pad.t + plotH, x2: pad.l + plotW, y2: pad.t + plotH, stroke: '#0f172a' })); - svg.appendChild(P8Helpers.svg.el('text', { x: pad.l + plotW / 2, y: H - 6, 'font-family':"'Inter',sans-serif", 'font-size': 11, 'font-weight': 700, fill: 'var(--p8-text)', 'text-anchor': 'middle', text: 'Время, с' })); - /* Curve path (will be updated) */ - const path = P8Helpers.svg.el('path', { d: '', fill: 'none', stroke: 'var(--th-mid, #f97316)', 'stroke-width': 3, 'stroke-linejoin': 'round', 'stroke-linecap': 'round' }); - svg.appendChild(path); - function updatePath(){ - if (!points.length) return; - const d = points.map((p, i) => (i === 0 ? 'M' : 'L') + tToPx(p.t).toFixed(1) + ',' + yToPx(p.T).toFixed(1)).join(' '); - path.setAttribute('d', d); - } - function currentT(){ return points[points.length-1].T; } - function currentPhase(T){ - if (T < 0) return 'лёд'; - if (T < 100) return T === 0 ? 'плавление' : 'вода'; - if (T === 100) return 'кипение'; - return 'пар'; - } - function tick(dt){ - if (!running) return; - const energy = power * dt; // J - let T = currentT(); - let newT = T; - if (T < 0) { - /* heating ice */ - const dT = energy / (c_ice * m); - newT = T + dT; - if (newT > 0) newT = 0; - } else if (T < 0.001 && energyAccumulated < lambda * m) { - /* phase transition (melting) */ - energyAccumulated += energy; - newT = 0; - if (energyAccumulated >= lambda * m) { - newT = 0.001; - } - } else if (T < 100) { - /* heating water */ - const dT = energy / (c_water * m); - newT = T + dT; - if (newT > 100) newT = 100; - } else if (T < 100.001 && energyAccumulated < (lambda + r_vap) * m) { - /* phase transition (boiling) */ - energyAccumulated += energy; - newT = 100; - if (energyAccumulated >= (lambda + r_vap) * m) { - newT = 100.001; - } - } else if (T < 120) { - const dT = energy / (c_water * m); // simplified for steam - newT = T + dT; - if (newT > 120) newT = 120; - } else { - running = false; - } - const lastP = points[points.length-1]; - points.push({ t: lastP.t + dt, T: newT }); - if (points.length > 600) points.shift(); - updatePath(); - document.getElementById('p8-iv6-temp').textContent = Math.round(newT); - document.getElementById('p8-iv6-phase').textContent = currentPhase(newT); - if (lastP.t > 300) running = false; - } - raf = P8Anim.raf(dt => tick(Math.min(dt * 4, 0.5))); // accelerate 4x for demo - /* Bind controls */ - const pwrInp = document.getElementById('p8-iv6-pwr'); - const pwrLab = document.getElementById('p8-iv6-pwr-val'); - pwrInp.oninput = () => { power = +pwrInp.value; pwrLab.textContent = power; }; - document.getElementById('p8-iv6-play').onclick = () => { - if (!running) { running = true; raf.start(); if (window.addXp) addXp(10, 'p8-iv6-melt'); } - }; - document.getElementById('p8-iv6-reset').onclick = () => { - running = false; raf.stop(); - energyAccumulated = 0; - points = [{ t: 0, T: -20 }]; - updatePath(); - document.getElementById('p8-iv6-temp').textContent = '-20'; - document.getElementById('p8-iv6-phase').textContent = 'лёд'; - }; - updatePath(); -} - -function _initP6_iv6(){ - const sb = document.getElementById('p6-iv6-sandbox'); - if (!sb || !window.P8Helpers || !window.P8Anim) return; - const svg = P8Helpers.svg.create(560, 240); - svg.setAttribute('width','100%'); svg.setAttribute('height','100%'); svg.style.display='block'; - sb.appendChild(svg); - /* Vessel positions (will animate to centre on mix) */ - const v1 = { x: 140, y: 130, m: 0.5, T: 80, color: '#fb923c' }; - const v2 = { x: 420, y: 130, m: 1.0, T: 20, color: '#7dd3fc' }; - const finalState = { active: false, T: 50, fillFraction: 0.5 }; - function drawVessel(x, y, m, T, color){ - const g = P8Helpers.svg.el('g', { transform: 'translate('+x+','+y+')' }); - const h = 30 + m * 50; - const w = 70; - /* Glass */ - g.appendChild(P8Helpers.svg.el('rect', { x:-w/2, y:-h, width:w, height:h, rx:6, fill:'rgba(255,255,255,.6)', stroke:'#0f172a', 'stroke-width':1.5 })); - /* Liquid */ - g.appendChild(P8Helpers.svg.el('rect', { x:-w/2+3, y:-h+5, width:w-6, height:h-8, rx:4, fill: P8Helpers.thermal.tempColor(T/100) })); - /* Label */ - g.appendChild(P8Helpers.svg.el('text', { x:0, y:18, 'font-family':"'JetBrains Mono',monospace", 'font-size':11, 'font-weight':700, fill:'#0f172a', 'text-anchor':'middle', text: 'm='+m.toFixed(1)+' кг' })); - g.appendChild(P8Helpers.svg.el('text', { x:0, y:32, 'font-family':"'JetBrains Mono',monospace", 'font-size':11, 'font-weight':700, fill:'#0f172a', 'text-anchor':'middle', text: 'T='+Math.round(T)+'°C' })); - return g; - } - let v1G = drawVessel(v1.x, v1.y, v1.m, v1.T, v1.color); - let v2G = drawVessel(v2.x, v2.y, v2.m, v2.T, v2.color); - svg.appendChild(v1G); svg.appendChild(v2G); - function redraw(){ - svg.innerHTML = ''; - if (!finalState.active) { - v1G = drawVessel(v1.x, v1.y, v1.m, v1.T, v1.color); - v2G = drawVessel(v2.x, v2.y, v2.m, v2.T, v2.color); - svg.appendChild(v1G); svg.appendChild(v2G); - } else { - /* Single combined vessel */ - const cv = drawVessel(280, 130, v1.m + v2.m, finalState.T, P8Helpers.thermal.tempColor(finalState.T/100)); - svg.appendChild(cv); - /* Result label */ - svg.appendChild(P8Helpers.svg.el('text', { x:280, y:60, 'font-family':"'Unbounded',sans-serif", 'font-size':14, 'font-weight':800, fill:'var(--th-mid,#f97316)', 'text-anchor':'middle', text: 'T_итог = '+Math.round(finalState.T)+' °C' })); - } - } - /* Hook up scrubbers */ - function bindScrub(inputId, valId, obj, prop){ - const input = document.getElementById(inputId); - const lab = document.getElementById(valId); - if (!input || !lab) return; - input.addEventListener('input', () => { - const v = parseFloat(input.value); - obj[prop] = v; - lab.textContent = v.toFixed(prop === 'm' ? 1 : 0); - if (finalState.active) { - finalState.active = false; - document.getElementById('p6-iv6-tf').textContent = '—'; - } - redraw(); - }); - } - bindScrub('p6-iv6-m1', 'p6-iv6-m1-val', v1, 'm'); - bindScrub('p6-iv6-t1', 'p6-iv6-t1-val', v1, 'T'); - bindScrub('p6-iv6-m2', 'p6-iv6-m2-val', v2, 'm'); - bindScrub('p6-iv6-t2', 'p6-iv6-t2-val', v2, 'T'); - /* Mix button */ - document.getElementById('p6-iv6-mix').onclick = () => { - const T = (v1.m * v1.T + v2.m * v2.T) / (v1.m + v2.m); - finalState.active = true; - finalState.T = T; - P8Anim.tween({ - from: v1.T, to: T, duration: 1200, easing: 'cubicInOut', - onUpdate: t => { - finalState.T = t; - redraw(); - document.getElementById('p6-iv6-tf').textContent = Math.round(t); - } - }); - if (window.addXp) addXp(10, 'p6-iv6-mix'); - }; - document.getElementById('p6-iv6-reset').onclick = () => { - finalState.active = false; - document.getElementById('p6-iv6-tf').textContent = '—'; - redraw(); - }; - redraw(); -} - -function _initP3_iv6(){ - const sb = document.getElementById('p3-iv6-sandbox'); - if (!sb || !window.P8Helpers || !window.P8Drag || !window.P8Anim) return; - const svg = P8Helpers.svg.create(560, 300); - svg.setAttribute('width','100%'); svg.setAttribute('height','100%'); svg.style.display='block'; - sb.appendChild(svg); - /* Горелка (drop zone) */ - const burner = P8Helpers.svg.el('g', { transform: 'translate(80, 240)' }); - burner.appendChild(P8Helpers.svg.el('rect', { x:-32, y:-8, width:64, height:32, rx:4, fill:'#475569' })); - burner.appendChild(P8Helpers.svg.el('rect', { x:-26, y:-22, width:52, height:14, rx:7, fill:'#dc2626' })); - burner.appendChild(P8Helpers.svg.el('text', { x:0, y:48, 'font-family':"'Inter',sans-serif", 'font-size':11, 'font-weight':700, fill:'#475569', 'text-anchor':'middle', text:'Горелка (drop)' })); - svg.appendChild(burner); - /* Палитра 4 стержней */ - const rods = [ - { name:'Медь', lam:400, color:'#b45309', x:200, y:50 }, - { name:'Серебро', lam:430, color:'#9ca3af', x:300, y:50 }, - { name:'Стекло', lam:0.8, color:'#bae6fd', x:400, y:50 }, - { name:'Дерево', lam:0.15,color:'#a16207', x:500, y:50 } - ]; - const rodEls = []; - rods.forEach((rod, i) => { - const g = P8Helpers.svg.el('g', { transform: 'translate('+rod.x+','+rod.y+')' }); - /* Sections of rod, each will be colored by T gradient when active */ - const segments = 12; - const segs = []; - for (let s = 0; s < segments; s++) { - const r = P8Helpers.svg.el('rect', { - x: -55 + s * (110/segments), y: -10, width: 110/segments, height: 20, - fill: rod.color, stroke: 'none' - }); - g.appendChild(r); - segs.push(r); - } - /* Frame */ - g.appendChild(P8Helpers.svg.el('rect', { x:-55, y:-10, width:110, height:20, rx:3, fill:'none', stroke:'#0f172a', 'stroke-width':1.5 })); - g.appendChild(P8Helpers.svg.el('text', { x:0, y:-18, 'font-family':"'Inter',sans-serif", 'font-size':11, 'font-weight':700, fill:'#0f172a', 'text-anchor':'middle', text: rod.name })); - g.appendChild(P8Helpers.svg.el('text', { x:0, y:30, 'font-family':"'JetBrains Mono',monospace", 'font-size':9, 'font-weight':600, fill:'var(--p8-muted, #64748b)', 'text-anchor':'middle', text: 'λ='+rod.lam })); - svg.appendChild(g); - rodEls.push({ rod, g, segs, x: rod.x, y: rod.y }); - }); - /* Active sim state */ - let activeIdx = -1; - let simLoop = null; - let simTime = 0; - const matEl = document.getElementById('p3-iv6-mat'); - const lamEl = document.getElementById('p3-iv6-lam'); - const tendEl = document.getElementById('p3-iv6-tend'); - function resetColors(rodObj){ - rodObj.segs.forEach(s => s.setAttribute('fill', rodObj.rod.color)); - } - function startSim(rodObj){ - if (simLoop) simLoop.stop(); - simTime = 0; - /* λ нормализованный 0..1: log scale (15 -> 430) */ - const lamNorm = Math.min(1, Math.log10(rodObj.rod.lam + 1) / Math.log10(500)); - simLoop = P8Anim.raf((dt, t) => { - simTime += dt; - /* Diffusion-like: каждый сегмент i прогревается со скоростью lamNorm */ - const speed = lamNorm * 0.8 + 0.04; - rodObj.segs.forEach((seg, i) => { - const pos = i / (rodObj.segs.length - 1); - const wave = speed * simTime; - const heat = Math.max(0, Math.min(1, wave - pos)); - seg.setAttribute('fill', P8Helpers.thermal.tempColor(heat * 0.85 + 0.1)); - }); - /* T-end value */ - const endHeat = Math.max(0, Math.min(1, speed * simTime - 0.95)); - const tEnd = Math.round(20 + endHeat * 80); - if (tendEl) tendEl.textContent = tEnd; - if (simTime > 30) simLoop.stop(); - }); - simLoop.start(); - if (matEl) matEl.textContent = rodObj.rod.name; - if (lamEl) lamEl.textContent = rodObj.rod.lam; - } - /* Attach drag to each rod */ - rodEls.forEach((rodObj, i) => { - P8Drag.attach(rodObj.g, { - container: svg, - onMove: (ev, pos) => { - rodObj.x = pos.x; - rodObj.y = pos.y; - rodObj.g.setAttribute('transform', 'translate('+rodObj.x+','+rodObj.y+')'); - }, - onEnd: (ev, pos) => { - /* Check if dropped near burner */ - if (Math.abs(pos.x - 80) < 70 && Math.abs(pos.y - 240) < 50) { - /* Snap to position above burner */ - P8Anim.tween({ - from: 0, to: 1, duration: 320, easing: 'cubicOut', - onUpdate: k => { - rodObj.x = pos.x + (80 + 55 - pos.x) * k; - rodObj.y = pos.y + (220 - pos.y) * k; - rodObj.g.setAttribute('transform', 'translate('+rodObj.x+','+rodObj.y+')'); - } - }); - /* Reset other rods to original */ - rodEls.forEach((other, j) => { - if (j === i) return; - resetColors(other); - }); - activeIdx = i; - startSim(rodObj); - if (window.addXp) addXp(10, 'p3-iv6-conduct'); - } - } - }); - }); - /* Help text */ - svg.appendChild(P8Helpers.svg.el('text', { - x: 280, y: 290, - 'font-family': "'Inter', sans-serif", 'font-size': 10, - fill: 'var(--p8-muted, #64748b)', 'text-anchor': 'middle', - text: 'Перетащи стержень на горелку • Чем выше λ — тем быстрее цвет дойдёт до конца' - })); -} .dark{--bg:#0a0a0e; --card:#13120a; --card-soft:#18160a; --text:#fef9e7; --ink:#fef9e7; --muted:#a39070; --border:#2a2512} *{margin:0;padding:0;box-sizing:border-box;-webkit-tap-highlight-color:transparent} html,body{font-family:'Inter',system-ui,sans-serif;background:var(--bg);color:var(--text);line-height:1.55;font-size:15px} @@ -1623,24 +1294,396 @@ function build_p2(){ +'
Задача: 1 / 5Правильно: 0
' +''; - /* IV6 — Heat Conductor Bench (Phase 1.2) */ + /* IV6 — flagship интерактив (заглушка Phase 1, наполнение в Phase 1.2) */ h += '
' - +'
IV-6
Тепловая лавочка — какой материал быстрее проводит тепло?
' - +'
Перетащи один из стержней (медь, дерево, стекло, серебро) на горелку. Цветовая карта покажет, как тепло движется по стержню. Чем больше λ — тем быстрее.
' - +'
' - +'
' - +'
Материал
' - +'
λВт/(м·К)
' - +'
T дальнего конца°C
' + +'
IV-6
Новый интерактив §2
' + +'
Готовится: интерактивная визуализация с drag-and-drop для углубления темы. Скоро будет доступна.
' + +'
' + +'' + +'
Phase 1.2 — coming soon
' +'
' +'
'; - + box.innerHTML = h + secNavFor('p2') + readButton('p2'); + renderMath(box); + wireReadBtn('p2'); + _initp2_iv5(); + + _initP2_sim(); + _initP2_quiz(); + _initP2_dnd(); + _initP2_mcq(); +} + +function _initp2_iv5(){ + const TASKS = [{"q":"Газу передали $Q = 200$ Дж теплоты, и он совершил работу $A = 60$ Дж. На сколько увеличилась его внутренняя энергия? ($\\Delta U = Q - A$)","ans":140,"tol":2,"why":"$\\Delta U = Q - A = 200 - 60 = 140$ Дж (первое начало термодинамики)."},{"q":"Над газом совершили работу $A_{внеш} = 150$ Дж, газ отдал $Q = 50$ Дж тепла. На сколько изменилась $U$?","ans":100,"tol":2,"why":"$\\Delta U = A_{внеш} - Q_{отд} = 150 - 50 = 100$ Дж."},{"q":"Газ адиабатно (без теплообмена, $Q = 0$) расширился, совершив $A = 80$ Дж. Найдите $|\\Delta U|$.","ans":80,"tol":2,"why":"При $Q = 0$: $\\Delta U = -A = -80$ Дж. Модуль изменения $|\\Delta U| = 80$ Дж."},{"q":"Молотом массой $0{,}5$ кг, движущимся со скоростью $v = 4$ м/с, ударили по гвоздю. Вся кинетическая энергия перешла в тепло. На сколько Джоулей увеличилась $U$ гвоздя?","ans":4,"tol":0.1,"why":"$E_к = \\dfrac{mv^2}{2} = \\dfrac{0{,}5 \\cdot 16}{2} = 4$ Дж $= \\Delta U$."},{"q":"Газу сообщили $Q = 500$ Дж, при этом $\\Delta U = 350$ Дж. Какую работу совершил газ?","ans":150,"tol":3,"why":"$A = Q - \\Delta U = 500 - 350 = 150$ Дж."}]; + let i = 0, ok = 0, awarded = false; + function render(){ + const t = TASKS[i]; const wrap = document.getElementById('p2-tasks5'); if(!wrap) return; + wrap.innerHTML = + '
Задача '+(i+1)+'. '+t.q+'
' + +'
' + +'' + +'' + +'
' + +'' + +'
'; + if (window.renderMathInElement) try { renderMathInElement(wrap, {delimiters:[{left:'$',right:'$',display:false}],throwOnError:false}); } catch(e){} + document.getElementById('p2-iv5-go').onclick = () => { + const v = parseFloat(document.getElementById('p2-iv5-inp').value.replace(',','.')); + const fb = document.getElementById('p2-iv5-fb'); + const wh = document.getElementById('p2-iv5-why-wrap'); + if (Math.abs(v - t.ans) <= t.tol) { + fb.className = 'feedback ok'; fb.innerHTML = 'Верно!'; ok++; + document.getElementById('p2-tasks5-ok').textContent = ok; + wh.style.display = 'block'; + } else { + fb.className = 'feedback fail'; fb.innerHTML = 'Не совсем. Ожидался $' + t.ans + '$. Загляни в подсказку.'; + if (window.renderMathInElement) try { renderMathInElement(fb, {delimiters:[{left:'$',right:'$',display:false}],throwOnError:false}); } catch(e){} + } + }; + document.getElementById('p2-iv5-hint').onclick = () => { + const wh = document.getElementById('p2-iv5-why-wrap'); + wh.style.display = wh.style.display === 'block' ? 'none' : 'block'; + }; + document.getElementById('p2-iv5-next').onclick = () => { + i = (i + 1) % TASKS.length; + document.getElementById('p2-tasks5-i').textContent = i + 1; + render(); + if (ok === TASKS.length && !awarded) { awarded = true; if (typeof addXp === 'function') addXp(20, 'p2-iv5'); } + }; + } + render(); +} + +/* === §2 IV-1: симуляция «работа vs теплопередача» === */ +function _initP2_sim(){ + _killSim('p2sim'); + const svg = document.getElementById('p2-sim'); if(!svg) return; + const W=460, H=200; + /* левая сцена: брусок скользит по доске */ + /* правая сцена: два тела касаются, температуры → среднее */ + let running = false; + let tA = 20; /* температура бруска */ + let tH = 90, tC = 10; /* контакт горячий/холодный */ + let blockX = 60; + let t = 0; + function reset(){ + tA = 20; tH = 90; tC = 10; blockX = 60; t = 0; + document.getElementById('p2-tA').textContent = '20'; + document.getElementById('p2-uA').textContent = '0'; + document.getElementById('p2-tH').textContent = '90'; + document.getElementById('p2-tC').textContent = '10'; + paint(); + } + function paint(){ + let s = ''; + /* === ЛЕВАЯ СЦЕНА: трение === */ + /* доска */ + s += ''; + /* штриховка под доской */ + for(let i=22;i<218;i+=10) s += ''; + /* брусок (цвет по температуре) */ + const cA = window.PHYS.tempColor(tA, 20, 80); + s += ''; + /* стрелка-сила F (рука толкает) */ + s += window.PHYS.drawArrow ? window.PHYS.drawArrow(blockX-30, 106, blockX-5, 106, '#10b981', 2.2, 9) : ''; + s += 'F'; + /* трение (внизу бруска) */ + s += 'трение нагревает'; + /* термометр для бруска */ + s += window.PHYS.thermometer(220, 50, 80, 0, 100, tA); + /* подпись */ + s += 'РАБОТА (трение)'; + /* === разделитель === */ + s += ''; + /* === ПРАВАЯ СЦЕНА: контакт === */ + const cH = window.PHYS.tempColor(tH, 0, 100); + const cCo = window.PHYS.tempColor(tC, 0, 100); + s += ''; + s += ''; + /* стрелки потока тепла */ + s += window.PHYS.drawArrow ? window.PHYS.drawArrow(326, 100, 344, 100, '#dc2626', 2.2, 8) : ''; + s += window.PHYS.drawArrow ? window.PHYS.drawArrow(326, 120, 344, 120, '#dc2626', 2.2, 8) : ''; + s += 'Q'; + /* подписи температур */ + s += ''+tH.toFixed(0)+' °C'; + s += ''+tC.toFixed(0)+' °C'; + s += 'ТЕПЛОПЕРЕДАЧА'; + svg.innerHTML = s; + } + function tick(){ + if(!running){ _SIMS.p2sim.raf = requestAnimationFrame(tick); return; } + if(!_isVisible('p2')){ _SIMS.p2sim.raf = requestAnimationFrame(tick); return; } + t += 1; + /* брусок двигается слева направо и нагревается */ + blockX += 0.8; + if(blockX > 170){ blockX = 60; } + if(tA < 80) tA += 0.18; + /* контакт: tH и tC сходятся к среднему */ + const avg = (tH + tC) / 2; + if(Math.abs(tH - avg) > 0.05){ + tH -= (tH - avg) * 0.012; + tC += (avg - tC) * 0.012; + } + document.getElementById('p2-tA').textContent = tA.toFixed(0); + document.getElementById('p2-uA').textContent = (tA - 20).toFixed(0) === '0' ? '0' : '+'+(tA - 20).toFixed(0); + document.getElementById('p2-tH').textContent = tH.toFixed(0); + document.getElementById('p2-tC').textContent = tC.toFixed(0); + paint(); + _SIMS.p2sim.raf = requestAnimationFrame(tick); + } + document.getElementById('p2-sim-start').addEventListener('click', ()=>{ + running = !running; + document.getElementById('p2-sim-start').textContent = running ? 'Пауза' : 'Запустить'; + }); + document.getElementById('p2-sim-reset').addEventListener('click', ()=>{ + running = false; + document.getElementById('p2-sim-start').textContent = 'Запустить'; + reset(); + }); + _SIMS.p2sim = { raf: 0 }; + reset(); + _SIMS.p2sim.raf = requestAnimationFrame(tick); +} + +/* === §2 IV-2: викторина работа/теплопередача === */ +function _initP2_quiz(){ + const QS = [ + {sit:'Долго трёшь руки одна о другую — они нагреваются.', ans:'W', why:'Работа сил трения переходит во внутреннюю энергию рук.'}, + {sit:'Чай в стакане остывает на столе.', ans:'H', why:'Теплопередача от чая в воздух и стенки стакана — без совершения работы.'}, + {sit:'Велосипедный насос нагревается при накачивании колеса.', ans:'W', why:'Воздух в насосе сжимается — над ним совершается работа.'}, + {sit:'Металлическая ложка в горячем чае нагревается.', ans:'H', why:'Теплопередача (теплопроводность) от чая к ложке.'}, + {sit:'Гвоздь забивают молотком — шляпка гвоздя нагревается.', ans:'W', why:'Кинетическая энергия молотка переходит в работу деформации/трения.'}, + {sit:'Лёд в холодильнике плавится, если открыть дверцу.', ans:'H', why:'Тёплый воздух комнаты передаёт энергию льду.'}, + {sit:'Греется проволока в фене — горячий воздух дует на руки.', ans:'H', why:'Конвекция: поток горячего воздуха переносит энергию.'}, + {sit:'Спички в коробке от долгой тряски нагрелись.', ans:'W', why:'Работа сил трения внутри коробки.'} + ]; + let i = 0, ok = 0; + function render(){ + const q = QS[i]; + const wrap = document.getElementById('p2-quiz'); + if(!wrap) return; + wrap.innerHTML = + '
'+q.sit+'
' + +'
' + +'' + +'' + +'
' + +''; + document.getElementById('p2-quiz-r').textContent = (i+1); + document.getElementById('p2-quiz-ok').textContent = ok; + wrap.querySelectorAll('[data-pick]').forEach(btn=>{ + btn.addEventListener('click', ()=>{ + if(btn.disabled) return; + const pick = btn.dataset.pick; + wrap.querySelectorAll('[data-pick]').forEach(b=>b.disabled=true); + const fb = document.getElementById('p2-quiz-fb'); + if(pick === q.ans){ + ok++; + fb.className='feedback ok'; fb.innerHTML='✓ Верно. '+q.why; + addXp(3,'p2-quiz'); bumpProgress('p2', 4); + } else { + fb.className='feedback fail'; fb.innerHTML='✗ Не то. '+q.why; + } + document.getElementById('p2-quiz-ok').textContent = ok; + }); + }); + } + document.getElementById('p2-quiz-next').addEventListener('click', ()=>{ + i = (i+1) % QS.length; render(); + }); + render(); +} + +/* === §2 IV-3: DnD сортировка === */ +function _initP2_dnd(){ + const items = [ + {id:'a', cat:'work', html:'трение ладонями'}, + {id:'b', cat:'work', html:'сжатие воздуха в насосе'}, + {id:'c', cat:'work', html:'удар молотком по гвоздю'}, + {id:'d', cat:'work', html:'строгание доски рубанком'}, + {id:'e', cat:'heat', html:'остывание чая в стакане'}, + {id:'f', cat:'heat', html:'нагрев ложки в супе'}, + {id:'g', cat:'heat', html:'Солнце греет асфальт'}, + {id:'h', cat:'heat', html:'батарея греет комнату'} + ]; + const dnd = setupSorter({ + poolId:'p2-dnd-pool', + scopeSelector:'#sec-p2', + cats:['work','heat'], + items: items, + columnLayout:false + }); + document.getElementById('p2-dnd-check').addEventListener('click', ()=>{ + const fb = document.getElementById('p2-dnd-fb'); + let wrong = 0, total = items.length; + items.forEach(it=>{ if(dnd.placed[it.id] !== it.cat) wrong++; }); + if(wrong === 0){ + fb.className='feedback ok'; fb.innerHTML='✓ Идеально! +15 XP. Работа связана с движением макротел, теплопередача — с разностью температур.'; + addXp(15,'p2-dnd'); bumpProgress('p2', 20); + } else { + fb.className='feedback fail'; fb.innerHTML='✗ Ошибок: '+wrong+' из '+total+'. Подсказка: трение, сжатие, удар, строгание — это работа.'; + } + }); + document.getElementById('p2-dnd-reset').addEventListener('click', ()=>{ + dnd.reset(); + const fb = document.getElementById('p2-dnd-fb'); fb.style.display='none'; + }); +} + +/* === §2 IV-4: MCQ тренажёр === */ +function _initP2_mcq(){ + const QS = [ + {q:'Какими способами можно изменить $U$ тела?', opts:['Только работой','Только теплопередачей','Работой и теплопередачей','Только нагревом'], ans:2, why:'Это два равноправных способа.'}, + {q:'Что нагревает наковальню при ковке?', opts:['Теплопередача','Работа удара','Излучение','Конвекция'], ans:1, why:'Кинетическая энергия молотка совершает работу деформации.'}, + {q:'Какой вид теплопередачи греет руку от костра, если не подходить близко?', opts:['Теплопроводность','Конвекция','Излучение','Работа'], ans:2, why:'Через воздух (плохой проводник) и без контакта — это излучение.'}, + {q:'Чай в термосе долго не остывает, потому что…', opts:['у термоса большая масса','стенки термоса тонкие','двойные стенки и вакуум между ними подавляют все виды теплопередачи','чай совершает работу'], ans:2, why:'Вакуум — нет конвекции и проводности; зеркальные стенки уменьшают излучение.'}, + {q:'Какой пример НЕ относится к теплопередаче?', opts:['Ложка нагрелась в супе','Воздух у радиатора поднимается вверх','Снег тает на солнце','Гвоздь нагрелся, когда его забили молотком'], ans:3, why:'Гвоздь нагрелся работой удара.'}, + {q:'Знак $\\Delta U$ для остывающего тела?', opts:['$\\Delta U > 0$','$\\Delta U < 0$','$\\Delta U = 0$','зависит от массы'], ans:1, why:'$T$ падает $\\Rightarrow$ $U$ уменьшается $\\Rightarrow$ $\\Delta U < 0$.'} + ]; + let i = 0, ok = 0, done = 0, awarded = false; + function render(){ + const q = QS[i]; + const wrap = document.getElementById('p2-mcq'); + if(!wrap) return; + let h = '
Вопрос '+(i+1)+'. '+q.q+'
'; + h += '
'; + q.opts.forEach((opt, k)=>{ + h += ''; + }); + h += '
'; + h += '
'; + wrap.innerHTML = h; + document.getElementById('p2-mcq-i').textContent = (i+1); + document.getElementById('p2-mcq-ok').textContent = ok; + wrap.querySelectorAll('[data-k]').forEach(btn=>{ + btn.addEventListener('click', ()=>{ + if(btn.disabled) return; + wrap.querySelectorAll('[data-k]').forEach(b=>b.disabled=true); + const k = +btn.dataset.k; + const fb = document.getElementById('p2-mcq-fb'); + if(k === q.ans){ + ok++; done++; + fb.className='feedback ok'; fb.innerHTML='✓ Верно. '+q.why; + addXp(2,'p2-mcq'); bumpProgress('p2', 3); + } else { + done++; + fb.className='feedback fail'; fb.innerHTML='✗ Не то. '+q.why; + } + document.getElementById('p2-mcq-ok').textContent = ok; + renderMath(wrap); + if(done >= QS.length && !awarded && ok >= 4){ + awarded = true; + setTimeout(()=>{ + const wgFb = document.getElementById('p2-mcq-fb'); + wgFb.className='feedback ok'; + wgFb.innerHTML='✓ +15 XP — тренажёр пройден ('+ok+' из '+QS.length+').'; + addXp(15,'p2-mcq-bonus'); bumpProgress('p2', 15); + }, 600); + } + }); + }); + const nextBtn = document.getElementById('p2-mcq-next'); + if(nextBtn) nextBtn.addEventListener('click', ()=>{ i=(i+1)%QS.length; render(); }); + renderMath(wrap); + } + render(); +} + +/* ====================================================================== + PHASE 1 · WAVE 2 — §3, §4, §5 + ====================================================================== */ + +/* ======== §3 — Теплопроводность ======== */ +function build_p3(){ + const box = document.getElementById('p3-body'); + let h = ''; + + h += makeCard('theory', 'Что такое теплопроводность', '§ 3.1', + '

Теплопроводность — передача внутренней энергии от более горячих частей тела к более холодным без переноса вещества.

' + +'

Механизм: быстрые молекулы (в горячей части) сталкиваются с медленными (в холодной) и передают им часть своей $E_k$. Так энергия путешествует через тело молекула за молекулой.

' + +'

Само вещество при этом остаётся на месте — двигается только энергия.

' + ); + h += makeCard('rule', 'Хорошие и плохие проводники', '§ 3.2', + '

Хорошие проводники тепла:

' + +'
  • металлы: серебро, медь, алюминий, железо;
  • в них свободные электроны быстро переносят энергию.
' + +'

Плохие проводники (тепловые изоляторы):

' + +'
  • дерево, стекло, кирпич, пластик;
  • шерсть, мех, пух, вата (они «держат» воздух между волокнами);
  • сами газы и жидкости (кроме ртути);
  • вакуум — идеальный изолятор: нет частиц $\\Rightarrow$ нечем передавать.
' + ); + h += makeCard('example', 'Зачем это нам', '§ 3.3', + '
    ' + +'
  • Ручка сковороды — из дерева или пластика, чтобы не обжечь руку.
  • ' + +'
  • Шуба греет не сама — между волосками много воздуха.
  • ' + +'
  • Стеклопакет: два стекла + воздух между ними плохо проводят тепло.
  • ' + +'
  • В термосе между двумя стенками — вакуум (минимум теплопроводности).
  • ' + +'
' + ); + + /* IV1 — симуляция теплопроводности */ + h += '
' + +'
IV-1
Нагрев стержня
' + +'
Один конец стержня — горячий ($T_{гор}$), другой — холодный ($T_{хол}$). Тепло перетекает с горячего конца на холодный. Цвет показывает температуру вдоль стержня. Меняй $\\alpha$ — коэффициент теплопроводности.
' + +'
' + +'' + +'' + +'' + +'
' + +'' + +'
' + +'
'; + + /* IV2 — викторина «лучший проводник» */ + h += '
' + +'
IV-2
Какой материал проводит тепло лучше?
' + +'
Выбери материал с лучшей теплопроводностью.
' + +'
' + +'
' + +'
Раунд: 1 / 6Правильно: 0
' + +'
'; + + /* IV3 — DnD хороший/плохой */ + h += '
' + +'
IV-3
Сортировка материалов
' + +'
Перетащи материалы в нужную группу.
' + +'
' + +'
' + +'
Хорошие проводники
' + +'
Плохие проводники
' + +'
' + +'
' + +'' + +'
'; + + /* IV4 — MCQ */ + h += '
' + +'
IV-4
Тренажёр: 6 вопросов
' + +'
4+ правильных ответа — +15 XP.
' + +'
' + +'
Вопрос: 1 / 6Правильно: 0
' + +'
'; + + /* IV5 — Расчётные задачи (auto-injected) */ + h += '
' + +'
IV-5
Тренажёр: 5 расчётных задач
' + +'
Введи числовой ответ (можно с точкой как разделителем). Решено все верно — +20 XP.
' + +'
' + +'
Задача: 1 / 5Правильно: 0
' + +'
'; + + /* IV6 — flagship интерактив (заглушка Phase 1, наполнение в Phase 1.3) */ + h += '
' + +'
IV-6
Новый интерактив §3
' + +'
Готовится: интерактивная визуализация с drag-and-drop для углубления темы. Скоро будет доступна.
' + +'
' + +'' + +'
Phase 1.3 — coming soon
' + +'
' + +'
'; box.innerHTML = h + secNavFor('p3') + readButton('p3'); renderMath(box); wireReadBtn('p3'); - _initP3_iv6(); _initp3_iv5(); _initP3_sim(); @@ -1902,30 +1945,684 @@ function build_p4(){ +'
Задача: 1 / 5Правильно: 0
' +'
'; - /* IV6 — Heat Mixer (Phase 1.2) */ + /* IV6 — flagship интерактив (заглушка Phase 1, наполнение в Phase 1.4) */ h += '
' - +'
IV-6
Смесь двух жидкостей — рассчитай конечную T
' - +'
Перетащи ёмкости друг к другу — они смешаются. Масса и начальная температура каждой — на скрубберах ниже. Конечная температура считается по уравнению теплового баланса $c m_1 (T_1 - T) = c m_2 (T - T_2)$.
' - +'
' - +'
' - +'
m₁0.5кг
' - +'
T₁80°C
' - +'
m₂1.0кг
' - +'
T₂20°C
' - +'
' - +'
' - +'
T_итог°C
' - +'' - +'' + +'
IV-6
Новый интерактив §4
' + +'
Готовится: интерактивная визуализация с drag-and-drop для углубления темы. Скоро будет доступна.
' + +'
' + +'' + +'
Phase 1.4 — coming soon
' +'
' +'
'; - + box.innerHTML = h + secNavFor('p4') + readButton('p4'); + renderMath(box); + wireReadBtn('p4'); + _initp4_iv5(); + + _initP4_sim(); + _initP4_quiz(); + _initP4_dnd(); + _initP4_mcq(); +} + +function _initp4_iv5(){ + const TASKS = [{"q":"Плотность тёплого воздуха в $\\rho_1 = 1{,}1$ кг/м³, холодного $\\rho_2 = 1{,}3$ кг/м³. На сколько % холодный плотнее? $((\\rho_2 - \\rho_1)/\\rho_1) \\cdot 100$.","ans":18,"tol":1,"why":"$(1{,}3 - 1{,}1)/1{,}1 \\cdot 100 \\approx 18\\,\\%$."},{"q":"Радиатор отдаёт мощность $P = 1500$ Вт, нагревая воздух массой $m = 50$ кг за $t = 60$ с. На сколько $\\Delta T$ нагрелся воздух? ($c_{возд} = 1000$ Дж/(кг·К))","ans":1.8,"tol":0.1,"why":"$\\Delta T = Q/(cm) = (P\\cdot t)/(cm) = (1500 \\cdot 60)/(1000 \\cdot 50) = 1{,}8$ К."},{"q":"Вода нагревается снизу. Где будет тёплая вода: $a)$ снизу, $b)$ сверху? Введите 2, если сверху, 1, если снизу.","ans":2,"tol":0.1,"why":"Тёплая вода легче — поднимается вверх. Это и есть конвекция."},{"q":"Холодильник остужает $m = 2$ кг воздуха с $T_1 = 25$ до $T_2 = 5\\,^\\circ$C. Какое тепло (в кДж) он унёс? ($c = 1000$)","ans":40,"tol":1,"why":"$Q = cm\\Delta T = 1000 \\cdot 2 \\cdot 20 = 40\\,000$ Дж $= 40$ кДж."},{"q":"Ветер охлаждает кожу. Если без ветра тело отдаёт $P_0 = 50$ Вт, а с ветром $P = 200$ Вт, во сколько раз быстрее идёт теплоотдача?","ans":4,"tol":0.1,"why":"$P/P_0 = 200/50 = 4$ раза."}]; + let i = 0, ok = 0, awarded = false; + function render(){ + const t = TASKS[i]; const wrap = document.getElementById('p4-tasks5'); if(!wrap) return; + wrap.innerHTML = + '
Задача '+(i+1)+'. '+t.q+'
' + +'
' + +'' + +'' + +'
' + +'' + +'
'; + if (window.renderMathInElement) try { renderMathInElement(wrap, {delimiters:[{left:'$',right:'$',display:false}],throwOnError:false}); } catch(e){} + document.getElementById('p4-iv5-go').onclick = () => { + const v = parseFloat(document.getElementById('p4-iv5-inp').value.replace(',','.')); + const fb = document.getElementById('p4-iv5-fb'); + const wh = document.getElementById('p4-iv5-why-wrap'); + if (Math.abs(v - t.ans) <= t.tol) { + fb.className = 'feedback ok'; fb.innerHTML = 'Верно!'; ok++; + document.getElementById('p4-tasks5-ok').textContent = ok; + wh.style.display = 'block'; + } else { + fb.className = 'feedback fail'; fb.innerHTML = 'Не совсем. Ожидался $' + t.ans + '$. Загляни в подсказку.'; + if (window.renderMathInElement) try { renderMathInElement(fb, {delimiters:[{left:'$',right:'$',display:false}],throwOnError:false}); } catch(e){} + } + }; + document.getElementById('p4-iv5-hint').onclick = () => { + const wh = document.getElementById('p4-iv5-why-wrap'); + wh.style.display = wh.style.display === 'block' ? 'none' : 'block'; + }; + document.getElementById('p4-iv5-next').onclick = () => { + i = (i + 1) % TASKS.length; + document.getElementById('p4-tasks5-i').textContent = i + 1; + render(); + if (ok === TASKS.length && !awarded) { awarded = true; if (typeof addXp === 'function') addXp(20, 'p4-iv5'); } + }; + } + render(); +} + +function _initP4_sim(){ + _killSim('p4sim'); + const svg = document.getElementById('p4-sim'); if(!svg) return; + const W=460, H=240, vesselX=120, vesselY=20, vesselW=220, vesselH=180; + /* 30 частиц жидкости */ + const N = 30; + const ps = []; + for(let i=0;i 0 ? 1 : -1) * 0.8; + else if(yNorm > 0.82) p.vx = (dx < 0 ? 1 : -1) * 0.8; + else p.vx *= 0.85; + } else { + p.T = Math.max(20, p.T - 0.1); + p.vx *= 0.94; p.vy *= 0.94; + } + p.x += p.vx; p.y += p.vy; + /* стенки */ + if(p.x < vesselX+6){ p.x = vesselX+6; p.vx = Math.abs(p.vx); } + if(p.x > vesselX+vesselW-6){ p.x = vesselX+vesselW-6; p.vx = -Math.abs(p.vx); } + if(p.y < vesselY+6){ p.y = vesselY+6; p.vy = Math.abs(p.vy); } + if(p.y > vesselY+vesselH-6){ p.y = vesselY+vesselH-6; p.vy = -Math.abs(p.vy); } + } + let s = ''; + /* сосуд */ + s += ''; + /* нагреватель снизу */ + s += ''; + s += ''+(heaterOn?'НАГРЕВАТЕЛЬ ВКЛ':'выкл')+''; + /* частицы */ + for(const p of ps){ + const c = window.PHYS.tempColor(p.T, 20, 90); + s += ''; + } + /* стрелки потока (декоративные) */ + if(heaterOn){ + const cx = vesselX + vesselW/2; + s += window.PHYS.drawArrow(cx, vesselY+vesselH-20, cx, vesselY+30, '#dc2626', 2, 9); + s += window.PHYS.drawArrow(vesselX+18, vesselY+30, vesselX+18, vesselY+vesselH-20, '#2563eb', 2, 9); + s += window.PHYS.drawArrow(vesselX+vesselW-18, vesselY+30, vesselX+vesselW-18, vesselY+vesselH-20, '#2563eb', 2, 9); + } + svg.innerHTML = s; + _SIMS.p4sim.raf = requestAnimationFrame(tick); + } + _SIMS.p4sim = { raf: 0 }; + _SIMS.p4sim.raf = requestAnimationFrame(tick); + document.getElementById('p4-on').addEventListener('click', ()=>{ + heaterOn = !heaterOn; + document.getElementById('p4-on').textContent = heaterOn ? 'Выключить нагрев' : 'Включить нагрев'; + }); + document.getElementById('p4-reset').addEventListener('click', ()=>{ + heaterOn = false; + document.getElementById('p4-on').textContent = 'Включить нагрев'; + for(let i=0;i'+q.sit+'
' + +'
' + +'' + +'' + +'
' + +'
'; + document.getElementById('p4-quiz-r').textContent = (i+1); + document.getElementById('p4-quiz-ok').textContent = ok; + wrap.querySelectorAll('[data-pick]').forEach(btn=>{ + btn.addEventListener('click', ()=>{ + if(btn.disabled) return; wrap.querySelectorAll('[data-pick]').forEach(b=>b.disabled=true); + const fb = document.getElementById('p4-quiz-fb'); + if(btn.dataset.pick === q.ans){ ok++; fb.className='feedback ok'; fb.innerHTML='✓ Верно. '+q.why; addXp(3,'p4-quiz'); bumpProgress('p4', 4); } + else { fb.className='feedback fail'; fb.innerHTML='✗ Не то. '+q.why; } + document.getElementById('p4-quiz-ok').textContent = ok; + }); + }); + } + document.getElementById('p4-quiz-next').addEventListener('click', ()=>{ i=(i+1)%QS.length; render(); }); + render(); +} + +function _initP4_dnd(){ + const items = [ + {id:'rad', cat:'yes', html:'воздух у радиатора'}, + {id:'ket', cat:'yes', html:'вода в кипящем чайнике'}, + {id:'oce', cat:'yes', html:'течения в океане'}, + {id:'wnd', cat:'yes', html:'ветер в атмосфере'}, + {id:'sto', cat:'no', html:'нагрев стального стержня'}, + {id:'top', cat:'no', html:'вода, нагреваемая сверху'}, + {id:'vac', cat:'no', html:'жидкость в невесомости'}, + {id:'wal', cat:'no', html:'кирпичная стена'} + ]; + const dnd = setupSorter({ poolId:'p4-dnd-pool', scopeSelector:'#sec-p4', cats:['yes','no'], items, columnLayout:false }); + document.getElementById('p4-dnd-check').addEventListener('click', ()=>{ + const fb = document.getElementById('p4-dnd-fb'); + let wrong = 0; items.forEach(it=>{ if(dnd.placed[it.id] !== it.cat) wrong++; }); + if(wrong===0){ fb.className='feedback ok'; fb.innerHTML='✓ Идеально! +15 XP. Конвекция требует текучей среды + разности температур + гравитации.'; addXp(15,'p4-dnd'); bumpProgress('p4', 20); } + else { fb.className='feedback fail'; fb.innerHTML='✗ Ошибок: '+wrong+'. В твёрдых телах и при нагреве сверху конвекции нет.'; } + }); + document.getElementById('p4-dnd-reset').addEventListener('click', ()=>{ dnd.reset(); const fb=document.getElementById('p4-dnd-fb'); fb.style.display='none'; }); +} + +function _initP4_mcq(){ + const QS = [ + {q:'Что переносится при конвекции?', opts:['Только энергия','Только вещество','Вещество вместе с энергией','Электромагнитные волны'], ans:2, why:'Жидкость или газ движется вместе с теплотой.'}, + {q:'Почему в твёрдых телах нет конвекции?', opts:['Они холодные','Молекулы не могут свободно перемещаться','Они не проводят тепло','Они отражают энергию'], ans:1, why:'В твёрдом теле молекулы прочно связаны и не образуют потоков.'}, + {q:'Как направлен поток в нагреваемой снизу жидкости?', opts:['Горячий вниз, холодный вверх','Горячий вверх, холодный вниз','Хаотически','Только по краям'], ans:1, why:'Нагретая часть менее плотная — всплывает.'}, + {q:'Зачем радиатор ставят под окном?', opts:['Чтобы не мешал','Чтобы тёплый воздух поднимался, не пропуская холодного с улицы','Так красивее','Для экономии труб'], ans:1, why:'Восходящий тёплый поток сразу отсекает падающий холодный.'}, + {q:'Возможна ли конвекция в открытом космосе?', opts:['Да, как на Земле','Только в атмосфере планет','Нет, нужна гравитация и среда','Только у звёзд'], ans:2, why:'В вакууме нет среды; в невесомости нет всплывания.'}, + {q:'Куда направить пламя свечи, чтобы быстрее закипела вода в стакане?', opts:['Сверху','Снизу','Сбоку','Не имеет значения'], ans:1, why:'Нагрев снизу запускает конвекцию — вода прогреется быстрее.'} + ]; + let i = 0, ok = 0, done = 0, awarded = false; + function render(){ + const q = QS[i]; const wrap = document.getElementById('p4-mcq'); if(!wrap) return; + let h = '
Вопрос '+(i+1)+'. '+q.q+'
'; + h += '
'; + q.opts.forEach((opt, k)=>{ h += ''; }); + h += '
'; + wrap.innerHTML = h; + document.getElementById('p4-mcq-i').textContent = (i+1); + document.getElementById('p4-mcq-ok').textContent = ok; + wrap.querySelectorAll('[data-k]').forEach(btn=>{ + btn.addEventListener('click', ()=>{ + if(btn.disabled) return; wrap.querySelectorAll('[data-k]').forEach(b=>b.disabled=true); + const k = +btn.dataset.k; const fb = document.getElementById('p4-mcq-fb'); + if(k===q.ans){ ok++; done++; fb.className='feedback ok'; fb.innerHTML='✓ Верно. '+q.why; addXp(2,'p4-mcq'); bumpProgress('p4', 3); } + else { done++; fb.className='feedback fail'; fb.innerHTML='✗ Не то. '+q.why; } + document.getElementById('p4-mcq-ok').textContent = ok; + if(done >= QS.length && !awarded && ok >= 4){ awarded = true; setTimeout(()=>{ const wf = document.getElementById('p4-mcq-fb'); wf.className='feedback ok'; wf.innerHTML='✓ +15 XP — тренажёр пройден ('+ok+' из '+QS.length+').'; addXp(15,'p4-mcq-bonus'); bumpProgress('p4', 15); }, 600); } + }); + }); + const nb = document.getElementById('p4-mcq-next'); if(nb) nb.addEventListener('click', ()=>{ i=(i+1)%QS.length; render(); }); + } + render(); +} + +/* ======== §5 — Излучение ======== */ +function build_p5(){ + const box = document.getElementById('p5-body'); + let h = ''; + + h += makeCard('theory', 'Что такое тепловое излучение', '§ 5.1', + '

Излучение — передача энергии электромагнитными волнами. Не требует среды: идёт через вакуум.

' + +'

Любое тело с температурой выше абсолютного нуля излучает. Чем выше $T$, тем интенсивнее излучение и тем более коротковолновое (горячая печь — инфракрасный диапазон; Солнце — видимый свет).

' + +'

Так Солнце греет Землю, прошив 150 млн км вакуума за 8 минут.

' + ); + h += makeCard('rule', 'Поглощение и отражение', '§ 5.2', + '' + +'

Правило: что хорошо поглощает, то хорошо и излучает.

' + ); + h += makeCard('example', 'Применение', '§ 5.3', + '' + ); + + /* IV1 — солнечный нагрев чёрного и белого */ + h += '
' + +'
IV-1
Чёрное и белое под Солнцем
' + +'
Включи Солнце. Чёрная пластина поглощает излучение и быстро нагревается, белая отражает и нагревается медленно.
' + +'' + +'
' + +'
' + +'Чёрная пластина: $T = $ 20 °C' + +'Белая пластина: $T = $ 20 °C' + +'
' + +'
'; + + /* IV2 — квикфайр «правда/ложь» */ + h += '
' + +'
IV-2
Правда или ложь?
' + +'
Прочитай утверждение и реши, правдиво оно или нет.
' + +'
' + +'
' + +'
Раунд: 1 / 7Правильно: 0
' + +'
'; + + /* IV3 — DnD 3 вида теплопередачи */ + h += '
' + +'
IV-3
3 вида теплопередачи
' + +'
Сортируй примеры на три группы: проводность, конвекция, излучение.
' + +'
' + +'
' + +'
Проводность
' + +'
Конвекция
' + +'
Излучение
' + +'
' + +'
' + +'
' + +'
'; + + /* IV4 — MCQ */ + h += '
' + +'
IV-4
Тренажёр: 6 вопросов
' + +'
4+ правильных — +15 XP.
' + +'
' + +'
Вопрос: 1 / 6Правильно: 0
' + +'
'; + + /* IV5 — Расчётные задачи (auto-injected) */ + h += '
' + +'
IV-5
Тренажёр: 5 расчётных задач
' + +'
Введи числовой ответ (можно с точкой как разделителем). Решено все верно — +20 XP.
' + +'
' + +'
Задача: 1 / 5Правильно: 0
' + +'
'; + + /* IV6 — flagship интерактив (заглушка Phase 1, наполнение в Phase 1.5) */ + h += '
' + +'
IV-6
Новый интерактив §5
' + +'
Готовится: интерактивная визуализация с drag-and-drop для углубления темы. Скоро будет доступна.
' + +'
' + +'' + +'
Phase 1.5 — coming soon
' + +'
' + +'
'; + + box.innerHTML = h + secNavFor('p5') + readButton('p5'); + renderMath(box); + wireReadBtn('p5'); + _initp5_iv5(); + + _initP5_sim(); + _initP5_quiz(); + _initP5_dnd(); + _initP5_mcq(); +} + +function _initp5_iv5(){ + const TASKS = [{"q":"Солнце нагревает квадратный метр земной поверхности с мощностью $P = 1000$ Вт. Сколько теплоты получит $S = 5$ м² за $t = 60$ с?","ans":300000,"tol":5000,"why":"$Q = P \\cdot S \\cdot t = 1000 \\cdot 5 \\cdot 60 = 300\\,000$ Дж = $300$ кДж."},{"q":"Черное тело излучает в 2 раза эффективнее белого. Если белое тело отдаёт $P_1 = 100$ Вт, сколько отдаст чёрное при той же $T$?","ans":200,"tol":5,"why":"$P_{черн} = 2 \\cdot P_{белого} = 2 \\cdot 100 = 200$ Вт."},{"q":"Какая температура (в К) горячей плиты, если её излучение в 16 раз сильнее излучения тела при $T_0 = 300$ К? ($P \\propto T^4$)","ans":600,"tol":10,"why":"$P/P_0 = (T/T_0)^4 = 16$, откуда $T/T_0 = 2$, $T = 600$ К."},{"q":"Какой цвет одежды летом холоднее: белый или чёрный? Введите 1, если чёрный, 2, если белый.","ans":2,"tol":0.1,"why":"Белая отражает солнечное излучение лучше — в ней прохладнее."},{"q":"Тело площадью $S = 0{,}5$ м² излучает $P = 200$ Вт. Найдите интенсивность излучения $I = P/S$ (Вт/м²).","ans":400,"tol":10,"why":"$I = P/S = 200/0{,}5 = 400$ Вт/м²."}]; + let i = 0, ok = 0, awarded = false; + function render(){ + const t = TASKS[i]; const wrap = document.getElementById('p5-tasks5'); if(!wrap) return; + wrap.innerHTML = + '
Задача '+(i+1)+'. '+t.q+'
' + +'
' + +'' + +'' + +'
' + +'' + +'
'; + if (window.renderMathInElement) try { renderMathInElement(wrap, {delimiters:[{left:'$',right:'$',display:false}],throwOnError:false}); } catch(e){} + document.getElementById('p5-iv5-go').onclick = () => { + const v = parseFloat(document.getElementById('p5-iv5-inp').value.replace(',','.')); + const fb = document.getElementById('p5-iv5-fb'); + const wh = document.getElementById('p5-iv5-why-wrap'); + if (Math.abs(v - t.ans) <= t.tol) { + fb.className = 'feedback ok'; fb.innerHTML = 'Верно!'; ok++; + document.getElementById('p5-tasks5-ok').textContent = ok; + wh.style.display = 'block'; + } else { + fb.className = 'feedback fail'; fb.innerHTML = 'Не совсем. Ожидался $' + t.ans + '$. Загляни в подсказку.'; + if (window.renderMathInElement) try { renderMathInElement(fb, {delimiters:[{left:'$',right:'$',display:false}],throwOnError:false}); } catch(e){} + } + }; + document.getElementById('p5-iv5-hint').onclick = () => { + const wh = document.getElementById('p5-iv5-why-wrap'); + wh.style.display = wh.style.display === 'block' ? 'none' : 'block'; + }; + document.getElementById('p5-iv5-next').onclick = () => { + i = (i + 1) % TASKS.length; + document.getElementById('p5-tasks5-i').textContent = i + 1; + render(); + if (ok === TASKS.length && !awarded) { awarded = true; if (typeof addXp === 'function') addXp(20, 'p5-iv5'); } + }; + } + render(); +} + +function _initP5_sim(){ + _killSim('p5sim'); + const svg = document.getElementById('p5-sim'); if(!svg) return; + const W=460, H=240; + let sunOn = false; + let tB = 20, tW = 20; /* black, white plate temps */ + function reset(){ tB=20; tW=20; sunOn=false; document.getElementById('p5-on').textContent='Включить Солнце'; document.getElementById('p5-tb').textContent='20'; document.getElementById('p5-tw').textContent='20'; } + function tick(){ + if(!_isVisible('p5')){ _SIMS.p5sim.raf = requestAnimationFrame(tick); return; } + if(sunOn){ + /* чёрная: поглощает 0.92, белая: 0.15 */ + if(tB < 75) tB += 0.32; + if(tW < 35) tW += 0.05; + } else { + if(tB > 20) tB -= 0.10; + if(tW > 20) tW -= 0.10; + } + document.getElementById('p5-tb').textContent = tB.toFixed(0); + document.getElementById('p5-tw').textContent = tW.toFixed(0); + /* draw */ + let s = ''; + /* Солнце */ + const cx = 230, cy = 36; + s += ''; + if(sunOn){ + for(let i=0;i<12;i++){ + const a = i*Math.PI/6; + const x1 = cx + 26*Math.cos(a), y1 = cy + 26*Math.sin(a); + const x2 = cx + 36*Math.cos(a), y2 = cy + 36*Math.sin(a); + s += ''; + } + } + /* Лучи к пластинам */ + if(sunOn){ + const ray = (x1,y1,x2,y2,col)=>''; + /* к чёрной */ + s += ray(cx-22, cy+18, 120, 175, '#fbbf24'); + s += ray(cx-10, cy+22, 145, 175, '#fbbf24'); + s += ray(cx, cy+22, 170, 175, '#fbbf24'); + /* к белой */ + s += ray(cx+22, cy+18, 340, 175, '#fbbf24'); + s += ray(cx+10, cy+22, 315, 175, '#fbbf24'); + s += ray(cx, cy+22, 290, 175, '#fbbf24'); + /* отражённые от белой (вверх) */ + s += ''; + s += ''; + s += ''; + } + /* чёрная пластина */ + const cB = sunOn ? window.PHYS.tempColor(tB, 20, 80) : '#1f2937'; + s += ''; + s += ''+tB.toFixed(0)+' °C'; + s += 'ЧЁРНАЯ'; + /* белая пластина */ + s += ''; + s += ''+tW.toFixed(0)+' °C'; + s += 'БЕЛАЯ'; + svg.innerHTML = s; + _SIMS.p5sim.raf = requestAnimationFrame(tick); + } + _SIMS.p5sim = { raf: 0 }; + _SIMS.p5sim.raf = requestAnimationFrame(tick); + document.getElementById('p5-on').addEventListener('click', ()=>{ + sunOn = !sunOn; + document.getElementById('p5-on').textContent = sunOn ? 'Выключить Солнце' : 'Включить Солнце'; + }); + document.getElementById('p5-reset').addEventListener('click', reset); +} + +function _initP5_quiz(){ + const QS = [ + {st:'Излучение требует среды (например, воздуха) для распространения.', ans:'F', why:'Излучение проходит через вакуум, что и доказывает солнечный свет.'}, + {st:'Тёмные тела сильнее нагреваются под Солнцем, чем светлые.', ans:'T', why:'Тёмная поверхность поглощает большую долю излучения.'}, + {st:'Любое тело с $T > 0$ К излучает.', ans:'T', why:'Тепловое излучение есть всегда, чем горячее — тем сильнее.'}, + {st:'Чем горячее тело, тем меньше излучения оно отдаёт.', ans:'F', why:'Наоборот: интенсивность излучения резко растёт с температурой.'}, + {st:'Хороший поглотитель — хороший излучатель.', ans:'T', why:'Это правило Кирхгофа: $a = \\varepsilon$.'}, + {st:'Зеркальная поверхность плохо излучает.', ans:'T', why:'Зеркало почти всё отражает — а значит и излучает мало.'}, + {st:'Излучение нельзя обнаружить, если тело не светится видимым светом.', ans:'F', why:'Тело при комнатной температуре излучает инфракрасные волны — их видит тепловизор.'} + ]; + let i = 0, ok = 0; + function render(){ + const q = QS[i]; const wrap = document.getElementById('p5-quiz'); if(!wrap) return; + wrap.innerHTML = + '
"'+q.st+'"
' + +'
' + +'' + +'' + +'
' + +''; + document.getElementById('p5-quiz-r').textContent = (i+1); + document.getElementById('p5-quiz-ok').textContent = ok; + wrap.querySelectorAll('[data-pick]').forEach(btn=>{ + btn.addEventListener('click', ()=>{ + if(btn.disabled) return; wrap.querySelectorAll('[data-pick]').forEach(b=>b.disabled=true); + const fb = document.getElementById('p5-quiz-fb'); + if(btn.dataset.pick === q.ans){ ok++; fb.className='feedback ok'; fb.innerHTML='✓ Верно. '+q.why; addXp(3,'p5-quiz'); bumpProgress('p5', 4); } + else { fb.className='feedback fail'; fb.innerHTML='✗ Не то. '+q.why; } + document.getElementById('p5-quiz-ok').textContent = ok; + renderMath(wrap); + }); + }); + renderMath(wrap); + } + document.getElementById('p5-quiz-next').addEventListener('click', ()=>{ i=(i+1)%QS.length; render(); }); + render(); +} + +function _initP5_dnd(){ + const items = [ + {id:'spoon', cat:'cond', html:'нагрев ложки в супе'}, + {id:'bar', cat:'cond', html:'нагрев конца железного прута'}, + {id:'iron', cat:'cond', html:'утюг гладит бельё'}, + {id:'rad', cat:'conv', html:'батарея греет комнату'}, + {id:'ket', cat:'conv', html:'вода в кипящем чайнике'}, + {id:'wnd', cat:'conv', html:'ветры в атмосфере'}, + {id:'sun', cat:'rad', html:'Солнце греет Землю'}, + {id:'fire', cat:'rad', html:'тепло от костра на расстоянии'}, + {id:'lamp', cat:'rad', html:'нагрев руки под лампой накаливания'} + ]; + const dnd = setupSorter({ poolId:'p5-dnd-pool', scopeSelector:'#sec-p5', cats:['cond','conv','rad'], items, columnLayout:false }); + document.getElementById('p5-dnd-check').addEventListener('click', ()=>{ + const fb = document.getElementById('p5-dnd-fb'); + let wrong = 0; items.forEach(it=>{ if(dnd.placed[it.id] !== it.cat) wrong++; }); + if(wrong===0){ fb.className='feedback ok'; fb.innerHTML='✓ Идеально! +20 XP. Ты освоил все 3 вида теплопередачи.'; addXp(20,'p5-dnd'); bumpProgress('p5', 25); } + else { fb.className='feedback fail'; fb.innerHTML='✗ Ошибок: '+wrong+'. Проверь: контакт = проводность; потоки = конвекция; через пустоту = излучение.'; } + }); + document.getElementById('p5-dnd-reset').addEventListener('click', ()=>{ dnd.reset(); const fb=document.getElementById('p5-dnd-fb'); fb.style.display='none'; }); +} + +function _initP5_mcq(){ + const QS = [ + {q:'Через какую среду НЕ может идти излучение?', opts:['Через воздух','Через стекло','Через вакуум','Излучение проходит через всё перечисленное'], ans:3, why:'Излучение распространяется и в среде, и в вакууме.'}, + {q:'Какая поверхность нагреется сильнее на Солнце?', opts:['Зеркальная','Белая','Чёрная','Прозрачная'], ans:2, why:'Чёрная поглощает больше всего излучения.'}, + {q:'Какие тела излучают?', opts:['Только горячие','Только светящиеся','Любые с $T > 0$ К','Только Солнце'], ans:2, why:'Тепловое излучение существует у любого тела с ненулевой температурой.'}, + {q:'Какой вид теплопередачи переносит тепло от Солнца к Земле?', opts:['Теплопроводность','Конвекция','Излучение','Все три'], ans:2, why:'Между Солнцем и Землёй вакуум — работает только излучение.'}, + {q:'Чем покрашен правильный термос изнутри?', opts:['Чёрной краской','Зеркальной плёнкой','Деревом','Стеклом'], ans:1, why:'Зеркальная поверхность плохо излучает — тепло сохраняется.'}, + {q:'Чёрная футболка в жару нагревается потому что…', opts:['легче белой','лучше поглощает излучение','быстрее остывает','тоньше'], ans:1, why:'Тёмная ткань поглощает большую часть солнечного излучения.'} + ]; + let i = 0, ok = 0, done = 0, awarded = false; + function render(){ + const q = QS[i]; const wrap = document.getElementById('p5-mcq'); if(!wrap) return; + let h = '
Вопрос '+(i+1)+'. '+q.q+'
'; + h += '
'; + q.opts.forEach((opt, k)=>{ h += ''; }); + h += '
'; + wrap.innerHTML = h; + document.getElementById('p5-mcq-i').textContent = (i+1); + document.getElementById('p5-mcq-ok').textContent = ok; + wrap.querySelectorAll('[data-k]').forEach(btn=>{ + btn.addEventListener('click', ()=>{ + if(btn.disabled) return; wrap.querySelectorAll('[data-k]').forEach(b=>b.disabled=true); + const k = +btn.dataset.k; const fb = document.getElementById('p5-mcq-fb'); + if(k===q.ans){ ok++; done++; fb.className='feedback ok'; fb.innerHTML='✓ Верно. '+q.why; addXp(2,'p5-mcq'); bumpProgress('p5', 3); } + else { done++; fb.className='feedback fail'; fb.innerHTML='✗ Не то. '+q.why; } + document.getElementById('p5-mcq-ok').textContent = ok; + renderMath(wrap); + if(done >= QS.length && !awarded && ok >= 4){ awarded = true; setTimeout(()=>{ const wf = document.getElementById('p5-mcq-fb'); wf.className='feedback ok'; wf.innerHTML='✓ +15 XP — тренажёр пройден ('+ok+' из '+QS.length+').'; addXp(15,'p5-mcq-bonus'); bumpProgress('p5', 15); }, 600); } + }); + }); + const nb = document.getElementById('p5-mcq-next'); if(nb) nb.addEventListener('click', ()=>{ i=(i+1)%QS.length; render(); }); + renderMath(wrap); + } + render(); +} + +/* ====================================================================== + PHASE 1 · WAVE 3 — §6, §7 + ====================================================================== */ + +/* Таблица удельных теплоёмкостей (Дж/(кг·К)) */ +const MAT_C = [ + {key:'water', name:'вода', c:4200}, + {key:'ice', name:'лёд', c:2100}, + {key:'oil', name:'масло раст.',c:2000}, + {key:'wood', name:'дерево', c:2400}, + {key:'glass', name:'стекло', c:840 }, + {key:'al', name:'алюминий', c:920 }, + {key:'fe', name:'железо', c:460 }, + {key:'brass', name:'латунь', c:380 }, + {key:'cu', name:'медь', c:380 }, + {key:'lead', name:'свинец', c:130 }, + {key:'hg', name:'ртуть', c:140 } +]; + +/* Таблица удельных теплот сгорания (Дж/кг) */ +const MAT_Q = [ + {key:'wood', name:'дрова', q:1.0e7}, + {key:'peat', name:'торф', q:1.5e7}, + {key:'coal', name:'каменный уголь', q:3.0e7}, + {key:'alc', name:'спирт', q:2.7e7}, + {key:'oil', name:'нефть', q:4.4e7}, + {key:'gas', name:'природный газ', q:4.4e7}, + {key:'kero', name:'керосин', q:4.6e7}, + {key:'gasoline',name:'бензин', q:4.6e7} +]; + +/* ======== §6 — Q = cm ΔT ======== */ +function build_p6(){ + const box = document.getElementById('p6-body'); + let h = ''; + + h += makeCard('theory', 'Закон нагревания', '§ 6.1', + '

Чтобы изменить температуру тела массой $m$ на $\\Delta T = T_2 - T_1$, нужно сообщить ему (или отнять у него) количество теплоты:

' + +'

$$Q = c\\,m\\,\\Delta T$$

' + +'
    ' + +'
  • $Q$ — количество теплоты, Дж;
  • ' + +'
  • $c$ — удельная теплоёмкость вещества, Дж/(кг·К);
  • ' + +'
  • $m$ — масса, кг;
  • ' + +'
  • $\\Delta T$ — изменение температуры, К или °C (разница одна).
  • ' + +'
' + +'

Если $T_2 > T_1$ — тело нагревается, $Q > 0$. Если $T_2 < T_1$ — остывает, $Q < 0$.

' + ); + h += makeCard('rule', 'Что такое удельная теплоёмкость', '§ 6.2', + '

$c$ — количество теплоты, нужное чтобы нагреть 1 кг вещества на 1 К (или 1 °C).

' + +'

У воды $c = 4200$ Дж/(кг·К) — это очень много. Поэтому:

' + +'
    ' + +'
  • вода долго греется на плите;
  • ' + +'
  • вода долго остывает (поэтому грелка из воды держит тепло);
  • ' + +'
  • океан смягчает климат прибрежных стран.
  • ' + +'
' + +'

У металлов $c$ маленькая: медь 380, железо 460. Они быстро нагреваются и быстро остывают.

' + ); + h += makeCard('example', 'Уравнение теплового баланса', '§ 6.3', + '

Когда горячее тело отдаёт тепло холодному в изолированной системе (без потерь), вся отданная теплота получена холодным:

' + +'

$$Q_{отд} = Q_{пол}$$

' + +'

Если смешать массы $m_1$ при $T_1$ и $m_2$ при $T_2$ одного вещества:

' + +'

$$T = \\dfrac{m_1 T_1 + m_2 T_2}{m_1 + m_2}$$

' + +'

Это средневзвешенная температура.

' + ); + + /* IV1 — калькулятор Q = cmΔT с анимацией */ + let optsC = ''; + MAT_C.forEach(m=>{ optsC += ''; }); + h += '
' + +'
IV-1
Калькулятор $Q = cm\\Delta T$
' + +'
Выбери вещество, массу и изменение температуры — увидь, сколько энергии нужно. Термометр покажет процесс.
' + +'
' + +'' + +'' + +'' + +'
' + +'' + +'
' + +'$c$ = 4200 Дж/(кг·К)' + +'$Q$ = 2.10 × 10^5 Дж = 210 кДж' + +'Это столько энергии съедает плита или электрочайник.' + +'
' + +'
'; + + /* IV2 — калькулятор смешивания */ + h += '
' + +'
IV-2
Смешивание воды
' + +'
В термос налили $m_1$ кг воды при $T_1$ °C и долили $m_2$ кг воды при $T_2$ °C. Какая будет итоговая температура (без потерь)? Формула: $T = (m_1 T_1 + m_2 T_2)/(m_1 + m_2)$.
' + +'
' + +'' + +'' + +'' + +'' + +'
' + +'
$T_{итог} = $ 36.7 °C
' + +'
'; + + /* IV3 — DnD ранжирование c */ + h += '
' + +'
IV-3
У какого вещества $c$ больше?
' + +'
Перетащи вещества так, чтобы они шли от меньшего $c$ к большему. Подсказка: у воды $c$ рекордно велика, у металлов — мала.
' + +'
' + +'
' + +'
1. Меньше всех
' + +'
2
' + +'
3
' + +'
4
' + +'
5. Больше всех
' + +'
' + +'
' + +'' + +'
'; + + /* IV4 — тренажёр расчётных задач */ + h += '
' + +'
IV-4
Тренажёр: 6 расчётных задач
' + +'
Введи числовой ответ. 4+ верных решения — +15 XP. Допуск ±2%.
' + +'
' + +'
Задача: 1 / 6Правильно: 0
' + +'
'; + + /* IV6 — flagship интерактив (заглушка Phase 1, наполнение в Phase 1.6) */ + h += '
' + +'
IV-6
Новый интерактив §6
' + +'
Готовится: интерактивная визуализация с drag-and-drop для углубления темы. Скоро будет доступна.
' + +'
' + +'' + +'
Phase 1.6 — coming soon
' + +'
' + +'
'; box.innerHTML = h + secNavFor('p6') + readButton('p6'); renderMath(box); wireReadBtn('p6'); - _initP6_iv6(); _initP6_calc(); _initP6_mix(); @@ -2124,26 +2821,271 @@ function build_p7(){ +'
Задача: 1 / 5Правильно: 0
' +''; - /* IV6 — Phase Diagram T(t) (Phase 1.2) */ + /* IV6 — flagship интерактив (заглушка Phase 1, наполнение в Phase 1.7) */ h += '
' - +'
IV-6
График плавления — почему T не растёт?
' - +'
Запусти нагрев льда и наблюдай за графиком T(t). При плавлении энергия идёт на разрушение кристаллической решётки — T держится постоянной (плато при 0°C). Двигай ползунок мощности нагревателя — крутизна меняется.
' - +'
' - +'
' - +'
Мощность500Вт
' - +'
Фазалёд
' - +'
T-20°C
' - +'' - +'' + +'
IV-6
Новый интерактив §7
' + +'
Готовится: интерактивная визуализация с drag-and-drop для углубления темы. Скоро будет доступна.
' + +'
' + +'' + +'
Phase 1.7 — coming soon
' +'
' +'
'; - + box.innerHTML = h + secNavFor('p7') + readButton('p7'); + renderMath(box); + wireReadBtn('p7'); + + _initP7_calc(); + _initP7_quiz(); + _initP7_dnd(); + _initP7_tasks(); +} + +function _initP7_calc(){ + const svg = document.getElementById('p7-sim'); if(!svg) return; + function update(){ + const q = +document.getElementById('p7-fuel').value; + const m = +document.getElementById('p7-m').value; + document.getElementById('p7-mv').textContent = m.toFixed(1); + document.getElementById('p7-qv').innerHTML = q.toExponential(1).replace('+','').replace('e', ' × 10')+''; + const Q = q * m; + document.getElementById('p7-q').innerHTML = (Q/1e6).toFixed(1)+' МДж'; + document.getElementById('p7-qkw').textContent = (Q/3.6e6).toFixed(2); + /* эквивалент: нагрев воды от 20 до 100 → ΔT=80, Q=cmΔT, m = Q/(c·80) */ + document.getElementById('p7-eqkg').textContent = (Q/(4200*80)).toFixed(1); + /* SVG: огонь + котелок */ + let s = ''; + /* печь */ + s += ''; + /* пламя */ + const flameH = 30 + Math.min(50, m*5); + s += ''; + s += ''; + /* топливо */ + s += ''+m.toFixed(1)+' кг'; + /* стрелка-энергия */ + s += 'Q = q m'; + s += '= '+q.toExponential(1).replace('+','')+' · '+m.toFixed(1)+''; + s += 'Q = '+(Q/1e6).toFixed(1)+' МДж'; + s += ''+(Q/3.6e6).toFixed(2)+' кВт·ч'; + svg.innerHTML = s; + } + document.getElementById('p7-fuel').addEventListener('change', update); + document.getElementById('p7-m').addEventListener('input', update); + update(); +} + +function _initP7_quiz(){ + const QS = [ + {A:'дрова', B:'бензин', ans:'B', why:'У бензина $q \\approx 4{,}6 \\cdot 10^7$, у дров $\\approx 10^7$ — бензин в 4,5 раза мощнее.'}, + {A:'торф', B:'каменный уголь', ans:'B', why:'У угля $q$ вдвое больше, чем у торфа.'}, + {A:'спирт', B:'природный газ', ans:'B', why:'У газа $4{,}4 \\cdot 10^7$, у спирта $2{,}7 \\cdot 10^7$.'}, + {A:'керосин', B:'дрова', ans:'A', why:'Керосин — продукт переработки нефти, $q$ в 4,6 раза больше.'}, + {A:'нефть', B:'бензин', ans:'B', why:'У бензина чуть больше: $4{,}6$ против $4{,}4 \\cdot 10^7$.'}, + {A:'торф', B:'дрова', ans:'B', why:'У дров $q \\approx 10^7$, у торфа $1{,}5 \\cdot 10^7$ — торф мощнее. Правильный ответ: торф, ответ A.', flip:true} + ]; + let i = 0, ok = 0; + function render(){ + const q = QS[i]; const wrap = document.getElementById('p7-quiz'); if(!wrap) return; + /* для flip-задачи правильный ответ A */ + const correctAns = q.flip ? 'A' : q.ans; + wrap.innerHTML = + '
' + +'' + +'' + +'
' + +''; + document.getElementById('p7-quiz-r').textContent = (i+1); + document.getElementById('p7-quiz-ok').textContent = ok; + wrap.querySelectorAll('[data-pick]').forEach(btn=>{ + btn.addEventListener('click', ()=>{ + if(btn.disabled) return; wrap.querySelectorAll('[data-pick]').forEach(b=>b.disabled=true); + const fb = document.getElementById('p7-quiz-fb'); + if(btn.dataset.pick === correctAns){ ok++; fb.className='feedback ok'; fb.innerHTML='✓ Верно. '+q.why; addXp(3,'p7-quiz'); bumpProgress('p7', 4); } + else { fb.className='feedback fail'; fb.innerHTML='✗ Не то. '+q.why; } + document.getElementById('p7-quiz-ok').textContent = ok; + renderMath(wrap); + }); + }); + renderMath(wrap); + } + document.getElementById('p7-quiz-next').addEventListener('click', ()=>{ i=(i+1)%QS.length; render(); }); + render(); +} + +function _initP7_dnd(){ + /* 5 топлив по возрастанию q: дрова(1) → торф(1.5) → спирт(2.7) → уголь(3) → бензин(4.6), все ×10^7 */ + const items = [ + {id:'wd', cat:'r1', html:'дрова ($10^7$)'}, + {id:'pt', cat:'r2', html:'торф ($1{,}5 \\cdot 10^7$)'}, + {id:'al', cat:'r3', html:'спирт ($2{,}7 \\cdot 10^7$)'}, + {id:'cl', cat:'r4', html:'уголь ($3 \\cdot 10^7$)'}, + {id:'gs', cat:'r5', html:'бензин ($4{,}6 \\cdot 10^7$)'} + ]; + const dnd = setupSorter({ poolId:'p7-dnd-pool', scopeSelector:'#sec-p7', cats:['r1','r2','r3','r4','r5'], items, columnLayout:false }); + document.getElementById('p7-dnd-check').addEventListener('click', ()=>{ + const fb = document.getElementById('p7-dnd-fb'); + let wrong = 0; items.forEach(it=>{ if(dnd.placed[it.id] !== it.cat) wrong++; }); + if(wrong===0){ fb.className='feedback ok'; fb.innerHTML='✓ Идеально! +15 XP. Углеводороды (нефть, бензин, газ) — самые энергоёмкие.'; addXp(15,'p7-dnd'); bumpProgress('p7', 20); } + else { fb.className='feedback fail'; fb.innerHTML='✗ Ошибок: '+wrong+'. Подсказка: бензин — самый мощный из распространённых.'; } + }); + document.getElementById('p7-dnd-reset').addEventListener('click', ()=>{ dnd.reset(); const fb=document.getElementById('p7-dnd-fb'); fb.style.display='none'; }); +} + +function _initP7_tasks(){ + const TASKS = [ + {q:'Сколько энергии (в МДж) выделится при полном сгорании 2 кг бензина? ($q = 4{,}6 \\cdot 10^7$)', ans: 92, tol: 2, why:'$Q = qm = 4{,}6 \\cdot 10^7 \\cdot 2 = 9{,}2 \\cdot 10^7 = 92$ МДж.'}, + {q:'Какую массу (в кг) дров нужно сжечь, чтобы получить 50 МДж энергии? ($q = 10^7$)', ans: 5, tol: 0.1, why:'$m = Q/q = 5 \\cdot 10^7 / 10^7 = 5$ кг.'}, + {q:'При сгорании 0,5 кг угля выделилось 15 МДж. Чему равно $q$ (в МДж/кг)?', ans: 30, tol: 1, why:'$q = Q/m = 15/0{,}5 = 30$ МДж/кг — это каменный уголь.'}, + {q:'Котёл с $\\eta = 80\\%$ сжёг 2 кг дров. Какая полезная энергия (в МДж)? ($q = 10^7$)', ans: 16, tol: 0.3, why:'$Q_{сгор}=2 \\cdot 10^7 = 20$ МДж. $Q_{пол} = \\eta \\cdot Q_{сгор} = 0{,}8 \\cdot 20 = 16$ МДж.'}, + {q:'Сколько кг бензина даст столько же энергии, что и 9,2 кг дров? ($q_{дров}=10^7$, $q_{бенз}=4{,}6 \\cdot 10^7$)', ans: 2, tol: 0.05, why:'Энергия дров: $9{,}2 \\cdot 10^7$ Дж. $m_{бенз}=E/q_{бенз}=9{,}2 \\cdot 10^7/(4{,}6 \\cdot 10^7) = 2$ кг.'} + ]; + let i = 0, ok = 0, done = 0, awarded = false; + function render(){ + const t = TASKS[i]; const wrap = document.getElementById('p7-task'); if(!wrap) return; + wrap.innerHTML = + '
Задача '+(i+1)+'. '+t.q+'
' + +'
' + +'' + +'' + +'
' + +'
'+t.why+'
' + +''; + document.getElementById('p7-task-i').textContent = (i+1); + document.getElementById('p7-task-ok').textContent = ok; + document.getElementById('p7-task-go').addEventListener('click', ()=>{ + const v = parseFloat((document.getElementById('p7-task-inp').value || '').replace(',','.')); + const fb = document.getElementById('p7-task-fb'); + if(isNaN(v)){ fb.className='feedback fail'; fb.innerHTML='Введи число.'; return; } + done++; + if(Math.abs(v - t.ans) < t.tol){ ok++; fb.className='feedback ok'; fb.innerHTML='✓ Верно! '+t.why; addXp(4,'p7-task'); bumpProgress('p7', 6); } + else { fb.className='feedback fail'; fb.innerHTML='✗ Не то. Правильный ответ: '+t.ans+'. '+t.why; } + document.getElementById('p7-task-ok').textContent = ok; + renderMath(wrap); + if(done >= TASKS.length && !awarded && ok >= 4){ awarded = true; setTimeout(()=>{ const wf=document.getElementById('p7-task-fb'); wf.className='feedback ok'; wf.innerHTML='✓ +15 XP — расчёты сданы ('+ok+'/'+TASKS.length+').'; addXp(15,'p7-task-bonus'); bumpProgress('p7', 15); }, 600); } + }); + document.getElementById('p7-task-hint').addEventListener('click', ()=>{ document.getElementById('p7-task-hint-txt').classList.toggle('show'); }); + document.getElementById('p7-task-next').addEventListener('click', ()=>{ i=(i+1)%TASKS.length; render(); }); + renderMath(wrap); + } + render(); +} + +/* ====================================================================== + PHASE 1 · WAVE 4 — §8, §9 + ====================================================================== */ + +/* Таблица температур плавления и λ */ +const MAT_MELT = [ + {key:'mercury',name:'ртуть', Tm:-39, lam:1.18e4}, + {key:'ice', name:'лёд', Tm:0, lam:3.34e5}, + {key:'lead', name:'свинец', Tm:327, lam:2.5e4}, + {key:'zinc', name:'цинк', Tm:420, lam:1.12e5}, + {key:'al', name:'алюминий', Tm:660, lam:3.9e5}, + {key:'cu', name:'медь', Tm:1085, lam:2.1e5}, + {key:'fe', name:'железо', Tm:1539, lam:2.7e5} +]; + +/* ======== §8 — Плавление и кристаллизация ======== */ +function build_p8(){ + const box = document.getElementById('p8-body'); + let h = ''; + + h += makeCard('theory', 'Плавление', '§ 8.1', + '

Плавление — переход вещества из твёрдого состояния в жидкое.

' + +'

У каждого кристаллического вещества есть своя температура плавления $T_{пл}$ — она одинакова и для плавления, и для обратного процесса — кристаллизации.

' + +'

Во время плавления температура смеси «твёрдое + жидкое» не меняется, пока всё не расплавится. Подведённая теплота тратится на разрушение кристаллической решётки.

' + ); + h += makeCard('rule', 'График фазового перехода', '§ 8.2', + '

Если нагревать твёрдое тело и записывать температуру со временем, получится характерный график с плато:

' + +'
    ' + +'
  1. нагрев твёрдого ($T$ растёт);
  2. ' + +'
  3. плавление ($T = T_{пл} = $ const, идёт время);
  4. ' + +'
  5. нагрев жидкости ($T$ снова растёт).
  6. ' + +'
' + +'

При охлаждении — зеркальный процесс: $T$ падает $\\to$ плато кристаллизации $\\to$ $T$ продолжает падать.

' + ); + h += makeCard('example', 'Примеры', '§ 8.3', + '
    ' + +'
  • Лёд в стакане воды держит $T = 0$ °C, пока не растает весь.
  • ' + +'
  • Снег зимой переносит много энергии при таянии — поэтому весной долго холодно.
  • ' + +'
  • Расплавленный металл (магма, чугун) застывает в кристаллы при остывании.
  • ' + +'
  • Аморфные тела (стекло, смола) не имеют чёткой $T_{пл}$ — они плавятся плавно.
  • ' + +'
' + ); + + /* IV1 — главный визуал: график T(t) с плато */ + let optsT = ''; + MAT_MELT.forEach(m=>{ optsT += ''; }); + h += '
' + +'
IV-1
График плавления $T(t)$
' + +'
Выбери вещество — увидь характерный «ступенчатый» график. Время плато ∝ массе и удельной теплоте плавления.
' + +'
' + +'' + +'' + +'
' + +'' + +'
' + +' участок 1 — нагрев твёрдого' + +' участок 2 — плавление ($T = T_{пл}$)' + +' участок 3 — нагрев жидкости' + +'
' + +'
'; + + /* IV2 — квикфайр «температура смеси лёд+вода» */ + h += '
' + +'
IV-2
Сколько градусов в смеси?
' + +'
В стакане плавающий лёд и вода. Какая температура смеси?
' + +'
' + +'
' + +'
Раунд: 1 / 6Правильно: 0
' + +'
'; + + /* IV3 — DnD веществ по T_пл */ + h += '
' + +'
IV-3
Расставь по $T_{пл}$ (по возрастанию)
' + +'
От самого «мягкого» (плавится при низкой $T$) до самого тугоплавкого.
' + +'
' + +'
' + +'
1. Меньше
' + +'
2
' + +'
3
' + +'
4
' + +'
5. Больше
' + +'
' + +'
' + +'' + +'
'; + + /* IV4 — MCQ тренажёр */ + h += '
' + +'
IV-4
Тренажёр: 6 вопросов
' + +'
4+ правильных — +15 XP.
' + +'
' + +'
Вопрос: 1 / 6Правильно: 0
' + +'
'; + + /* IV5 — Расчётные задачи (auto-injected) */ + h += '
' + +'
IV-5
Тренажёр: 5 расчётных задач
' + +'
Введи числовой ответ (можно с точкой как разделителем). Решено все верно — +20 XP.
' + +'
' + +'
Задача: 1 / 5Правильно: 0
' + +'
'; + + /* IV6 — flagship интерактив (заглушка Phase 1, наполнение в Phase 1.8) */ + h += '
' + +'
IV-6
Новый интерактив §8
' + +'
Готовится: интерактивная визуализация с drag-and-drop для углубления темы. Скоро будет доступна.
' + +'
' + +'' + +'
Phase 1.8 — coming soon
' + +'
' + +'
'; box.innerHTML = h + secNavFor('p8') + readButton('p8'); renderMath(box); wireReadBtn('p8'); - _initP8_iv6(); _initp8_iv5(); _initP8_graph(); diff --git a/frontend/textbooks/physics_9_ch1.html b/frontend/textbooks/physics_9_ch1.html index b15ffb9..52b1e76 100644 --- a/frontend/textbooks/physics_9_ch1.html +++ b/frontend/textbooks/physics_9_ch1.html @@ -9,6 +9,7 @@ + @@ -805,7 +806,7 @@ function _injectTasks(id){ var body = document.getElementById(id + '-body'); if(!body || body.querySelector('.legacy-tasks')) return; body.insertAdjacentHTML('beforeend', _makeTaskBlock(id)); - setTimeout(function(){ try { if(window.renderTask) window.renderTask(id); if(window.renderNav) window.renderNav(id); } catch(e){} try { if(window.PHYS9_FINALS_INIT && /^final\d+$/.test(id)) window.PHYS9_FINALS_INIT(id); } catch(e){ console.warn("phys9 final init:", e.message); } try { if(window.PHYS9_CH1_WIDGETS && window.PHYS9_CH1_WIDGETS[id]) window.PHYS9_CH1_WIDGETS[id](); } catch(e){ console.warn('phys9 widget init:', e.message); } }, 60); + setTimeout(function(){ try { if(window.renderTask) window.renderTask(id); if(window.renderNav) window.renderNav(id); } catch(e){} try { if(window.PHYS9_FINALS_INIT && /^final\d+$/.test(id)) window.PHYS9_FINALS_INIT(id); } catch(e){ console.warn("phys9 final init:", e.message); } try { if(window.PHYS9_CH1_WIDGETS && window.PHYS9_CH1_WIDGETS[id]) window.PHYS9_CH1_WIDGETS[id](); } catch(e){ console.warn('phys9 widget init:', e.message); } try { if(window.PHYS9_FLAG_BASE){ if(id==='p5') window.PHYS9_FLAG_BASE.mount('F1','p5'); else if(id==='p9') window.PHYS9_FLAG_BASE.mount('F2','p9'); } } catch(e){ console.warn('phys9 flag init:', e.message); } }, 60); } var _origEnsureBuilt = ensureBuilt; ensureBuilt = function(id){ _origEnsureBuilt(id); _injectTasks(id); };