feat(phys11 W3): Глава 2 §7-§9 + расширение phys-fx.js (LCcircuit, ACgen, Transformer)

phys-fx.js (+3 электротехнических компонента):
- PHYS.LCcircuit: колебательный контур со схемой C↔L, провода, стрелка тока,
  заряды на пластинах (меняют знак), энергетические столбцы W_C и W_L,
  формула T=2π√(LC) с актуальным значением
- PHYS.ACgen: генератор переменного тока — слева вращающаяся рамка в B,
  справа график U(t)=U₀sin(ωt) с историей
- PHYS.Transformer: схема трансформатора с сердечником, обмотки N₁, N₂,
  входное U₁, расчётное U₂, коэф. трансформации k, отметка повышающий/понижающий

physics_11_ch2.html (~63 КБ, violet-тема):
- 2-кол layout с col-side, hero violet-градиент
- psel-grid 8 карточек (§7-§13 + Финал); §7-§9 активны
- Watermarks: LC, ∿, ≡, , ⚙, λ, ☣, ★

§7 Колебательный контур. Формула Томсона:
- 3 теор. карточки (контур, формула Томсона, превращения энергии)
- Инт. 1: LCcircuit с ползунками L (1-100 мГн), C (0.1-10 мкФ)
- Инт. 2: расчёт T, ν (5 input)
- Инт. 3: аналогии и свойства (5 MC)
- Босс §7: 5 этапов, +70 XP

§8 Вынужденные ЭМ колеб. Переменный ток:
- 2 теор. карточки (генератор, действ. значения I₀/√2)
- Инт. 1: ACgen (вращ. рамка → синусоида) с ползунком ω
- Инт. 2: расчёт I/I₀, U/U₀ (5 input)
- Инт. 3: теория действующих значений (5 MC)
- Босс §8: 5 этапов, +70 XP

§9 Трансформатор:
- 2 теор. карточки (устройство, коэф. трансформации, I₁U₁=I₂U₂)
- Инт. 1: Transformer с ползунками N₁ (50-1000), N₂ (10-1000), U₁ (12-10000 В)
- Инт. 2: расчёт U₂, I₂, k (5 input)
- Инт. 3: повышающий/понижающий (5 MC)
- Босс §9: 5 этапов, +70 XP

§10-§13, Финал — stub-карточки 'в разработке (W4)'.

