// ====== OPTICS EXPERIMENTS ======

// ---------- 1. Pinhole Camera ----------
function PinholeExp({ category, name, catColor }) {
  const [objectH, setObjectH] = useState(100); // mm (object height)
  const [objectDist, setObjectDist] = useState(200); // mm
  const [boxLen, setBoxLen] = useState(150); // mm
  const [isLit, setIsLit] = useState(true);

  // pinhole imaging: image is inverted, image height = objectH * boxLen / objectDist
  const imageH = objectH * boxLen / objectDist;

  const canvasW = 720, canvasH = 440;
  const scale = 1; // px per mm
  const centerY = canvasH / 2;
  const objectX = 80;
  const pinholeX = objectX + objectDist * scale;
  const screenX = pinholeX + boxLen * scale;

  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={objectH} onChange={setObjectH} min={30} max={150} step={5} unit=" mm" />
          <ControlSlider label="物距 u" value={objectDist} onChange={setObjectDist} min={100} max={300} step={10} unit=" mm" />
          <ControlSlider label="像距 v（箱长）" value={boxLen} onChange={boxLen} min={60} max={250} step={10} unit=" mm" />
        </div>
        <div className="panel-section">
          <div className="panel-section-title">光源</div>
          <ControlToggle label="点亮光源" checked={isLit} onChange={setIsLit} />
        </div>
      </>
    );
    if (tab === 'data') return (
      <>
        <div className="meter-row">
          <MeterCard label="物高" value={objectH} unit=" mm" />
          <MeterCard label="像高" value={imageH.toFixed(1)} unit=" mm" sub="倒立实像" />
        </div>
        <FormulaBox
          expr="h像 / h物 = v / u"
          subs={[
            `物距 <span class="val">u = ${objectDist} mm</span>`,
            `像距 <span class="val">v = ${boxLen} mm</span>`,
            `像高 <span class="val">h' = ${objectH} × ${boxLen} / ${objectDist} = ${imageH.toFixed(1)} mm</span>`,
            `像为 <strong style="color:#111114">倒立</strong>的 <strong style="color:#FF8C42">实像</strong>`,
          ]}
        />
      </>
    );
    if (tab === 'principle') return (
      <ExplainBox title="光的直线传播 · 小孔成像">
        <p><strong>原理：</strong>光在同种均匀介质中沿直线传播。</p>
        <p style={{marginTop:6}}>物体发出的光通过小孔，在光屏上形成倒立的实像。</p>
        <p style={{marginTop:6}}><strong>规律：</strong></p>
        <p>• 像为倒立的实像</p>
        <p>• 像的大小 = 物高 × 像距 / 物距</p>
        <p>• 像的形状与物体相同，与孔的形状无关</p>
        <p style={{marginTop:6}}>应用：针孔照相机、日食月食、影子</p>
      </ExplainBox>
    );
  };

  const objTopY = centerY - objectH / 2 * scale;
  const objBotY = centerY + objectH / 2 * scale;
  const imgTopY = centerY + imageH / 2 * scale;
  const imgBotY = centerY - imageH / 2 * scale;

  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" style={{ background: 'linear-gradient(180deg, #111114 0%, #0d0d0f 100%)' }}>
          {/* ground line */}
          <line x1="0" y1={centerY} x2={canvasW} y2={centerY} stroke="#242428" strokeWidth="1" strokeDasharray="3,3" />
          <text x="10" y={centerY + 16} fill="#6b6b72" fontSize="10">光轴</text>

          {/* object (candle / arrow) */}
          <g transform={`translate(${objectX}, ${centerY})`}>
            <line x1="0" y1={objectH / 2} x2="0" y2={-objectH / 2}
              stroke={isLit ? '#FF6B9D' : '#666'} strokeWidth="3" />
            {/* arrow head */}
            <polygon points={`0,${-objectH / 2 - 8} -7,${-objectH / 2 + 4} 7,${-objectH / 2 + 4}`}
              fill={isLit ? '#FF6B9D' : '#666'} />
            <text x="0" y={objectH / 2 + 20} textAnchor="middle" fill="#8FA3BD" fontSize="11">物 h={objectH}mm</text>
          </g>

          {/* pinhole box */}
          <g>
            {/* front face with pinhole */}
            <line x1={pinholeX} y1={centerY - 100} x2={pinholeX} y2={centerY - 2}
              stroke="#8B5A2B" strokeWidth="4" />
            <line x1={pinholeX} y1={centerY + 2} x2={pinholeX} y2={centerY + 100}
              stroke="#8B5A2B" strokeWidth="4" />
            {/* top/bottom */}
            <line x1={pinholeX} y1={centerY - 100} x2={screenX} y2={centerY - 100}
              stroke="#8B5A2B" strokeWidth="2" />
            <line x1={pinholeX} y1={centerY + 100} x2={screenX} y2={centerY + 100}
              stroke="#8B5A2B" strokeWidth="2" />
            {/* screen (back face) */}
            <rect x={screenX - 2} y={centerY - 100} width="4" height="200"
              fill="#fff" stroke="#ddd" strokeWidth="1" />
            {/* pinhole */}
            <circle cx={pinholeX} cy={centerY} r="3" fill="#fff" />
            <text x={(pinholeX + screenX)/2} y={centerY + 120} textAnchor="middle" fill="#8FA3BD" fontSize="11">
              像距 v = {boxLen} mm
            </text>
          </g>

          {/* light rays from top of object through pinhole */}
          {isLit && (
            <g>
              {/* top ray */}
              <line x1={objectX} y1={objTopY} x2={pinholeX} y2={centerY}
                stroke="#FFD700" strokeWidth="1.5" opacity="0.6">
                <animate attributeName="opacity" values="0.3;0.8;0.3" dur="1.5s" repeatCount="indefinite" />
              </line>
              <line x1={pinholeX} y1={centerY} x2={screenX} y2={imgTopY}
                stroke="#FFD700" strokeWidth="1.5" opacity="0.6">
                <animate attributeName="opacity" values="0.3;0.8;0.3" dur="1.5s" repeatCount="indefinite" />
              </line>
              {/* bottom ray */}
              <line x1={objectX} y1={objBotY} x2={pinholeX} y2={centerY}
                stroke="#FFD700" strokeWidth="1.5" opacity="0.4" />
              <line x1={pinholeX} y1={centerY} x2={screenX} y2={imgBotY}
                stroke="#FFD700" strokeWidth="1.5" opacity="0.4" />
            </g>
          )}

          {/* image on screen */}
          <g transform={`translate(${screenX}, ${centerY})`}>
            {isLit && (
              <>
                <line x1="0" y1={imageH / 2} x2="0" y2={-imageH / 2}
                  stroke="#FF6B9D" strokeWidth="2.5" opacity="0.85" />
                <polygon points={`0,${imageH / 2 + 8} -7,${imageH / 2 - 4} 7,${imageH / 2 - 4}`}
                  fill="#FF6B9D" opacity="0.85" />
              </>
            )}
            <text x="10" y="6" fill="#8FA3BD" fontSize="11">像 h'={imageH.toFixed(0)}mm</text>
          </g>

          {/* u distance label */}
          <text x={(objectX + pinholeX)/2} y={centerY + 150} textAnchor="middle" fill="#8FA3BD" fontSize="11">
            物距 u = {objectDist} mm
          </text>
        </svg>
      </div>
    </ExperimentShell>
  );
}

