// ====== ELECTROMAGNETISM EXPERIMENTS ======

// ---------- 1. Magnetic Field & Field Lines ----------
function MagneticFieldExp({ category, name, catColor }) {
  const [magnetType, setMagnetType] = useState('bar'); // bar, horseshoe, twobar
  const [showIronFilings, setShowIronFilings] = useState(true);
  const [showCompass, setShowCompass] = useState(true);

  const canvasW = 650, canvasH = 420;
  const cx = canvasW / 2, cy = canvasH / 2;

  // Generate field line points for different magnet configurations
  const fieldLines = useMemo(() => {
    const lines = [];
    const poles = [];

    if (magnetType === 'bar') {
      poles.push({ x: cx - 100, y: cy, type: 'N' });
      poles.push({ x: cx + 100, y: cy, type: 'S' });
    } else if (magnetType === 'horseshoe') {
      poles.push({ x: cx - 80, y: cy - 60, type: 'N' });
      poles.push({ x: cx + 80, y: cy - 60, type: 'S' });
    } else { // two bars - same poles facing
      poles.push({ x: cx - 120, y: cy, type: 'N' });
      poles.push({ x: cx - 40, y: cy, type: 'N' });
      poles.push({ x: cx + 40, y: cy, type: 'S' });
      poles.push({ x: cx + 120, y: cy, type: 'S' });
    }

    // Trace field lines from N poles
    const nPoles = poles.filter(p => p.type === 'N');
    const numLines = 16;

    for (const np of nPoles) {
      for (let i = 0; i < numLines; i++) {
        const angle = (i / numLines) * Math.PI * 2;
        const points = [];
        let x = np.x + Math.cos(angle) * 15;
        let y = np.y + Math.sin(angle) * 15;

        for (let step = 0; step < 800; step++) {
          points.push({ x, y });
          // compute field direction
          let bx = 0, by = 0;
          for (const p of poles) {
            const dx = x - p.x;
            const dy = y - p.y;
            const r = Math.sqrt(dx*dx + dy*dy);
            if (r < 10) break;
            const sign = p.type === 'N' ? 1 : -1;
            bx += sign * dx / (r*r*r) * 10000;
            by += sign * dy / (r*r*r) * 10000;
          }
          const b = Math.sqrt(bx*bx + by*by);
          if (b < 0.01) break;
          const stepLen = 2;
          x += bx / b * stepLen;
          y += by / b * stepLen;

          if (x < 20 || x > canvasW - 20 || y < 20 || y > canvasH - 20) break;
          // stop if near S pole
          let nearS = false;
          for (const p of poles) {
            if (p.type === 'S') {
              const d = Math.sqrt((x-p.x)**2 + (y-p.y)**2);
              if (d < 15) { nearS = true; break; }
            }
          }
          if (nearS) break;
        }

        if (points.length > 5) {
          lines.push(points);
        }
      }
    }

    return { lines, poles };
  }, [magnetType, cx, cy]);

  // Iron filings positions (fixed seed)
  const ironFilings = useMemo(() => {
    const filings = [];
    const nPoles = fieldLines.poles;
    for (let i = 0; i < 200; i++) {
      const x = 30 + (i * 97 % (canvasW - 60));
      const y = 30 + ((i * 53) % (canvasH - 60));
      // compute field angle at this point
      let bx = 0, by = 0;
      for (const p of nPoles) {
        const dx = x - p.x;
        const dy = y - p.y;
        const r = Math.sqrt(dx*dx + dy*dy);
        if (r < 20) continue;
        const sign = p.type === 'N' ? 1 : -1;
        bx += sign * dx / (r*r*r) * 10000;
        by += sign * dy / (r*r*r) * 10000;
      }
      const b = Math.sqrt(bx*bx + by*by);
      if (b < 0.001) continue;
      const len = Math.min(8, 2 + b * 0.005);
      filings.push({
        x1: x - bx/b * len/2,
        y1: y - by/b * len/2,
        x2: x + bx/b * len/2,
        y2: y + by/b * len/2,
        len,
      });
    }
    return filings;
  }, [fieldLines]);

  // compass needles at various positions
  const compasses = useMemo(() => {
    const arr = [];
    const positions = [
      { x: cx - 180, y: cy },
      { x: cx - 100, y: cy - 80 },
      { x: cx + 100, y: cy - 80 },
      { x: cx + 180, y: cy },
      { x: cx + 100, y: cy + 80 },
      { x: cx - 100, y: cy + 80 },
      { x: cx, y: cy - 120 },
      { x: cx, y: cy + 120 },
    ];
    const nPoles = fieldLines.poles;

    for (const pos of positions) {
      let bx = 0, by = 0;
      for (const p of nPoles) {
        const dx = pos.x - p.x;
        const dy = pos.y - p.y;
        const r = Math.sqrt(dx*dx + dy*dy);
        if (r < 10) continue;
        const sign = p.type === 'N' ? 1 : -1;
        bx += sign * dx / (r*r*r) * 10000;
        by += sign * dy / (r*r*r) * 10000;
      }
      const angle = Math.atan2(by, bx) * 180 / Math.PI;
      arr.push({ x: pos.x, y: pos.y, angle });
    }
    return arr;
  }, [fieldLines, cx, cy]);

  const panelTabs = [
    { id: 'controls', label: '操作' },
    { id: 'data', label: '观察' },
    { id: 'principle', label: '原理' },
  ];

  const panelContent = (tab) => {
    if (tab === 'controls') return (
      <>
        <div className="panel-section">
          <div className="panel-section-title">磁体类型</div>
          <div className="btn-row">
            <button className={`btn ${magnetType === 'bar' ? 'primary' : ''}`} style={{flex:1}}
              onClick={() => setMagnetType('bar')}>条形磁体</button>
            <button className={`btn ${magnetType === 'horseshoe' ? 'primary' : ''}`} style={{flex:1}}
              onClick={() => setMagnetType('horseshoe')}>蹄形磁体</button>
          </div>
          <button className={`btn ${magnetType === 'twobar' ? 'primary' : ''}`} style={{width: '100%', marginTop: 6}}
            onClick={() => setMagnetType('twobar')}>
            异名磁极相对
          </button>
        </div>
        <div className="panel-section">
          <div className="panel-section-title">显示</div>
          <ControlToggle label="铁屑分布" checked={showIronFilings} onChange={setShowIronFilings} />
          <ControlToggle label="小磁针" checked={showCompass} onChange={setShowCompass} />
        </div>
      </>
    );
    if (tab === 'data') return (
      <>
        <div className="meter-row">
          <MeterCard label="磁体类型" value={
            magnetType === 'bar' ? '条形' : magnetType === 'horseshoe' ? '蹄形' : '两磁铁'
          } unit="" sub={`${fieldLines.poles.length/2}对磁极`} full />
        </div>
        <div className="explain-box">
          <h4>观察要点</h4>
          <p>1. 磁感线从 <strong style={{color:'#FF3B30'}}>N极</strong> 出发，回到 <strong style={{color:'#4DB8FF'}}>S极</strong></p>
          <p style={{marginTop:4}}>2. 磁感线是闭合的曲线</p>
          <p style={{marginTop:4}}>3. 磁极附近磁感线最密，磁场最强</p>
          <p style={{marginTop:4}}>4. 小磁针静止时N极指向与磁场方向一致</p>
          <p style={{marginTop:4}}>5. 异名磁极相吸，同名磁极相斥</p>
        </div>
      </>
    );
    if (tab === 'principle') return (
      <ExplainBox title="磁体与磁场">
        <p><strong>磁性：</strong>能吸引铁、钴、镍等物质的性质。</p>
        <p style={{marginTop:6}}><strong>磁极：</strong>磁体上磁性最强的部分，任何磁体都有N极和S极。</p>
        <p style={{marginTop:6}}><strong>相互作用：</strong>同名磁极相互排斥，异名磁极相互吸引。</p>
        <p style={{marginTop:6}}><strong>磁场：</strong>磁体周围存在的一种特殊物质。</p>
        <p>• 基本性质：对放入其中的磁体有力的作用</p>
        <p>• 方向：小磁针静止时N极所指的方向</p>
        <p style={{marginTop:6}}><strong>磁感线：</strong>描述磁场的假想曲线。</p>
        <p>• 从N极出发，回到S极</p>
        <p>• 曲线越密表示磁场越强</p>
        <p>• 磁感线不相交</p>
      </ExplainBox>
    );
  };

  return (
    <ExperimentShell category={category} name={name} catColor={catColor}
      panelTabs={panelTabs} panelContent={panelContent}>
      <div className="sim-canvas-wrap">
        <svg width={canvasW} height={canvasH} className="sim-canvas paper-bg">

          {/* iron filings */}
          {showIronFilings && (
            <g>
              {ironFilings.map((f, i) => (
                <line key={i} x1={f.x1} y1={f.y1} x2={f.x2} y2={f.y2}
                  stroke="#666" strokeWidth="0.8" opacity="0.6" />
              ))}
            </g>
          )}

          {/* field lines */}
          {showCompass && fieldLines.lines.map((line, i) => (
            <polyline key={i}
              points={line.map(p => `${p.x},${p.y}`).join(' ')}
              fill="none" stroke={catColor} strokeWidth="0.8" opacity="0.4" />
          ))}

          {/* magnets */}
          {magnetType === 'bar' && (
            <g>
              {/* N pole (red) */}
              <rect x={cx - 140} y={cy - 25} width="80" height="50" fill="#FF3B30" stroke="#B71C1C" strokeWidth="2" rx="4" />
              <text x={cx - 100} y={cy + 5} textAnchor="middle" fill="#fff" fontSize="18" fontWeight="bold">N</text>
              {/* S pole (blue) */}
              <rect x={cx + 60} y={cy - 25} width="80" height="50" fill="#4DB8FF" stroke="#1565C0" strokeWidth="2" rx="4" />
              <text x={cx + 100} y={cy + 5} textAnchor="middle" fill="#fff" fontSize="18" fontWeight="bold">S</text>
            </g>
          )}

          {magnetType === 'horseshoe' && (
            <g>
              {/* U-shaped magnet */}
              <path d={`M ${cx - 80} ${cy - 100} L ${cx - 80} ${cy + 40} Q ${cx - 80} ${cy + 60} ${cx - 60} ${cy + 60}
                L ${cx + 60} ${cy + 60} Q ${cx + 80} ${cy + 60} ${cx + 80} ${cy + 40} L ${cx + 80} ${cy - 100}`}
                fill="none" stroke="#888" strokeWidth="30" strokeLinecap="round" />
              {/* N pole tip */}
              <rect x={cx - 95} y={cy - 100} width="30" height="30" fill="#FF3B30" rx="3" />
              <text x={cx - 80} y={cy - 80} textAnchor="middle" fill="#fff" fontSize="14" fontWeight="bold">N</text>
              {/* S pole tip */}
              <rect x={cx + 65} y={cy - 100} width="30" height="30" fill="#4DB8FF" rx="3" />
              <text x={cx + 80} y={cy - 80} textAnchor="middle" fill="#fff" fontSize="14" fontWeight="bold">S</text>
            </g>
          )}

          {magnetType === 'twobar' && (
            <g>
              {/* left: NS bar */}
              <rect x={cx - 160} y={cy - 20} width="60" height="40" fill="#FF3B30" stroke="#B71C1C" strokeWidth="2" rx="3" />
              <text x={cx - 130} y={cy + 5} textAnchor="middle" fill="#fff" fontSize="14" fontWeight="bold">N</text>
              <rect x={cx - 100} y={cy - 20} width="60" height="40" fill="#4DB8FF" stroke="#1565C0" strokeWidth="2" rx="3" />
              <text x={cx - 70} y={cy + 5} textAnchor="middle" fill="#fff" fontSize="14" fontWeight="bold">S</text>

              {/* right: NS bar */}
              <rect x={cx + 40} y={cy - 20} width="60" height="40" fill="#FF3B30" stroke="#B71C1C" strokeWidth="2" rx="3" />
              <text x={cx + 70} y={cy + 5} textAnchor="middle" fill="#fff" fontSize="14" fontWeight="bold">N</text>
              <rect x={cx + 100} y={cy - 20} width="60" height="40" fill="#4DB8FF" stroke="#1565C0" strokeWidth="2" rx="3" />
              <text x={cx + 130} y={cy + 5} textAnchor="middle" fill="#fff" fontSize="14" fontWeight="bold">S</text>

              <text x={cx} y={cy - 45} textAnchor="middle" fill="#FF3B30" fontSize="11">
                S ←→ N 异名磁极相吸
              </text>
            </g>
          )}

          {/* compasses */}
          {showCompass && compasses.map((c, i) => (
            <g key={i} transform={`translate(${c.x}, ${c.y})`}>
              <circle r="12" fill="#fff" stroke="#999" strokeWidth="1" opacity="0.9" />
              <g transform={`rotate(${c.angle})`}>
                <line x1="-8" y1="0" x2="8" y2="0" stroke="#FF3B30" strokeWidth="1.5" />
                <polygon points="8,0 4,-3 4,3" fill="#FF3B30" />
                <text x="-9" y="3" fill="#4DB8FF" fontSize="7" textAnchor="end">S</text>
              </g>
              <circle r="2" fill="#333" />
            </g>
          ))}

          <text x={canvasW/2} y="30" textAnchor="middle" fill="#5E3A18" fontSize="13" fontWeight="bold">
            磁感线分布
          </text>
        </svg>
      </div>
    </ExperimentShell>
  );
}

