// Phase 3 — Ch3 Световые явления: hero + 9 section watermarks + 9 IV-6.
'use strict';
const fs = require('fs');
const path = require('path');

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

// === 1. Hero replacement ===
const SUN_WM = `<svg viewBox="0 0 100 100" aria-hidden="true">
  <circle cx="50" cy="50" r="22" />
  <g><line x1="50" y1="8" x2="50" y2="22" stroke="currentColor" stroke-width="4" stroke-linecap="round"/>
  <line x1="50" y1="78" x2="50" y2="92" stroke="currentColor" stroke-width="4" stroke-linecap="round"/>
  <line x1="8" y1="50" x2="22" y2="50" stroke="currentColor" stroke-width="4" stroke-linecap="round"/>
  <line x1="78" y1="50" x2="92" y2="50" stroke="currentColor" stroke-width="4" stroke-linecap="round"/>
  <line x1="20" y1="20" x2="30" y2="30" stroke="currentColor" stroke-width="4" stroke-linecap="round"/>
  <line x1="70" y1="70" x2="80" y2="80" stroke="currentColor" stroke-width="4" stroke-linecap="round"/>
  <line x1="80" y1="20" x2="70" y2="30" stroke="currentColor" stroke-width="4" stroke-linecap="round"/>
  <line x1="30" y1="70" x2="20" y2="80" stroke="currentColor" stroke-width="4" stroke-linecap="round"/></g>
</svg>`;

const NEW_HERO = `<header class="p8-hero">
  <div class="p8-hero-wm">${SUN_WM}</div>
  <div class="p8-hero-meter" id="p8-meter-ch3"><span id="p8-meter-val">λ=550</span> нм</div>
  <div class="p8-hero-inner">
    <div class="p8-hero-eyebrow">Глава 3 · 9 параграфов</div>
    <h1 class="p8-hero-title">Световые явления</h1>
    <div class="p8-hero-sub">Лучи, тени, отражение, преломление, линзы, дисперсия, глаз. Перетаскивайте источники света и зеркала, наблюдайте за лучами и спектром.</div>
    <div class="hdr-side" style="margin-top:18px;display:flex;gap:8px;flex-wrap:wrap;position:relative;z-index:1">
      <a href="/textbook/physics-8" class="hdr-btn"><svg class="ic" viewBox="0 0 24 24"><polyline points="15 18 9 12 15 6"/></svg> К физике 8</a>
      <button id="search-btn" class="hdr-btn"><svg class="ic" viewBox="0 0 24 24"><circle cx="11" cy="11" r="7"/><path d="m21 21-4-4"/></svg> Поиск</button>
      <button id="sidebar-btn" class="hdr-btn"><svg class="ic" viewBox="0 0 24 24"><line x1="4" y1="6" x2="20" y2="6"/><line x1="4" y1="12" x2="20" y2="12"/><line x1="4" y1="18" x2="14" y2="18"/></svg> Шпаргалка</button>
      <button id="theme-btn" class="hdr-btn"><svg class="ic" viewBox="0 0 24 24"><path d="M21 12.8A9 9 0 1 1 11.2 3a7 7 0 0 0 9.8 9.8z"/></svg><span id="theme-lab">Тёмная</span></button>
    </div>
  </div>
</header>`;

const oldHdrRegex = /<header class="hdr">[\s\S]*?<\/header>/;
if (h.match(oldHdrRegex)) {
  h = h.replace(oldHdrRegex, NEW_HERO);
  console.log('Hero replaced');
}

// === 2. Live meter (wavelength cycles through visible spectrum) ===
const METER_SCRIPT = `
<script>
/* P8 hero meter — анимация длины волны (Phase 3 spectrum) */
(function(){
  function init(){
    const el = document.getElementById('p8-meter-val');
    if (!el || !window.P8Anim) return;
    const targets = [{ l: 400, c:'#7c3aed' }, { l: 470, c:'#2563eb' }, { l: 550, c:'#16a34a' }, { l: 600, c:'#f59e0b' }, { l: 700, c:'#dc2626' }];
    let i = 0;
    function step(){
      const from = parseFloat((el.textContent || '550').replace(/\\D/g,'')) || 550;
      const target = targets[i % targets.length];
      P8Anim.tween({
        from, to: target.l, duration: 1100, easing: 'cubicInOut',
        onUpdate: v => { el.textContent = 'λ=' + Math.round(v); el.style.color = target.c; },
        onComplete: () => { i++; setTimeout(step, 1400); }
      });
    }
    setTimeout(step, 1200);
  }
  if (document.readyState === 'loading') document.addEventListener('DOMContentLoaded', init);
  else init();
})();
</script>
`;
if (!h.includes('P8 hero meter')) {
  h = h.replace('</body>', METER_SCRIPT + '\n</body>');
  console.log('Meter added');
}