// ---------- 2. Reflection Law ----------
function ReflectionExp({ category, name, catColor }) {
  const [angleI, setAngleI] = useState(30); // degrees
  const [showNormal, setShowNormal] = useState(true);
  const [showRays, setShowRays] = useState(true);

  const canvasW = 700, canvasH = 420;
  const cx = canvasW / 2, cy = canvasH / 2 + 20;
  const mirrorLen = 260;

  const rad = angleI * Math.PI / 180;
  const rayLen = 160;
  const incidentEnd = { x: cx - rayLen * Math.sin(rad), y: cy - rayLen * Math.cos(rad) };
  const reflectEnd = { x: cx + rayLen * Math.sin(rad), y: cy - rayLen * Math.cos(rad) };

  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={angleI} onChange={setAngleI} min={0} max={85} step={1} unit="°" />
        </div>
        <div className="panel-section">
          <div className="panel-section-title">显示选项</div>
          <ControlToggle label="显示法线" checked={showNormal} onChange={setShowNormal} />
          <ControlToggle label="显示光线" checked={showRays} onChange={setShowRays} />
        </div>
      </>
    );
    if (tab === 'data') return (
      <>
        <div className="meter-row">
          <MeterCard label="入射角 i" value={angleI} unit="°" />
          <MeterCard label="反射角 r" value={angleI} unit="°" sub="r = i" />
        </div>
        <FormulaBox
          expr="反射角 = 入射角"
          subs={[
            `入射角 <span class="val">i = ${angleI}°</span>`,
            `反射角 <span class="val">r = ${angleI}°</span>`,
            `反射光线、入射光线、法线在同一平面内`,
            `反射光线和入射光线分居法线两侧`,
          ]}
        />
      </>
    );
    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 style={{marginTop:6}}><strong>两角相等：</strong>反射角等于入射角。</p>
        <p style={{marginTop:6}}>光路是可逆的。</p>
        <p style={{marginTop:6}}>入射角和反射角都是相对于法线的角度。</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">
          {/* protractor background */}
          <g transform={`translate(${cx}, ${cy})`}>
            {/* protractor arc */}
            <path d={`M -130 0 A 130 130 0 0 1 130 0 Z`}
              fill="none" stroke="#aaa" strokeWidth="1" opacity="0.5" />
            {/* angle markers */}
            {Array.from({length: 10}, (_, i) => {
              const a = (i + 1) * 9 * Math.PI / 180;
              return (
                <g key={i}>
                  <line x1={-125 * Math.sin(a)} y1={-125 * Math.cos(a)}
                    x2={-130 * Math.sin(a)} y2={-130 * Math.cos(a)}
                    stroke="#aaa" strokeWidth="1" opacity="0.6" />
                  <line x1={125 * Math.sin(a)} y1={-125 * Math.cos(a)}
                    x2={130 * Math.sin(a)} y2={-130 * Math.cos(a)}
                    stroke="#aaa" strokeWidth="1" opacity="0.6" />
                </g>
              );
            })}
            {/* 30, 60 degree labels */}
            {[30, 60].map(d => {
              const r = d * Math.PI / 180;
              return (
                <g key={d}>
                  <text x={-140 * Math.sin(r)} y={-140 * Math.cos(r) + 4} textAnchor="middle"
                    fill="#8B5A2B" fontSize="10">{d}°</text>
                  <text x={140 * Math.sin(r)} y={-140 * Math.cos(r) + 4} textAnchor="middle"
                    fill="#8B5A2B" fontSize="10">{d}°</text>
                </g>
              );
            })}
          </g>

          {/* mirror */}
          <g>
            <rect x={cx - mirrorLen / 2} y={cy - 2} width={mirrorLen} height="4"
              fill="#8B5A2B" />
            {/* mirror surface (glass side) */}
            <line x1={cx - mirrorLen / 2} y1={cy - 4} x2={cx + mirrorLen / 2} y2={cy - 4}
              stroke="#c0c0c0" strokeWidth="2" />
            {/* hatching for back side */}
            {Array.from({length: 16}, (_, i) => {
              const x = cx - mirrorLen / 2 + i * mirrorLen / 15;
              return <line key={i} x1={x} y1={cy + 2} x2={x + 6} y2={cy + 12}
                stroke="#5E3A18" strokeWidth="1" />;
            })}
          </g>

          {/* normal line */}
          {showNormal && (
            <g>
              <line x1={cx} y1={cy - 160} x2={cx} y2={cy + 30}
                stroke="#52C795" strokeWidth="1.5" strokeDasharray="6,4" />
              <text x={cx + 6} y={cy - 148} fill="#52C795" fontSize="11" fontWeight="bold">法线 N</text>
            </g>
          )}

          {/* rays */}
          {showRays && (
            <g>
              {/* incident ray with arrow */}
              <line x1={incidentEnd.x} y1={incidentEnd.y} x2={cx} y2={cy}
                stroke="#FFD700" strokeWidth="2.5" />
              <polygon
                points={`${cx},${cy} ${cx - 8 * Math.sin(rad) + 4 * Math.cos(rad)},${cy - 8 * Math.cos(rad) - 4 * Math.sin(rad)} ${cx - 8 * Math.sin(rad) - 4 * Math.cos(rad)},${cy - 8 * Math.cos(rad) + 4 * Math.sin(rad)}`}
                fill="#FFD700" />

              {/* reflected ray with arrow */}
              <line x1={cx} y1={cy} x2={reflectEnd.x} y2={reflectEnd.y}
                stroke="#FF6B9D" strokeWidth="2.5" />
              <polygon
                points={`${reflectEnd.x},${reflectEnd.y} ${reflectEnd.x - 8 * Math.sin(rad) + 4 * Math.cos(rad)},${reflectEnd.y - 8 * Math.cos(rad) - 4 * Math.sin(rad)} ${reflectEnd.x - 8 * Math.sin(rad) - 4 * Math.cos(rad)},${reflectEnd.y - 8 * Math.cos(rad) + 4 * Math.sin(rad)}`}
                fill="#FF6B9D" />

              {/* angle arc for i */}
              <path d={`M ${cx} ${cy - 40} A 40 40 0 0 0 ${cx - 40 * Math.sin(rad)} ${cy - 40 * Math.cos(rad)}`}
                fill="none" stroke="#FFD700" strokeWidth="1.5" />
              <text x={cx - 30 * Math.sin(rad/2)} y={cy - 35 * Math.cos(rad/2)} fill="#FFD700" fontSize="11" fontWeight="bold">i</text>

              {/* angle arc for r */}
              <path d={`M ${cx} ${cy - 40} A 40 40 0 0 1 ${cx + 40 * Math.sin(rad)} ${cy - 40 * Math.cos(rad)}`}
                fill="none" stroke="#FF6B9D" strokeWidth="1.5" />
              <text x={cx + 22 * Math.sin(rad/2)} y={cy - 35 * Math.cos(rad/2)} fill="#FF6B9D" fontSize="11" fontWeight="bold">r</text>
            </g>
          )}

          {/* point of incidence */}
          <circle cx={cx} cy={cy} r="4" fill="#fff" stroke="#333" strokeWidth="1.5" />

          {/* labels */}
          <text x={incidentEnd.x - 10} y={incidentEnd.y - 8} fill="#FFD700" fontSize="11" textAnchor="end">入射光线</text>
          <text x={reflectEnd.x + 10} y={reflectEnd.y - 8} fill="#FF6B9D" fontSize="11">反射光线</text>
          <text x={cx} y={cy + 30} textAnchor="middle" fill="#5E3A18" fontSize="11">入射点 O</text>
        </svg>
      </div>
    </ExperimentShell>
  );
}