LocalStorage: physics11_ch2_*, общий physics11_xp
Server sync: /api/textbooks/physics-11-ch2/progress
This commit is contained in:
Maxim Dolgolyov
2026-05-29 18:15:00 +03:00
parent 7aa681b503
commit a09616450f
2 changed files with 1114 additions and 154 deletions
+237
View File
@@ -595,4 +595,241 @@ class LongitudinalWave {
}
P.LongitudinalWave = LongitudinalWave;
/* ============================================================ */
/* LCcircuit — колебательный контур */
/* q(t) = Q0 cos(ωt), i(t) = -Q0 ω sin(ωt), ω = 1/√(LC) */
/* ============================================================ */
class LCcircuit {
constructor(container, opts){
opts = opts || {};
this.el = (typeof container === 'string') ? document.querySelector(container) : container;
this.W = opts.width || 480;
this.H = opts.height || 280;
this.L = opts.L !== undefined ? opts.L : 0.01; /* Гн */
this.C = opts.C !== undefined ? opts.C : 1e-6; /* Ф */
this.Q0 = opts.Q0 !== undefined ? opts.Q0 : 1.0; /* нормированный заряд */
this.color = opts.color || '#7c3aed';
this.paused = false;
this.t = 0;
util.subscribe(this);
util.observe(this);
this.render();
}
setL(L){ this.L = Math.max(1e-4, L); }
setC(C){ this.C = Math.max(1e-9, C); }
period(){ return 2 * Math.PI * Math.sqrt(this.L * this.C); }
freq(){ return 1 / this.period(); }
update(dt){ this.t += dt; }
render(){
if (!this.el) return;
const W = this.W, H = this.H;
const T = this.period();
const omega = 2 * Math.PI / T;
const phase = omega * this.t;
const q = this.Q0 * Math.cos(phase);
const i = -this.Q0 * omega * Math.sin(phase);
/* Геометрия: C слева вверху, L справа вверху, соединены проводами */
const cx = W / 2, cy = H / 2 - 20;
const cap = {x: cx - 100, y: cy};
const ind = {x: cx + 100, y: cy};
/* Energies (для подсветки): W_C ~ q², W_L ~ i² */
const WC = q * q;
const WL = (i / omega) * (i / omega); /* в норм. единицах */
const total = WC + WL;
const cOpacity = 0.3 + 0.7 * WC / total;
const lOpacity = 0.3 + 0.7 * WL / total;
let svg = util.svgFrame(W, H, {bg:'#f8fafc'});
/* Провода */
const wireY1 = cy - 50, wireY2 = cy + 50;
svg += '<path d="M ' + cap.x + ' ' + (cy - 18) + ' L ' + cap.x + ' ' + wireY1 + ' L ' + ind.x + ' ' + wireY1 + ' L ' + ind.x + ' ' + (cy - 18) + '" fill="none" stroke="#0f172a" stroke-width="2"/>';
svg += '<path d="M ' + cap.x + ' ' + (cy + 18) + ' L ' + cap.x + ' ' + wireY2 + ' L ' + ind.x + ' ' + wireY2 + ' L ' + ind.x + ' ' + (cy + 18) + '" fill="none" stroke="#0f172a" stroke-width="2"/>';
/* Конденсатор: две параллельные пластины */
const plateW = 36;
svg += '<g stroke="#0f172a" stroke-width="3">';
svg += '<line x1="' + (cap.x - plateW/2) + '" y1="' + (cap.y - 18) + '" x2="' + (cap.x + plateW/2) + '" y2="' + (cap.y - 18) + '"/>';
svg += '<line x1="' + (cap.x - plateW/2) + '" y1="' + (cap.y + 18) + '" x2="' + (cap.x + plateW/2) + '" y2="' + (cap.y + 18) + '"/>';
svg += '</g>';
/* Заряды на пластинах */
const sign = q > 0 ? 1 : -1;
const topCh = sign > 0 ? '+' : '';
const botCh = sign > 0 ? '' : '+';
const qAbsNorm = Math.abs(q) / this.Q0;
svg += '<text x="' + cap.x + '" y="' + (cap.y - 24) + '" text-anchor="middle" font-family="JetBrains Mono,monospace" font-size="14" font-weight="800" fill="#dc2626" opacity="' + (qAbsNorm).toFixed(2) + '">' + topCh + '</text>';
svg += '<text x="' + cap.x + '" y="' + (cap.y + 36) + '" text-anchor="middle" font-family="JetBrains Mono,monospace" font-size="14" font-weight="800" fill="#2563eb" opacity="' + (qAbsNorm).toFixed(2) + '">' + botCh + '</text>';
svg += '<text x="' + (cap.x - 30) + '" y="' + (cap.y + 5) + '" text-anchor="end" font-family="JetBrains Mono,monospace" font-size="13" fill="#dc2626" font-weight="700">C</text>';
/* Индуктор: петли */
const coils = 4, coilR = 8, coilW = 64;
let coilPath = 'M ' + (ind.x - coilW/2) + ' ' + ind.y;
for (let k = 0; k < coils; k++){
const x0 = ind.x - coilW/2 + (coilW / coils) * k;
coilPath += ' a ' + coilR + ' ' + coilR + ' 0 0 1 ' + (coilW / coils) + ' 0';
}
svg += '<path d="' + coilPath + '" fill="none" stroke="#0f172a" stroke-width="2.4"/>';
svg += '<text x="' + (ind.x + 40) + '" y="' + (ind.y + 5) + '" font-family="JetBrains Mono,monospace" font-size="13" fill="#2563eb" font-weight="700">L</text>';
/* Стрелка тока */
const iDir = i > 0 ? 1 : -1;
const iAbs = Math.abs(i) / (this.Q0 * omega);
if (iAbs > 0.05){
const aY = wireY1 - 14;
const aX1 = cx - 30 * iDir;
const aX2 = cx + 30 * iDir;
svg += '<line x1="' + aX1 + '" y1="' + aY + '" x2="' + aX2 + '" y2="' + aY + '" stroke="#16a34a" stroke-width="2.4" opacity="' + iAbs.toFixed(2) + '"/>';
svg += '<polygon points="' + aX2 + ',' + aY + ' ' + (aX2 - 8 * iDir) + ',' + (aY - 4) + ' ' + (aX2 - 8 * iDir) + ',' + (aY + 4) + '" fill="#16a34a" opacity="' + iAbs.toFixed(2) + '"/>';
svg += '<text x="' + cx + '" y="' + (aY - 6) + '" text-anchor="middle" font-family="JetBrains Mono,monospace" font-size="12" fill="#16a34a" font-weight="700">i</text>';
}
/* Энергетические столбцы */
const eY = H - 36, eH = 24;
svg += '<rect x="40" y="' + eY + '" width="120" height="' + eH + '" fill="#fef3c7" stroke="#d97706" stroke-width="1"/>';
svg += '<rect x="40" y="' + eY + '" width="' + (120 * WC / total).toFixed(1) + '" height="' + eH + '" fill="#dc2626" opacity="0.7"/>';
svg += '<text x="' + (40 + 60) + '" y="' + (eY + eH + 12) + '" text-anchor="middle" font-family="JetBrains Mono,monospace" font-size="10" fill="#475569">W_C</text>';
svg += '<rect x="' + (W - 160) + '" y="' + eY + '" width="120" height="' + eH + '" fill="#fef3c7" stroke="#d97706" stroke-width="1"/>';
svg += '<rect x="' + (W - 160) + '" y="' + eY + '" width="' + (120 * WL / total).toFixed(1) + '" height="' + eH + '" fill="#2563eb" opacity="0.7"/>';
svg += '<text x="' + (W - 100) + '" y="' + (eY + eH + 12) + '" text-anchor="middle" font-family="JetBrains Mono,monospace" font-size="10" fill="#475569">W_L</text>';
/* Подпись периода */
const Tdisp = T < 1e-3 ? (T * 1e6).toFixed(1) + ' мкс' : (T * 1e3).toFixed(2) + ' мс';
svg += '<text x="' + (W / 2) + '" y="22" text-anchor="middle" font-family="JetBrains Mono,monospace" font-size="13" fill="' + this.color + '" font-weight="800">T = 2π√(LC) = ' + Tdisp + '</text>';
svg += '</svg>';
this.el.innerHTML = svg;
}
}
P.LCcircuit = LCcircuit;
/* ============================================================ */
/* ACgen — генератор переменного тока (вращающаяся рамка в B) */
/* ============================================================ */
class ACgen {
constructor(container, opts){
opts = opts || {};
this.el = (typeof container === 'string') ? document.querySelector(container) : container;
this.W = opts.width || 540;
this.H = opts.height || 240;
this.omega = opts.omega !== undefined ? opts.omega : 2 * Math.PI; /* рад/с */
this.U0 = opts.U0 !== undefined ? opts.U0 : 1.0;
this.color = opts.color || '#7c3aed';
this.tWindow = opts.tWindow || 4;
this.paused = false;
this.t = 0;
this.history = [];
util.subscribe(this);
util.observe(this);
this.render();
}
setOmega(w){ this.omega = w; this.history = []; }
update(dt){
this.t += dt;
this.history.push([this.t, this.U0 * Math.sin(this.omega * this.t)]);
while (this.history.length && this.history[0][0] < this.t - this.tWindow) this.history.shift();
}
render(){
if (!this.el) return;
const W = this.W, H = this.H;
/* Левая часть: рамка в магнитном поле; правая: график U(t) */
const leftW = 200;
const rightLeft = leftW + 10;
let svg = util.svgFrame(W, H, {bg:'#f8fafc'});
/* Магнитное поле (стрелки B) */
svg += '<g stroke="#94a3b8" stroke-width="1.2">';
for (let i = 0; i < 5; i++){
const x = 20 + i * 40;
svg += '<line x1="' + x + '" y1="20" x2="' + x + '" y2="' + (H - 20) + '" marker-end="url(#acgen-arr)"/>';
}
svg += '</g>';
svg += '<defs><marker id="acgen-arr" viewBox="0 0 10 10" refX="9" refY="5" markerWidth="6" markerHeight="6" orient="auto"><path d="M 0 0 L 10 5 L 0 10 z" fill="#94a3b8"/></marker></defs>';
svg += '<text x="14" y="14" font-family="JetBrains Mono,monospace" font-size="11" fill="#475569" font-weight="700">B</text>';
/* Вращающаяся рамка: эллипс, отображающий проекцию прямоугольника */
const fx = 110, fy = H / 2;
const phi = this.omega * this.t;
const rx = 40 * Math.abs(Math.cos(phi));
const ry = 30;
svg += '<ellipse cx="' + fx + '" cy="' + fy + '" rx="' + rx.toFixed(1) + '" ry="' + ry + '" fill="none" stroke="#dc2626" stroke-width="2.4"/>';
/* Ось вращения */
svg += '<line x1="' + fx + '" y1="' + (fy - 50) + '" x2="' + fx + '" y2="' + (fy + 50) + '" stroke="#0f172a" stroke-width="2" stroke-dasharray="4 3"/>';
/* Контакт скользящих колец (схема) */
svg += '<line x1="' + fx + '" y1="' + (fy + 50) + '" x2="' + fx + '" y2="' + (fy + 70) + '" stroke="#0f172a" stroke-width="2"/>';
svg += '<line x1="' + fx + '" y1="' + (fy + 70) + '" x2="' + rightLeft + '" y2="' + (fy + 70) + '" stroke="#0f172a" stroke-width="2"/>';
/* График U(t) — справа */
const gPad = 26;
const tMin = Math.max(0, this.t - this.tWindow);
const ax = util.axes(W - rightLeft, H, gPad, this.tWindow, [-this.U0 * 1.2, this.U0 * 1.2]);
svg += '<g transform="translate(' + rightLeft + ' 0)">' + ax.svg + '</g>';
if (this.history.length > 1){
const pts = this.history.map(([t, y]) => (rightLeft + ax.left + (t - tMin) * (ax.right - ax.left) / this.tWindow).toFixed(1) + ',' + ax.toY(y).toFixed(1));
svg += '<polyline points="' + pts.join(' ') + '" fill="none" stroke="' + this.color + '" stroke-width="2.4"/>';
}
/* Подпись */
svg += '<text x="' + (rightLeft + 20) + '" y="20" font-family="JetBrains Mono,monospace" font-size="12" fill="' + this.color + '" font-weight="700">U(t) = U₀ sin(ωt)</text>';
svg += '</svg>';
this.el.innerHTML = svg;
}
}
P.ACgen = ACgen;
/* ============================================================ */
/* Transformer — схема трансформатора с расчётом */
/* ============================================================ */
class Transformer {
constructor(container, opts){
opts = opts || {};
this.el = (typeof container === 'string') ? document.querySelector(container) : container;
this.W = opts.width || 520;
this.H = opts.height || 240;
this.N1 = opts.N1 || 200;
this.N2 = opts.N2 || 50;
this.U1 = opts.U1 || 220;
this.color = opts.color || '#7c3aed';
this.render();
}
setN1(n){ this.N1 = Math.max(1, n|0); this.render(); }
setN2(n){ this.N2 = Math.max(1, n|0); this.render(); }
setU1(u){ this.U1 = u; this.render(); }
update(){ /* статика */ }
render(){
if (!this.el) return;
const W = this.W, H = this.H;
const k = this.N1 / this.N2;
const U2 = this.U1 / k;
const cy = H / 2;
let svg = util.svgFrame(W, H, {bg:'#f8fafc'});
/* Сердечник трансформатора (прямоугольник с вырезом) */
const coreL = 110, coreR = W - 110;
svg += '<rect x="' + coreL + '" y="' + (cy - 70) + '" width="' + (coreR - coreL) + '" height="140" fill="none" stroke="#64748b" stroke-width="6"/>';
svg += '<rect x="' + (coreL + 16) + '" y="' + (cy - 54) + '" width="' + (coreR - coreL - 32) + '" height="108" fill="#f8fafc" stroke="none"/>';
/* Первичная обмотка слева */
const coil1X = coreL + 24;
for (let i = 0; i < 6; i++){
const y = cy - 50 + i * 16;
svg += '<path d="M ' + (coil1X - 12) + ' ' + y + ' a 8 8 0 0 1 8 -8 a 8 8 0 0 1 8 8 a 8 8 0 0 1 -8 8 a 8 8 0 0 1 -8 -8 z" fill="none" stroke="#dc2626" stroke-width="2.4"/>';
}
/* Вторичная обмотка справа */
const coil2X = coreR - 24;
/* Адаптируем число витков визуально (max 8 для удобства) */
const visTurns2 = Math.max(2, Math.min(10, Math.round(6 * this.N2 / this.N1)));
for (let i = 0; i < visTurns2; i++){
const y = cy - 50 + i * (100 / visTurns2);
svg += '<path d="M ' + (coil2X - 12) + ' ' + y + ' a 6 6 0 0 1 8 -6 a 6 6 0 0 1 8 6 a 6 6 0 0 1 -8 6 a 6 6 0 0 1 -8 -6 z" fill="none" stroke="#2563eb" stroke-width="2.4"/>';
}
/* Провода-выходы первичной */
svg += '<line x1="' + (coil1X - 12) + '" y1="' + (cy - 50) + '" x2="20" y2="' + (cy - 50) + '" stroke="#dc2626" stroke-width="2"/>';
svg += '<line x1="' + (coil1X - 12) + '" y1="' + (cy + 50) + '" x2="20" y2="' + (cy + 50) + '" stroke="#dc2626" stroke-width="2"/>';
/* Провода вторичной */
svg += '<line x1="' + (coil2X + 4) + '" y1="' + (cy - 50) + '" x2="' + (W - 20) + '" y2="' + (cy - 50) + '" stroke="#2563eb" stroke-width="2"/>';
svg += '<line x1="' + (coil2X + 4) + '" y1="' + (cy + 50) + '" x2="' + (W - 20) + '" y2="' + (cy + 50) + '" stroke="#2563eb" stroke-width="2"/>';
/* Подписи N1, N2, U1, U2 */
svg += '<text x="' + (coil1X - 16) + '" y="' + (cy - 60) + '" font-family="JetBrains Mono,monospace" font-size="13" fill="#dc2626" font-weight="800">N₁ = ' + this.N1 + '</text>';
svg += '<text x="' + (coil2X + 16) + '" y="' + (cy - 60) + '" font-family="JetBrains Mono,monospace" font-size="13" fill="#2563eb" font-weight="800" text-anchor="end">N₂ = ' + this.N2 + '</text>';
svg += '<text x="' + (coil2X + 6) + '" y="' + (cy - 60) + '" font-family="JetBrains Mono,monospace" font-size="13" fill="#2563eb" font-weight="800">N₂ = ' + this.N2 + '</text>';
svg += '<text x="20" y="' + (cy - 56) + '" font-family="JetBrains Mono,monospace" font-size="12" fill="#dc2626">U₁ = ' + this.U1.toFixed(0) + ' В</text>';
svg += '<text x="' + (W - 20) + '" y="' + (cy - 56) + '" font-family="JetBrains Mono,monospace" font-size="12" fill="#2563eb" text-anchor="end">U₂ = ' + U2.toFixed(1) + ' В</text>';
/* Подпись коэф. */
svg += '<text x="' + (W/2) + '" y="22" text-anchor="middle" font-family="JetBrains Mono,monospace" font-size="13" fill="' + this.color + '" font-weight="800">k = N₁/N₂ = ' + k.toFixed(2) + '</text>';
svg += '<text x="' + (W/2) + '" y="' + (H - 12) + '" text-anchor="middle" font-family="JetBrains Mono,monospace" font-size="11" fill="#64748b">' + (k > 1 ? 'понижающий' : 'повышающий') + '</text>';
svg += '</svg>';
this.el.innerHTML = svg;
}
}
P.Transformer = Transformer;
})();
File diff suppressed because it is too large Load Diff