// ---------- 2. Oersted Experiment ----------
function OerstedExp({ category, name, catColor }) {
  const [current, setCurrent] = useState(0); // A
  const [currentDir, setCurrentDir] = useState(1); // 1 right, -1 left
  const [isOn, setIsOn] = useState(false);

  const canvasW = 600, canvasH = 400;
  const cx = canvasW / 2, cy = canvasH / 2;

  // Compass deflection angle: proportional to current, direction depends on current direction
  // Right-hand rule: thumb in current direction, fingers curl in B direction
  // Below wire: if current right (+x), B field is into page (-z) → wait, let's think top view
  // Actually compass below wire: current right → field circles counterclockwise → below wire field points left
  // Right-hand rule: thumb along current, fingers curl in B direction
  // Below the wire, B points to the LEFT when current flows RIGHT (clockwise current from above view = counterclockwise deflection from observer front view)
  // Here we use: current right → B below wire points left → compass N pole turns left (counterclockwise / negative)
  const deflectionAngle = isOn ? -current * currentDir * 8 : 0;

  const panelTabs = [
    { id: 'controls', label: '操作' },
    { id: 'data', label: '数据/规律' },
    { id: 'principle', label: '原理' },
  ];

  const panelContent = (tab) => {
    if (tab === 'controls') return (
      <>
        <div className="panel-section">
          <div className="panel-section-title">电流控制</div>
          <ControlSlider label="电流大小" value={current} onChange={setCurrent} min={0.5} max={5} step={0.1} unit=" A" />
          <div className="btn-row">
            <button className={`btn ${currentDir === 1 ? 'primary' : ''}`} style={{flex:1}}
              onClick={() => setCurrentDir(1)}>电流向右 →</button>
            <button className={`btn ${currentDir === -1 ? 'primary' : ''}`} style={{flex:1}}
              onClick={() => setCurrentDir(-1)}>← 电流向左</button>
          </div>
        </div>
        <div className="panel-section">
          <ControlToggle label="接通电源" checked={isOn} onChange={setIsOn} />
        </div>
      </>
    );
    if (tab === 'data') return (
      <>
        <div className="meter-row">
          <MeterCard label="电流" value={isOn ? current : '0.0'} unit=" A"
            sub={currentDir === 1 ? '向右' : '向左'} />
          <MeterCard label="磁针偏转角" value={Math.abs(deflectionAngle).toFixed(1)} unit="°"
            sub={deflectionAngle < -0.1 ? 'N极向左偏（逆时针）' : deflectionAngle > 0.1 ? 'N极向右偏（顺时针）' : '无偏转'} />
        </div>
        <FormulaBox
          expr="电流的磁效应（奥斯特实验）"
          subs={[
            `通电导线周围存在磁场，叫电流的磁效应`,
            `磁场方向与电流方向有关`,
            `电流越大，磁场越强，磁针偏转越明显`,
            isOn
              ? `电流 I = <span class="val">${current} A</span>，偏转 <span class="val">${Math.abs(deflectionAngle).toFixed(1)}°</span>`
              : `<span style="color:#111114">通电前，磁针指向南北方向</span>`,
            `<strong style="color:${catColor}">安培定则（右手螺旋定则）：</strong>右手握住导线，大拇指指向电流方向，四指环绕方向为磁场方向`,
          ]}
        />
      </>
    );
    if (tab === 'principle') return (
      <ExplainBox title="奥斯特实验">
        <p><strong>发现者：</strong>丹麦物理学家奥斯特，1820年。</p>
        <p style={{marginTop:6}}><strong>现象：</strong>通电导线下方的小磁针发生偏转。</p>
        <p style={{marginTop:6}}><strong>结论：</strong></p>
        <p>1. 通电导线周围存在磁场（电流的磁效应）</p>
        <p>2. 磁场方向与电流方向有关</p>
        <p style={{marginTop:6}}><strong>意义：</strong>第一个揭示了电与磁之间的联系。</p>
        <p style={{marginTop:6}}><strong>安培定则（右手螺旋定则）：</strong></p>
        <p>右手握住直导线，让大拇指指向电流方向，弯曲的四指所指的方向就是磁感线的环绕方向。</p>
      </ExplainBox>
    );
  };

  return (
    <ExperimentShell category={category} name={name} catColor={catColor}
      panelTabs={panelTabs} panelContent={panelContent}>
      <div className="sim-canvas-wrap">
        <svg width={canvasW} height={canvasH} className="sim-canvas paper-bg">
          {/* wire */}
          <rect x="60" y={cy - 20} width="480" height="12" fill="#B87333" stroke="#8B5A2B" strokeWidth="1" rx="3" />
          <text x="540" y={cy - 24} fill="#5E3A18" fontSize="11">导线</text>

          {/* current flow animation */}
          {isOn && (
            <g>
              {[0, 0.25, 0.5, 0.75].map((phase, i) => (
                <circle key={i} r="4" fill="#F5D547">
                  <animateMotion dur="1.5s" repeatCount="indefinite" begin={`${-i * 0.375}s`}>
                    <mpath href={`#currentPath-${currentDir}`} />
                  </animateMotion>
                </circle>
              ))}
              <path id="currentPath-1" d="M 70 200 L 530 200" fill="none" />
              <path id="currentPath--1" d="M 530 200 L 70 200" fill="none" />
            </g>
          )}

          {/* current direction arrow */}
          {isOn && (
            <text x={cx} y={cy - 28} textAnchor="middle" fill="#FF6B3D" fontSize="14" fontWeight="bold">
              {currentDir === 1 ? '→ 电流方向 →' : '← 电流方向 ←'}
            </text>
          )}

          {/* magnetic field circles around wire (cross-section view hint) */}
          {isOn && (
            <g transform={`translate(${cx}, ${cy - 14})`}>
              {[15, 25, 35].map(r => (
                <circle key={r} cx="0" cy="0" r={r} fill="none"
                  stroke={catColor} strokeWidth="0.8" opacity={1 - r/50} strokeDasharray="3,3">
                  <animateTransform attributeName="transform" type="rotate"
                    from={currentDir === 1 ? '0' : '360'}
                    to={currentDir === 1 ? '360' : '0'}
                    dur={`${3 - current*0.5}s`} repeatCount="indefinite" />
                </circle>
              ))}
              <text x="45" y="-10" fill={catColor} fontSize="10">磁场</text>
            </g>
          )}

          {/* compass below wire */}
          <g transform={`translate(${cx}, ${cy + 80}) rotate(${deflectionAngle})`}>
            <circle r="35" fill="#fff" stroke="#333" strokeWidth="2" />
            {/* N needle */}
            <polygon points="0,-30 -6,0 0,5 6,0" fill="#FF3B30" stroke="#B71C1C" strokeWidth="1" />
            {/* S needle */}
            <polygon points="0,30 -6,0 0,-5 6,0" fill="#fff" stroke="#666" strokeWidth="1" />
            <circle cx="0" cy="0" r="4" fill="#333" />
            <text x="0" y="-35" textAnchor="middle" fill="#FF3B30" fontSize="11" fontWeight="bold">N</text>
            <text x="0" y="48" textAnchor="middle" fill="#666" fontSize="11">S</text>
          </g>

          {/* E W S N markers */}
          <g transform={`translate(${cx}, ${cy + 80})`}>
            <text x="-55" y="4" fill="#888" fontSize="10">W</text>
            <text x="50" y="4" fill="#888" fontSize="10">E</text>
          </g>

          {/* battery */}
          <g transform="translate(60, 180)">
            <line x1="0" y1="40" x2="0" y2="80" stroke="#333" strokeWidth="4" />
            <line x1="-6" y1="47" x2="6" y2="47" stroke="#333" strokeWidth="3" />
            <text x="-10" y="38" fill="#FF3B30" fontSize="10">+</text>
          </g>
          <line x1="60" y1={cy - 14} x2="60" y2="220" stroke="#B87333" strokeWidth="6" />
          <line x1="540" y1={cy - 14} x2="540" y2="280" stroke="#B87333" strokeWidth="6" />

          <text x={cx} y="40" textAnchor="middle" fill="#5E3A18" fontSize="13" fontWeight="bold">
            奥斯特实验 — 电流的磁效应
          </text>
          <text x={cx} y="60" textAnchor="middle" fill="#888" fontSize="11">
            {isOn ? `磁针偏转 ${Math.abs(deflectionAngle).toFixed(1)}°` : '闭合开关，观察小磁针变化'}
          </text>
        </svg>
      </div>
    </ExperimentShell>
  );
}