// ---------- 3. Refraction ----------
function RefractionExp({ category, name, catColor }) {
  const [angleI, setAngleI] = useState(30);
  const [medium2, setMedium2] = useState('glass'); // water, glass, diamond
  const [direction, setDirection] = useState('down'); // 'down' = air→medium, 'up' = medium→air

  const mediumData = {
    water:   { name: '水',     n: 1.33, color: 'rgba(77,184,255,0.3)' },
    glass:   { name: '玻璃',   n: 1.50, color: 'rgba(180,200,220,0.35)' },
    diamond: { name: '金刚石', n: 2.42, color: 'rgba(220,220,255,0.4)' },
  };
  const n_medium = mediumData[medium2].n;
  const n_air = 1.0;
  // Determine which side has which n based on direction
  const n1 = direction === 'down' ? n_air : n_medium;  // incident side
  const n2 = direction === 'down' ? n_medium : n_air;  // refracted side
  const medium1Name = direction === 'down' ? '空气' : mediumData[medium2].name;
  const medium2Name = direction === 'down' ? mediumData[medium2].name : '空气';

  const radI = angleI * Math.PI / 180;
  // Snell's law: n1*sin(i) = n2*sin(r)
  const sinR = n1 * Math.sin(radI) / n2;
  // Critical angle only exists when going from denser to less dense (n1 > n2)
  const hasCriticalAngle = n1 > n2;
  const criticalAngle = hasCriticalAngle ? Math.asin(n2 / n1) * 180 / Math.PI : null;
  let radR = 0;
  let isTIR = false;
  if (sinR > 1) {
    isTIR = true;
    radR = radI; // reflection angle = incident angle (law of reflection)
  } else {
    radR = Math.asin(sinR);
  }
  const angleR = radR * 180 / Math.PI;

  const canvasW = 700, canvasH = 440;
  const cx = canvasW / 2, cy = canvasH / 2;
  const rayLen = 180;

  const incStart = { x: cx - rayLen * Math.sin(radI), y: cy - rayLen * Math.cos(radI) };
  const refrEnd = { x: cx + rayLen * Math.sin(radR), y: cy + rayLen * Math.cos(radR) };

  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" style={{ display: 'grid', gridTemplateColumns: '1fr 1fr 1fr', gap: 6 }}>
            {Object.entries(mediumData).map(([k, v]) => (
              <button key={k} className={`btn sm ${medium2 === k ? 'primary' : ''}`}
                onClick={() => setMedium2(k)}>
                {v.name}
              </button>
            ))}
          </div>
        </div>
        <div className="panel-section">
          <div className="panel-section-title">光线方向</div>
          <div className="btn-row" style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 6 }}>
            <button className={`btn sm ${direction === 'down' ? 'primary' : ''}`}
              onClick={() => setDirection('down')}>
              空气 → {mediumData[medium2].name}
            </button>
            <button className={`btn sm ${direction === 'up' ? 'primary' : ''}`}
              onClick={() => setDirection('up')}>
              {mediumData[medium2].name} → 空气
            </button>
          </div>
        </div>
        <div className="panel-section">
          <div className="panel-section-title">参数调节</div>
          <ControlSlider label="入射角 i" value={angleI} onChange={setAngleI} min={0} max={85} step={1} unit="°" />
          {hasCriticalAngle && (
            <div style={{ fontSize: 12, color: 'var(--text-secondary)', marginTop: 8, padding: '8px 12px', background: 'var(--bg-soft)', borderRadius: 6 }}>
              临界角 C = <strong style={{ color: 'var(--accent)' }}>{criticalAngle.toFixed(1)}°</strong>
              <br />
              {angleI > criticalAngle ? '入射角 {'>'} 临界角，发生全反射' : '入射角 {"<"} 临界角，有折射光'}
            </div>
          )}
        </div>
      </>
    );
    if (tab === 'data') return (
      <>
        <div className="meter-row">
          <MeterCard label="入射角" value={angleI.toFixed(1)} unit="°" />
          <MeterCard label="折射角" value={isTIR ? '全反射' : angleR.toFixed(1)} unit={isTIR ? '' : '°'} />
        </div>
        <div className="meter-row">
          <MeterCard label={`n₁ (${medium1Name})`} value={n1.toFixed(2)} unit="" sub="入射侧" />
          <MeterCard label={`n₂ (${medium2Name})`} value={n2.toFixed(2)} unit="" sub="折射侧" />
        </div>
        <FormulaBox
          expr="n₁·sin(i) = n₂·sin(r)"
          subs={[
            `${n1.toFixed(2)} × sin(${angleI}°) = ${n2.toFixed(2)} × sin(r)`,
            `sin(r) = ${Math.min(sinR, 1).toFixed(3)}`,
            isTIR
              ? `<strong style="color:#111114">全反射：入射角大于临界角，光全部反射回原介质</strong>`
              : `折射角 <span class="val">r = ${angleR.toFixed(1)}°</span>`,
            hasCriticalAngle
              ? `临界角 C = arcsin(n₂/n₁) = <span class="val">${criticalAngle.toFixed(1)}°</span>`
              : n2 > n1
                ? `光从${medium1Name}斜射入${medium2Name}，折射角 {"<"} 入射角，向法线偏折`
                : `光从${medium1Name}斜射入${medium2Name}，折射角 {"<"} 入射角`,
            `垂直入射时（i = 0°），传播方向不变`,
          ]}
        />
      </>
    );
    if (tab === 'principle') return (
      <ExplainBox title="光的折射">
        <p><strong>折射：</strong>光从一种介质斜射入另一种介质时，传播方向一般会发生偏折。</p>
        <p style={{marginTop:6}}><strong>折射定律：</strong></p>
        <p>• 三线共面：折射光线、入射光线、法线在同一平面</p>
        <p>• 两线分居法线两侧</p>
        <p>• n₁·sin(i) = n₂·sin(r)（斯涅尔定律）</p>
        <p style={{marginTop:6}}>光从空气斜射入水/玻璃时，折射角 { '<' } 入射角（靠近法线）。</p>
        <p style={{marginTop:6}}>光从水/玻璃斜射入空气时，折射角 { '>' } 入射角（远离法线）。</p>
        <p style={{marginTop:6}}>垂直入射时（i = 0°），传播方向不变。</p>
        <p style={{marginTop:8}}><strong>全反射：</strong>光从光密介质射向光疏介质，当入射角 ≥ 临界角时，光全部反射回原介质，不发生折射。</p>
        <p>• 临界角：sinC = n₂/n₁（n₁ {'>'} n₂）</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">
          {/* medium 2 (bottom) */}
          <rect x="0" y={cy} width={canvasW} height={canvasH - cy}
            fill={mediumData[medium2].color} />

          {/* interface line */}
          <line x1="0" y1={cy} x2={canvasW} y2={cy} stroke="#8B5A2B" strokeWidth="2" />

          {/* normal line */}
          <line x1={cx} y1={cy - 190} x2={cx} y2={cy + 190}
            stroke="#52C795" strokeWidth="1.5" strokeDasharray="6,4" />
          <text x={cx + 6} y={cy - 178} fill="#52C795" fontSize="11" fontWeight="bold">法线</text>

          {/* medium labels */}
          <text x={canvasW - 20} y={cy - 10} textAnchor="end" fill="#8B5A2B" fontSize="11">空气 n={n_air}</text>
          <text x={canvasW - 20} y={cy + 20} textAnchor="end" fill="#8B5A2B" fontSize="11">
            {mediumData[medium2].name} n={n_medium}
          </text>

          {/* rays group: flip vertically when direction is 'up' (light from below) */}
          <g transform={direction === 'up' ? `scale(-1, -1) translate(${-canvasW}, ${-canvasH})` : ''}>
            <g transform={direction === 'up' ? `translate(${canvasW - 2*cx}, 0)` : ''}>
          {/* incident ray */}
          <line x1={incStart.x} y1={incStart.y} x2={cx} y2={cy}
            stroke="#FFD700" strokeWidth="2.5" />
          <polygon
            points={`${cx},${cy} ${cx - 8 * Math.sin(radI) + 4 * Math.cos(radI)},${cy - 8 * Math.cos(radI) - 4 * Math.sin(radI)} ${cx - 8 * Math.sin(radI) - 4 * Math.cos(radI)},${cy - 8 * Math.cos(radI) + 4 * Math.sin(radI)}`}
            fill="#FFD700" />

          {/* refracted or reflected ray */}
          {isTIR ? (
            // total internal reflection
            <line x1={cx} y1={cy} x2={cx + rayLen * Math.sin(radI)} y2={cy - rayLen * Math.cos(radI)}
              stroke="#dc2626" strokeWidth="2.5" />
          ) : (
            <>
              <line x1={cx} y1={cy} x2={refrEnd.x} y2={refrEnd.y}
                stroke="#FF6B9D" strokeWidth="2.5" />
              <polygon
                points={`${refrEnd.x},${refrEnd.y} ${refrEnd.x - 8 * Math.sin(radR) - 4 * Math.cos(radR)},${refrEnd.y - 8 * Math.cos(radR) + 4 * Math.sin(radR)} ${refrEnd.x - 8 * Math.sin(radR) + 4 * Math.cos(radR)},${refrEnd.y - 8 * Math.cos(radR) - 4 * Math.sin(radR)}`}
                fill="#FF6B9D" />
            </>
          )}

          {/* partial reflection (faint) */}
          {!isTIR && (
            <line x1={cx} y1={cy} x2={cx + rayLen * 0.5 * Math.sin(radI)} y2={cy - rayLen * 0.5 * Math.cos(radI)}
              stroke="#999" strokeWidth="1" opacity="0.4" strokeDasharray="4,4" />
          )}

          {/* angle labels */}
          <path d={`M ${cx} ${cy - 50} A 50 50 0 0 0 ${cx - 50 * Math.sin(radI)} ${cy - 50 * Math.cos(radI)}`}
            fill="none" stroke="#FFD700" strokeWidth="1.5" />
          <text x={cx - 60 * Math.sin(radI/2)} y={cy - 50 * Math.cos(radI/2)} fill="#FFD700" fontSize="11" fontWeight="bold">i</text>

          {!isTIR && (
            <>
              <path d={`M ${cx} ${cy + 50} A 50 50 0 0 1 ${cx + 50 * Math.sin(radR)} ${cy + 50 * Math.cos(radR)}`}
                fill="none" stroke="#FF6B9D" strokeWidth="1.5" />
              <text x={cx + 55 * Math.sin(radR/2)} y={cy + 50 * Math.cos(radR/2) + 12} fill="#FF6B9D" fontSize="11" fontWeight="bold">r</text>
            </>
          )}
            </g>
          </g>

          {/* point */}
          <circle cx={cx} cy={cy} r="4" fill="#fff" stroke="#333" strokeWidth="1.5" />

          {/* labels (unflipped) */}
          <text x={direction === 'down' ? incStart.x - 10 : canvasW - incStart.x + 10}
            y={direction === 'down' ? incStart.y - 6 : canvasH - incStart.y + 16}
            fill="#FFD700" fontSize="11"
            textAnchor={direction === 'down' ? 'end' : 'start'}>入射光线</text>
          {isTIR ? (
            <text x={direction === 'down' ? cx + rayLen * 0.5 * Math.sin(radI) + 10 : canvasW - (cx + rayLen * 0.5 * Math.sin(radI)) - 10}
              y={direction === 'down' ? cy - rayLen * 0.5 * Math.cos(radI) - 6 : canvasH - (cy - rayLen * 0.5 * Math.cos(radI)) + 16}
              fill="#dc2626" fontSize="11"
              textAnchor={direction === 'down' ? 'start' : 'end'}>
              反射光线（全反射）
            </text>
          ) : (
            <text x={direction === 'down' ? refrEnd.x + 10 : canvasW - refrEnd.x - 10}
              y={direction === 'down' ? refrEnd.y + 6 : canvasH - refrEnd.y + 16}
              fill="#FF6B9D" fontSize="11"
              textAnchor={direction === 'down' ? 'start' : 'end'}>
              折射光线
            </text>
          )}
        </svg>
      </div>
    </ExperimentShell>
  );
}

