// Phase 2.2 — флагман-интерактивы для критических §:
// §12 Charge sandbox, §17 Field visualizer, §22 Ohm's law,
// §25 Parallel resistors, §28 Magnet polarity, §30 Эрстед.
'use strict';
const fs = require('fs');
const path = require('path');

const DST = path.join(__dirname, '..', '..', 'frontend', 'textbooks', 'physics_8_ch2.html');
let h = fs.readFileSync(DST, 'utf8');

function makeStubText(n) {
  return `/* IV6 — flagship интерактив (заглушка Phase 2, наполнение в Phase 2.${n}) */
  h += '<div class="wg p8-iv6">'
    +'<div class="wg-header"><span class="wg-badge p8-badge p8-badge-electric">IV-6</span><div class="wg-title">Новый интерактив §${n}</div></div>'
    +'<div class="wg-help">Готовится: интерактивная визуализация с drag-and-drop для углубления темы. Скоро будет доступна.</div>'
    +'<div style="padding:30px;text-align:center;color:var(--p8-muted);font-style:italic">'
      +'<svg viewBox="0 0 24 24" style="width:32px;height:32px;stroke:currentColor;fill:none;stroke-width:1.5;opacity:.4"><circle cx="12" cy="12" r="9"/><path d="M12 7v5l3 2"/></svg>'
      +'<div style="margin-top:8px;font-size:.86rem">Phase 2.${n} — coming soon</div>'
    +'</div>'
    +'</div>';`;
}

function replaceStub(pid, n, widgetHtml, initFn) {
  const stubLF = makeStubText(n);
  const stubCRLF = stubLF.replace(/\n/g, '\r\n');
  let stubText = null;
  if (h.includes(stubLF)) stubText = stubLF;
  else if (h.includes(stubCRLF)) stubText = stubCRLF;
  if (!stubText) { console.warn(`${pid}: stub not found`); return false; }
  const eol = stubText === stubCRLF ? '\r\n' : '\n';
  const widget = widgetHtml.trim().replace(/\n/g, eol);
  h = h.replace(stubText, widget);
  h = h.replace(`wireReadBtn('${pid}');`, `wireReadBtn('${pid}');\n  _init${pid.toUpperCase()}_iv6();`);
  const fnStart = h.indexOf(`function build_${pid}()`);
  const fnEnd = h.indexOf('\n}\n', fnStart);
  h = h.slice(0, fnEnd + 3) + '\n' + initFn.trim() + '\n' + h.slice(fnEnd + 3);
  console.log(`${pid}: replaced`);
  return true;
}

// ============================================================
// §12 — Charge sandbox: click anywhere to add charge
// ============================================================
const P12_HTML = `/* IV6 — Charge Sandbox (Phase 2.2) */
  h += '<div class="wg p8-iv6">'
    +'<div class="wg-header"><span class="wg-badge p8-badge p8-badge-electric">IV-6</span><div class="wg-title">Песочница зарядов — наблюдай взаимодействие</div></div>'
    +'<div class="wg-help">Клик ЛКМ → добавить +заряд, клик ПКМ → добавить -заряд. Перетаскивай существующие. Стрелки показывают силы взаимодействия (закон Кулона $F = k|q_1 q_2|/r^2$).</div>'
    +'<div class="p8-sandbox" id="p12-iv6-sandbox" style="height:300px"></div>'
    +'<div style="margin-top:10px;display:flex;gap:10px;flex-wrap:wrap">'
      +'<button class="btn primary" id="p12-iv6-add-pos">+ Добавить +</button>'
      +'<button class="btn primary" id="p12-iv6-add-neg" style="background:#2563eb;border-color:#2563eb">+ Добавить −</button>'
      +'<button class="btn" id="p12-iv6-clear">Очистить</button>'
      +'<div class="p8-readout"><span class="p8-readout-label">Зарядов</span><span class="p8-readout-value" id="p12-iv6-count">0</span></div>'
    +'</div>'
    +'</div>';`;

