feat(phys11 ch4): Wave 8 — Глава 4 «Основы СТО» (§24-§26 + Финал)

- phys-fx.js: PHYS.GammaPlot (график γ(β)), PHYS.TimeDilation (двое часов), PHYS.LengthContraction (стержень в покое и в движении)
- ch4 — синяя тема (--pri:#2563eb), watermark c/γ/E/★
- §24: принцип отн. Галилея, кризис эфира, опыт Майкельсона – Морли
- §25: 2 постулата Эйнштейна, замедление времени Δτ=γΔτ₀, сокращение L=L₀/γ, релятив. сложение скоростей
- §26: релятив. импульс p=γmv, E=γmc², E₀=mc², E²=(pc)²+(mc²)², аннигиляция, ядерная энергия
- 6 квизов + 3 босса (b1-b3)
- Финал: 3 интегральных босса (fb1-fb3), +150 XP бонус, ачивка ch4_master
This commit is contained in:
Maxim Dolgolyov
2026-05-29 18:56:52 +03:00
parent 06db392f6a
commit 9df4fd5e24
2 changed files with 1113 additions and 102 deletions
+192
View File
@@ -1540,4 +1540,196 @@ class TwoLensSystem {
}
P.TwoLensSystem = TwoLensSystem;
/* ============================================================ */
/* GammaPlot — график γ(β) и τ/τ₀, L/L₀ */
/* ============================================================ */
class GammaPlot {
constructor(container, opts){
opts = opts || {};
this.el = (typeof container === 'string') ? document.querySelector(container) : container;
this.W = opts.width || 580;
this.H = opts.height || 280;
this.beta = opts.beta !== undefined ? opts.beta : 0.5; /* v/c */
this.color = opts.color || '#2563eb';
this.paused = true;
this.render();
}
setBeta(v){ this.beta = Math.max(0, Math.min(0.999, v)); this.render(); }
update(){}
render(){
if (!this.el) return;
const W = this.W, H = this.H;
const pad = 40;
const left = pad, right = W - pad - 100, top = 30, bot = H - 40;
let svg = util.svgFrame(W, H, {bg:'#f8fafc'});
/* Сетка */
svg += '<g stroke="#e2e8f0" stroke-width="0.8">';
for (let i = 0; i <= 10; i++){
const x = left + i * (right - left) / 10;
svg += '<line x1="' + x + '" y1="' + top + '" x2="' + x + '" y2="' + bot + '"/>';
}
for (let i = 0; i <= 6; i++){
const y = top + i * (bot - top) / 6;
svg += '<line x1="' + left + '" y1="' + y + '" x2="' + right + '" y2="' + y + '"/>';
}
svg += '</g>';
/* Оси */
svg += '<line x1="' + left + '" y1="' + bot + '" x2="' + right + '" y2="' + bot + '" stroke="#0f172a" stroke-width="1.4"/>';
svg += '<line x1="' + left + '" y1="' + top + '" x2="' + left + '" y2="' + bot + '" stroke="#0f172a" stroke-width="1.4"/>';
/* Метки осей */
svg += '<text x="' + ((left + right) / 2) + '" y="' + (bot + 26) + '" text-anchor="middle" font-family="JetBrains Mono,monospace" font-size="11" fill="#475569">β = v/c</text>';
svg += '<text x="' + (left - 24) + '" y="' + ((top + bot) / 2) + '" text-anchor="middle" font-family="JetBrains Mono,monospace" font-size="11" fill="#475569" transform="rotate(-90 ' + (left - 24) + ' ' + ((top + bot) / 2) + ')">γ</text>';
/* γ — растёт; ограничим до 6 */
let pts = '';
for (let i = 0; i <= 100; i++){
const b = i / 100 * 0.99;
const g = 1 / Math.sqrt(1 - b * b);
const x = left + b * (right - left);
const y = bot - Math.min(6, g) * (bot - top) / 6;
pts += x.toFixed(1) + ',' + y.toFixed(1) + ' ';
}
svg += '<polyline points="' + pts + '" fill="none" stroke="' + this.color + '" stroke-width="2.4"/>';
/* γ = 1 базовая линия */
svg += '<line x1="' + left + '" y1="' + (bot - (bot - top) / 6) + '" x2="' + right + '" y2="' + (bot - (bot - top) / 6) + '" stroke="#94a3b8" stroke-width="0.8" stroke-dasharray="3 3"/>';
svg += '<text x="' + (left - 6) + '" y="' + (bot - (bot - top) / 6 + 4) + '" text-anchor="end" font-family="JetBrains Mono,monospace" font-size="10" fill="#94a3b8">1</text>';
/* Текущая точка */
const g = 1 / Math.sqrt(1 - this.beta * this.beta);
const cx = left + this.beta * (right - left);
const cy = bot - Math.min(6, g) * (bot - top) / 6;
svg += '<line x1="' + cx + '" y1="' + bot + '" x2="' + cx + '" y2="' + cy.toFixed(1) + '" stroke="#dc2626" stroke-width="1" stroke-dasharray="3 3"/>';
svg += '<circle cx="' + cx + '" cy="' + cy.toFixed(1) + '" r="5" fill="#dc2626"/>';
/* Подпись значения */
const panelX = right + 12;
svg += '<text x="' + panelX + '" y="' + (top + 14) + '" font-family="JetBrains Mono,monospace" font-size="11" fill="#0f172a" font-weight="700">β = ' + this.beta.toFixed(3) + '</text>';
svg += '<text x="' + panelX + '" y="' + (top + 32) + '" font-family="JetBrains Mono,monospace" font-size="11" fill="#2563eb" font-weight="700">γ = ' + g.toFixed(3) + '</text>';
svg += '<text x="' + panelX + '" y="' + (top + 50) + '" font-family="JetBrains Mono,monospace" font-size="10" fill="#475569">τ = γτ₀</text>';
svg += '<text x="' + panelX + '" y="' + (top + 64) + '" font-family="JetBrains Mono,monospace" font-size="10" fill="#475569">L = L₀/γ</text>';
svg += '<text x="' + panelX + '" y="' + (top + 82) + '" font-family="JetBrains Mono,monospace" font-size="10" fill="#475569">τ/τ₀ = ' + g.toFixed(2) + '</text>';
svg += '<text x="' + panelX + '" y="' + (top + 96) + '" font-family="JetBrains Mono,monospace" font-size="10" fill="#475569">L/L₀ = ' + (1 / g).toFixed(2) + '</text>';
svg += '<text x="' + (W/2) + '" y="20" text-anchor="middle" font-family="JetBrains Mono,monospace" font-size="11" fill="#475569" font-weight="700">γ = 1/√(1 - β²)</text>';
svg += '</svg>';
this.el.innerHTML = svg;
}
}
P.GammaPlot = GammaPlot;
/* ============================================================ */
/* TimeDilation — двое часов: «покоящиеся» и «движущиеся» */
/* ============================================================ */
class TimeDilation {
constructor(container, opts){
opts = opts || {};
this.el = (typeof container === 'string') ? document.querySelector(container) : container;
this.W = opts.width || 600;
this.H = opts.height || 240;
this.beta = opts.beta !== undefined ? opts.beta : 0.5;
this.t = 0;
this.paused = false;
this._render();
util.subscribe(this);
util.observe(this);
}
setBeta(v){ this.beta = Math.max(0, Math.min(0.99, v)); }
update(dt){ this.t += dt; }
drawClock(svg, cx, cy, r, time, label, color){
let s = svg;
s += '<circle cx="' + cx + '" cy="' + cy + '" r="' + r + '" fill="#fff" stroke="' + color + '" stroke-width="2.4"/>';
/* Метки часов 12, 3, 6, 9 */
for (let i = 0; i < 12; i++){
const a = i * Math.PI / 6;
const x1 = cx + (r - 6) * Math.sin(a), y1 = cy - (r - 6) * Math.cos(a);
const x2 = cx + (r - 2) * Math.sin(a), y2 = cy - (r - 2) * Math.cos(a);
s += '<line x1="' + x1.toFixed(1) + '" y1="' + y1.toFixed(1) + '" x2="' + x2.toFixed(1) + '" y2="' + y2.toFixed(1) + '" stroke="#475569" stroke-width="' + (i % 3 === 0 ? 2 : 1) + '"/>';
}
/* Стрелка секундная (один оборот = 6 «времени») */
const a = (time / 6) * 2 * Math.PI;
const hx = cx + (r - 12) * Math.sin(a), hy = cy - (r - 12) * Math.cos(a);
s += '<line x1="' + cx + '" y1="' + cy + '" x2="' + hx.toFixed(1) + '" y2="' + hy.toFixed(1) + '" stroke="' + color + '" stroke-width="2.6" stroke-linecap="round"/>';
s += '<circle cx="' + cx + '" cy="' + cy + '" r="3" fill="' + color + '"/>';
s += '<text x="' + cx + '" y="' + (cy + r + 22) + '" text-anchor="middle" font-family="JetBrains Mono,monospace" font-size="11" fill="' + color + '" font-weight="700">' + label + '</text>';
s += '<text x="' + cx + '" y="' + (cy + r + 38) + '" text-anchor="middle" font-family="JetBrains Mono,monospace" font-size="10" fill="#475569">t = ' + time.toFixed(2) + ' с</text>';
return s;
}
render(){
if (!this.el) return;
const W = this.W, H = this.H;
const g = 1 / Math.sqrt(1 - this.beta * this.beta);
/* Часы покоя — реальное время */
const t0 = this.t;
/* Движущиеся — идут медленнее в γ раз для наблюдателя */
const tmov = this.t / g;
let svg = util.svgFrame(W, H, {bg:'#f8fafc'});
svg = this.drawClock(svg, 140, 100, 55, t0, 'часы наблюдателя', '#0f172a');
svg = this.drawClock(svg, W - 140, 100, 55, tmov, 'часы в движ. системе', '#dc2626');
/* Стрелка движения */
svg += '<line x1="220" y1="' + (H - 30) + '" x2="' + (W - 220) + '" y2="' + (H - 30) + '" stroke="#94a3b8" stroke-width="1.4"/>';
svg += '<polygon points="' + (W - 220) + ',' + (H - 30) + ' ' + (W - 232) + ',' + (H - 36) + ' ' + (W - 232) + ',' + (H - 24) + '" fill="#94a3b8"/>';
svg += '<text x="' + (W/2) + '" y="' + (H - 36) + '" text-anchor="middle" font-family="JetBrains Mono,monospace" font-size="11" fill="#475569">v → · β = ' + this.beta.toFixed(2) + ' · γ = ' + g.toFixed(2) + '</text>';
svg += '<text x="' + (W/2) + '" y="22" text-anchor="middle" font-family="JetBrains Mono,monospace" font-size="11" fill="#475569" font-weight="700">Δτ = γ · Δτ₀ (движущиеся часы идут медленнее)</text>';
svg += '</svg>';
this.el.innerHTML = svg;
}
_render(){ this.render(); }
}
P.TimeDilation = TimeDilation;
/* ============================================================ */
/* LengthContraction — стержень в покое и в движении */
/* ============================================================ */
class LengthContraction {
constructor(container, opts){
opts = opts || {};
this.el = (typeof container === 'string') ? document.querySelector(container) : container;
this.W = opts.width || 600;
this.H = opts.height || 220;
this.beta = opts.beta !== undefined ? opts.beta : 0.5;
this.L0 = opts.L0 !== undefined ? opts.L0 : 320;
this.paused = true;
this.render();
}
setBeta(v){ this.beta = Math.max(0, Math.min(0.99, v)); this.render(); }
update(){}
render(){
if (!this.el) return;
const W = this.W, H = this.H;
const g = 1 / Math.sqrt(1 - this.beta * this.beta);
const L = this.L0 / g;
let svg = util.svgFrame(W, H, {bg:'#f8fafc'});
/* Подпись */
svg += '<text x="' + (W/2) + '" y="20" text-anchor="middle" font-family="JetBrains Mono,monospace" font-size="11" fill="#475569" font-weight="700">L = L₀ · √(1 - β²) = L₀/γ</text>';
/* Стержень в покое */
const cx = W / 2;
const y1 = 60;
svg += '<text x="' + (cx - this.L0/2 - 10) + '" y="' + (y1 + 5) + '" text-anchor="end" font-family="JetBrains Mono,monospace" font-size="11" fill="#0f172a" font-weight="700">покой:</text>';
svg += '<rect x="' + (cx - this.L0/2) + '" y="' + (y1 - 12) + '" width="' + this.L0 + '" height="24" fill="#dbeafe" stroke="#0f172a" stroke-width="1.6"/>';
/* Деления */
for (let i = 0; i <= 10; i++){
const x = cx - this.L0/2 + i * this.L0 / 10;
svg += '<line x1="' + x + '" y1="' + (y1 - 12) + '" x2="' + x + '" y2="' + (y1 + 12) + '" stroke="#0f172a" stroke-width="0.8"/>';
}
svg += '<text x="' + cx + '" y="' + (y1 + 32) + '" text-anchor="middle" font-family="JetBrains Mono,monospace" font-size="11" fill="#0f172a">L₀ = ' + this.L0 + ' (собственная длина)</text>';
/* Стержень в движении */
const y2 = 140;
svg += '<text x="' + (cx - L/2 - 10) + '" y="' + (y2 + 5) + '" text-anchor="end" font-family="JetBrains Mono,monospace" font-size="11" fill="#dc2626" font-weight="700">движется:</text>';
svg += '<rect x="' + (cx - L/2) + '" y="' + (y2 - 12) + '" width="' + L.toFixed(1) + '" height="24" fill="#fee2e2" stroke="#dc2626" stroke-width="1.6"/>';
for (let i = 0; i <= 10; i++){
const x = cx - L/2 + i * L / 10;
svg += '<line x1="' + x.toFixed(1) + '" y1="' + (y2 - 12) + '" x2="' + x.toFixed(1) + '" y2="' + (y2 + 12) + '" stroke="#dc2626" stroke-width="0.8"/>';
}
/* Стрелка скорости */
svg += '<line x1="' + (cx + L/2 + 10) + '" y1="' + y2 + '" x2="' + (cx + L/2 + 60) + '" y2="' + y2 + '" stroke="#dc2626" stroke-width="2"/>';
svg += '<polygon points="' + (cx + L/2 + 60) + ',' + y2 + ' ' + (cx + L/2 + 50) + ',' + (y2 - 6) + ' ' + (cx + L/2 + 50) + ',' + (y2 + 6) + '" fill="#dc2626"/>';
svg += '<text x="' + (cx + L/2 + 30) + '" y="' + (y2 - 10) + '" text-anchor="middle" font-family="JetBrains Mono,monospace" font-size="10" fill="#dc2626" font-weight="700">v</text>';
svg += '<text x="' + cx + '" y="' + (y2 + 32) + '" text-anchor="middle" font-family="JetBrains Mono,monospace" font-size="11" fill="#dc2626">L = ' + L.toFixed(1) + ' · L/L₀ = ' + (1/g).toFixed(3) + '</text>';
/* Параметры */
svg += '<text x="' + (W - 12) + '" y="' + (H - 10) + '" text-anchor="end" font-family="JetBrains Mono,monospace" font-size="10" fill="#475569">β = ' + this.beta.toFixed(2) + ' · γ = ' + g.toFixed(2) + '</text>';
svg += '</svg>';
this.el.innerHTML = svg;
}
}
P.LengthContraction = LengthContraction;
})();
File diff suppressed because it is too large Load Diff