// ---------- 4. Convex Lens Imaging ----------
function ConvexLensExp({ category, name, catColor }) {
  const [f, setF] = useState(100); // focal length in px units (mm)
  const [u, setU] = useState(250); // object distance
  const [objectH, setObjectH] = useState(60);

  // 1/f = 1/u + 1/v
  const v = u > f ? 1 / (1/f - 1/u) : -1 / (1/f - 1/u); // virtual image if u < f
  const isVirtual = u < f;
  const m = Math.abs(v / u);
  const imageH = objectH * m;
  const isUpright = isVirtual; // virtual = upright, real = inverted

  const canvasW = 740, canvasH = 420;
  const scale = 1;
  const centerY = canvasH / 2;
  const lensX = canvasW / 2;
  const objectX = lensX - u;
  const imageX = isVirtual ? lensX - v : lensX + v;

  const getRegion = () => {
    if (u > 2 * f) return 'u {'>'} 2f：倒立缩小实像（照相机）';
    if (u === 2 * f) return 'u = 2f：倒立等大实像';
    if (u > f && u < 2 * f) return 'f { '<' } u { '<' } 2f：倒立放大实像（投影仪）';
    if (u === f) return 'u = f：不成像';
    return 'u { '<' } f：正立放大虚像（放大镜）';
  };

  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="物距 u" value={u} onChange={setU} min={30} max={340} step={5} unit=" mm" />
          <ControlSlider label="焦距 f" value={f} onChange={setF} min={40} max={150} step={5} unit=" mm" />
          <ControlSlider label="物体高度" value={objectH} onChange={setObjectH} min={20} max={80} step={2} unit=" mm" />
        </div>
        <div className="panel-section">
          <div className="panel-section-title">快捷位置</div>
          <div className="btn-row">
            <button className="btn sm" onClick={() => setU(3 * f)}>u=3f</button>
            <button className="btn sm" onClick={() => setU(2 * f)}>u=2f</button>
            <button className="btn sm" onClick={() => setU(1.5 * f)}>u=1.5f</button>
            <button className="btn sm" onClick={() => setU(f)}>u=f</button>
            <button className="btn sm" onClick={() => setU(0.5 * f)}>u=0.5f</button>
          </div>
        </div>
      </>
    );
    if (tab === 'data') return (
      <>
        <div className="meter-row">
          <MeterCard label="物距 u" value={u} unit=" mm" />
          <MeterCard label="像距 v" value={u === f ? '∞' : Math.round(v)} unit={u === f ? '' : ' mm'} />
        </div>
        <div className="meter-row">
          <MeterCard label="像的性质" value={isVirtual ? '虚像' : '实像'} unit="" sub={isUpright ? '正立' : '倒立'} />
          <MeterCard label="放大率" value={m.toFixed(2)} unit="×" sub={m > 1 ? '放大' : m < 1 ? '缩小' : '等大'} />
        </div>
        <FormulaBox
          expr="1/f = 1/u + 1/v"
          subs={[
            `焦距 <span class="val">f = ${f} mm</span>`,
            `物距 <span class="val">u = ${u} mm</span>`,
            u === f
              ? `<strong style="color:#111114">u=f 时不成像（v→∞）</strong>`
              : `像距 <span class="val">v = ${Math.round(v)} mm</span> ${isVirtual ? '（虚像）' : ''}`,
            `<strong style="color:${catColor}">${getRegion()}</strong>`,
          ]}
        />
      </>
    );
    if (tab === 'principle') return (
      <ExplainBox title="凸透镜成像规律">
        <p><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>• u {'>'} 2f：倒立、缩小、实像（照相机）</p>
        <p>• u = 2f：倒立、等大、实像</p>
        <p>• f {'<'} u {'<'} 2f：倒立、放大、实像（投影仪）</p>
        <p>• u = f：不成像</p>
        <p>• u {'<'} f：正立、放大、虚像（放大镜）</p>
      </ExplainBox>
    );
  };

  // Calculate special rays
  const objTop = { x: objectX, y: centerY - objectH };
  const lensTop = { x: lensX, y: centerY - objectH }; // where parallel ray hits lens approx

  // Ray 1: parallel to axis, then through focal point
  const ray1After = isVirtual
    ? { x: lensX + 200, y: centerY + (200 / f) * objectH } // diverging from virtual focus
    : { x: lensX + v, y: centerY + imageH * (isUpright ? -1 : 1) }; // through real focal point to image

  // Actually compute properly. After parallel ray hits lens at y = objTop.y - centerY + centerY...
  // parallel ray at height h from axis: after lens passes through focal point at (f, 0)
  // equation: y - h = (0 - h)/(f - 0) * (x - 0) where lens at origin
  // slope = -h/f
  // For virtual image case (u < f): rays diverge, appear to come from virtual image
  const h = objectH; // distance above axis
  const slopeParallel = -h / f; // after lens, going to right focal point

  // Ray 2: through center, straight
  const slopeCenter = -objectH / u; // from object top through center (0,0) -> line y = slopeCenter * x

  // Find real image location: where slopeParallel ray intersects axis? No, find where rays meet
  // Parallel ray: y = h + slopeParallel * x (x measured from lens, positive to right)
  // Center ray: y = slopeCenter * x
  // At image: h + slopeParallel * vImg = slopeCenter * vImg
  // vImg = h / (slopeCenter - slopeParallel)
  // Hmm, let's just use the lens formula result for position

  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">
          {/* optical axis */}
          <line x1="20" y1={centerY} x2={canvasW - 20} y2={centerY}
            stroke="#aaa" strokeWidth="1" strokeDasharray="4,4" />
          <text x="30" y={centerY + 14} fill="#888" fontSize="10">主光轴</text>

          {/* focal points */}
          <circle cx={lensX - f} cy={centerY} r="3" fill="#FF6B9D" />
          <text x={lensX - f} y={centerY + 16} textAnchor="middle" fill="#FF6B9D" fontSize="10" fontWeight="bold">F</text>
          <circle cx={lensX + f} cy={centerY} r="3" fill="#FF6B9D" />
          <text x={lensX + f} y={centerY + 16} textAnchor="middle" fill="#FF6B9D" fontSize="10" fontWeight="bold">F'</text>

          <circle cx={lensX - 2*f} cy={centerY} r="2" fill="#FFB84D" />
          <text x={lensX - 2*f} y={centerY + 16} textAnchor="middle" fill="#FFB84D" fontSize="9">2F</text>
          <circle cx={lensX + 2*f} cy={centerY} r="2" fill="#FFB84D" />
          <text x={lensX + 2*f} y={centerY + 16} textAnchor="middle" fill="#FFB84D" fontSize="9">2F'</text>

          {/* lens */}
          <g transform={`translate(${lensX}, ${centerY})`}>
            <ellipse cx="0" cy="0" rx="8" ry="140"
              fill="rgba(200,220,240,0.5)" stroke="#4DB8FF" strokeWidth="1.5" />
            <line x1="-4" y1="-140" x2="0" y2="-146" stroke="#4DB8FF" strokeWidth="1.5" />
            <line x1="4" y1="-140" x2="0" y2="-146" stroke="#4DB8FF" strokeWidth="1.5" />
            <line x1="-4" y1="140" x2="0" y2="146" stroke="#4DB8FF" strokeWidth="1.5" />
            <line x1="4" y1="140" x2="0" y2="146" stroke="#4DB8FF" strokeWidth="1.5" />
          </g>

          {/* object (arrow) */}
          <g>
            <line x1={objectX} y1={centerY} x2={objectX} y2={centerY - objectH}
              stroke="#52C795" strokeWidth="3" />
            <polygon points={`${objectX},${centerY - objectH - 10} ${objectX - 6},${centerY - objectH + 4} ${objectX + 6},${centerY - objectH + 4}`}
              fill="#52C795" />
            <text x={objectX} y={centerY + 20} textAnchor="middle" fill="#52C795" fontSize="11" fontWeight="bold">物</text>
          </g>

          {/* rays */}
          {u !== f && (
            <g>
              {/* Ray 1: parallel to axis, then through right focal point */}
              <line x1={objectX} y1={centerY - objectH} x2={lensX} y2={centerY - objectH}
                stroke="#FFD700" strokeWidth="1.5" opacity="0.8" />
              <line x1={lensX} y1={centerY - objectH}
                x2={lensX + 300}
                y2={centerY - objectH + (300 / f) * objectH}
                stroke="#FFD700" strokeWidth="1.5" opacity="0.8" />

              {/* Ray 2: through optical center */}
              <line x1={objectX} y1={centerY - objectH}
                x2={lensX + 300}
                y2={centerY - objectH + (300 / u) * objectH}
                stroke="#FF6B9D" strokeWidth="1.5" opacity="0.8" />

              {/* Virtual image rays (dashed extensions) */}
              {isVirtual && (
                <>
                  <line x1={lensX} y1={centerY - objectH}
                    x2={imageX}
                    y2={centerY - imageH}
                    stroke="#FFD700" strokeWidth="1" strokeDasharray="4,4" opacity="0.5" />
                  <line x1={lensX} y1={centerY}
                    x2={imageX}
                    y2={centerY - imageH}
                    stroke="#FF6B9D" strokeWidth="1" strokeDasharray="4,4" opacity="0.5" />
                </>
              )}
            </g>
          )}

          {/* image */}
          {u !== f && (
            <g>
              <line x1={imageX} y1={centerY}
                x2={imageX} y2={isUpright ? centerY - imageH : centerY + imageH}
                stroke={isVirtual ? '#FF6B9D' : '#E8A33D'}
                strokeWidth="3"
                strokeDasharray={isVirtual ? '6,4' : ''}
                opacity={isVirtual ? 0.7 : 1} />
              <polygon
                points={`${imageX},${isUpright ? centerY - imageH - 10 : centerY + imageH + 10} ${imageX - 6},${isUpright ? centerY - imageH + 4 : centerY + imageH - 4} ${imageX + 6},${isUpright ? centerY - imageH + 4 : centerY + imageH - 4}`}
                fill={isVirtual ? '#FF6B9D' : '#E8A33D'}
                opacity={isVirtual ? 0.7 : 1} />
              <text x={imageX} y={isUpright ? centerY - imageH - 16 : centerY + imageH + 24}
                textAnchor="middle"
                fill={isVirtual ? '#FF6B9D' : '#E8A33D'}
                fontSize="11" fontWeight="bold">
                {isVirtual ? '虚像' : '实像'}
              </text>
            </g>
          )}

          {/* u=f label */}
          {u === f && (
            <text x={lensX + 100} y={centerY - 100} fill="#FF5C5C" fontSize="13" fontWeight="bold">
              u = f 时不成像
            </text>
          )}

          {/* scale */}
          <text x="20" y="30" fill="#888" fontSize="10">f = {f} mm</text>
        </svg>
      </div>
    </ExperimentShell>
  );
}