// ---------- 3. Electromagnet ----------
function ElectromagnetExp({ category, name, catColor }) {
  const [current, setCurrent] = useState(1); // A
  const [turns, setTurns] = useState(100); // number of turns
  const [hasCore, setHasCore] = useState(true);
  const [isOn, setIsOn] = useState(false);
  const [currentDir, setCurrentDir] = useState(1); // 1 = current enters left (left→right through coil), -1 = enters right

  // Magnetic strength ~ N * I * core_factor
  const coreFactor = hasCore ? 5 : 1;
  const strength = isOn ? turns * current * coreFactor / 100 : 0; // normalized
  const pinsAttracted = Math.floor(strength * 8); // number of pins attracted

  const canvasW = 640, canvasH = 420;
  const cx = canvasW / 2, cy = 180;

  const panelTabs = [
    { id: 'controls', label: '操作' },
    { id: 'data', label: '数据/规律' },
    { id: 'principle', label: '原理' },
  ];

  const panelContent = (tab) => {
    if (tab === 'controls') return (
      <>
        <div className="panel-section">
          <div className="panel-section-title">参数调节</div>
          <ControlSlider label="电流大小" value={current} onChange={setCurrent} min={0.2} max={3} step={0.1} unit=" A" />
          <ControlSlider label="线圈匝数" value={turns} onChange={setTurns} min={20} max={200} step={10} unit=" 匝" />
        </div>
        <div className="panel-section">
          <div className="panel-section-title">铁芯</div>
          <ControlToggle label="插入铁芯" checked={hasCore} onChange={setHasCore} />
        </div>
        <div className="panel-section">
          <div className="panel-section-title">电流方向</div>
          <div className="btn-row">
            <button className={`btn sm ${currentDir === 1 ? 'primary' : ''}`} style={{flex:1}}
              onClick={() => setCurrentDir(1)}>左进右出</button>
            <button className={`btn sm ${currentDir === -1 ? 'primary' : ''}`} style={{flex:1}}
              onClick={() => setCurrentDir(-1)}>右进左出</button>
          </div>
        </div>
        <div className="panel-section">
          <ControlToggle label="接通电源" checked={isOn} onChange={setIsOn} />
        </div>
      </>
    );
    if (tab === 'data') return (
      <>
        <div className="meter-row">
          <MeterCard label="电流" value={isOn ? current : '0'} unit=" A" />
          <MeterCard label="匝数" value={turns} unit=" 匝" />
        </div>
        <div className="meter-row">
          <MeterCard label="铁芯" value={hasCore ? '有' : '无'} unit="" sub={hasCore ? '磁性增强5倍' : '磁性较弱'} />
          <MeterCard label="吸引大头针" value={pinsAttracted} unit=" 枚" sub={`磁性强弱`} />
        </div>
        <FormulaBox
          expr="电磁铁磁性 ∝ 匝数 × 电流 × 铁芯"
          subs={[
            `电流 I = <span class="val">${current} A</span>，方向：${currentDir === 1 ? '左进右出' : '右进左出'}`,
            `匝数 N = <span class="val">${turns} 匝</span>`,
            hasCore ? `有铁芯：磁性大大增强` : `无铁芯：磁性较弱`,
            `磁极：左端 <span class="val">${currentDir === 1 ? 'S极' : 'N极'}</span>，右端 <span class="val">${currentDir === 1 ? 'N极' : 'S极'}</span>`,
            `磁性强弱指标：<span class="val">${strength.toFixed(2)}</span>（相对值）`,
            `影响因素：电流越大、匝数越多、有铁芯 → 磁性越强`,
            `<strong style="color:${catColor}">安培定则：</strong>右手握住螺线管，四指弯向电流方向，大拇指指向N极`,
          ]}
        />
      </>
    );
    if (tab === 'principle') return (
      <ExplainBox title="电磁铁">
        <p><strong>构造：</strong>通电螺线管 + 铁芯。</p>
        <p style={{marginTop:6}}><strong>原理：</strong>电流的磁效应，铁芯被磁化使磁性大大增强。</p>
        <p style={{marginTop:6}}><strong>影响磁性强弱的因素：</strong></p>
        <p>1. 电流大小：电流越大，磁性越强</p>
        <p>2. 线圈匝数：匝数越多，磁性越强</p>
        <p>3. 有无铁芯：有铁芯磁性强得多</p>
        <p style={{marginTop:6}}><strong>特点：</strong></p>
        <p>• 磁性有无可以由通断电控制</p>
        <p>• 磁性强弱可以由电流大小/匝数控制</p>
        <p>• 磁极可以由电流方向控制</p>
        <p style={{marginTop:8}}><strong>安培定则（右手螺旋定则）：</strong></p>
        <p>右手握住螺线管，让四指弯向电流的方向，大拇指所指的那端就是螺线管的N极。</p>
        <p style={{marginTop:6}}><strong>应用：</strong>电磁起重机、电铃、电磁继电器、磁悬浮列车等。</p>
      </ExplainBox>
    );
  };

  return (
    <ExperimentShell category={category} name={name} catColor={catColor}
      panelTabs={panelTabs} panelContent={panelContent}>
      <div className="sim-canvas-wrap">
        <svg width={canvasW} height={canvasH} className="sim-canvas paper-bg">
          {/* solenoid coil */}
          <g transform={`translate(${cx}, ${cy})`}>
            {/* core (iron) */}
            {hasCore && (
              <rect x="-100" y="-15" width="200" height="30" fill="#A0A0A0" stroke="#666" strokeWidth="1.5" rx="3" />
            )}

            {/* coil wires */}
            {Array.from({length: Math.min(40, Math.floor(turns/5))}, (_, i) => {
              const x = -95 + i * (190 / (Math.min(40, Math.floor(turns/5)) - 1));
              return (
                <g key={i}>
                  <ellipse cx={x} cy="0" rx="6" ry={hasCore ? 22 : 18}
                    fill="none" stroke="#B87333" strokeWidth="2.5" />
                </g>
              );
            })}

            {/* N and S poles - determined by right-hand rule (安培定则) */}
            {/* Winding: front side goes from upper-left to lower-right (正绕) */}
            {/* currentDir=1 (left in, right out): right end = N, left end = S */}
            {/* currentDir=-1 (right in, left out): left end = N, right end = S */}
            {isOn && (
              <>
                <text x="-115" y="5" fill={currentDir === 1 ? '#4DB8FF' : '#FF3B30'} fontSize="16" fontWeight="bold">{currentDir === 1 ? 'S' : 'N'}</text>
                <text x="105" y="5" fill={currentDir === 1 ? '#FF3B30' : '#4DB8FF'} fontSize="16" fontWeight="bold">{currentDir === 1 ? 'N' : 'S'}</text>
                {/* field lines: emerge from N pole */}
                {[0, 1, 2].map(i => (
                  <g key={i}>
                    {currentDir === 1 ? (
                      <path d={`M 100 0 Q ${130 + i*20} ${-30 - i*20} ${160 + i*20} ${-10 - i*10}`}
                        fill="none" stroke={catColor} strokeWidth="1" opacity="0.5" />
                    ) : (
                      <path d={`M -100 0 Q ${-130 - i*20} ${-30 - i*20} ${-160 - i*20} ${-10 - i*10}`}
                        fill="none" stroke={catColor} strokeWidth="1" opacity="0.5" />
                    )}
                  </g>
                ))}
              </>
            )}
          </g>

          {/* wires to battery */}
          <line x1={cx - 100} y1={cy - 15} x2="150" y2={cy - 15}
            stroke="#B87333" strokeWidth="3" />
          <line x1="150" y1={cy - 15} x2="150" y2="320"
            stroke="#B87333" strokeWidth="3" />

          {/* battery */}
          <g transform="translate(150, 320)">
            <line x1="0" y1="0" x2="0" y2="30" stroke="#333" strokeWidth="4" />
            <line x1="-6" y1="5" x2="6" y2="5" stroke="#333" strokeWidth="3" />
            <text x="-10" y="-2" fill="#FF3B30" fontSize="10">+</text>
            <text x="12" y="15" fill="#5E3A18" fontSize="10">电源</text>
          </g>

          {/* switch */}
          <g transform="translate(150, 260)">
            <circle cx="0" cy="0" r="5" fill="#333" />
            <circle cx="0" cy="25" r="5" fill="#333" />
            {isOn ? (
              <line x1="0" y1="0" x2="0" y2="25" stroke="#333" strokeWidth="3" />
            ) : (
              <line x1="0" y1="0" x2="15" y2="8" stroke="#333" strokeWidth="3" />
            )}
            <text x="12" y="14" fill="#5E3A18" fontSize="10">S</text>
          </g>

          {/* ammeter */}
          <g transform="translate(150, 180)">
            <circle r="16" fill="#fff" stroke="#333" strokeWidth="2" />
            <text x="0" y="4" textAnchor="middle" fontSize="10" fill="#333" fontWeight="bold">A</text>
            <text x="0" y="22" textAnchor="middle" fill="#52C795" fontSize="9">{current}A</text>
          </g>

          {/* pins / iron filings attracted */}
          <g transform={`translate(${cx}, ${cy + 80})`}>
            {isOn && Array.from({length: pinsAttracted}, (_, i) => {
              const angle = (i / pinsAttracted) * Math.PI - Math.PI/2;
              const r = 40 + (i % 3) * 10;
              const x = Math.cos(angle) * r + (i * 7 % 30 - 15);
              const y = Math.abs(Math.sin(angle)) * 30 + (i % 5) * 6;
              return (
                <g key={i} transform={`translate(${x}, ${y}) rotate(${i * 30})`}>
                  <ellipse cx="0" cy="0" rx="3" ry="1.5" fill="#666" />
                </g>
              );
            })}
            {!isOn && (
              <>
                {Array.from({length: 12}, (_, i) => (
                  <g key={i} transform={`translate(${-60 + i * 10}, 40)`}>
                    <ellipse cx="0" cy="0" rx="4" ry="2" fill="#666" />
                  </g>
                ))}
              </>
            )}
            <text x="0" y="70" textAnchor="middle" fill="#5E3A18" fontSize="11">
              大头针（吸引 {pinsAttracted} 枚）
            </text>
          </g>

          <text x={cx} y="35" textAnchor="middle" fill="#5E3A18" fontSize="13" fontWeight="bold">
            电磁铁 — 影响磁性强弱的因素
          </text>
        </svg>
      </div>
    </ExperimentShell>
  );
}