const P12_INIT = `
function _initP12_iv6(){
  const sb = document.getElementById('p12-iv6-sandbox');
  if (!sb || !window.P8Helpers || !window.P8Drag) return;
  const W = 560, H = 300;
  const canvas = document.createElement('canvas');
  canvas.width = W; canvas.height = H;
  canvas.style.width = '100%'; canvas.style.height = '100%'; canvas.style.display = 'block';
  sb.appendChild(canvas);
  const ctx = canvas.getContext('2d');
  const charges = [];
  let nextSign = 1;
  function draw(){
    ctx.fillStyle = '#fafafa';
    ctx.fillRect(0, 0, W, H);
    /* Forces between pairs */
    for (let i = 0; i < charges.length; i++) {
      for (let j = i + 1; j < charges.length; j++) {
        const a = charges[i], b = charges[j];
        const dx = b.x - a.x, dy = b.y - a.y;
        const r2 = dx*dx + dy*dy;
        if (r2 < 100) continue;
        const r = Math.sqrt(r2);
        const F = 4e6 * a.sign * b.sign / r2;
        const fx = F * dx / r, fy = F * dy / r;
        /* Arrow from a in direction (-fx, -fy) means: force on a from b */
        const len = Math.min(80, Math.abs(F) * 5);
        const dir = a.sign * b.sign > 0 ? -1 : 1;
        const aex = a.x + dir * fx / Math.abs(F) * len;
        const aey = a.y + dir * fy / Math.abs(F) * len;
        ctx.strokeStyle = a.sign * b.sign > 0 ? '#dc2626' : '#16a34a';
        ctx.lineWidth = 1.5;
        ctx.beginPath();
        ctx.moveTo(a.x, a.y);
        ctx.lineTo(aex, aey);
        ctx.stroke();
        /* Arrowhead */
        const ang = Math.atan2(aey - a.y, aex - a.x);
        ctx.beginPath();
        ctx.moveTo(aex, aey);
        ctx.lineTo(aex - 7 * Math.cos(ang - 0.3), aey - 7 * Math.sin(ang - 0.3));
        ctx.lineTo(aex - 7 * Math.cos(ang + 0.3), aey - 7 * Math.sin(ang + 0.3));
        ctx.closePath();
        ctx.fillStyle = ctx.strokeStyle;
        ctx.fill();
      }
    }
    /* Charges */
    charges.forEach(c => {
      const color = c.sign > 0 ? '#dc2626' : '#2563eb';
      const fill = c.sign > 0 ? '#fecaca' : '#bfdbfe';
      ctx.fillStyle = fill;
      ctx.strokeStyle = color;
      ctx.lineWidth = 2.5;
      ctx.beginPath();
      ctx.arc(c.x, c.y, 18, 0, 2 * Math.PI);
      ctx.fill();
      ctx.stroke();
      ctx.fillStyle = color;
      ctx.font = "bold 18px sans-serif";
      ctx.textAlign = 'center';
      ctx.textBaseline = 'middle';
      ctx.fillText(c.sign > 0 ? '+' : '−', c.x, c.y + 1);
    });
    document.getElementById('p12-iv6-count').textContent = charges.length;
  }
  const drag = P8Drag.attachCanvas(canvas, {
    objects: charges.map(c => ({ ...c, r: 22 })),
    onPickup: c => {},
    onDrag: (c, pos) => {
      /* Sync back to charges by id */
      const orig = charges.find(ch => ch === c || (ch.id === c.id));
      if (orig) { orig.x = pos.x; orig.y = pos.y; }
      draw();
    },
    onClick: (pos) => {
      charges.push({ x: pos.x, y: pos.y, sign: nextSign, id: Date.now() + Math.random() });
      drag.updateObjects(charges.map(c => ({ ...c, r: 22 })));
      draw();
      if (window.addXp && charges.length === 2) addXp(10, 'p12-iv6-first');
    }
  });
  document.getElementById('p12-iv6-add-pos').onclick = () => {
    nextSign = 1;
    charges.push({ x: 80 + Math.random() * (W - 160), y: 80 + Math.random() * (H - 160), sign: 1, id: Date.now() + Math.random() });
    drag.updateObjects(charges.map(c => ({ ...c, r: 22 })));
    draw();
  };
  document.getElementById('p12-iv6-add-neg').onclick = () => {
    nextSign = -1;
    charges.push({ x: 80 + Math.random() * (W - 160), y: 80 + Math.random() * (H - 160), sign: -1, id: Date.now() + Math.random() });
    drag.updateObjects(charges.map(c => ({ ...c, r: 22 })));
    draw();
  };
  document.getElementById('p12-iv6-clear').onclick = () => {
    charges.length = 0;
    drag.updateObjects([]);
    draw();
  };
  draw();
}
`;
replaceStub('p12', 12, P12_HTML, P12_INIT);