// === 3. Section watermarks ===
const SEC_SYMBOLS = {
  p32: '<svg viewBox="0 0 100 100"><circle cx="50" cy="50" r="14" fill="currentColor"/><g stroke="currentColor" stroke-width="4" stroke-linecap="round"><line x1="50" y1="14" x2="50" y2="26"/><line x1="50" y1="74" x2="50" y2="86"/><line x1="14" y1="50" x2="26" y2="50"/><line x1="74" y1="50" x2="86" y2="50"/></g></svg>',
  p33: '<svg viewBox="0 0 100 100"><circle cx="32" cy="40" r="10" fill="currentColor"/><rect x="50" y="34" width="14" height="40" fill="currentColor"/><polygon points="68,40 92,30 92,80 68,70" fill="currentColor" opacity="0.5"/></svg>',
  p34: '<svg viewBox="0 0 100 100"><line x1="20" y1="20" x2="50" y2="50" stroke="currentColor" stroke-width="5"/><line x1="50" y1="50" x2="80" y2="20" stroke="currentColor" stroke-width="5"/><line x1="50" y1="50" x2="50" y2="90" stroke="currentColor" stroke-width="2" stroke-dasharray="4 4"/></svg>',
  p35: '<svg viewBox="0 0 100 100"><line x1="30" y1="20" x2="30" y2="80" stroke="currentColor" stroke-width="4"/><g stroke="currentColor" stroke-width="1.5"><line x1="30" y1="30" x2="22" y2="34"/><line x1="30" y1="45" x2="22" y2="49"/><line x1="30" y1="60" x2="22" y2="64"/><line x1="30" y1="75" x2="22" y2="79"/></g><circle cx="60" cy="50" r="6" fill="currentColor"/></svg>',
  p36: '<svg viewBox="0 0 100 100"><path d="M30 20 Q 20 50, 30 80" stroke="currentColor" stroke-width="5" fill="none"/><line x1="48" y1="50" x2="70" y2="50" stroke="currentColor" stroke-width="2" stroke-dasharray="3 3"/><circle cx="70" cy="50" r="3" fill="currentColor"/></svg>',
  p37: '<svg viewBox="0 0 100 100"><line x1="20" y1="20" x2="50" y2="50" stroke="currentColor" stroke-width="5"/><line x1="50" y1="50" x2="80" y2="80" stroke="currentColor" stroke-width="5" stroke-dasharray="0"/><line x1="0" y1="50" x2="100" y2="50" stroke="currentColor" stroke-width="2"/></svg>',
  p38: '<svg viewBox="0 0 100 100"><ellipse cx="50" cy="50" rx="10" ry="36" fill="currentColor" opacity="0.4"/><line x1="0" y1="50" x2="100" y2="50" stroke="currentColor" stroke-width="2"/><circle cx="25" cy="50" r="2" fill="currentColor"/><circle cx="75" cy="50" r="2" fill="currentColor"/></svg>',
  p39: '<svg viewBox="0 0 100 100"><polygon points="40,20 80,50 40,80" stroke="currentColor" stroke-width="4" fill="none"/><line x1="20" y1="50" x2="40" y2="50" stroke="currentColor" stroke-width="3"/><g stroke-width="2.5" fill="none"><line x1="60" y1="40" x2="90" y2="30" stroke="#dc2626"/><line x1="60" y1="50" x2="90" y2="50" stroke="#16a34a"/><line x1="60" y1="60" x2="90" y2="70" stroke="#2563eb"/></g></svg>',
  p40: '<svg viewBox="0 0 100 100"><ellipse cx="50" cy="50" rx="36" ry="22" fill="none" stroke="currentColor" stroke-width="3"/><circle cx="50" cy="50" r="12" fill="currentColor"/><circle cx="50" cy="50" r="5" fill="#fff"/></svg>'
};

let secWmInjected = 0;
for (const pid of Object.keys(SEC_SYMBOLS)) {
  const symbol = SEC_SYMBOLS[pid];
  const secOpenRegex = new RegExp(`(<section[^>]+id="sec-${pid}"[^>]*>)`);
  if (h.match(secOpenRegex) && !h.includes(`p8-sec-wm-${pid}`)) {
    const wmDiv = `<div class="p8-sec-wm" id="p8-sec-wm-${pid}" aria-hidden="true">${symbol}</div>`;
    h = h.replace(secOpenRegex, '$1\n  ' + wmDiv);
    secWmInjected++;
  }
}
console.log('Section watermarks:', secWmInjected);

// === 4. Stub function ===
function makeStubText(n) {
  return `/* IV6 — flagship интерактив (заглушка Phase 3, наполнение в Phase 3.${n}) */
  h += '<div class="wg p8-iv6">'
    +'<div class="wg-header"><span class="wg-badge p8-badge p8-badge-spectrum">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 3.${n} — coming soon</div>'
    +'</div>'
    +'</div>';`;
}

function replaceWithReal(pid, n, widgetHtml, initFn) {
  // Two paths: stub already present (need to replace) OR no stub (just inject before box.innerHTML).
  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;
  const eol = (h.indexOf('\r\n') >= 0) ? '\r\n' : '\n';
  const widget = widgetHtml.trim().replace(/\n/g, eol);
  if (stubText) {
    h = h.replace(stubText, widget);
  } else {
    // Inject before box.innerHTML
    const marker = `box.innerHTML = h + secNavFor('${pid}') + readButton('${pid}');`;
    if (!h.includes(marker)) { console.warn(`${pid}: no marker`); return false; }
    h = h.replace(marker, widget + eol + eol + '  ' + marker);
  }
  // Add init call
  h = h.replace(`wireReadBtn('${pid}');`, `wireReadBtn('${pid}');${eol}  _init${pid.toUpperCase()}_iv6();`);
  // Append init function after build_pN
  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}: injected real IV-6`);
  return true;
}

// === Compact widget builder ===
function widget(pid, n, title, help, height, body, init) {
  const html = `/* IV6 — ${title} (Phase 3) */
  h += '<div class="wg p8-iv6">'
    +'<div class="wg-header"><span class="wg-badge p8-badge p8-badge-spectrum">IV-6</span><div class="wg-title">${title}</div></div>'
    +'<div class="wg-help">${help}</div>'
    +'<div class="p8-sandbox" id="${pid}-iv6-sandbox" style="height:${height}px"></div>'
    ${body}
    +'</div>';`;
  const initFn = `