// ---------- 4. Motor Principle ----------
function MotorExp({ category, name, catColor }) {
  const [current, setCurrent] = useState(1);
  const [currentDir, setCurrentDir] = useState(1);
  const [magStrength, setMagStrength] = useState(1);
  const [magDir, setMagDir] = useState(1); // 1 = left→right N→S (field right), -1 = right→left (field left)
  const [isOn, setIsOn] = useState(false);
  const [angle, setAngle] = useState(0);
  const rafRef = useRef();

  // Motor rotation speed: F = BIL, direction depends on both I and B
  // Reversing either reverses rotation; reversing both leaves it unchanged
  const speed = isOn ? current * magStrength * currentDir * magDir * 60 : 0; // deg/sec

  useEffect(() => {
    if (!isOn) return;
    let last = performance.now();
    const tick = (now) => {
      const dt = (now - last) / 1000;
      last = now;
      setAngle(a => a + speed * dt);
      rafRef.current = requestAnimationFrame(tick);
    };
    rafRef.current = requestAnimationFrame(tick);
    return () => cancelAnimationFrame(rafRef.current);
  }, [isOn, speed]);

  const canvasW = 620, canvasH = 420;
  const cx = canvasW / 2, cy = canvasH / 2;

  const panelTabs = [
    { id: 'controls', label: '操作' },
    { id: 'data', label: '数据/规律' },
    { id: 'principle', label: '原理' },
  ];

  const panelContent = (tab) => {
    if (tab === 'controls') return (
      <>
        <div className="panel-section">
          <div className="panel-section-title">参数调节</div>
          <ControlSlider label="电流大小" value={current} onChange={setCurrent} min={0.2} max={3} step={0.1} unit=" A" />
          <ControlSlider label="磁场强弱" value={magStrength} onChange={setMagStrength} min={0.5} max={2} step={0.1} unit="" />
        </div>
        <div className="panel-section">
          <div className="panel-section-title">电流方向</div>
          <div className="btn-row">
            <button className={`btn ${currentDir === 1 ? 'primary' : ''}`} style={{flex:1}}
              onClick={() => setCurrentDir(1)}>正向</button>
            <button className={`btn ${currentDir === -1 ? 'primary' : ''}`} style={{flex:1}}
              onClick={() => setCurrentDir(-1)}>反向</button>
          </div>
        </div>
        <div className="panel-section">
          <div className="panel-section-title">磁场方向</div>
          <div className="btn-row">
            <button className={`btn ${magDir === 1 ? 'primary' : ''}`} style={{flex:1}}
              onClick={() => setMagDir(1)}>左N右S</button>
            <button className={`btn ${magDir === -1 ? 'primary' : ''}`} style={{flex:1}}
              onClick={() => setMagDir(-1)}>左S右N</button>
          </div>
        </div>
        <div className="panel-section">
          <ControlToggle label="接通电源" checked={isOn} onChange={setIsOn} />
        </div>
      </>
    );
    if (tab === 'data') return (
      <>
        <div className="meter-row">
          <MeterCard label="电流" value={isOn ? current : '0'} unit=" A" />
          <MeterCard label="转速" value={Math.abs(speed).toFixed(0)} unit="°/s"
            sub={speed > 0 ? '顺时针' : speed < 0 ? '逆时针' : '静止'} />
        </div>
        <FormulaBox
          expr="F = B · I · L"
          subs={[
            `磁场对通电导体有力的作用`,
            `力的大小与电流 I 和磁场 B 成正比`,
            `转速 ∝ 电流 × 磁场强弱`,
            isOn
              ? `当前：I = <span class="val">${current} A</span>，B = <span class="val">${magStrength}×</span>`
              : `通电后导体受力运动`,
            `电流方向改变 → 受力方向改变 → 转动方向改变`,
            `磁场方向改变 → 受力方向改变 → 转动方向改变`,
            `同时改变电流和磁场方向 → 受力方向不变`,
            `<strong style="color:${catColor}">这是电动机的原理</strong>`,
          ]}
        />
      </>
    );
    if (tab === 'principle') return (
      <ExplainBox title="磁场对通电导体的作用">
        <p><strong>现象：</strong>通电导体在磁场中受到力的作用。</p>
        <p style={{marginTop:6}}><strong>力的方向：</strong>与电流方向和磁场方向都有关。</p>
        <p>• 只改变电流方向 → 受力方向改变</p>
        <p>• 只改变磁场方向 → 受力方向改变</p>
        <p>• 同时改变两者 → 受力方向不变</p>
        <p style={{marginTop:6}}><strong>左手定则：</strong></p>
        <p>伸开左手，使拇指与四指垂直，让磁感线穿入手心，四指指向电流方向，拇指所指就是受力方向。</p>
        <p style={{marginTop:6}}><strong>电动机：</strong></p>
        <p>• 原理：通电线圈在磁场中受力转动</p>
        <p>• 能量转化：电能 → 机械能</p>
        <p>• 换向器：自动改变线圈中电流方向，使线圈持续转动</p>
      </ExplainBox>
    );
  };

  return (
    <ExperimentShell category={category} name={name} catColor={catColor}
      panelTabs={panelTabs} panelContent={panelContent}>
      <div className="sim-canvas-wrap">
        <svg width={canvasW} height={canvasH} className="sim-canvas paper-bg">
          {/* N pole magnet (left when magDir=1, right when magDir=-1) */}
          <rect x="60" y={cy - 80} width="60" height="160" fill={magDir === 1 ? '#FF3B30' : '#4DB8FF'} stroke={magDir === 1 ? '#B71C1C' : '#1565C0'} strokeWidth="2" rx="4" />
          <text x="90" y={cy + 5} textAnchor="middle" fill="#fff" fontSize="24" fontWeight="bold">{magDir === 1 ? 'N' : 'S'}</text>

          {/* S pole magnet (right when magDir=1, left when magDir=-1) */}
          <rect x={cx + 120} y={cy - 80} width="60" height="160" fill={magDir === 1 ? '#4DB8FF' : '#FF3B30'} stroke={magDir === 1 ? '#1565C0' : '#B71C1C'} strokeWidth="2" rx="4" />
          <text x={cx + 150} y={cy + 5} textAnchor="middle" fill="#fff" fontSize="24" fontWeight="bold">{magDir === 1 ? 'S' : 'N'}</text>

          {/* field lines */}
          {isOn && Array.from({length: 9}, (_, i) => (
            <line key={i}
              x1="120" y1={cy - 60 + i * 15}
              x2={cx + 120} y2={cy - 60 + i * 15}
              stroke={catColor} strokeWidth="0.8" strokeDasharray="4,4" opacity={magStrength * 0.4} />
          ))}

          {/* armature (rotating coil) */}
          <g transform={`translate(${cx}, ${cy}) rotate(${angle})`}>
            {/* coil */}
            <rect x="-40" y="-30" width="80" height="60" fill="none"
              stroke="#B87333" strokeWidth="3" />
            {/* commutator */}
            <circle r="8" fill="#D4A853" stroke="#8B6914" strokeWidth="1.5" />
            <line x1="-8" y1="0" x2="8" y2="0" stroke="#8B6914" strokeWidth="1" />
            {/* axle */}
            <circle r="3" fill="#666" />

            {/* current direction indicators */}
            {isOn && (
              <>
                {/* left side: current direction */}
                <circle cx="-40" cy="0" r="5" fill="#fff" stroke="#333" strokeWidth="1" />
                <text x="-40" y="4" textAnchor="middle" fontSize="8" fill="#FF3B30" fontWeight="bold">
                  {currentDir === 1 ? '⊙' : '⊗'}
                </text>
                {/* right side */}
                <circle cx="40" cy="0" r="5" fill="#fff" stroke="#333" strokeWidth="1" />
                <text x="40" y="4" textAnchor="middle" fontSize="8" fill="#FF3B30" fontWeight="bold">
                  {currentDir === 1 ? '⊗' : '⊙'}
                </text>
              </>
            )}

            {/* force arrows */}
            {isOn && (
              <>
                {/* left side force */}
                <g transform="translate(-40, 0)">
                  <line x1="0" y1="0" x2={-current * currentDir * 15} y2="0"
                    stroke="#52C795" strokeWidth="2" />
                  <polygon
                    points={`${-current*currentDir*15},0 ${-current*currentDir*15+5*Math.sign(current*currentDir)},-3 ${-current*currentDir*15+5*Math.sign(current*currentDir)},3`}
                    fill="#52C795" />
                </g>
                {/* right side force */}
                <g transform="translate(40, 0)">
                  <line x1="0" y1="0" x2={current * currentDir * 15} y2="0"
                    stroke="#52C795" strokeWidth="2" />
                  <polygon
                    points={`${current*currentDir*15},0 ${current*currentDir*15-5*Math.sign(current*currentDir)},-3 ${current*currentDir*15-5*Math.sign(current*currentDir)},3`}
                    fill="#52C795" />
                </g>
              </>
            )}
          </g>

          {/* rotation direction indicator */}
          {isOn && (
            <g transform={`translate(${cx}, ${cy - 100})`}>
              <text x="0" y="0" textAnchor="middle" fill="#FF6B3D" fontSize="13" fontWeight="bold">
                {speed > 0 ? '↻ 顺时针转动' : '↺ 逆时针转动'}
              </text>
            </g>
          )}

          {/* brushes */}
          <rect x={cx - 20} y={cy + 50} width="6" height="20" fill="#888" rx="2" />
          <rect x={cx + 14} y={cy + 50} width="6" height="20" fill="#888" rx="2" />
          <text x={cx} y={cy + 85} textAnchor="middle" fill="#666" fontSize="9">电刷</text>

          <text x={cx} y="30" textAnchor="middle" fill="#5E3A18" fontSize="13" fontWeight="bold">
            电动机原理 — 通电线圈在磁场中受力转动
          </text>
        </svg>
      </div>
    </ExperimentShell>
  );
}