// ============================================================
// §17 — Field visualizer
// ============================================================
const P17_HTML = `/* IV6 — Field Visualizer (Phase 2.2) */
  h += '<div class="wg p8-iv6">'
    +'<div class="wg-header"><span class="wg-badge p8-badge p8-badge-electric">IV-6</span><div class="wg-title">Силовые линии — карта поля</div></div>'
    +'<div class="wg-help">Перетаскивай заряды. Силовые линии рисуются live: выходят из + и заходят в −. Густота линий = напряжённость $E$.</div>'
    +'<div class="p8-sandbox" id="p17-iv6-sandbox" style="height:320px"></div>'
    +'<div style="margin-top:10px;display:flex;gap:10px;flex-wrap:wrap">'
      +'<button class="btn" id="p17-iv6-add-pos">+ Заряд</button>'
      +'<button class="btn" id="p17-iv6-add-neg">− Заряд</button>'
      +'<button class="btn" id="p17-iv6-clear">Сброс</button>'
    +'</div>'
    +'</div>';`;

const P17_INIT = `
function _initP17_iv6(){
  const sb = document.getElementById('p17-iv6-sandbox');
  if (!sb || !window.P8Drag) return;
  const W = 560, H = 320;
  const canvas = document.createElement('canvas');
  canvas.width = W; canvas.height = H;
  canvas.style.width = '100%'; canvas.style.height = '100%'; canvas.style.display = 'block';
  sb.appendChild(canvas);
  const ctx = canvas.getContext('2d');
  let charges = [
    { x: 200, y: 160, sign: 1, r: 22 },
    { x: 360, y: 160, sign: -1, r: 22 }
  ];
  function E(x, y) {
    let ex = 0, ey = 0;
    charges.forEach(c => {
      const dx = x - c.x, dy = y - c.y;
      const r2 = dx*dx + dy*dy;
      if (r2 < 200) return;
      const r = Math.sqrt(r2);
      const k = 5000 * c.sign / r2;
      ex += k * dx / r; ey += k * dy / r;
    });
    return { ex, ey, mag: Math.sqrt(ex*ex + ey*ey) };
  }
  function draw(){
    ctx.fillStyle = '#fafafa';
    ctx.fillRect(0, 0, W, H);
    /* Draw field lines starting from + charges */
    charges.filter(c => c.sign > 0).forEach(c => {
      for (let i = 0; i < 16; i++) {
        const a = i * 2 * Math.PI / 16;
        let x = c.x + 25 * Math.cos(a);
        let y = c.y + 25 * Math.sin(a);
        ctx.strokeStyle = '#dc2626';
        ctx.lineWidth = 1.2;
        ctx.globalAlpha = 0.75;
        ctx.beginPath();
        ctx.moveTo(x, y);
        for (let step = 0; step < 200; step++) {
          const e = E(x, y);
          if (e.mag < 0.01) break;
          const dx = e.ex / e.mag * 3;
          const dy = e.ey / e.mag * 3;
          x += dx; y += dy;
          if (x < 0 || x > W || y < 0 || y > H) break;
          /* Stop near - charge */
          let nearNeg = false;
          for (const neg of charges) {
            if (neg.sign < 0 && (x - neg.x)**2 + (y - neg.y)**2 < 600) { nearNeg = true; break; }
          }
          ctx.lineTo(x, y);
          if (nearNeg) break;
        }
        ctx.stroke();
        ctx.globalAlpha = 1;
      }
    });
    /* Charges */
    charges.forEach(c => {
      const color = c.sign > 0 ? '#dc2626' : '#2563eb';
      const fill = c.sign > 0 ? '#fecaca' : '#bfdbfe';
      ctx.fillStyle = fill;
      ctx.strokeStyle = color;
      ctx.lineWidth = 2.5;
      ctx.beginPath();
      ctx.arc(c.x, c.y, 20, 0, 2 * Math.PI);
      ctx.fill();
      ctx.stroke();
      ctx.fillStyle = color;
      ctx.font = "bold 20px sans-serif";
      ctx.textAlign = 'center';
      ctx.textBaseline = 'middle';
      ctx.fillText(c.sign > 0 ? '+' : '−', c.x, c.y + 1);
    });
  }
  const drag = P8Drag.attachCanvas(canvas, {
    objects: charges,
    onDrag: () => draw()
  });
  document.getElementById('p17-iv6-add-pos').onclick = () => {
    charges.push({ x: 100 + Math.random() * (W - 200), y: 80 + Math.random() * (H - 160), sign: 1, r: 22 });
    drag.updateObjects(charges);
    draw();
  };
  document.getElementById('p17-iv6-add-neg').onclick = () => {
    charges.push({ x: 100 + Math.random() * (W - 200), y: 80 + Math.random() * (H - 160), sign: -1, r: 22 });
    drag.updateObjects(charges);
    draw();
  };
  document.getElementById('p17-iv6-clear').onclick = () => {
    charges.length = 0;
    charges.push({ x: 200, y: 160, sign: 1, r: 22 }, { x: 360, y: 160, sign: -1, r: 22 });
    drag.updateObjects(charges);
    draw();
  };
  draw();
}
`;
replaceStub('p17', 17, P17_HTML, P17_INIT);

