ypv пре 2 недеља
родитељ
комит
dbf4f0fbd2
2 измењених фајлова са 521 додато и 79 уклоњено
  1. 503 79
      ПМ.03.01/3.1.400/001/index.html
  2. 18 0
      ПМ.03.01/3.1.400/001/specification.md

+ 503 - 79
ПМ.03.01/3.1.400/001/index.html

@@ -352,6 +352,7 @@
     #inductionCanvas { cursor: grab; touch-action: none; }
     #inductionCanvas.dragging { cursor: grabbing; }
     #inductiveViz { cursor: default; touch-action: none; }
+    #capacitiveViz { cursor: default; touch-action: none; }
     .hint { font-size: 12px; color: var(--ink-light); font-style: italic; margin-top: 8px; }
     @media (max-width: 640px) {
         body { padding: 10px; }
@@ -579,15 +580,15 @@
         <div class="scene" data-scene="9">
             <div class="scene-num">Механизм 2 · ёмкостный</div>
             <h2 class="scene-title">Ёмкостная наводка</h2>
-            <p>Источник — провод с переменным напряжением. Среда — электрическое поле. Приёмник — провод или плата, на которой наводится заряд. Два провода рядом образуют паразитный конденсатор.</p>
+            <p>Источник — провод с переменным напряжением. Среда — электрическое поле. Приёмник — провод или плата. Меняйте напряжение, частоту, расстояние и площадь. Провод можно перетаскивать мышью, углы приёмника — тянуть и менять площадь.</p>
             <div class="viz">
-                <canvas id="capacitiveViz" width="800" height="240"></canvas>
-                <div class="viz-caption">Электрическое поле между проводами</div>
+                <canvas id="capacitiveViz" width="800" height="440"></canvas>
+                <div class="viz-caption">Плотность линий = напряжённость поля E · мерцание = частота · стрелка на приёмнике = наведённый заряд</div>
             </div>
             <div class="two-col">
                 <div>
                     <div class="control-group">
-                        <div class="control-label"><span>Напряжение, В</span><span class="val" id="capVoltVal">500</span></div>
+                        <div class="control-label"><span>Напряжение источника, В</span><span class="val" id="capVoltVal">500</span></div>
                         <input type="range" id="capVolt" min="0" max="1000" step="50" value="500">
                     </div>
                     <div class="control-group">
@@ -601,19 +602,19 @@
                         <input type="range" id="capDist" min="1" max="20" step="1" value="10">
                     </div>
                     <div class="control-group">
-                        <div class="control-label"><span>Площадь, см²</span><span class="val" id="capAreaVal">50</span></div>
+                        <div class="control-label"><span>Площадь приёмника, см²</span><span class="val" id="capAreaVal">50</span></div>
                         <input type="range" id="capArea" min="0" max="100" step="5" value="50">
                     </div>
                 </div>
             </div>
             <div class="indicator">
-                <small>Наведённое напряжение</small>
+                <small>Мгновенное наведённое напряжение</small>
                 <span><span id="capResult">0.0</span> мВ</span>
             </div>
-            <div class="feedback" id="capFeedback">Двигайте ползунки, чтобы увидеть, как меняется наводка.</div>
+            <div class="feedback" id="capFeedback">Двигайте ползунки или перетаскивайте провод и углы приёмника.</div>
             <div class="notebook">
                 <p><strong>Ёмкостная наводка</strong> — наведение напряжения в проводнике за счёт электрического поля соседнего проводника с переменным напряжением.</p>
-                <p>Зависит от напряжения, частоты, площади и расстояния. При постоянном напряжении (частота = 0) наводки нет — нет изменения заряда.</p>
+                <p>Зависит от напряжения, частоты, площади и расстояния. При постоянном напряжении (частота = 0) наводки нет — заряд не меняется.</p>
             </div>
         </div>
 
@@ -755,7 +756,7 @@
         if (index === 3) initInductionScene();
         if (index === 4) { drawCapacitor(); drawInductor(); }
         if (index === 7) initInductive();
-        if (index === 8) drawCapacitive();
+        if (index === 8) initCapacitive8();
         if (index === 9) drawGalvanic();
         if (index === 11) initQuiz();
 
@@ -847,7 +848,7 @@
         drawWireB();
     });
 
-    // === СЦЕНА 4: Индукция ===
+    // === СЦЕНА 4: Индукция (магнит и катушка) ===
     const induction = {
         canvas: null, ctx: null,
         magnet: { x: 100, y: 130, w: 70, h: 60, dragging: false, offsetX: 0 },
@@ -1131,7 +1132,7 @@
         ctx.fillText('+', gx + gr + 12, gy - 5);
     }
 