// ---------- 5. Electromagnetic Induction ----------
function InductionExp({ category, name, catColor }) {
  const [magSpeed, setMagSpeed] = useState(1);
  const [magDirection, setMagDirection] = useState(1); // 1 into coil, -1 out
  const [isMoving, setIsMoving] = useState(false);
  const [coilTurns, setCoilTurns] = useState(100);
  const [magStrength, setMagStrength] = useState(1);
  const [time, setTime] = useState(0);
  const rafRef = useRef();

  // Induced current: ε = N * ΔΦ/Δt (Faraday's law)
  // Simplified: I_induced ∝ N * v * B
  const inducedCurrent = isMoving
    ? coilTurns / 100 * magSpeed * magStrength * magDirection * 0.5
    : 0;

  const [magPos, setMagPos] = useState(-100); // x position of magnet

  useEffect(() => {
    if (!isMoving) return;
    let last = performance.now();
    const tick = (now) => {
      const dt = (now - last) / 1000;
      last = now;
      setTime(t => t + dt);
      setMagPos(p => {
        const newP = p + magSpeed * magDirection * 50 * dt;
        // bounce at edges
        if (newP > 60) { setMagDirection(-1); return 60; }
        if (newP < -100) { setMagDirection(1); return -100; }
        return newP;
      });
      rafRef.current = requestAnimationFrame(tick);
    };
    rafRef.current = requestAnimationFrame(tick);
    return () => cancelAnimationFrame(rafRef.current);
  }, [isMoving, magSpeed, magDirection]);

  const canvasW = 620, canvasH = 420;
  const cx = canvasW / 2, cy = canvasH / 2;

  const panelTabs = [
    { id: 'controls', label: '操作' },
    { id: 'data', label: '数据/规律' },
    { id: 'principle', label: '原理' },
  ];

  const panelContent = (tab) => {
    if (tab === 'controls') return (
      <>
        <div className="panel-section">
          <div className="panel-section-title">参数调节</div>
          <ControlSlider label="磁体运动速度" value={magSpeed} onChange={setMagSpeed} min={0.2} max={2} step={0.1} unit="" />
          <ControlSlider label="线圈匝数" value={coilTurns} onChange={setCoilTurns} min={20} max={200} step={10} unit=" 匝" />
          <ControlSlider label="磁体强弱" value={magStrength} onChange={setMagStrength} min={0.5} max={2} step={0.1} unit="" />
        </div>
        <div className="panel-section">
          <div className="panel-section-title">操作</div>
          <button className={`btn ${isMoving ? 'primary' : ''}`} style={{ width: '100%', justifyContent: 'center' }}
            onClick={() => setIsMoving(!isMoving)}>
            {isMoving ? '⏸ 停止移动' : '▶ 插入/拔出磁体'}
          </button>
          <div className="btn-row" style={{ marginTop: 8 }}>
            <button className="btn sm" onClick={() => setMagDirection(1)}>插入N极</button>
            <button className="btn sm" onClick={() => setMagDirection(-1)}>拔出N极</button>
          </div>
        </div>
      </>
    );
    if (tab === 'data') return (
      <>
        <div className="meter-row">
          <MeterCard label="感应电流" value={Math.abs(inducedCurrent).toFixed(2)} unit=" mA"
            sub={inducedCurrent > 0 ? '正向' : inducedCurrent < 0 ? '反向' : '无'} />
          <MeterCard label="灵敏电流计" value={Math.abs(inducedCurrent * 10).toFixed(0)} unit=" 格"
            sub={inducedCurrent > 0 ? '右偏' : inducedCurrent < 0 ? '左偏' : '指零'} />
        </div>
        <FormulaBox
          expr="ε = N · ΔΦ/Δt（法拉第电磁感应定律）"
          subs={[
            `闭合电路中磁通量变化 → 产生感应电流`,
            `感应电动势与匝数 N 成正比`,
            `磁通量变化越快（ΔΦ/Δt越大）→ 感应电流越大`,
            `感应电流方向与磁场方向和运动方向有关`,
            isMoving
              ? `当前：N=${coilTurns}匝，v=${magSpeed}，B=${magStrength} → I = <span class="val">${Math.abs(inducedCurrent).toFixed(2)} mA</span>`
              : `磁体不动，磁通量不变 → 无感应电流`,
            `<strong style="color:${catColor}">这是发电机的原理</strong>`,
          ]}
        />
      </>
    );
    if (tab === 'principle') return (
      <ExplainBox title="电磁感应">
        <p><strong>发现者：</strong>英国物理学家法拉第，1831年。</p>
        <p style={{marginTop:6}}><strong>电磁感应：</strong>闭合电路的一部分导体在磁场中做切割磁感线运动时，导体中产生电流的现象。</p>
        <p style={{marginTop:6}}><strong>感应电流：</strong>电磁感应产生的电流。</p>
        <p style={{marginTop:6}}><strong>产生条件：</strong></p>
        <p>1. 闭合电路</p>
        <p>2. 一部分导体做切割磁感线运动</p>
        <p style={{marginTop:6}}><strong>影响感应电流方向的因素：</strong></p>
        <p>• 磁场方向</p>
        <p>• 导体运动方向</p>
        <p style={{marginTop:6}}><strong>发电机：</strong></p>
        <p>• 原理：电磁感应现象</p>
        <p>• 能量转化：机械能 → 电能</p>
        <p>• 交流电：大小和方向周期性变化</p>
      </ExplainBox>
    );
  };

  // Galvanometer needle
  const galvoAngle = -60 + Math.min(120, Math.abs(inducedCurrent) * 30) * Math.sign(inducedCurrent);

  return (
    <ExperimentShell category={category} name={name} catColor={catColor}
      panelTabs={panelTabs} panelContent={panelContent}>
      <div className="sim-canvas-wrap">
        <svg width={canvasW} height={canvasH} className="sim-canvas paper-bg">
          {/* coil */}
          <g transform={`translate(${cx}, ${cy})`}>
            {/* coil windings (side view) */}
            {Array.from({length: 8}, (_, i) => (
              <ellipse key={i}
                cx={(i - 3.5) * 8} cy="0"
                rx="5" ry="50"
                fill="none" stroke="#B87333" strokeWidth="2.5" />
            ))}
            <text x="0" y="70" textAnchor="middle" fill="#5E3A18" fontSize="11">
              线圈 {coilTurns} 匝
            </text>
          </g>

          {/* magnet (moving left-right) */}
          <g transform={`translate(${cx + 120 + magPos}, ${cy})`}>
            {/* N pole facing coil */}
            <rect x="-20" y="-25" width="40" height="50" fill="#FF3B30" stroke="#B71C1C" strokeWidth="2" rx="3" />
            <text x="0" y="5" textAnchor="middle" fill="#fff" fontSize="16" fontWeight="bold">N</text>
            <rect x="-40" y="-25" width="20" height="50" fill="#4DB8FF" stroke="#1565C0" strokeWidth="2" rx="3" />
            <text x="-30" y="5" textAnchor="middle" fill="#fff" fontSize="12" fontWeight="bold">S</text>

            {/* velocity arrow */}
            {isMoving && (
              <g transform="translate(0, -45)">
                <text x="0" y="0" textAnchor="middle" fill="#FF6B3D" fontSize="12" fontWeight="bold">
                  {magDirection === 1 ? '→ 插入' : '← 拔出'}
                </text>
              </g>
            )}
          </g>

          {/* wires connecting coil to galvanometer */}
          <line x1={cx - 30} y1={cy + 50} x2={cx - 30} y2={cy + 120}
            stroke="#B87333" strokeWidth="2" />
          <line x1={cx + 30} y1={cy + 50} x2={cx + 30} y2={cy + 120}
            stroke="#B87333" strokeWidth="2" />

          {/* galvanometer (sensitive ammeter) */}
          <g transform={`translate(${cx}, ${cy + 140})`}>
            <circle r="45" fill="#fff" stroke="#333" strokeWidth="2" />
            {/* scale */}
            {[-60, -40, -20, 0, 20, 40, 60].map(a => {
              const rad = a * Math.PI / 180 - Math.PI/2;
              return (
                <line key={a}
                  x1={Math.cos(rad) * 35} y1={Math.sin(rad) * 35 + 10}
                  x2={Math.cos(rad) * 40} y2={Math.sin(rad) * 40 + 10}
                  stroke="#333" strokeWidth="1" />
              );
            })}
            <text x="0" y="-20" textAnchor="middle" fontSize="10" fill="#333" fontWeight="bold">G</text>
            <text x="-20" y="-5" textAnchor="middle" fontSize="8" fill="#666">−</text>
            <text x="20" y="-5" textAnchor="middle" fontSize="8" fill="#666">+</text>
            {/* needle */}
            <line x1="0" y1="10"
              x2={Math.cos(galvoAngle * Math.PI/180 - Math.PI/2) * 32}
              y2={10 + Math.sin(galvoAngle * Math.PI/180 - Math.PI/2) * 32}
              stroke="#FF3B30" strokeWidth="1.5" />
            <circle cx="0" cy="10" r="3" fill="#333" />
            <text x="0" y="35" textAnchor="middle" fill="#5E3A18" fontSize="10">灵敏电流计</text>
          </g>

          <text x={cx} y="35" textAnchor="middle" fill="#5E3A18" fontSize="13" fontWeight="bold">
            电磁感应 — 发电机原理
          </text>
          <text x={cx} y="55" textAnchor="middle" fill="#888" fontSize="11">
            {isMoving
              ? `感应电流 ${Math.abs(inducedCurrent).toFixed(2)} mA（${inducedCurrent >= 0 ? '正向' : '反向'}）`
              : '磁体运动时切割磁感线，产生感应电流'}
          </text>
        </svg>
      </div>
    </ExperimentShell>
  );
}

Object.assign(window, {
  MagneticFieldExp,
  OerstedExp,
  ElectromagnetExp,
  MotorExp,
  InductionExp,
});