// ============================================================
// §22 — Ohm's law sandbox
// ============================================================
const P22_HTML = `/* IV6 — Ohm's Law (Phase 2.2) */
  h += '<div class="wg p8-iv6">'
    +'<div class="wg-header"><span class="wg-badge p8-badge p8-badge-electric">IV-6</span><div class="wg-title">Закон Ома: $I = U/R$</div></div>'
    +'<div class="wg-help">Двигай напряжение $U$ и сопротивление $R$. Ток $I = U/R$ обновляется в реальном времени. Лампочка светится ярче с ростом тока.</div>'
    +'<div class="p8-sandbox" id="p22-iv6-sandbox" style="height:220px"></div>'
    +'<div style="margin-top:10px;display:grid;grid-template-columns:1fr 1fr;gap:8px">'
      +'<div class="p8-scrubber"><span class="p8-scrubber-label">U</span><input type="range" id="p22-iv6-u" min="0.5" max="12" step="0.1" value="6"><span class="p8-scrubber-value"><span id="p22-iv6-u-val">6.0</span><span class="p8-unit">В</span></span></div>'
      +'<div class="p8-scrubber"><span class="p8-scrubber-label">R</span><input type="range" id="p22-iv6-r" min="1" max="100" step="1" value="12"><span class="p8-scrubber-value"><span id="p22-iv6-r-val">12</span><span class="p8-unit">Ом</span></span></div>'
    +'</div>'
    +'<div style="margin-top:8px"><div class="p8-readout"><span class="p8-readout-label">I = U/R</span><span class="p8-readout-value" id="p22-iv6-i">0.50</span><span class="p8-readout-unit">А</span></div></div>'
    +'</div>';`;

const P22_INIT = `
function _initP22_iv6(){
  const sb = document.getElementById('p22-iv6-sandbox');
  if (!sb || !window.P8Helpers) return;
  const svg = P8Helpers.svg.create(560, 220);
  svg.setAttribute('width','100%'); svg.setAttribute('height','100%'); svg.style.display='block';
  sb.appendChild(svg);
  let U = 6, R = 12;
  function render(){
    svg.innerHTML = '';
    const I = U / R;
    /* Circuit */
    /* Battery */
    svg.appendChild(P8Helpers.em.circuitComponent('battery', 120, 110, 'h', U+' В'));
    /* Resistor */
    svg.appendChild(P8Helpers.em.circuitComponent('resistor', 280, 110, 'h', R+' Ом'));
    /* Lamp (brightness varies with I) */
    const lampG = P8Helpers.svg.el('g', { transform: 'translate(440, 110)' });
    const brightness = Math.min(1, I / 1.5);
    lampG.appendChild(P8Helpers.svg.el('circle', { cx: 0, cy: 0, r: 26, fill: '#fef3c7', opacity: brightness * 0.6 + 0.1 }));
    lampG.appendChild(P8Helpers.svg.el('circle', { cx: 0, cy: 0, r: 16, fill: '#fef3c7', stroke: '#0f172a', 'stroke-width': 2 }));
    if (brightness > 0.3) {
      lampG.appendChild(P8Helpers.svg.el('circle', { cx: 0, cy: 0, r: 30, fill: 'none', stroke: '#facc15', 'stroke-width': 3, opacity: brightness }));
    }
    lampG.appendChild(P8Helpers.svg.el('line', { x1: -10, y1: -10, x2: 10, y2: 10, stroke: '#0f172a', 'stroke-width': 1.5 }));
    lampG.appendChild(P8Helpers.svg.el('line', { x1: -10, y1: 10, x2: 10, y2: -10, stroke: '#0f172a', 'stroke-width': 1.5 }));
    svg.appendChild(lampG);
    /* Connect wires */
    svg.appendChild(P8Helpers.svg.el('line', { x1: 150, y1: 110, x2: 250, y2: 110, stroke: '#0f172a', 'stroke-width': 2 }));
    svg.appendChild(P8Helpers.svg.el('line', { x1: 310, y1: 110, x2: 414, y2: 110, stroke: '#0f172a', 'stroke-width': 2 }));
    svg.appendChild(P8Helpers.svg.el('line', { x1: 466, y1: 110, x2: 510, y2: 110, stroke: '#0f172a', 'stroke-width': 2 }));
    svg.appendChild(P8Helpers.svg.el('line', { x1: 510, y1: 110, x2: 510, y2: 170, stroke: '#0f172a', 'stroke-width': 2 }));
    svg.appendChild(P8Helpers.svg.el('line', { x1: 90, y1: 110, x2: 90, y2: 170, stroke: '#0f172a', 'stroke-width': 2 }));
    svg.appendChild(P8Helpers.svg.el('line', { x1: 90, y1: 170, x2: 510, y2: 170, stroke: '#0f172a', 'stroke-width': 2 }));
    /* Current label */
    svg.appendChild(P8Helpers.svg.el('text', { x: 300, y: 195, 'font-family':"'JetBrains Mono',monospace", 'font-size':14, 'font-weight':800, fill:'var(--el-mid,#06b6d4)', 'text-anchor':'middle', text: 'I = '+I.toFixed(2)+' А' }));
    document.getElementById('p22-iv6-i').textContent = I.toFixed(2);
  }
  document.getElementById('p22-iv6-u').oninput = ev => { U = +ev.target.value; document.getElementById('p22-iv6-u-val').textContent = U.toFixed(1); render(); };
  document.getElementById('p22-iv6-r').oninput = ev => { R = +ev.target.value; document.getElementById('p22-iv6-r-val').textContent = R; render(); };
  render();
}
`;
replaceStub('p22', 22, P22_HTML, P22_INIT);