function _init${pid.toUpperCase()}_iv6(){
  const sb = document.getElementById('${pid}-iv6-sandbox');
  if (!sb || !window.P8Helpers) return;
  ${init}
}
`;
  replaceWithReal(pid, n, html, initFn);
}

// ============================================================
// §32 — Источники света (типы)
// ============================================================
widget('p32', 32, 'Точечные и протяжённые источники',
  'Точечный источник (свеча издалека) даёт чёткие тени. Протяжённый (Солнце) — размытые с полутенью.',
  240,
  '+\'<div style="margin-top:10px;display:flex;gap:10px;flex-wrap:wrap"><button class="btn primary" id="p32-iv6-point">Точечный</button><button class="btn" id="p32-iv6-ext">Протяжённый</button></div>\'',
  `
  const svg = P8Helpers.svg.create(560, 240);
  svg.setAttribute('width','100%'); svg.setAttribute('height','100%'); svg.style.display='block';
  sb.appendChild(svg);
  let mode = 'point';
  function render(){
    svg.innerHTML = '';
    /* Object */
    svg.appendChild(P8Helpers.svg.el('rect', { x: 230, y: 90, width: 30, height: 60, fill: '#475569' }));
    if (mode === 'point') {
      svg.appendChild(P8Helpers.svg.el('circle', { cx: 80, cy: 120, r: 12, fill: '#facc15', stroke: '#0f172a', 'stroke-width': 2 }));
      svg.appendChild(P8Helpers.svg.el('line', { x1: 92, y1: 110, x2: 230, y2: 90, stroke: '#facc15', 'stroke-width': 2, 'stroke-dasharray': '3 3' }));
      svg.appendChild(P8Helpers.svg.el('line', { x1: 92, y1: 130, x2: 230, y2: 150, stroke: '#facc15', 'stroke-width': 2, 'stroke-dasharray': '3 3' }));
      /* Sharp shadow */
      svg.appendChild(P8Helpers.svg.el('polygon', { points: '260,90 460,30 460,210 260,150', fill: '#0f172a', opacity: 0.7 }));
      svg.appendChild(P8Helpers.svg.el('text', { x: 380, y: 220, 'font-family':"'Inter',sans-serif", 'font-size':12, 'font-weight':700, fill: '#fff', 'text-anchor':'middle', text: 'Чёткая тень' }));
    } else {
      /* Sun */
      svg.appendChild(P8Helpers.svg.el('circle', { cx: 80, cy: 120, r: 30, fill: '#facc15', stroke: '#0f172a', 'stroke-width': 2 }));
      svg.appendChild(P8Helpers.svg.el('line', { x1: 110, y1: 90, x2: 230, y2: 90, stroke: '#facc15', 'stroke-width': 1.5, 'stroke-dasharray': '3 3' }));
      svg.appendChild(P8Helpers.svg.el('line', { x1: 110, y1: 150, x2: 230, y2: 150, stroke: '#facc15', 'stroke-width': 1.5, 'stroke-dasharray': '3 3' }));
      /* Sharp inner shadow (umbra) */
      svg.appendChild(P8Helpers.svg.el('polygon', { points: '260,100 380,80 380,160 260,140', fill: '#0f172a', opacity: 0.7 }));
      /* Penumbra */
      svg.appendChild(P8Helpers.svg.el('polygon', { points: '260,90 460,30 380,80 260,100', fill: '#0f172a', opacity: 0.3 }));
      svg.appendChild(P8Helpers.svg.el('polygon', { points: '260,150 380,160 460,210 260,150', fill: '#0f172a', opacity: 0.3 }));
      svg.appendChild(P8Helpers.svg.el('text', { x: 320, y: 220, 'font-family':"'Inter',sans-serif", 'font-size':12, 'font-weight':700, fill: '#0f172a', 'text-anchor':'middle', text: 'Тень + полутень' }));
    }
  }
  document.getElementById('p32-iv6-point').onclick = () => { mode = 'point'; render(); };
  document.getElementById('p32-iv6-ext').onclick = () => { mode = 'ext'; render(); };
  render();
  `);

// ============================================================
// §33 — Тени (расстояние источник-объект)
// ============================================================
widget('p33', 33, 'Тень и её размер',
  'Двигай источник света — наблюдай, как меняется размер тени. Чем ближе источник — тем больше тень.',
  240,
  '+\'<div class="p8-scrubber" style="margin-top:10px"><span class="p8-scrubber-label">Источник X</span><input type="range" id="p33-iv6-x" min="20" max="200" step="2" value="80"><span class="p8-scrubber-value"><span id="p33-iv6-x-val">80</span><span class="p8-unit">px</span></span></div>\'',
  `
  const svg = P8Helpers.svg.create(560, 240);
  svg.setAttribute('width','100%'); svg.setAttribute('height','100%'); svg.style.display='block';
  sb.appendChild(svg);
  let lampX = 80;
  function render(){
    svg.innerHTML = '';
    /* Light */
    svg.appendChild(P8Helpers.svg.el('circle', { cx: lampX, cy: 120, r: 12, fill: '#facc15', stroke: '#0f172a', 'stroke-width': 2 }));
    /* Object */
    const objX = 300;
    svg.appendChild(P8Helpers.svg.el('rect', { x: objX - 12, y: 90, width: 24, height: 60, fill: '#475569' }));
    /* Rays + shadow */
    const wallX = 510;
    const t = (wallX - lampX) / (objX - lampX);
    const yTop = 120 + (90 - 120) * t;
    const yBot = 120 + (150 - 120) * t;
    svg.appendChild(P8Helpers.svg.el('line', { x1: lampX + 12, y1: 110, x2: wallX, y2: yTop, stroke: '#facc15', 'stroke-width': 1.5, 'stroke-dasharray': '3 3' }));
    svg.appendChild(P8Helpers.svg.el('line', { x1: lampX + 12, y1: 130, x2: wallX, y2: yBot, stroke: '#facc15', 'stroke-width': 1.5, 'stroke-dasharray': '3 3' }));
    /* Wall */
    svg.appendChild(P8Helpers.svg.el('line', { x1: wallX, y1: 20, x2: wallX, y2: 220, stroke: '#0f172a', 'stroke-width': 3 }));
    /* Shadow on wall */
    svg.appendChild(P8Helpers.svg.el('rect', { x: wallX, y: yTop, width: 28, height: yBot - yTop, fill: '#0f172a', opacity: 0.7 }));
    svg.appendChild(P8Helpers.svg.el('text', { x: wallX + 14, y: yTop - 5, 'font-family':"'JetBrains Mono',monospace", 'font-size':10, 'font-weight':700, fill:'#0f172a', 'text-anchor':'middle', text: 'h='+(yBot-yTop).toFixed(0) }));
  }
  document.getElementById('p33-iv6-x').oninput = ev => { lampX = +ev.target.value; document.getElementById('p33-iv6-x-val').textContent = lampX; render(); };
  render();
  `);

// ============================================================
// §34 — Отражение (угол падения = угол отражения)
// ============================================================
widget('p34', 34, 'Закон отражения',
  'Двигай угол падения — угол отражения равен ему.',
  240,
  '+\'<div class="p8-scrubber" style="margin-top:10px"><span class="p8-scrubber-label">Угол α</span><input type="range" id="p34-iv6-a" min="0" max="80" step="1" value="35"><span class="p8-scrubber-value"><span id="p34-iv6-a-val">35</span><span class="p8-unit">°</span></span></div>\'',
  `
  const svg = P8Helpers.svg.create(560, 240);
  svg.setAttribute('width','100%'); svg.setAttribute('height','100%'); svg.style.display='block';
  sb.appendChild(svg);
  let alpha = 35;
  function render(){
    svg.innerHTML = '';
    const cx = 280, cy = 200;
    /* Mirror */
    svg.appendChild(P8Helpers.optics.mirrorPlane(80, 200, 480, 200));
    /* Normal */
    svg.appendChild(P8Helpers.svg.el('line', { x1: cx, y1: 200, x2: cx, y2: 30, stroke: '#475569', 'stroke-width': 1.5, 'stroke-dasharray': '5 3' }));
    svg.appendChild(P8Helpers.svg.el('text', { x: cx + 8, y: 40, 'font-family':"'Inter',sans-serif", 'font-size':11, 'font-weight':700, fill:'#475569', text: 'нормаль' }));
    /* Incident ray */
    const rad = alpha * Math.PI / 180;
    const len = 150;
    const inX = cx - len * Math.sin(rad);
    const inY = cy - len * Math.cos(rad);
    svg.appendChild(P8Helpers.optics.rayLine(inX, inY, cx, cy, { color: '#facc15', width: 3, glow: true }));
    /* Reflected ray */
    const rX = cx + len * Math.sin(rad);
    const rY = cy - len * Math.cos(rad);
    svg.appendChild(P8Helpers.optics.rayLine(cx, cy, rX, rY, { color: '#facc15', width: 3, glow: true }));
    /* Angle labels */
    svg.appendChild(P8Helpers.svg.el('text', { x: cx - 25, y: 130, 'font-family':"'JetBrains Mono',monospace", 'font-size':13, 'font-weight':800, fill:'#dc2626', text: 'α='+alpha+'°' }));
    svg.appendChild(P8Helpers.svg.el('text', { x: cx + 8, y: 130, 'font-family':"'JetBrains Mono',monospace", 'font-size':13, 'font-weight':800, fill:'#16a34a', text: 'β='+alpha+'°' }));
  }
  document.getElementById('p34-iv6-a').oninput = ev => { alpha = +ev.target.value; document.getElementById('p34-iv6-a-val').textContent = alpha; render(); };
  render();
  `);

// ============================================================
// §35 — Плоское зеркало (объект → мнимое изображение)
// ============================================================
widget('p35', 35, 'Плоское зеркало',
  'Двигай объект — мнимое изображение появляется за зеркалом на том же расстоянии.',
  240,
  '+\'<div class="p8-scrubber" style="margin-top:10px"><span class="p8-scrubber-label">Дистанция d</span><input type="range" id="p35-iv6-d" min="50" max="200" step="2" value="100"><span class="p8-scrubber-value"><span id="p35-iv6-d-val">100</span><span class="p8-unit">px</span></span></div>\'',
  `
  const svg = P8Helpers.svg.create(560, 240);
  svg.setAttribute('width','100%'); svg.setAttribute('height','100%'); svg.style.display='block';
  sb.appendChild(svg);
  let d = 100;
  function render(){
    svg.innerHTML = '';
    const mirX = 280;
    /* Mirror */
    svg.appendChild(P8Helpers.svg.el('line', { x1: mirX, y1: 40, x2: mirX, y2: 200, stroke: '#0f172a', 'stroke-width': 4 }));
    /* Hatch */
    for (let i = 0; i < 12; i++) {
      svg.appendChild(P8Helpers.svg.el('line', { x1: mirX, y1: 45 + i * 14, x2: mirX + 8, y2: 49 + i * 14, stroke: '#475569', 'stroke-width': 1.5 }));
    }
    /* Object (arrow) */
    const objX = mirX - d;
    svg.appendChild(P8Helpers.svg.el('line', { x1: objX, y1: 180, x2: objX, y2: 90, stroke: '#dc2626', 'stroke-width': 3 }));
    svg.appendChild(P8Helpers.svg.el('polygon', { points: objX+',85 '+(objX-6)+',95 '+(objX+6)+',95', fill: '#dc2626' }));
    svg.appendChild(P8Helpers.svg.el('text', { x: objX, y: 220, 'font-family':"'Inter',sans-serif", 'font-size':11, 'font-weight':700, fill:'#dc2626', 'text-anchor':'middle', text: 'объект' }));
    /* Virtual image */
    const imgX = mirX + d;
    svg.appendChild(P8Helpers.svg.el('line', { x1: imgX, y1: 180, x2: imgX, y2: 90, stroke: '#94a3b8', 'stroke-width': 3, 'stroke-dasharray': '4 3' }));
    svg.appendChild(P8Helpers.svg.el('polygon', { points: imgX+',85 '+(imgX-6)+',95 '+(imgX+6)+',95', fill: '#94a3b8' }));
    svg.appendChild(P8Helpers.svg.el('text', { x: imgX, y: 220, 'font-family':"'Inter',sans-serif", 'font-size':11, 'font-weight':700, fill:'#94a3b8', 'text-anchor':'middle', text: 'мнимое изображение' }));
    /* Distance arrows */
    svg.appendChild(P8Helpers.svg.el('line', { x1: objX, y1: 60, x2: mirX, y2: 60, stroke: '#475569', 'stroke-width': 1.5 }));
    svg.appendChild(P8Helpers.svg.el('line', { x1: mirX, y1: 60, x2: imgX, y2: 60, stroke: '#475569', 'stroke-width': 1.5 }));
    svg.appendChild(P8Helpers.svg.el('text', { x: (objX + mirX) / 2, y: 52, 'font-family':"'JetBrains Mono',monospace", 'font-size':11, 'font-weight':700, fill:'#475569', 'text-anchor':'middle', text: 'd='+d }));
    svg.appendChild(P8Helpers.svg.el('text', { x: (mirX + imgX) / 2, y: 52, 'font-family':"'JetBrains Mono',monospace", 'font-size':11, 'font-weight':700, fill:'#475569', 'text-anchor':'middle', text: 'd='+d }));
  }
  document.getElementById('p35-iv6-d').oninput = ev => { d = +ev.target.value; document.getElementById('p35-iv6-d-val').textContent = d; render(); };
  render();
  `);

// ============================================================
// §36 — Сферическое зеркало (фокус)
// ============================================================
widget('p36', 36, 'Сферическое зеркало',
  'Двигай расстояние объекта до фокуса — изображение меняет тип (увеличенное/уменьшенное, прямое/перевёрнутое).',
  240,
  '+\'<div class="p8-scrubber" style="margin-top:10px"><span class="p8-scrubber-label">Объект → зеркало</span><input type="range" id="p36-iv6-d" min="50" max="250" step="2" value="180"><span class="p8-scrubber-value"><span id="p36-iv6-d-val">180</span><span class="p8-unit">мм</span></span></div>\'',
  `
  const svg = P8Helpers.svg.create(560, 240);
  svg.setAttribute('width','100%'); svg.setAttribute('height','100%'); svg.style.display='block';
  sb.appendChild(svg);
  const F = 100, mirX = 480;
  let d = 180;
  function render(){
    svg.innerHTML = '';
    /* Mirror curve */
    svg.appendChild(P8Helpers.svg.el('path', { d: 'M '+mirX+' 60 Q '+(mirX-30)+' 120, '+mirX+' 180', stroke: '#0f172a', 'stroke-width': 4, fill: 'none' }));
    /* Axis */
    svg.appendChild(P8Helpers.svg.el('line', { x1: 50, y1: 120, x2: mirX, y2: 120, stroke: '#94a3b8', 'stroke-width': 1, 'stroke-dasharray': '4 3' }));
    /* Focus */
    svg.appendChild(P8Helpers.svg.el('circle', { cx: mirX - F, cy: 120, r: 3, fill: '#16a34a' }));
    svg.appendChild(P8Helpers.svg.el('text', { x: mirX - F, y: 138, 'font-family':"'JetBrains Mono',monospace", 'font-size':10, 'font-weight':700, fill:'#16a34a', 'text-anchor':'middle', text: 'F' }));
    /* Object */
    const objX = mirX - d;
    svg.appendChild(P8Helpers.svg.el('line', { x1: objX, y1: 120, x2: objX, y2: 80, stroke: '#dc2626', 'stroke-width': 2.5 }));
    svg.appendChild(P8Helpers.svg.el('polygon', { points: objX+',75 '+(objX-4)+',82 '+(objX+4)+',82', fill: '#dc2626' }));
    /* Lens formula: 1/v - 1/d = 1/F, here mirror equation: 1/v + 1/d = 1/F (using d positive in front) */
    const v = 1 / (1 / F - 1 / d);
    const imgX = mirX - v;
    const h_img = 40 * v / d * -1;
    svg.appendChild(P8Helpers.svg.el('line', { x1: imgX, y1: 120, x2: imgX, y2: 120 + h_img, stroke: '#2563eb', 'stroke-width': 2.5, 'stroke-dasharray': v < 0 ? '4 3' : '0' }));
    svg.appendChild(P8Helpers.svg.el('polygon', { points: imgX+','+(120 + h_img - Math.sign(h_img) * 4)+' '+(imgX-4)+','+(120 + h_img + 1)+' '+(imgX+4)+','+(120 + h_img + 1), fill: '#2563eb' }));
    svg.appendChild(P8Helpers.svg.el('text', { x: 280, y: 220, 'font-family':"'JetBrains Mono',monospace", 'font-size':11, 'font-weight':700, fill:'#0f172a', 'text-anchor':'middle', text: 'd='+d+' мм, F='+F+', v='+v.toFixed(0)+' мм' }));
  }
  document.getElementById('p36-iv6-d').oninput = ev => { d = +ev.target.value; document.getElementById('p36-iv6-d-val').textContent = d; render(); };
  render();
  `);

// ============================================================
// §37 — Преломление (углы)
// ============================================================
widget('p37', 37, 'Преломление света',
  'Двигай угол падения. На границе двух сред (воздух/вода n=1.33) угол преломления меньше.',
  240,
  '+\'<div class="p8-scrubber" style="margin-top:10px"><span class="p8-scrubber-label">Угол α</span><input type="range" id="p37-iv6-a" min="0" max="80" step="1" value="40"><span class="p8-scrubber-value"><span id="p37-iv6-a-val">40</span><span class="p8-unit">°</span></span></div>\'',
  `
  const svg = P8Helpers.svg.create(560, 240);
  svg.setAttribute('width','100%'); svg.setAttribute('height','100%'); svg.style.display='block';
  sb.appendChild(svg);
  let alpha = 40;
  const n1 = 1, n2 = 1.33;
  function render(){
    svg.innerHTML = '';
    const cx = 280, cy = 120;
    /* Water region */
    svg.appendChild(P8Helpers.svg.el('rect', { x: 0, y: 120, width: 560, height: 120, fill: '#7dd3fc', opacity: 0.35 }));
    /* Interface */
    svg.appendChild(P8Helpers.svg.el('line', { x1: 30, y1: cy, x2: 530, y2: cy, stroke: '#0f172a', 'stroke-width': 2 }));
    /* Normal */
    svg.appendChild(P8Helpers.svg.el('line', { x1: cx, y1: 20, x2: cx, y2: 220, stroke: '#475569', 'stroke-width': 1.5, 'stroke-dasharray': '4 3' }));
    /* Incident */
    const aRad = alpha * Math.PI / 180;
    const len = 120;
    const inX = cx - len * Math.sin(aRad);
    const inY = cy - len * Math.cos(aRad);
    svg.appendChild(P8Helpers.optics.rayLine(inX, inY, cx, cy, { color: '#facc15', width: 3, glow: true }));
    /* Snell: n1 sin α = n2 sin β */
    const beta = Math.asin(Math.min(1, n1 / n2 * Math.sin(aRad)));
    const rX = cx + len * Math.sin(beta);
    const rY = cy + len * Math.cos(beta);
    svg.appendChild(P8Helpers.optics.rayLine(cx, cy, rX, rY, { color: '#facc15', width: 3, glow: true }));
    /* Labels */
    svg.appendChild(P8Helpers.svg.el('text', { x: cx - 25, y: 70, 'font-family':"'JetBrains Mono',monospace", 'font-size':13, 'font-weight':800, fill:'#dc2626', text: 'α='+alpha+'°' }));
    svg.appendChild(P8Helpers.svg.el('text', { x: cx + 8, y: 175, 'font-family':"'JetBrains Mono',monospace", 'font-size':13, 'font-weight':800, fill:'#16a34a', text: 'β='+(beta * 180 / Math.PI).toFixed(1)+'°' }));
    svg.appendChild(P8Helpers.svg.el('text', { x: 50, y: 50, 'font-family':"'Inter',sans-serif", 'font-size':11, 'font-weight':700, fill:'#475569', text: 'n₁=1 (воздух)' }));
    svg.appendChild(P8Helpers.svg.el('text', { x: 50, y: 215, 'font-family':"'Inter',sans-serif", 'font-size':11, 'font-weight':700, fill:'#0f172a', text: 'n₂=1.33 (вода)' }));
  }
  document.getElementById('p37-iv6-a').oninput = ev => { alpha = +ev.target.value; document.getElementById('p37-iv6-a-val').textContent = alpha; render(); };
  render();
  `);

// ============================================================
// §38 — Линзы (3 главных луча)
// ============================================================
widget('p38', 38, 'Собирающая линза — построение изображения',
  'Двигай объект. Три главных луча: через центр (прямо), параллельно главной оси (через F), через F (параллельно оси).',
  280,
  '+\'<div class="p8-scrubber" style="margin-top:10px"><span class="p8-scrubber-label">Дистанция d</span><input type="range" id="p38-iv6-d" min="50" max="280" step="2" value="180"><span class="p8-scrubber-value"><span id="p38-iv6-d-val">180</span><span class="p8-unit">мм</span></span></div>\'',
  `
  const svg = P8Helpers.svg.create(560, 280);
  svg.setAttribute('width','100%'); svg.setAttribute('height','100%'); svg.style.display='block';
  sb.appendChild(svg);
  let d = 180;
  const F = 100, lensX = 320, axisY = 150;
  function render(){
    svg.innerHTML = '';
    /* Axis */
    svg.appendChild(P8Helpers.svg.el('line', { x1: 30, y1: axisY, x2: 530, y2: axisY, stroke: '#94a3b8', 'stroke-width': 1, 'stroke-dasharray': '4 3' }));
    /* Lens */
    svg.appendChild(P8Helpers.optics.lensSVG(lensX, axisY, 140, 'converging'));
    /* F marks */
    svg.appendChild(P8Helpers.svg.el('circle', { cx: lensX - F, cy: axisY, r: 3, fill: '#16a34a' }));
    svg.appendChild(P8Helpers.svg.el('circle', { cx: lensX + F, cy: axisY, r: 3, fill: '#16a34a' }));
    svg.appendChild(P8Helpers.svg.el('text', { x: lensX - F - 8, y: axisY + 18, 'font-family':"'JetBrains Mono',monospace", 'font-size':10, 'font-weight':700, fill:'#16a34a', text: 'F' }));
    svg.appendChild(P8Helpers.svg.el('text', { x: lensX + F + 8, y: axisY + 18, 'font-family':"'JetBrains Mono',monospace", 'font-size':10, 'font-weight':700, fill:'#16a34a', text: 'F' }));
    /* Object */
    const objX = lensX - d;
    const objH = 50;
    svg.appendChild(P8Helpers.svg.el('line', { x1: objX, y1: axisY, x2: objX, y2: axisY - objH, stroke: '#dc2626', 'stroke-width': 3 }));
    svg.appendChild(P8Helpers.svg.el('polygon', { points: objX+','+(axisY-objH-6)+' '+(objX-5)+','+(axisY-objH+2)+' '+(objX+5)+','+(axisY-objH+2), fill: '#dc2626' }));
    /* Thin lens equation: 1/v - 1/(-d) = 1/F → v = dF/(d-F) (object on left, d>0) */
    const v = (d * F) / (d - F);
    const imgX = lensX + v;
    const imgH = objH * v / d;
    /* Three principal rays */
    /* Ray 1: parallel to axis, refracts through far F */
    svg.appendChild(P8Helpers.optics.rayLine(objX, axisY - objH, lensX, axisY - objH, { color: '#facc15', width: 1.5, arrow: false }));
    svg.appendChild(P8Helpers.optics.rayLine(lensX, axisY - objH, imgX, axisY + imgH, { color: '#facc15', width: 1.5, arrow: false }));
    /* Ray 2: through optic center */
    svg.appendChild(P8Helpers.optics.rayLine(objX, axisY - objH, imgX, axisY + imgH, { color: '#16a34a', width: 1.5, arrow: false }));
    /* Ray 3: through near F, refracts parallel */
    svg.appendChild(P8Helpers.optics.rayLine(objX, axisY - objH, lensX, axisY + ((lensX - objX) / (lensX - F - objX)) * (-objH) - (-objH) * ((lensX - F - objX) / (lensX - F - objX) - 1), { color: '#2563eb', width: 1.5, arrow: false }));
    /* Image */
    svg.appendChild(P8Helpers.svg.el('line', { x1: imgX, y1: axisY, x2: imgX, y2: axisY + imgH, stroke: '#2563eb', 'stroke-width': 3 }));
    svg.appendChild(P8Helpers.svg.el('polygon', { points: imgX+','+(axisY+imgH+6)+' '+(imgX-5)+','+(axisY+imgH-2)+' '+(imgX+5)+','+(axisY+imgH-2), fill: '#2563eb' }));
    svg.appendChild(P8Helpers.svg.el('text', { x: 280, y: 260, 'font-family':"'JetBrains Mono',monospace", 'font-size':11, 'font-weight':700, fill:'#0f172a', 'text-anchor':'middle', text: 'd='+d+' мм, F='+F+', v='+v.toFixed(0)+' мм' }));
  }
  document.getElementById('p38-iv6-d').oninput = ev => { d = +ev.target.value; document.getElementById('p38-iv6-d-val').textContent = d; render(); };
  render();
  `);

// ============================================================
// §39 — Дисперсия (призма + спектр)
// ============================================================
widget('p39', 39, 'Дисперсия — разложение белого света',
  'Через призму белый свет разлагается на спектр. Угол отклонения зависит от длины волны: красный отклоняется меньше, фиолетовый больше.',
  240,
  '',
  `
  const svg = P8Helpers.svg.create(560, 240);
  svg.setAttribute('width','100%'); svg.setAttribute('height','100%'); svg.style.display='block';
  sb.appendChild(svg);
  function render(){
    svg.innerHTML = '';
    /* Incident white */
    svg.appendChild(P8Helpers.svg.el('line', { x1: 30, y1: 120, x2: 200, y2: 120, stroke: '#fff', 'stroke-width': 5 }));
    svg.appendChild(P8Helpers.svg.el('line', { x1: 30, y1: 120, x2: 200, y2: 120, stroke: '#facc15', 'stroke-width': 2 }));
    svg.appendChild(P8Helpers.svg.el('text', { x: 30, y: 105, 'font-family':"'Inter',sans-serif", 'font-size':11, 'font-weight':700, fill:'#0f172a', text: 'Белый свет' }));
    /* Prism */
    svg.appendChild(P8Helpers.svg.el('polygon', { points: '200,180 280,40 360,180', fill: 'rgba(125,211,252,.35)', stroke: '#0284c7', 'stroke-width': 2 }));
    /* Spectrum out */
    const colors = [
      { c: '#dc2626', off: 0, label: 'красный' },
      { c: '#f97316', off: 8, label: 'оранжевый' },
      { c: '#facc15', off: 16, label: 'жёлтый' },
      { c: '#16a34a', off: 24, label: 'зелёный' },
      { c: '#0ea5e9', off: 32, label: 'голубой' },
      { c: '#2563eb', off: 40, label: 'синий' },
      { c: '#7c3aed', off: 48, label: 'фиолетовый' }
    ];
    colors.forEach((cl, i) => {
      svg.appendChild(P8Helpers.svg.el('line', { x1: 290, y1: 120, x2: 530, y2: 100 + cl.off, stroke: cl.c, 'stroke-width': 2.5, 'stroke-linecap': 'round' }));
      svg.appendChild(P8Helpers.svg.el('text', { x: 535, y: 104 + cl.off, 'font-family':"'Inter',sans-serif", 'font-size':9, 'font-weight':700, fill: cl.c, text: cl.label }));
    });
  }
  render();
  `);

// ============================================================
// §40 — Глаз / коррекция (близорукость / дальнозоркость)
// ============================================================
widget('p40', 40, 'Глаз: аккомодация и очки',
  'Нормальный глаз: лучи фокусируются на сетчатке. Близорукий — перед сетчаткой (нужна рассеивающая). Дальнозоркий — за сетчаткой (нужна собирающая).',
  240,
  '+\'<div style="margin-top:10px;display:flex;gap:10px;flex-wrap:wrap"><button class="btn primary" id="p40-iv6-normal">Норма</button><button class="btn" id="p40-iv6-myop">Близорукость</button><button class="btn" id="p40-iv6-hyper">Дальнозоркость</button></div>\'',
  `
  const svg = P8Helpers.svg.create(560, 240);
  svg.setAttribute('width','100%'); svg.setAttribute('height','100%'); svg.style.display='block';
  sb.appendChild(svg);
  let mode = 'normal';
  function render(){
    svg.innerHTML = '';
    /* Eye outline */
    svg.appendChild(P8Helpers.svg.el('ellipse', { cx: 380, cy: 120, rx: 90, ry: 70, fill: '#fff', stroke: '#0f172a', 'stroke-width': 2 }));
    /* Cornea */
    svg.appendChild(P8Helpers.svg.el('path', { d: 'M 290 105 Q 270 120, 290 135', fill: '#bae6fd', stroke: '#0284c7', 'stroke-width': 2 }));
    /* Lens */
    svg.appendChild(P8Helpers.svg.el('ellipse', { cx: 310, cy: 120, rx: 8, ry: 26, fill: 'rgba(125,211,252,.55)', stroke: '#0284c7', 'stroke-width': 1.5 }));
    /* Retina */
    svg.appendChild(P8Helpers.svg.el('path', { d: 'M 440 80 Q 470 120, 440 160', stroke: '#dc2626', 'stroke-width': 3, fill: 'none' }));
    svg.appendChild(P8Helpers.svg.el('text', { x: 465, y: 85, 'font-family':"'Inter',sans-serif", 'font-size':10, 'font-weight':700, fill:'#dc2626', text: 'сетчатка' }));
    /* Rays */
    const focusX = mode === 'normal' ? 440 : (mode === 'myop' ? 420 : 480);
    const colorFocus = mode === 'normal' ? '#16a34a' : '#dc2626';
    /* 3 incoming rays */
    [80, 120, 160].forEach(y => {
      svg.appendChild(P8Helpers.svg.el('line', { x1: 50, y1: y, x2: 305, y2: y, stroke: '#facc15', 'stroke-width': 2 }));
      svg.appendChild(P8Helpers.svg.el('line', { x1: 315, y1: y, x2: focusX, y2: 120, stroke: '#facc15', 'stroke-width': 2 }));
    });
    /* Focus point */
    svg.appendChild(P8Helpers.svg.el('circle', { cx: focusX, cy: 120, r: 5, fill: colorFocus }));
    /* Correction lens if needed */
    if (mode === 'myop') {
      svg.appendChild(P8Helpers.optics.lensSVG(180, 120, 70, 'diverging'));
      svg.appendChild(P8Helpers.svg.el('text', { x: 180, y: 200, 'font-family':"'Inter',sans-serif", 'font-size':10, 'font-weight':700, fill:'#2563eb', 'text-anchor':'middle', text: '−дптр (рассеивающая)' }));
    } else if (mode === 'hyper') {
      svg.appendChild(P8Helpers.optics.lensSVG(180, 120, 70, 'converging'));
      svg.appendChild(P8Helpers.svg.el('text', { x: 180, y: 200, 'font-family':"'Inter',sans-serif", 'font-size':10, 'font-weight':700, fill:'#dc2626', 'text-anchor':'middle', text: '+дптр (собирающая)' }));
    }
    /* Label */
    const labels = { normal: 'Норма: фокус на сетчатке', myop: 'Близорукость: фокус перед сетчаткой', hyper: 'Дальнозоркость: фокус за сетчаткой' };
    svg.appendChild(P8Helpers.svg.el('text', { x: 280, y: 222, 'font-family':"'JetBrains Mono',monospace", 'font-size':11, 'font-weight':700, fill:'#0f172a', 'text-anchor':'middle', text: labels[mode] }));
  }
  document.getElementById('p40-iv6-normal').onclick = () => { mode = 'normal'; render(); };
  document.getElementById('p40-iv6-myop').onclick = () => { mode = 'myop'; render(); };
  document.getElementById('p40-iv6-hyper').onclick = () => { mode = 'hyper'; render(); };
  render();
  `);

fs.writeFileSync(DST, h);
console.log('ch3 final 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);