-    // === СЦЕНА 5: Конденсатор и индуктивность ===
+    // === СЦЕНА 5: Конденсатор и индуктивность (демо) ===
     function drawCapacitor() {
         const c = document.getElementById('capacitorViz');
         if (!c) return;
@@ -1262,7 +1263,7 @@
         drawInductor();
     });
 
-    // === СЦЕНА 8: Индуктивная наводка (расширенная) ===
+    // === СЦЕНА 8: Индуктивная наводка ===
     const ind8 = {
         canvas: null,
         ctx: null,
@@ -1436,7 +1437,6 @@
         if (x2 > 780) x2 = 780;
         if (y2 > 420) y2 = 420;
 
-        // Ограничение по расстоянию
         const wireRightEdge = ind8.wire.x + ind8.wire.w / 2;
         if (x1 < wireRightEdge + 1 * ind8.pxPerCm) {
             const shift = (wireRightEdge + 1 * ind8.pxPerCm) - x1;
@@ -1451,7 +1451,6 @@
         l.w = x2 - x1;
         l.h = y2 - y1;
 
-        // Синхронизация слайдеров
         const areaCm2 = Math.round((l.w * l.h) / 250);
         document.getElementById('indArea').value = Math.max(0, Math.min(100, areaCm2));
         document.getElementById('indAreaVal').textContent = Math.max(0, Math.min(100, areaCm2));
@@ -1776,89 +1775,514 @@
         ctx.fillText(sign + Math.abs(emf).toFixed(1) + ' мВ', cx, cy + r + 18);
     }
 
-    function drawCapacitive() {
-        const canvas = document.getElementById('capacitiveViz');
-        const ctx = canvas.getContext('2d');
-        function render() {
-            const w = canvas.width, h = canvas.height;
-            ctx.fillStyle = '#f2e9d6';
-            ctx.fillRect(0, 0, w, h);
-            ctx.fillStyle = '#1a4a6a';
-            ctx.fillRect(80, 30, 14, h - 60);
-            ctx.fillStyle = '#1a4a6a';
-            ctx.font = 'bold 12px monospace';
-            ctx.textAlign = 'center';
-            ctx.fillText('U', 87, 20);
-            const U = parseFloat(document.getElementById('capVolt').value);
-            const lineCount = Math.max(1, Math.floor(U / 100));
-            ctx.strokeStyle = 'rgba(26,74,106,0.35)';
-            ctx.lineWidth = 1.5;
-            for (let i = 0; i < lineCount; i++) {
-                const y = 40 + i * (h - 80) / Math.max(1, lineCount - 1);
-                ctx.beginPath();
-                ctx.moveTo(94, y);
-                ctx.lineTo(520, y);
-                ctx.stroke();
+    // === СЦЕНА 9: Ёмкостная наводка ===
+    const cap8 = {
+        canvas: null,
+        ctx: null,
+        src: { x: 280, w: 26, h: 280, cy: 220 },
+        recv: { x1: 560, y1: 140, w: 40, h: 160 },
+        pxPerCm: 18,
+        phase: 0,
+        lastT: 0,
+        dragMode: null,
+        dragOffset: { x: 0, y: 0 },
+        initialized: false
+    };
+
+    function initCapacitive8() {
+        const c = document.getElementById('capacitiveViz');
+        if (!c) return;
+        cap8.canvas = c;
+        cap8.ctx = c.getContext('2d');
+
+        if (!cap8.initialized) {
+            cap8.initialized = true;
+
+            c.addEventListener('mousedown', onCap8Down);
+            c.addEventListener('mousemove', onCap8Move);
+            c.addEventListener('mouseup', onCap8Up);
+            c.addEventListener('mouseleave', onCap8Up);
+            c.addEventListener('touchstart', onCap8Down, { passive: false });
+            c.addEventListener('touchmove', onCap8Move, { passive: false });
+            c.addEventListener('touchend', onCap8Up);
+            c.addEventListener('touchcancel', onCap8Up);
+
+            ['capVolt', 'capFreq'].forEach(id => {
+                document.getElementById(id).addEventListener('input', updateCapacitive8);
+            });
+            document.getElementById('capDist').addEventListener('input', function() {
+                applyCap8DistSlider();
+                updateCapacitive8();
+            });
+            document.getElementById('capArea').addEventListener('input', function() {
+                applyCap8AreaSlider();
+                updateCapacitive8();
+            });
+
+            applyCap8AreaSlider();
+            applyCap8DistSlider();
+
+            cap8.lastT = performance.now();
+            requestAnimationFrame(drawCap8);
+        }
+    }
+
+    function getCap8Coords(e) {
+        const c = cap8.canvas;
+        const rect = c.getBoundingClientRect();
+        const sx = c.width / rect.width;
+        const sy = c.height / rect.height;
+        let cx, cy;
+        if (e.touches && e.touches.length) {
+            cx = e.touches[0].clientX;
+            cy = e.touches[0].clientY;
+        } else {
+            cx = e.clientX;
+            cy = e.clientY;
+        }
+        return { x: (cx - rect.left) * sx, y: (cy - rect.top) * sy };
+    }
+
+    function onCap8Down(e) {
+        e.preventDefault();
+        const p = getCap8Coords(e);
+        const r = cap8.recv;
+
+        const corners = [
+            { name: 'tl', x: r.x1, y: r.y1 },
+            { name: 'tr', x: r.x1 + r.w, y: r.y1 },
+            { name: 'bl', x: r.x1, y: r.y1 + r.h },
+            { name: 'br', x: r.x1 + r.w, y: r.y1 + r.h }
+        ];
+        for (const corner of corners) {
+            if (Math.hypot(p.x - corner.x, p.y - corner.y) < 18) {
+                cap8.dragMode = corner.name;
+                return;
             }
-            ctx.fillStyle = '#2a5a2a';
-            ctx.fillRect(520, 40, 14, h - 80);
-            ctx.fillStyle = '#2a5a2a';
-            ctx.fillText('приёмник', 527, h - 20);
-            const cap = calcCapacitive();
-            if (cap > 0.1) {
-                const charges = Math.min(8, Math.floor(cap / 10));
-                for (let i = 0; i < charges; i++) {
-                    ctx.fillStyle = '#8b1a1a';
-                    ctx.beginPath();
-                    ctx.arc(545, 50 + i * 20, 3, 0, Math.PI*2);
-                    ctx.fill();
-                }
+        }
+
+        const s = cap8.src;
+        const srcLeft = s.x - s.w/2 - 8;
+        const srcRight = s.x + s.w/2 + 8;
+        const srcTop = s.cy - s.h/2;
+        const srcBottom = s.cy + s.h/2;
+        if (p.x >= srcLeft && p.x <= srcRight && p.y >= srcTop && p.y <= srcBottom) {
+            cap8.dragMode = 'src';
+            cap8.dragOffset = { x: p.x - s.x, y: 0 };
+            cap8.canvas.style.cursor = 'grabbing';
+        }
+    }
+
+    function onCap8Move(e) {
+        const p = getCap8Coords(e);
+
+        if (!cap8.dragMode) {
+            const r = cap8.recv;
+            const corners = [
+                [r.x1, r.y1], [r.x1 + r.w, r.y1],
+                [r.x1, r.y1 + r.h], [r.x1 + r.w, r.y1 + r.h]
+            ];
+            let overCorner = false;
+            for (const [cx, cy] of corners) {
+                if (Math.hypot(p.x - cx, p.y - cy) < 18) { overCorner = true; break; }
             }
-            requestAnimationFrame(render);
+            if (overCorner) {
+                cap8.canvas.style.cursor = 'nwse-resize';
+            } else if (Math.abs(p.x - cap8.src.x) < 20 && p.y > 60 && p.y < 380) {
+                cap8.canvas.style.cursor = 'grab';
+            } else {
+                cap8.canvas.style.cursor = 'default';
+            }
+            return;
         }
-        render();
+
+        e.preventDefault();
+
+        if (cap8.dragMode === 'src') {
+            let newX = p.x - cap8.dragOffset.x;
+            const recvLeft = cap8.recv.x1;
+            const minDistPx = 1 * cap8.pxPerCm;
+            const maxDistPx = 20 * cap8.pxPerCm;
+            const srcRightEdge = newX + cap8.src.w / 2;
+            let distPx = recvLeft - srcRightEdge;
+            if (distPx < minDistPx) newX = recvLeft - minDistPx - cap8.src.w / 2;
+            if (distPx > maxDistPx) newX = recvLeft - maxDistPx - cap8.src.w / 2;
+            if (newX < 30) newX = 30;
+            cap8.src.x = newX;
+
+            const d = Math.max(1, Math.min(20, Math.round((recvLeft - (newX + cap8.src.w / 2)) / cap8.pxPerCm)));
+            const slider = document.getElementById('capDist');
+            slider.value = d;
+            document.getElementById('capDistVal').textContent = d;
+            return;
+        }
+
+        const r = cap8.recv;
+        let x1 = r.x1, y1 = r.y1, x2 = r.x1 + r.w, y2 = r.y1 + r.h;
+        if (cap8.dragMode === 'tl') { x1 = p.x; y1 = p.y; }
+        if (cap8.dragMode === 'tr') { x2 = p.x; y1 = p.y; }
+        if (cap8.dragMode === 'bl') { x1 = p.x; y2 = p.y; }
+        if (cap8.dragMode === 'br') { x2 = p.x; y2 = p.y; }
+
+        const minW = 24, minH = 60, maxW = 80, maxH = 300;
+
+        if (x2 - x1 < minW) {
+            if (cap8.dragMode === 'tl' || cap8.dragMode === 'bl') x1 = x2 - minW;
+            else x2 = x1 + minW;
+        }
+        if (x2 - x1 > maxW) {
+            if (cap8.dragMode === 'tl' || cap8.dragMode === 'bl') x1 = x2 - maxW;
+            else x2 = x1 + maxW;
+        }
+        if (y2 - y1 < minH) {
+            if (cap8.dragMode === 'tl' || cap8.dragMode === 'tr') y1 = y2 - minH;
+            else y2 = y1 + minH;
+        }
+        if (y2 - y1 > maxH) {
+            if (cap8.dragMode === 'tl' || cap8.dragMode === 'tr') y1 = y2 - maxH;
+            else y2 = y1 + maxH;
+        }
+
+        if (x1 < 20) x1 = 20;
+        if (y1 < 40) y1 = 40;
+        if (x2 > 780) x2 = 780;
+        if (y2 > 400) y2 = 400;
+
+        const srcRightEdge = cap8.src.x + cap8.src.w / 2;
+        if (x1 < srcRightEdge + 1 * cap8.pxPerCm) {
+            x1 = srcRightEdge + 1 * cap8.pxPerCm;
+            if (x2 - x1 < minW) x2 = x1 + minW;
+        }
+
+        r.x1 = x1;
+        r.y1 = y1;
+        r.w = x2 - x1;
+        r.h = y2 - y1;
+
+        const areaCm2 = Math.round((r.w * r.h) / 200);
+        document.getElementById('capArea').value = Math.max(0, Math.min(100, areaCm2));
+        document.getElementById('capAreaVal').textContent = Math.max(0, Math.min(100, areaCm2));
+
+        const distPx = r.x1 - srcRightEdge;
+        const distCm = Math.round(distPx / cap8.pxPerCm);
+        document.getElementById('capDist').value = Math.max(1, Math.min(20, distCm));
+        document.getElementById('capDistVal').textContent = Math.max(1, Math.min(20, distCm));
     }
 
-    function calcCapacitive() {
-        const U = parseFloat(document.getElementById('capVolt').value);
-        const f = parseFloat(document.getElementById('capFreq').value);
+    function onCap8Up() {
+        if (cap8.dragMode) {
+            cap8.dragMode = null;
+            cap8.canvas.style.cursor = 'default';
+        }
+    }
+
+    function applyCap8DistSlider() {
         const d = parseFloat(document.getElementById('capDist').value);
+        cap8.src.x = cap8.recv.x1 - d * cap8.pxPerCm - cap8.src.w / 2;
+        if (cap8.src.x < 30) cap8.src.x = 30;
+    }
+
+    function applyCap8AreaSlider() {
         const S = parseFloat(document.getElementById('capArea').value);
-        if (U === 0 || f === 0 || S === 0 || d === 0) return 0;
-        const base = (U / 1000) * (f / 1000) * (S / 100) * (1 / d) * 100;
-        return Math.min(base, 99.9);
+        const targetArea = Math.max(1800, S * 200);
+        const aspect = cap8.recv.w / cap8.recv.h || 0.3;
+        let newH = Math.sqrt(targetArea / aspect);
+        let newW = newH * aspect;
+        if (newW < 24) { newW = 24; newH = targetArea / 24; }
+        if (newH < 60) { newH = 60; newW = targetArea / 60; }
+        if (newW > 80) { newW = 80; newH = targetArea / 80; }
+        if (newH > 300) { newH = 300; newW = targetArea / 300; }
+        cap8.recv.w = newW;
+        cap8.recv.h = newH;
     }
 
-    function updateCapacitive() {
+    function updateCapacitive8() {
         document.getElementById('capVoltVal').textContent = document.getElementById('capVolt').value;
         document.getElementById('capFreqVal').textContent = document.getElementById('capFreq').value;
         document.getElementById('capDistVal').textContent = document.getElementById('capDist').value;
         document.getElementById('capAreaVal').textContent = document.getElementById('capArea').value;
-        const val = calcCapacitive();
-        document.getElementById('capResult').textContent = val.toFixed(1);
+    }
+
+    function drawCap8() {
+        const sceneEl = document.querySelector('.scene[data-scene="9"]');
+        if (!sceneEl || !sceneEl.classList.contains('active')) {
+            requestAnimationFrame(drawCap8);
+            return;
+        }
+        if (!cap8.ctx) { requestAnimationFrame(drawCap8); return; }
+
+        const now = performance.now();
+        let dt = (now - cap8.lastT) / 1000;
+        cap8.lastT = now;
+        if (dt > 0.15) dt = 0.15;
+        if (dt < 0) dt = 0;
+
         const U = parseFloat(document.getElementById('capVolt').value);
         const f = parseFloat(document.getElementById('capFreq').value);
-        const d = parseFloat(document.getElementById('capDist').value);
-        const S = parseFloat(document.getElementById('capArea').value);
-        let msg = '';
-        if (U === 0) msg = 'Напряжение = 0 → электрического поля нет → наводки нет.';
-        else if (f === 0) msg = 'Частота = 0 (постоянное напряжение) → заряд не меняется → наводки нет.';
-        else if (S === 0) msg = 'Площадь = 0 → ёмкость связи нулевая → наводки нет.';
-        else {
-            const parts = [];
-            if (U >= 700) parts.push('напряжение большое → заряд на пластинах велик');
-            if (f >= 700) parts.push('частота высокая → ток смещения больше');
-            if (S >= 70) parts.push('площадь большая → ёмкость больше');
-            if (d <= 5) parts.push('расстояние маленькое → ёмкость не ослабла');
-            if (parts.length === 0) parts.push('значения в среднем диапазоне');
-            msg = 'Наводка: ' + parts.join(', ') + '.';
+        const apparentF = f / 200;
+
+        if (f > 1) {
+            cap8.phase += 2 * Math.PI * apparentF * dt;
         }
-        document.getElementById('capFeedback').textContent = msg;
+
+        const Uinst = U * Math.cos(cap8.phase);
+        const Uabs = Math.min(1, Math.abs(Uinst) / 1000);
+
+        const ctx = cap8.ctx;
+        const cw = cap8.canvas.width;
+        const ch = cap8.canvas.height;
+
+        ctx.fillStyle = '#f2e9d6';
+        ctx.fillRect(0, 0, cw, ch);
+
+        const s = cap8.src;
+        const r = cap8.recv;
+
+        const srcRight = s.x + s.w / 2;
+        const recvLeft = r.x1;
+        const distPx = Math.max(1, recvLeft - srcRight);
+        const distCm = distPx / cap8.pxPerCm;
+        const areaCm2 = (r.w * r.h) / 200;
+
+        const overlapTop = Math.max(s.cy - s.h/2, r.y1);
+        const overlapBot = Math.min(s.cy + s.h/2, r.y1 + r.h);
+        const overlapH = Math.max(0, overlapBot - overlapTop);
+
+        const polarity = Uinst >= 0 ? 1 : -1;
+        drawCap8Field(ctx, srcRight, recvLeft, overlapTop, overlapBot, U, Uabs, polarity);
+        drawCap8Source(ctx, s, Uinst);
+        drawCap8Receiver(ctx, r, Uinst, Uabs, areaCm2);
+        drawCap8Distance(ctx, srcRight, recvLeft, (overlapTop + overlapBot) / 2, distCm);
+        drawCap8CurrentArrow(ctx, srcRight, recvLeft, overlapTop, overlapBot, Uinst, f);
+
+        ctx.fillStyle = '#4a5a7a';
+        ctx.font = '11px "Roboto Mono", monospace';
+        ctx.textAlign = 'left';
+        ctx.textBaseline = 'alphabetic';
+        ctx.fillText('S = ' + areaCm2.toFixed(0) + ' см²', r.x1 - 4, r.y1 - 24);
+
+        let vInst = 0;
+        if (f > 1 && U > 0 && areaCm2 > 0) {
+            vInst = 0.02 * U * areaCm2 * apparentF * Math.sin(cap8.phase) / Math.max(1, distCm * distCm);
+            if (vInst > 99.9) vInst = 99.9;
+            if (vInst < -99.9) vInst = -99.9;
+        }
+
+        const resEl = document.getElementById('capResult');
+        if (resEl) resEl.textContent = vInst.toFixed(1);
+
+        const fbEl = document.getElementById('capFeedback');
+        if (fbEl) {
+            let msg = '';
+            if (U === 0) {
+                msg = 'Напряжение = 0 → электрического поля нет → наведённый заряд = 0.';
+            } else if (f <= 1) {
+                msg = 'Частота = 0 (постоянное напряжение) → поле не меняется → наводки нет. Постоянное напряжение само по себе не даёт наводки в приёмнике, нужен ток смещения (dU/dt).';
+            } else if (areaCm2 < 15) {
+                msg = 'Площадь перекрытия мала → ёмкость связи слабая.';
+            } else {
+                const parts = [];
+                if (U >= 700) parts.push('напряжение большое → поле сильное');
+                if (f >= 700) parts.push('частота высокая → поле меняется быстро');
+                if (areaCm2 >= 70) parts.push('площадь большая → ёмкость больше');
+                if (distCm <= 5) parts.push('расстояние маленькое → поле не ослабло');
+                if (parts.length === 0) parts.push('значения в среднем диапазоне');
+                msg = 'Амплитуда наводки: ' + parts.join(', ') + '.';
+            }
+            fbEl.textContent = msg;
+        }
+
+        requestAnimationFrame(drawCap8);
+    }
+
+    function drawCap8Field(ctx, x1, x2, top, bot, U, Uabs, polarity) {
+        if (U <= 0 || bot <= top) return;
+
+        const height = bot - top;
+        const numLines = Math.max(1, Math.round(U / 100));
+        const alpha = 0.08 + Uabs * 0.85;
+        const lw = 0.8 + Uabs * 2.3;
+
+        const rCol = polarity > 0 ? 26 : 139;
+        const gCol = polarity > 0 ? 74 : 26;
+        const bCol = polarity > 0 ? 106 : 26;
+
+        for (let i = 0; i < numLines; i++) {
+            const t = numLines === 1 ? 0.5 : i / (numLines - 1);
+            const y = top + t * height;
+
+            if (Uabs > 0.4) {
+                ctx.shadowColor = `rgba(255,140,50,${Uabs * 0.5})`;
+                ctx.shadowBlur = 6 + Uabs * 8;
+            }
+            ctx.strokeStyle = `rgba(${rCol},${gCol},${bCol},${alpha})`;
+            ctx.lineWidth = lw;
+            ctx.beginPath();
+            ctx.moveTo(x1, y);
+            ctx.lineTo(x2, y);
+            ctx.stroke();
+            ctx.shadowBlur = 0;
+            ctx.shadowColor = 'transparent';
+        }
+    }
+
+    function drawCap8Source(ctx, s, Uinst) {
+        const x = s.x - s.w / 2;
+        const y = s.cy - s.h / 2;
+
+        ctx.fillStyle = 'rgba(0,0,0,0.15)';
+        ctx.fillRect(x + 3, y + 3, s.w, s.h);
+
+        ctx.fillStyle = '#1a4a6a';
+        ctx.fillRect(x, y, s.w, s.h);
+        ctx.strokeStyle = '#1a2a4a';
+        ctx.lineWidth = 2;
+        ctx.strokeRect(x, y, s.w, s.h);
+
+        const polarity = Uinst >= 0 ? '+' : '−';
+        ctx.fillStyle = 'rgba(255,255,255,0.92)';
+        ctx.font = 'bold 16px "PT Serif", serif';
+        ctx.textAlign = 'center';
+        ctx.textBaseline = 'middle';
+        const num = 7;
+        for (let i = 0; i < num; i++) {
+            const py = y + 30 + i * (s.h - 60) / (num - 1);
+            ctx.fillText(polarity, s.x, py);
+        }
+
+        ctx.fillStyle = '#1a4a6a';
+        ctx.font = 'bold 14px "Roboto Mono", monospace';
+        ctx.textAlign = 'center';
+        ctx.textBaseline = 'alphabetic';
+        ctx.fillText('U', s.x, y - 12);
+
+        ctx.fillStyle = '#4a5a7a';
+        ctx.font = '11px "Roboto Mono", monospace';
+        ctx.fillText('источник', s.x, y + s.h + 20);
+    }
+
+    function drawCap8Receiver(ctx, r, Uinst, Uabs, areaCm2) {
+        ctx.fillStyle = 'rgba(42,90,42,0.06)';
+        ctx.fillRect(r.x1, r.y1, r.w, r.h);
+
+        ctx.strokeStyle = '#2a5a2a';
+        ctx.lineWidth = 4;
+        ctx.strokeRect(r.x1, r.y1, r.w, r.h);
+
+        const corners = [
+            [r.x1, r.y1],
+            [r.x1 + r.w, r.y1],
+            [r.x1, r.y1 + r.h],
+            [r.x1 + r.w, r.y1 + r.h]
+        ];
+        corners.forEach(([cx, cy]) => {
+            ctx.fillStyle = '#2a5a2a';
+            ctx.beginPath();
+            ctx.arc(cx, cy, 7, 0, Math.PI * 2);
+            ctx.fill();
+            ctx.strokeStyle = '#fffef8';
+            ctx.lineWidth = 2;
+            ctx.stroke();
+        });
+
+        const polarity = Uinst >= 0 ? '−' : '+';
+        const numCharges = Math.floor(areaCm2 / 15);
+        if (numCharges > 0) {
+            const maxN = Math.min(numCharges, 10);
+            const step = (r.h - 40) / Math.max(1, maxN - 1);
+            ctx.fillStyle = '#8b1a1a';
+            ctx.font = 'bold 14px "PT Serif", serif';
+            ctx.textAlign = 'center';
+            ctx.textBaseline = 'middle';
+            for (let i = 0; i < maxN; i++) {
+                ctx.fillText(polarity, r.x1 + r.w / 2, r.y1 + 20 + i * step);
+            }
+        }
+
+        ctx.fillStyle = '#2a5a2a';
+        ctx.font = '11px "Roboto Mono", monospace';
+        ctx.textAlign = 'center';
+        ctx.textBaseline = 'alphabetic';
+        ctx.fillText('приёмник', r.x1 + r.w / 2, r.y1 + r.h + 20);
+    }
+
+    function drawCap8Distance(ctx, x1, x2, y, distCm) {
+        if (y <= 0 || y >= 440) y = 220;
+
+        ctx.strokeStyle = 'rgba(74,90,122,0.55)';
+        ctx.lineWidth = 1;
+        ctx.setLineDash([4, 4]);
+        ctx.beginPath();
+        ctx.moveTo(x1 + 4, y);
+        ctx.lineTo(x2 - 4, y);
+        ctx.stroke();
+        ctx.setLineDash([]);
+
+        ctx.fillStyle = 'rgba(74,90,122,0.85)';
+        ctx.beginPath();
+        ctx.moveTo(x1 + 4, y);
+        ctx.lineTo(x1 + 14, y - 5);
+        ctx.lineTo(x1 + 14, y + 5);
+        ctx.closePath();
+        ctx.fill();
+        ctx.beginPath();
+        ctx.moveTo(x2 - 4, y);
+        ctx.lineTo(x2 - 14, y - 5);
+        ctx.lineTo(x2 - 14, y + 5);
+        ctx.closePath();
+        ctx.fill();
+
+        ctx.fillStyle = '#1a2a4a';
+        ctx.font = 'bold 12px "Roboto Mono", monospace';
+        ctx.textAlign = 'center';
+        ctx.textBaseline = 'bottom';
+        ctx.fillText('d = ' + distCm.toFixed(1) + ' см', (x1 + x2) / 2, y - 6);
+    }
+
+    function drawCap8CurrentArrow(ctx, x1, x2, top, bot, Uinst, f) {
+        if (f <= 1) return;
+        const midX = (x1 + x2) / 2;
+        const midY = (top + bot) / 2;
+        if (!isFinite(midY)) return;
+
+        const Iinst = -Math.sin(cap8.phase);
+        const ratio = Math.abs(Iinst);
+        const alpha = 0.4 + ratio * 0.55;
+        const lw = 2 + ratio * 2;
+
+        const arrowLen = Math.min(40, (x2 - x1) * 0.3);
+        if (arrowLen < 10) return;
+
+        const dir = Iinst >= 0 ? 1 : -1;
+        const tipX = midX + dir * arrowLen;
+        const tailX = midX - dir * arrowLen * 0.4;
+
+        ctx.strokeStyle = `rgba(139,26,26,${alpha})`;
+        ctx.lineWidth = lw;
+        ctx.lineCap = 'round';
+        ctx.beginPath();
+        ctx.moveTo(tailX, midY);
+        ctx.lineTo(tipX, midY);
+        ctx.stroke();
+
+        const size = 6 + ratio * 5;
+        ctx.beginPath();
+        ctx.moveTo(tipX, midY);
+        ctx.lineTo(tipX - dir * size, midY - size * 0.6);
+        ctx.lineTo(tipX - dir * size, midY + size * 0.6);
+        ctx.closePath();
+        ctx.fillStyle = `rgba(139,26,26,${alpha})`;
+        ctx.fill();
+        ctx.lineCap = 'butt';
+
+        ctx.fillStyle = '#8b1a1a';
+        ctx.font = 'bold 10px "Roboto Mono", monospace';
+        ctx.textAlign = 'center';
+        ctx.textBaseline = 'top';
+        ctx.fillText('ток смещения', midX, midY + 12);
     }
-    ['capVolt', 'capFreq', 'capDist', 'capArea'].forEach(id => {
-        document.getElementById(id).addEventListener('input', updateCapacitive);
-    });
 
+    // === СЦЕНА 10: Гальваническая наводка ===
     function drawGalvanic() {
         const canvas = document.getElementById('galvanicViz');
         const ctx = canvas.getContext('2d');

+ 18 - 0
ПМ.03.01/3.1.400/001/specification.md

@@ -185,6 +185,24 @@
 Провод можно перетаскивать мышью (курсор grab/grabbing). Ползунок Расстояние синхронизирован — при перетаскивании он обновляется, а при движении ползунка двигается провод. <br>
 Площадь меняется двумя способами. У петли четыре угла-ручки, любой можно тянуть — противоположный угол остаётся на месте. Ползунок Площадь синхронизирован с текущей площадью. Мгновенная ЭДС показана знаком (+/−) и числом, меняется каждый кадр. При f=0 или I=0 — ровно 0. </td> 
 
+</tr><tr valign="top" textalign="left">
+<td> 6 </td><td> Механизм 2 · ёмкостный </td><td> Мало интерактива и он неинтуитивный </td><td> Добавь интерактив по аналогии с Механизм 1 · индуктивный </td><td> 3.1.400/001_m2_1 </td>
+</tr><tr valign="top" textalign="left">
+Переделана по аналогии с индуктивной (сцена 8), но с физикой ёмкостной связи: <br>
+<b>Источник перетаскивается.</b> Вертикальная пластина с зарядом (+/−) — перетаскивается мышью или пальцем по горизонтали. Ползунок «Расстояние» синхронизирован с её положением: при движении ползунка пластина сдвигается, при перетаскивании — обновляется ползунок.<br>
+<b>Приёмник меняет площадь.</b> Прямоугольник с 4 угловыми ручками — тяните любую, и площадь меняется. Ползунок «Площадь приёмника» синхронизирован: при движении ползунка размер меняется, при перетаскивании углов — обновляется ползунок.<br>
+<b>Густота поля от напряжения.</b> Количество горизонтальных линий между пластинами: round(U/100) — от 1 (при U=100) до 10 (при U=1000). Прозрачность и толщина тоже растут с напряжением. При U=0 поля нет.<br>
+Мерцание от частоты. Поле пульсирует, потому что при переменном напряжении меняется заряд на пластинах. На 500 Гц — 2.5 Гц визуальных колебаний, на 1000 Гц — 5 Гц. При f=0 — статично, полярность постоянная.<br>
+<b>Смена полярности.</b> При положительной полуволне источник показан с «+», приёмник — с «−». При отрицательной — наоборот. Знаки на пластинах переключаются в такт с частотой — видно, что заряд «перетекает» туда-сюда.<br>
+<b>Стрелка тока смещения.</b> Между пластинами появляется горизонтальная стрелка, направление меняется при смене знака напряжения. Это и есть физическая причина наводки: I_см ∝ dU/dt.<br>
+<b>Индикатор мгновенного напряжения</b> показывает знак (+/−) и величину, меняется каждый кадр. При f=0 — ровно 0 (постоянное напряжение не даёт наводки, как постоянный ток не даёт индуктивной).<br>
+<b>Крайние случаи:</b><br>
+- U=0 → поля нет, зарядов нет, наводки нет;<br>
+- f=0 → поле статично, заряды постоянны, наводки нет (ключевое отличие от гальванической);<br>
+- расстояние max → поле слабое;<br>
+- площадь min → мало заряда на приёмнике.
+
+
 </tr></table>
 ---