// ============================================================
// §25 — Parallel resistors
// ============================================================
const P25_HTML = `/* IV6 — Parallel resistors (Phase 2.2) */
  h += '<div class="wg p8-iv6">'
    +'<div class="wg-header"><span class="wg-badge p8-badge p8-badge-electric">IV-6</span><div class="wg-title">Параллельные резисторы: $1/R = 1/R_1 + 1/R_2$</div></div>'
    +'<div class="wg-help">Двигай $R_1, R_2$ — наблюдай как ток делится между ветвями ($I = I_1 + I_2$) и какое получается общее $R$.</div>'
    +'<div class="p8-sandbox" id="p25-iv6-sandbox" style="height:240px"></div>'
    +'<div style="margin-top:10px;display:grid;grid-template-columns:1fr 1fr;gap:8px">'
      +'<div class="p8-scrubber"><span class="p8-scrubber-label">R₁</span><input type="range" id="p25-iv6-r1" min="1" max="100" step="1" value="20"><span class="p8-scrubber-value"><span id="p25-iv6-r1-val">20</span><span class="p8-unit">Ом</span></span></div>'
      +'<div class="p8-scrubber"><span class="p8-scrubber-label">R₂</span><input type="range" id="p25-iv6-r2" min="1" max="100" step="1" value="30"><span class="p8-scrubber-value"><span id="p25-iv6-r2-val">30</span><span class="p8-unit">Ом</span></span></div>'
    +'</div>'
    +'<div style="margin-top:8px;display:flex;gap:10px;flex-wrap:wrap">'
      +'<div class="p8-readout"><span class="p8-readout-label">R_общ</span><span class="p8-readout-value" id="p25-iv6-r">12</span><span class="p8-readout-unit">Ом</span></div>'
      +'<div class="p8-readout"><span class="p8-readout-label">I₁</span><span class="p8-readout-value" id="p25-iv6-i1">0.6</span><span class="p8-readout-unit">А</span></div>'
      +'<div class="p8-readout"><span class="p8-readout-label">I₂</span><span class="p8-readout-value" id="p25-iv6-i2">0.4</span><span class="p8-readout-unit">А</span></div>'
    +'</div>'
    +'</div>';`;