// ---------- 5. Dispersion ----------
function DispersionExp({ category, name, catColor }) {
  const [angle, setAngle] = useState(40); // incident angle
  const [isLightOn, setIsLightOn] = useState(true);

  const canvasW = 720, canvasH = 440;
  const cx = canvasW / 2, cy = canvasH / 2;

  // prism: equilateral triangle
  const prismR = 120;
  const prismTop = { x: cx, y: cy - prismR };
  const prismBL = { x: cx - prismR * Math.sin(Math.PI/3), y: cy + prismR * Math.cos(Math.PI/3) };
  const prismBR = { x: cx + prismR * Math.sin(Math.PI/3), y: cy + prismR * Math.cos(Math.PI/3) };

  // Colors with approximate wavelengths and refractive indices (for flint glass approx)
  const colors = [
    { name: '红',   wavelength: 700, n: 1.513, color: '#FF3B30' },
    { name: '橙',   wavelength: 620, n: 1.518, color: '#FF9500' },
    { name: '黄',   wavelength: 580, n: 1.522, color: '#FFCC00' },
    { name: '绿',   wavelength: 550, n: 1.528, color: '#34C759' },
    { name: '蓝',   wavelength: 470, n: 1.538, color: '#007AFF' },
    { name: '靛',   wavelength: 440, n: 1.545, color: '#5856D6' },
    { name: '紫',   wavelength: 400, n: 1.553, color: '#AF52DE' },
  ];

  // Simplified dispersion: just fan out colors based on n
  // Incident from upper left, hits left face of prism
  const radI = angle * Math.PI / 180;

  // Entry point on left face (approximate midpoint)
  const entryY = cy - 10;
  const entryX = prismBL.x + (prismTop.x - prismBL.x) * (entryY - prismBL.y) / (prismTop.y - prismBL.y);
  // Actually compute intersection properly - let's just put entry point at a fixed spot
  const entryPt = { x: cx - 30, y: cy - 40 };

  // Inside prism: angle of refraction for each color
  // Left face angle: 30° from vertical (equilateral triangle, apex 60°)
  // For simplicity, use a visual approximation: colors fan out by angle proportional to n
  const baseExitAngle = 20; // degrees at exit face

  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={angle} onChange={setAngle} min={20} max={70} step={1} unit="°" />
        </div>
        <div className="panel-section">
          <div className="panel-section-title">光源</div>
          <ControlToggle label="白光光源" checked={isLightOn} onChange={setIsLightOn} />
        </div>
      </>
    );
    if (tab === 'data') return (
      <>
        <div className="meter-row">
          <MeterCard label="棱镜顶角" value={60} unit="°" sub="等边三棱镜" full />
        </div>
        <FormulaBox
          expr="n = c / v  → 不同色光折射率不同"
          subs={[
            `红光 n ≈ <span class="val">1.513</span>，偏折最小`,
            `紫光 n ≈ <span class="val">1.553</span>，偏折最大`,
            `白光是由各种色光混合而成的复色光`,
            `光的色散说明白光是由多种色光组成的`,
          ]}
        />
        <div className="explain-box" style={{ marginTop: 10 }}>
          <h4>七色光谱</h4>
          <div style={{ display: 'flex', height: 24, borderRadius: 4, overflow: 'hidden' }}>
            {colors.map(c => (
              <div key={c.name} style={{ flex: 1, background: c.color, display: 'flex', alignItems: 'center', justifyContent: 'center' }}>
                <span style={{ fontSize: 10, color: 'rgba(255,255,255,0.9)' }}>{c.name}</span>
              </div>
            ))}
          </div>
          <p style={{ marginTop: 8, fontSize: 12, color: 'var(--text-muted)' }}>
            按波长从长到短排列：红、橙、黄、绿、蓝、靛、紫
          </p>
        </div>
      </>
    );
    if (tab === 'principle') return (
      <ExplainBox title="光的色散">
        <p><strong>色散：</strong>白光通过三棱镜后被分解成各种颜色的光，这种现象叫光的色散。</p>
        <p style={{marginTop:6}}><strong>原因：</strong>不同颜色的光在玻璃中的折射率不同，偏折程度不同。</p>
        <p style={{marginTop:6}}>紫光偏折最大，红光偏折最小。</p>
        <p style={{marginTop:6}}><strong>七色光：</strong>红、橙、黄、绿、蓝、靛、紫。</p>
        <p style={{marginTop:6}}>白光是由各种色光混合而成的复色光。</p>
        <p style={{marginTop:6}}><strong>应用：</strong>彩虹是太阳光通过雨滴色散形成的。</p>
      </ExplainBox>
    );
  };

  // Compute exit ray for each color
  // Physics: higher n = more bending = larger exit angle
  // Red (i=0, n=1.513) bends least; Violet (i=6, n=1.553) bends most
  const getExitRays = () => {
    return colors.map((c, i) => {
      // spread angle based on refractive index: more n = more bend
      const spread = 35; // total spread angle
      const exitAngle = baseExitAngle + (i / (colors.length - 1)) * spread; // 0 = least bend (red), max = most bend (violet)
      const rad = exitAngle * Math.PI / 180;
      const rayLen = 200;
      const exitX = cx + 30 + (i - 3) * 0.5; // slightly spread entry inside prism
      const exitY = cy - 10 + (i - 3) * 0.5;
      return {
        ...c,
        x: exitX + rayLen * Math.cos(rad),
        y: exitY + rayLen * Math.sin(rad),
        exitX, exitY,
      };
    });
  };
  const exitRays = getExitRays();

  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" style={{ background: 'linear-gradient(180deg, #141416 0%, #111114 100%)' }}>
          {/* prism */}
          <polygon
            points={`${prismTop.x},${prismTop.y} ${prismBR.x},${prismBR.y} ${prismBL.x},${prismBL.y}`}
            fill="rgba(200, 220, 240, 0.25)"
            stroke="#aaccff"
            strokeWidth="2" />

          {/* incident white light beam */}
          {isLightOn && (
            <>
              <line x1="80" y1={cy - 40 - (angle - 40) * 0.8}
                x2={cx - 30} y2={cy - 40}
                stroke="#fff" strokeWidth="6" opacity="0.9" />
              <line x1="80" y1={cy - 40 - (angle - 40) * 0.8}
                x2={cx - 30} y2={cy - 40}
                stroke="#ffffff" strokeWidth="3" opacity="0.95" />

              {/* inside prism: fanning white to colors (approx) */}
              <line x1={cx - 30} y1={cy - 40} x2={cx + 10} y2={cy - 15}
                stroke="#fff" strokeWidth="4" opacity="0.6" />

              {/* colored rays exiting prism */}
              {exitRays.map((r, i) => (
                <line key={i}
                  x1={r.exitX} y1={r.exitY}
                  x2={r.x} y2={r.y}
                  stroke={r.color} strokeWidth="2.5" opacity="0.9" />
              ))}

              {/* spectrum band on right screen */}
              <defs>
                <linearGradient id="spectrumGrad" x1="0%" y1="0%" x2="0%" y2="100%">
                  {colors.map((c, i) => (
                    <stop key={i} offset={`${(i / (colors.length - 1)) * 100}%`} stopColor={c.color} />
                  ))}
                </linearGradient>
              </defs>
              <rect x={canvasW - 60} y={cy - 30} width="12" height="120"
                fill="url(#spectrumGrad)" opacity="0.85" rx="2" />
              <text x={canvasW - 54} y={cy - 36} fill="#fff" fontSize="10" textAnchor="middle">光谱</text>
            </>
          )}

          {/* labels */}
          <text x="80" y={cy - 60 - (angle - 40) * 0.8} fill="#fff" fontSize="11">
            白光
          </text>
          <text x={cx} y={cy + 90} textAnchor="middle" fill="#aaccff" fontSize="11">
            玻璃三棱镜
          </text>
        </svg>
      </div>
    </ExperimentShell>
  );
}

Object.assign(window, {
  PinholeExp,
  ReflectionExp,
  RefractionExp,
  ConvexLensExp,
  DispersionExp,
});