const P25_INIT = `
function _initP25_iv6(){
  const sb = document.getElementById('p25-iv6-sandbox');
  if (!sb || !window.P8Helpers) return;
  const svg = P8Helpers.svg.create(560, 240);
  svg.setAttribute('width','100%'); svg.setAttribute('height','100%'); svg.style.display='block';
  sb.appendChild(svg);
  const U = 12;
  let R1 = 20, R2 = 30;
  function render(){
    svg.innerHTML = '';
    const R = 1 / (1/R1 + 1/R2);
    const I1 = U / R1, I2 = U / R2, I = I1 + I2;
    /* Battery left */
    svg.appendChild(P8Helpers.em.circuitComponent('battery', 80, 120, 'h', U+' В'));
    /* Branch split */
    svg.appendChild(P8Helpers.svg.el('line', { x1: 110, y1: 120, x2: 200, y2: 120, stroke: '#0f172a', 'stroke-width': 2 }));
    svg.appendChild(P8Helpers.svg.el('line', { x1: 200, y1: 60, x2: 200, y2: 180, stroke: '#0f172a', 'stroke-width': 2 }));
    svg.appendChild(P8Helpers.svg.el('line', { x1: 380, y1: 60, x2: 380, y2: 180, stroke: '#0f172a', 'stroke-width': 2 }));
    svg.appendChild(P8Helpers.svg.el('line', { x1: 200, y1: 60, x2: 290, y2: 60, stroke: '#0f172a', 'stroke-width': 2 }));
    svg.appendChild(P8Helpers.svg.el('line', { x1: 320, y1: 60, x2: 380, y2: 60, stroke: '#0f172a', 'stroke-width': 2 }));
    svg.appendChild(P8Helpers.svg.el('line', { x1: 200, y1: 180, x2: 290, y2: 180, stroke: '#0f172a', 'stroke-width': 2 }));
    svg.appendChild(P8Helpers.svg.el('line', { x1: 320, y1: 180, x2: 380, y2: 180, stroke: '#0f172a', 'stroke-width': 2 }));
    /* R1 (top) */
    svg.appendChild(P8Helpers.em.circuitComponent('resistor', 305, 60, 'h', R1+' Ом'));
    /* R2 (bottom) */
    svg.appendChild(P8Helpers.em.circuitComponent('resistor', 305, 180, 'h', R2+' Ом'));
    /* Right wire */
    svg.appendChild(P8Helpers.svg.el('line', { x1: 380, y1: 120, x2: 510, y2: 120, stroke: '#0f172a', 'stroke-width': 2 }));
    svg.appendChild(P8Helpers.svg.el('line', { x1: 510, y1: 120, x2: 510, y2: 210, stroke: '#0f172a', 'stroke-width': 2 }));
    svg.appendChild(P8Helpers.svg.el('line', { x1: 50, y1: 120, x2: 50, y2: 210, stroke: '#0f172a', 'stroke-width': 2 }));
    svg.appendChild(P8Helpers.svg.el('line', { x1: 50, y1: 210, x2: 510, y2: 210, stroke: '#0f172a', 'stroke-width': 2 }));
    /* Current labels */
    svg.appendChild(P8Helpers.svg.el('text', { x: 290, y: 48, 'font-family':"'JetBrains Mono',monospace", 'font-size':11, 'font-weight':700, fill:'var(--el-mid,#06b6d4)', 'text-anchor':'middle', text: 'I₁ = '+I1.toFixed(2)+' А' }));
    svg.appendChild(P8Helpers.svg.el('text', { x: 290, y: 218, 'font-family':"'JetBrains Mono',monospace", 'font-size':11, 'font-weight':700, fill:'var(--el-mid,#06b6d4)', 'text-anchor':'middle', text: 'I₂ = '+I2.toFixed(2)+' А' }));
    svg.appendChild(P8Helpers.svg.el('text', { x: 150, y: 138, 'font-family':"'JetBrains Mono',monospace", 'font-size':11, 'font-weight':700, fill:'#dc2626', 'text-anchor':'middle', text: 'I = '+I.toFixed(2)+' А' }));
    document.getElementById('p25-iv6-r').textContent = R.toFixed(1);
    document.getElementById('p25-iv6-i1').textContent = I1.toFixed(2);
    document.getElementById('p25-iv6-i2').textContent = I2.toFixed(2);
  }
  document.getElementById('p25-iv6-r1').oninput = ev => { R1 = +ev.target.value; document.getElementById('p25-iv6-r1-val').textContent = R1; render(); };
  document.getElementById('p25-iv6-r2').oninput = ev => { R2 = +ev.target.value; document.getElementById('p25-iv6-r2-val').textContent = R2; render(); };
  render();
}
`;
replaceStub('p25', 25, P25_HTML, P25_INIT);

// ============================================================
// §28 — Magnet polarity demo
// ============================================================
const P28_HTML = `/* IV6 — Magnet polarity (Phase 2.2) */
  h += '<div class="wg p8-iv6">'
    +'<div class="wg-header"><span class="wg-badge p8-badge p8-badge-electric">IV-6</span><div class="wg-title">Магниты: разноимённые притягиваются</div></div>'
    +'<div class="wg-help">Перетаскивай магниты. При сближении одноимённых полюсов (N-N или S-S) — отталкивание (зелёные стрелки). Разноимённых (N-S) — притяжение (красные стрелки).</div>'
    +'<div class="p8-sandbox" id="p28-iv6-sandbox" style="height:240px"></div>'
    +'</div>';`;

const P28_INIT = `
function _initP28_iv6(){
  const sb = document.getElementById('p28-iv6-sandbox');
  if (!sb || !window.P8Drag) return;
  const W = 560, H = 240;
  const canvas = document.createElement('canvas');
  canvas.width = W; canvas.height = H;
  canvas.style.width='100%'; canvas.style.height='100%'; canvas.style.display='block';
  sb.appendChild(canvas);
  const ctx = canvas.getContext('2d');
  const magnets = [
    { x: 140, y: 120, angle: 0, r: 50 },
    { x: 420, y: 120, angle: 0, r: 50 }
  ];
  function drawMagnet(m){
    const w = 100, h = 32;
    ctx.save();
    ctx.translate(m.x, m.y);
    ctx.rotate(m.angle);
    /* N half (red) */
    ctx.fillStyle = '#dc2626';
    ctx.fillRect(-w/2, -h/2, w/2, h);
    /* S half (blue) */
    ctx.fillStyle = '#2563eb';
    ctx.fillRect(0, -h/2, w/2, h);
    ctx.strokeStyle = '#0f172a';
    ctx.lineWidth = 2;
    ctx.strokeRect(-w/2, -h/2, w, h);
    ctx.fillStyle = '#fff';
    ctx.font = "bold 18px sans-serif";
    ctx.textAlign = 'center';
    ctx.textBaseline = 'middle';
    ctx.fillText('N', -w/4, 0);
    ctx.fillText('S', w/4, 0);
    ctx.restore();
  }
  function draw(){
    ctx.fillStyle = '#fafafa';
    ctx.fillRect(0, 0, W, H);
    /* Compute interaction between the two magnets — their inner poles */
    /* Magnet 1: right side is S (blue, at +50), Magnet 2: left side is N (red, at -50) */
    const m1S_x = magnets[0].x + 50 * Math.cos(magnets[0].angle);
    const m1S_y = magnets[0].y + 50 * Math.sin(magnets[0].angle);
    const m2N_x = magnets[1].x - 50 * Math.cos(magnets[1].angle);
    const m2N_y = magnets[1].y - 50 * Math.sin(magnets[1].angle);
    const dx = m2N_x - m1S_x;
    const dy = m2N_y - m1S_y;
    const dist = Math.sqrt(dx*dx + dy*dy);
    if (dist < 250 && dist > 30) {
      /* N-S → attraction */
      const F = 5000 / (dist * dist);
      const ux = dx / dist, uy = dy / dist;
      const len = Math.min(50, F * 50);
      const color = '#dc2626';
      /* Arrow 1 from m1S toward m2N */
      ctx.strokeStyle = color; ctx.lineWidth = 2;
      ctx.beginPath();
      ctx.moveTo(m1S_x, m1S_y);
      ctx.lineTo(m1S_x + ux * len, m1S_y + uy * len);
      ctx.stroke();
      /* Arrow 2 from m2N back */
      ctx.beginPath();
      ctx.moveTo(m2N_x, m2N_y);
      ctx.lineTo(m2N_x - ux * len, m2N_y - uy * len);
      ctx.stroke();
      ctx.fillStyle = color;
      ctx.font = "bold 12px sans-serif";
      ctx.textAlign = 'center';
      ctx.fillText('притяжение', (m1S_x + m2N_x)/2, (m1S_y + m2N_y)/2 - 12);
    }
    magnets.forEach(drawMagnet);
  }
  /* Drag */
  const dragObjs = magnets.map((m, i) => ({ x: m.x, y: m.y, r: 50, idx: i }));
  const drag = P8Drag.attachCanvas(canvas, {
    objects: dragObjs,
    onDrag: (obj, pos) => {
      magnets[obj.idx].x = pos.x;
      magnets[obj.idx].y = pos.y;
      draw();
    }
  });
  draw();
}
`;
replaceStub('p28', 28, P28_HTML, P28_INIT);

// ============================================================
// §30 — Эрстед: wire + compass
// ============================================================
const P30_HTML = `/* IV6 — Эрстед (Phase 2.2) */
  h += '<div class="wg p8-iv6">'
    +'<div class="wg-header"><span class="wg-badge p8-badge p8-badge-electric">IV-6</span><div class="wg-title">Опыт Эрстеда: ток отклоняет стрелку</div></div>'
    +'<div class="wg-help">Включи ток в проводнике скрубером — стрелка компаса отклоняется. Направление поля вокруг провода определяется правилом правой руки.</div>'
    +'<div class="p8-sandbox" id="p30-iv6-sandbox" style="height:240px"></div>'
    +'<div style="margin-top:10px;display:flex;gap:14px;flex-wrap:wrap">'
      +'<div class="p8-scrubber" style="flex:1;min-width:240px"><span class="p8-scrubber-label">Ток</span><input type="range" id="p30-iv6-i" min="-5" max="5" step="0.1" value="0"><span class="p8-scrubber-value"><span id="p30-iv6-i-val">0.0</span><span class="p8-unit">А</span></span></div>'
      +'<div class="p8-readout"><span class="p8-readout-label">Угол</span><span class="p8-readout-value" id="p30-iv6-ang">0</span><span class="p8-readout-unit">°</span></div>'
    +'</div>'
    +'</div>';`;

const P30_INIT = `
function _initP30_iv6(){
  const sb = document.getElementById('p30-iv6-sandbox');
  if (!sb || !window.P8Helpers) return;
  const svg = P8Helpers.svg.create(560, 240);
  svg.setAttribute('width','100%'); svg.setAttribute('height','100%'); svg.style.display='block';
  sb.appendChild(svg);
  let I = 0;
  function render(){
    svg.innerHTML = '';
    /* Wire (horizontal) */
    svg.appendChild(P8Helpers.svg.el('line', { x1: 40, y1: 120, x2: 520, y2: 120, stroke: '#0f172a', 'stroke-width': 5 }));
    /* Current arrow direction */
    if (Math.abs(I) > 0.05) {
      const dir = I > 0 ? 1 : -1;
      const arrowX = 320;
      svg.appendChild(P8Helpers.svg.el('polygon', {
        points: dir > 0 ? (arrowX+8)+',120 '+(arrowX-12)+',114 '+(arrowX-12)+',126' : (arrowX-8)+',120 '+(arrowX+12)+',114 '+(arrowX+12)+',126',
        fill: '#dc2626'
      }));
      svg.appendChild(P8Helpers.svg.el('text', { x: 100, y: 110, 'font-family':"'JetBrains Mono',monospace", 'font-size':11, 'font-weight':700, fill:'#dc2626', text: 'I = '+I.toFixed(1)+' А' }));
    }
    /* Field lines around wire (concentric circles) */
    const intensity = Math.abs(I) / 5;
    if (intensity > 0.05) {
      [30, 50, 70, 90].forEach((r, i) => {
        svg.appendChild(P8Helpers.svg.el('circle', { cx: 280, cy: 120, r, fill: 'none', stroke: '#7c3aed', 'stroke-width': 1.5, opacity: intensity * (1 - i * 0.15), 'stroke-dasharray': '5 3' }));
      });
    }
    /* Compass below wire (initially N up = 0°) */
    const angle = Math.atan2(0, 1) * 180 / Math.PI; /* baseline */
    /* Angle deflection ∝ I (sign determines direction) */
    const deflection = Math.atan(I * 0.5) * 60; /* approx */
    /* Compass body */
    svg.appendChild(P8Helpers.svg.el('circle', { cx: 280, cy: 195, r: 28, fill: '#fff', stroke: '#0f172a', 'stroke-width': 2 }));
    svg.appendChild(P8Helpers.svg.el('text', { x: 280, y: 172, 'font-family':"'Unbounded',sans-serif", 'font-size':10, 'font-weight':800, fill:'#dc2626', 'text-anchor':'middle', text: 'N' }));
    /* Needle */
    const needleG = P8Helpers.svg.el('g', { transform: 'translate(280, 195) rotate('+deflection+')' });
    needleG.appendChild(P8Helpers.svg.el('polygon', { points: '-2,-22 2,-22 0,-2', fill: '#dc2626' }));
    needleG.appendChild(P8Helpers.svg.el('polygon', { points: '-2,22 2,22 0,2', fill: '#475569' }));
    needleG.appendChild(P8Helpers.svg.el('circle', { cx: 0, cy: 0, r: 3, fill: '#0f172a' }));
    svg.appendChild(needleG);
    document.getElementById('p30-iv6-ang').textContent = Math.round(deflection);
  }
  document.getElementById('p30-iv6-i').oninput = ev => { I = +ev.target.value; document.getElementById('p30-iv6-i-val').textContent = I.toFixed(1); render(); };
  render();
}
`;
replaceStub('p30', 30, P30_HTML, P30_INIT);

fs.writeFileSync(DST, h);
console.log('ch2 size:', h.length);

const scripts = [...h.matchAll(/<script>([\s\S]*?)<\/script>/g)];
for (const m of scripts) {
  try { new Function(m[1]); }
  catch (e) { console.error('JS PARSE FAIL:', e.message.slice(0, 200)); process.exit(1); }
}
console.log('inline JS parses OK');
const fns = [...h.matchAll(/function build_p(\d+)\(\)/g)].map(m => parseInt(m[1]));
console.log('Builders:', fns.length, fns);
