// ====== THERMAL PHYSICS EXPERIMENTS ======

// ---------- 1. Thermometer ----------
function ThermometerExp({ category, name, catColor }) {
  const [temperature, setTemperature] = useState(25); // °C
  const [beakerTemp, setBeakerTemp] = useState(25);
  const [isDipping, setIsDipping] = useState(false);
  const [time, setTime] = useState(0);
  const rafRef = useRef();

  // When dipping, thermometer temperature approaches beaker temp exponentially
  useEffect(() => {
    if (!isDipping) return;
    let last = performance.now();
    const tick = (now) => {
      const dt = (now - last) / 1000;
      last = now;
      setTime(t => t + dt);
      // approach equilibrium
      setTemperature(T => {
        const delta = beakerTemp - T;
        return T + delta * (1 - Math.exp(-dt * 1.5));
      });
      rafRef.current = requestAnimationFrame(tick);
    };
    rafRef.current = requestAnimationFrame(tick);
    return () => cancelAnimationFrame(rafRef.current);
  }, [isDipping, beakerTemp]);

  const canvasW = 600, canvasH = 440;

  // Thermometer dimensions
  const tX = 180;
  const bulbR = 18;
  const stemW = 12;
  const stemH = 280;
  const stemY = canvasH - 80 - stemH;
  const bulbY = canvasH - 80;

  // Mercury column height (from bulb)
  const minT = -20, maxT = 110;
  const mercuryH = (temperature - minT) / (maxT - minT) * (stemH - 20);

  // Scale marks
  const marks = [];
  for (let t = 0; t <= 100; t += 10) {
    const y = stemY + stemH - 10 - (t - minT) / (maxT - minT) * (stemH - 20);
    marks.push({ t, y });
  }

  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={beakerTemp} onChange={setBeakerTemp} min={0} max={100} step={1} unit=" ℃" />
        </div>
        <div className="panel-section">
          <div className="panel-section-title">操作</div>
          <button className={`btn ${isDipping ? 'primary' : ''}`} style={{ width: '100%', justifyContent: 'center' }}
            onClick={() => setIsDipping(!isDipping)}>
            {isDipping ? '⏫ 取出温度计' : '⏬ 将温度计放入液体'}
          </button>
        </div>
        <div className="panel-section">
          <button className="btn" style={{ width: '100%', justifyContent: 'center' }}
            onClick={() => { setTemperature(25); setBeakerTemp(25); setIsDipping(false); }}>
            ↺ 重置
          </button>
        </div>
      </>
    );
    if (tab === 'data') return (
      <>
        <div className="meter-row">
          <MeterCard label="温度计读数" value={temperature.toFixed(1)} unit=" ℃" sub="估读到0.1℃" full />
        </div>
        <FormulaBox
          expr="原理：液体热胀冷缩"
          subs={[
            `当前温度：<span class="val">${temperature.toFixed(1)} ℃</span>`,
            `量程：-20℃ ~ 110℃`,
            `分度值：1℃，估读到 0.1℃`,
            isDipping
              ? `<span style="color:#111114">温度计正在测量，液柱${temperature < beakerTemp ? '上升' : temperature > beakerTemp ? '下降' : '稳定'}</span>`
              : `<span style="color:#111114">请将温度计放入被测液体中</span>`,
          ]}
        />
        <div className="explain-box">
          <h4>读数方法</h4>
          <p>1. 温度计的玻璃泡要全部浸入被测液体中，不要碰到容器底或壁</p>
          <p style={{marginTop:4}}>2. 温度计示数稳定后再读数</p>
          <p style={{marginTop:4}}>3. 读数时玻璃泡要继续留在液体中，视线与液柱上表面相平</p>
          <p style={{marginTop:4}}>4. 注意分度值，估读到分度值的下一位</p>
        </div>
      </>
    );
    if (tab === 'principle') return (
      <ExplainBox title="温度计的使用">
        <p><strong>原理：</strong>利用液体热胀冷缩的性质制成。</p>
        <p style={{marginTop:6}}><strong>摄氏温度：</strong>规定冰水混合物的温度为0℃，标准大气压下沸水的温度为100℃。</p>
        <p style={{marginTop:6}}><strong>使用前：</strong>观察量程和分度值。</p>
        <p style={{marginTop:6}}><strong>使用时：</strong></p>
        <p>• 玻璃泡全部浸入被测液体</p>
        <p>• 不碰容器底和壁</p>
        <p>• 示数稳定后再读</p>
        <p>• 读数时玻璃泡留在液体中，视线与液柱上表面相平</p>
      </ExplainBox>
    );
  };

  // Beaker liquid color based on temperature
  const liquidColor = beakerTemp < 20 ? '#4DB8FF' : beakerTemp < 50 ? '#66CCFF' : beakerTemp < 80 ? '#FFB84D' : '#FF6B3D';

  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">
          {/* table */}
          <rect x="0" y={canvasH - 40} width={canvasW} height="40" fill="#A97442" />
          <rect x="0" y={canvasH - 44} width={canvasW} height="4" fill="#8B5A2B" />

          {/* beaker */}
          <g transform={`translate(${canvasW - 280}, ${canvasH - 200})`}>
            {/* glass */}
            <path d="M 0 0 L 0 150 Q 0 160 10 160 L 150 160 Q 160 160 160 150 L 160 0 Z"
              fill="rgba(200,230,255,0.2)" stroke="#666" strokeWidth="2" />
            {/* liquid */}
            <path d="M 4 156 Q 4 160 10 160 L 150 160 Q 156 160 156 156 L 156 30 L 4 30 Z"
              fill={liquidColor} opacity="0.7" />
            {/* rim */}
            <ellipse cx="80" cy="0" rx="80" ry="8" fill="none" stroke="#666" strokeWidth="2" />
            {/* spout */}
            <path d="M 0 0 L -10 -8 L 0 -5 Z" fill="#666" />
            <text x="80" y="180" textAnchor="middle" fill="#5E3A18" fontSize="12">
              烧杯 {beakerTemp}℃
            </text>
          </g>

          {/* thermometer */}
          <g transform={`translate(${tX}, ${isDipping ? 60 : -10})`}>
            {/* glass stem */}
            <rect x={-stemW/2 - 2} y={stemY - 2} width={stemW + 4} height={stemH + 4}
              fill="rgba(200,230,255,0.3)" stroke="#999" strokeWidth="1" rx="3" />

            {/* white backing */}
            <rect x={-stemW/2} y={stemY} width={stemW} height={stemH} fill="#fff" rx="2" />

            {/* scale marks */}
            {marks.map(m => (
              <g key={m.t}>
                <line x1={-stemW/2 + 2} y1={m.y} x2={-stemW/2 + 8} y2={m.y}
                  stroke="#333" strokeWidth="1" />
                <line x1={stemW/2 - 2} y1={m.y} x2={stemW/2 - 8} y2={m.y}
                  stroke="#333" strokeWidth="1" />
                <text x={-stemW/2 + 10} y={m.y + 3} fill="#333" fontSize="9">
                  {m.t}
                </text>
              </g>
            ))}

            {/* mercury column */}
            <rect x={-2} y={stemY + stemH - 10 - mercuryH} width="4" height={mercuryH}
              fill="#FF3B30" rx="1" />

            {/* bulb */}
            <circle cx="0" cy={bulbY} r={bulbR} fill="#FF3B30" />
            <circle cx="0" cy={bulbY} r={bulbR + 2} fill="none"
              stroke="rgba(200,230,255,0.5)" strokeWidth="2" />

            {/* °C label */}
            <text x={stemW/2 + 2} y={stemY + 15} fill="#666" fontSize="10">℃</text>
          </g>

          {/* reading line */}
          {isDipping && (
            <line
              x1={tX - stemW} y1={stemY + stemH - 10 - mercuryH + (isDipping ? 60 : -10)}
              x2={tX + 40} y2={stemY + stemH - 10 - mercuryH + (isDipping ? 60 : -10)}
              stroke="#FF3B30" strokeWidth="1" strokeDasharray="4,3" opacity="0.6" />
          )}
        </svg>
      </div>
    </ExperimentShell>
  );
}

// ---------- 2. Crystal vs Amorphous Melting ----------
function MeltingExp({ category, name, catColor }) {
  const canvasW = 650, canvasH = 440;
  const [material, setMaterial] = useState('crystal'); // crystal (海波/ice) or amorphous (wax)
  const [isHeating, setIsHeating] = useState(false);
  const [time, setTime] = useState(0); // minutes
  const [temperature, setTemperature] = useState(20);
  const [tempHistory, setTempHistory] = useState([20]);
  const rafRef = useRef();

  const crystalData = {
    name: '海波（晶体）',
    meltingPoint: 48,
    heatRate: 5, // degrees per minute before melting
    meltDuration: 4, // minutes at plateau
    heatRateAfter: 6,
    maxTemp: 90,
    color: '#4DB8FF',
  };

  const amorphousData = {
    name: '蜡（非晶体）',
    meltingPoint: null,
    heatRate: 3.5, // variable, just rises continuously
    maxTemp: 95,
    color: '#FFB84D',
  };

  const data = material === 'crystal' ? crystalData : amorphousData;

  useEffect(() => {
    if (!isHeating) return;
    let last = performance.now();
    const tick = (now) => {
      const dt = (now - last) / 1000; // seconds
      last = now;
      const dtMin = dt / 2; // scaled: 2 real sec = 1 experiment min

      setTime(t => {
        const newT = t + dtMin;
        let newTemp;

        if (material === 'crystal') {
          // Calculate temp based on time (simplified)
          const timeToMelt = (crystalData.meltingPoint - 20) / crystalData.heatRate;
          if (newT < timeToMelt) {
            newTemp = 20 + newT * crystalData.heatRate;
          } else if (newT < timeToMelt + crystalData.meltDuration) {
            newTemp = crystalData.meltingPoint + (Math.random() - 0.5) * 0.3; // slight fluctuation
          } else {
            const tAfter = newT - timeToMelt - crystalData.meltDuration;
            newTemp = Math.min(crystalData.maxTemp, crystalData.meltingPoint + tAfter * crystalData.heatRateAfter);
          }
        } else {
          // amorphous: continuous rise with decreasing rate
          newTemp = 20 + (1 - Math.exp(-newT * 0.12)) * (amorphousData.maxTemp - 20);
          // add slight waviness
          newTemp += Math.sin(newT * 3) * 0.2;
        }

        setTemperature(newTemp);
        if (Math.floor(newT * 10) > Math.floor(t * 10)) { // add sample every 0.1 min
          setTempHistory(h => [...h.slice(-100), newTemp]);
        }

        // stop if near max
        if (newTemp >= data.maxTemp - 0.5) {
          setIsHeating(false);
        }

        return newT;
      });

      rafRef.current = requestAnimationFrame(tick);
    };
    rafRef.current = requestAnimationFrame(tick);
    return () => cancelAnimationFrame(rafRef.current);
  }, [isHeating, material]);

  const reset = () => {
    setIsHeating(false);
    setTime(0);
    setTemperature(20);
    setTempHistory([20]);
  };

  const state = material === 'crystal'
    ? (temperature < crystalData.meltingPoint - 1 ? '固态' : temperature > crystalData.meltingPoint + 1 ? '液态' : '固液共存（熔化中）')
    : (temperature < 40 ? '固态（逐渐变软）' : temperature < 70 ? '变软中' : '液态');

  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 ${material === 'crystal' ? 'primary' : ''}`} style={{flex:1}}
              onClick={() => { setMaterial('crystal'); reset(); }}>
              晶体（海波）
            </button>
            <button className={`btn ${material === 'amorphous' ? 'primary' : ''}`} style={{flex:1}}
              onClick={() => { setMaterial('amorphous'); reset(); }}>
              非晶体（蜡）
            </button>
          </div>
        </div>
        <div className="panel-section">
          <div className="panel-section-title">加热控制</div>
          <button className={`btn ${isHeating ? 'primary' : ''}`} style={{ width: '100%', justifyContent: 'center' }}
            onClick={() => setIsHeating(!isHeating)}>
            <Icon name={isHeating ? 'Pause' : 'Fire'} size={14} />
            <span>{isHeating ? '暂停加热' : '开始加热'}</span>
          </button>
          <button className="btn" style={{ width: '100%', justifyContent: 'center', marginTop: 8 }}
            onClick={reset}>
            ↺ 重置实验
          </button>
        </div>
      </>
    );
    if (tab === 'data') return (
      <>
        <div className="meter-row">
          <MeterCard label="加热时间" value={time.toFixed(1)} unit=" min" />
          <MeterCard label="温度" value={temperature.toFixed(1)} unit=" ℃" sub={state} />
        </div>
        {material === 'crystal' && (
          <FormulaBox
            expr="晶体熔化：温度保持不变"
            subs={[
              `熔点：<span class="val">${crystalData.meltingPoint} ℃</span>`,
              `熔化过程中吸热，温度不变`,
              `熔化时处于固液共存状态`,
            ]}
          />
        )}
        {material === 'amorphous' && (
          <FormulaBox
            expr="非晶体熔化：温度持续上升"
            subs={[
              `没有固定的熔点`,
              `熔化过程中温度不断升高`,
              `过程：固态 → 变软 → 粘稠 → 液态`,
            ]}
          />
        )}
        <div className="chart-box" style={{ height: 200 }}>
          <MiniChart data={tempHistory} xLabel="时间" yLabel="温度℃" color="#111114"
            yMin={15} yMax={100} />
        </div>
      </>
    );
    if (tab === 'principle') return (
      <ExplainBox title="熔化与凝固">
        <p><strong>熔化：</strong>物质从固态变成液态的过程，需要吸热。</p>
        <p style={{marginTop:6}}><strong>晶体：</strong>有固定的熔点（如海波、冰、各种金属）。</p>
        <p>熔化时温度不变，处于固液共存状态。</p>
        <p style={{marginTop:6}}><strong>非晶体：</strong>没有固定的熔点（如蜡、松香、玻璃、沥青）。</p>
        <p>熔化过程中温度持续上升。</p>
        <p style={{marginTop:6}}><strong>凝固：</strong>物质从液态变成固态的过程，放热。</p>
        <p>同一晶体的凝固点和熔点相同。</p>
      </ExplainBox>
    );
  };

  // Substance state visual
  const getSubstanceFill = () => {
    if (material === 'crystal') {
      if (temperature < crystalData.meltingPoint - 2) return 'rgba(77,184,255,0.8)'; // solid
      if (temperature <= crystalData.meltingPoint + 2) return 'rgba(77,184,255,0.5)'; // melting
      return 'rgba(77,184,255,0.3)'; // liquid
    } else {
      const alpha = 0.8 - (temperature / 100) * 0.5;
      return `rgba(255,184,77,${alpha})`;
    }
  };

  return (
    <ExperimentShell category={category} name={name} catColor={catColor}
      panelTabs={panelTabs} panelContent={panelContent}>
      <div className="sim-canvas-wrap">
        <svg width={650} height={440} className="sim-canvas">
          {/* table */}
          <rect x="0" y={canvasH - 30} width={650} height="30" fill="#A97442" />

          {/* alcohol lamp */}
          <g transform={`translate(325, ${canvasH - 30})`}>
            {/* flame */}
            {isHeating && (
              <g>
                <ellipse cx="0" cy="-40" rx="12" ry="20" fill="#FFD700" opacity="0.9" />
                <ellipse cx="0" cy="-35" rx="7" ry="14" fill="#FF6B3D" />
                <ellipse cx="0" cy="-30" rx="4" ry="8" fill="#FFEB99" />
              </g>
            )}
            {/* lamp body */}
            <ellipse cx="0" cy="-5" rx="25" ry="8" fill="#B85C00" />
            <rect x="-22" y="-25" width="44" height="22" fill="#D47300" rx="3" />
            <rect x="-6" y="-35" width="12" height="10" fill="#8B4513" />
            <rect x="-3" y="-38" width="6" height="5" fill="#333" />
          </g>

          {/* beaker with water bath */}
          <g transform={`translate(260, ${canvasH - 200})`}>
            <path d="M 0 0 L 0 160 Q 0 170 10 170 L 120 170 Q 130 170 130 160 L 130 0 Z"
              fill="rgba(200,230,255,0.2)" stroke="#666" strokeWidth="2" />
            {/* water */}
            <path d="M 4 166 Q 4 170 10 170 L 120 170 Q 126 170 126 166 L 126 40 L 4 40 Z"
              fill="rgba(100,180,255,0.4)" />
            {/* test tube */}
            <g transform="translate(55, -30)">
              <rect x="0" y="0" width="20" height="100" rx="10"
                fill="rgba(255,255,255,0.3)" stroke="#888" strokeWidth="1.5" />
              {/* substance */}
              <rect x="2" y="60" width="16" height="38" rx="8"
                fill={getSubstanceFill()} />
              <text x="10" y="-5" textAnchor="middle" fill="#5E3A18" fontSize="10">
                {data.name}
              </text>
            </g>
            {/* thermometer in test tube */}
            <g transform="translate(62, -50)">
              <rect x="-2" y="0" width="4" height="80" fill="#fff" stroke="#999" strokeWidth="0.5" />
              <rect x="-1" y={70 - (temperature - 20) / 100 * 60} width="2" height={(temperature - 20) / 100 * 60}
                fill="#FF3B30" />
              <circle cx="0" cy="72" r="4" fill="#FF3B30" />
            </g>
            <text x="65" y="185" textAnchor="middle" fill="#5E3A18" fontSize="11">水浴加热</text>
          </g>

          {/* temp display */}
          <g transform="translate(80, 60)">
            <rect x="0" y="0" width="110" height="60" rx="6" fill="#141416" stroke="#333" strokeWidth="1" />
            <text x="10" y="22" fill="#8FA3BD" fontSize="10">温度</text>
            <text x="10" y="46" fill={catColor} fontSize="22" fontWeight="bold" fontFamily="JetBrains Mono, monospace">
              {temperature.toFixed(1)}℃
            </text>
          </g>

          {/* time display */}
          <g transform="translate(80, 130)">
            <rect x="0" y="0" width="110" height="40" rx="6" fill="#141416" stroke="#333" strokeWidth="1" />
            <text x="10" y="17" fill="#8FA3BD" fontSize="10">时间</text>
            <text x="10" y="34" fill="#52C795" fontSize="16" fontWeight="bold" fontFamily="JetBrains Mono, monospace">
              {time.toFixed(1)} min
            </text>
          </g>
        </svg>
      </div>
    </ExperimentShell>
  );
}

// ---------- 3. Water Boiling ----------
function BoilingExp({ category, name, catColor }) {
  const canvasW = 650, canvasH = 440;
  const [pressure, setPressure] = useState(1); // atm
  const [isHeating, setIsHeating] = useState(false);
  const [time, setTime] = useState(0);
  const [temperature, setTemperature] = useState(20);
  const [tempHistory, setTempHistory] = useState([20]);
  const rafRef = useRef();

  // Boiling point depends on pressure
  const boilingPoint = 100 * Math.pow(pressure, 0.3); // approximate

  useEffect(() => {
    if (!isHeating) return;
    let last = performance.now();
    const tick = (now) => {
      const dt = (now - last) / 1000;
      last = now;
      const dtMin = dt / 2;

      setTime(t => {
        const newT = t + dtMin;
        let newTemp;

        if (temperature < boilingPoint - 0.5) {
          const rate = 8 * (1 - (temperature - 20) / (boilingPoint - 20) * 0.3);
          newTemp = temperature + rate * dtMin;
          newTemp = Math.min(newTemp, boilingPoint);
        } else {
          newTemp = boilingPoint + (Math.random() - 0.5) * 0.4;
        }

        setTemperature(newTemp);
        if (Math.floor(newT * 10) > Math.floor(t * 10)) {
          setTempHistory(h => [...h.slice(-120), newTemp]);
        }

        return newT;
      });

      rafRef.current = requestAnimationFrame(tick);
    };
    rafRef.current = requestAnimationFrame(tick);
    return () => cancelAnimationFrame(rafRef.current);
  }, [isHeating, boilingPoint]);

  const reset = () => {
    setIsHeating(false);
    setTime(0);
    setTemperature(20);
    setTempHistory([20]);
  };

  const isBoiling = temperature >= boilingPoint - 0.5;
  const boilingState = isBoiling ? '沸腾中' : temperature > 70 ? '加热中（接近沸腾）' : temperature > 40 ? '加热中' : '初温';

  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={pressure} onChange={setPressure} min={0.5} max={2} step={0.05} unit=" atm" />
          <div style={{ fontSize: 12, color: 'var(--text-dim)', textAlign: 'center', marginTop: 4 }}>
            沸点 = <span style={{ color: catColor, fontWeight: 'bold' }}>{boilingPoint.toFixed(1)} ℃</span>
          </div>
        </div>
        <div className="panel-section">
          <div className="panel-section-title">加热控制</div>
          <button className={`btn ${isHeating ? 'primary' : ''}`} style={{ width: '100%', justifyContent: 'center' }}
            onClick={() => setIsHeating(!isHeating)}>
            <Icon name={isHeating ? 'Pause' : 'Fire'} size={14} />
            <span>{isHeating ? '停止加热' : '开始加热'}</span>
          </button>
          <button className="btn" style={{ width: '100%', justifyContent: 'center', marginTop: 8 }}
            onClick={reset}>
            ↺ 重置
          </button>
        </div>
      </>
    );
    if (tab === 'data') return (
      <>
        <div className="meter-row">
          <MeterCard label="温度" value={temperature.toFixed(1)} unit=" ℃" sub={boilingState} />
          <MeterCard label="沸点" value={boilingPoint.toFixed(1)} unit=" ℃" sub={`${pressure.toFixed(2)} atm`} />
        </div>
        <FormulaBox
          expr="液体沸腾时温度保持不变"
          subs={[
            `沸点随气压升高而升高`,
            `当前气压 <span class="val">${pressure.toFixed(2)} atm</span>`,
            `沸点 <span class="val">${boilingPoint.toFixed(1)} ℃</span>`,
            isBoiling
              ? `<span style="color:#111114">已达到沸点，继续吸热但温度不变</span>`
              : `<span style="color:#111114">加热中，温度上升</span>`,
          ]}
        />
        <div className="chart-box" style={{ height: 200 }}>
          <MiniChart data={tempHistory} yLabel="温度℃" color={catColor} yMin={15} yMax={Math.max(120, boilingPoint + 10)} />
        </div>
      </>
    );
    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>• 沸腾时继续吸热，但温度保持不变</p>
        <p>• 沸腾时气泡上升、变大，到水面破裂</p>
        <p style={{marginTop:6}}><strong>沸点与气压：</strong></p>
        <p>• 气压越高，沸点越高</p>
        <p>• 气压越低，沸点越低</p>
        <p>• 标准大气压下，水的沸点是100℃</p>
      </ExplainBox>
    );
  };

  // Bubbles
  const bubbles = [];
  if (isHeating) {
    const count = isBoiling ? 20 : Math.floor(temperature / 10);
    for (let i = 0; i < count; i++) {
      const seed = (i * 7 + Math.floor(time * 2)) % 100 / 100;
      const x = 100 + seed * 80;
      const phase = (time * (30 + i * 5) + i * 30) % 100 / 100;
      const y = 130 - phase * 120;
      const r = isBoiling ? 2 + phase * 8 : 2 + phase * 3;
      const opacity = isBoiling ? 1 - phase * 0.5 : 0.7 - phase * 0.5;
      if (y > 20 && opacity > 0) {
        bubbles.push({ x, y, r, opacity });
      }
    }
  }

  return (
    <ExperimentShell category={category} name={name} catColor={catColor}
      panelTabs={panelTabs} panelContent={panelContent}>
      <div className="sim-canvas-wrap">
        <svg width={650} height={440} className="sim-canvas">
          {/* table */}
          <rect x="0" y={410} width={650} height="30" fill="#A97442" />

          {/* steam */}
          {isBoiling && (
            <g>
              {[0,1,2,3,4].map(i => (
                <circle key={i}
                  cx={290 + Math.sin(time * 2 + i) * 10 + i * 8}
                  cy={80 + time * 20 + i * 15}
                  r={8 + i * 3}
                  fill="rgba(255,255,255,0.3)" />
              ))}
            </g>
          )}

          {/* beaker with water */}
          <g transform="translate(220, 220)">
            <path d="M 0 0 L 0 180 Q 0 190 10 190 L 140 190 Q 150 190 150 180 L 150 0 Z"
              fill="rgba(200,230,255,0.25)" stroke="#666" strokeWidth="2" />
            {/* water */}
            <path d="M 4 186 Q 4 190 10 190 L 140 190 Q 146 190 146 186 L 146 30 L 4 30 Z"
              fill="rgba(77,184,255,0.5)" />
            {/* water surface waviness when boiling */}
            {isBoiling && (
              <path d={`M 4 30 ${Array.from({length: 15}, (_, i) => {
                const x = 4 + i * 9.5;
                const y = 30 + Math.sin(time * 5 + i) * 2;
                return `Q ${x+4.75} ${y + 3}, ${x+9.5} ${y + Math.sin(time*5+i+1)*2}`;
              }).join(' ')} L 146 186 Q 146 190 140 190 L 10 190 Q 4 190 4 186 Z`}
                fill="rgba(77,184,255,0.5)" />
            )}

            {/* bubbles */}
            {bubbles.map((b, i) => (
              <circle key={i} cx={b.x} cy={b.y} r={b.r}
                fill="rgba(255,255,255,0.6)"
                stroke="rgba(200,230,255,0.8)" strokeWidth="0.5"
                opacity={b.opacity} />
            ))}

            {/* thermometer */}
            <g transform="translate(70, -40)">
              <rect x="-3" y="0" width="6" height="70" fill="#fff" stroke="#999" strokeWidth="0.5" rx="2" />
              <rect x="-1" y={60 - (temperature / 120) * 55} width="2" height={(temperature / 120) * 55}
                fill="#FF3B30" />
              <circle cx="0" cy="62" r="5" fill="#FF3B30" />
            </g>
          </g>

          {/* alcohol lamp */}
          <g transform="translate(295, 410)">
            {isHeating && (
              <g>
                <ellipse cx="0" cy="-35" rx="18" ry="25" fill="#FFD700" opacity="0.9" />
                <ellipse cx="0" cy="-30" rx="10" ry="18" fill="#FF6B3D" />
                <ellipse cx="0" cy="-25" rx="5" ry="10" fill="#FFEB99" />
              </g>
            )}
            <ellipse cx="0" cy="-5" rx="28" ry="8" fill="#B85C00" />
            <rect x="-25" y="-28" width="50" height="25" fill="#D47300" rx="3" />
            <rect x="-7" y="-38" width="14" height="12" fill="#8B4513" />
          </g>

          {/* temp display */}
          <g transform="translate(50, 50)">
            <rect x="0" y="0" width="130" height="60" rx="6" fill="#141416" stroke="#333" strokeWidth="1" />
            <text x="10" y="22" fill="#8FA3BD" fontSize="10">水温</text>
            <text x="10" y="46" fill={catColor} fontSize="24" fontWeight="bold" fontFamily="JetBrains Mono, monospace">
              {temperature.toFixed(1)}℃
            </text>
          </g>
        </svg>
      </div>
    </ExperimentShell>
  );
}

// ---------- 4. Specific Heat ----------
function SpecificHeatExp({ category, name, catColor }) {
  const canvasW = 650, canvasH = 440;
  const [substance, setSubstance] = useState('water');
  const [mass, setMass] = useState(200); // g
  const [power, setPower] = useState(100); // W
  const [isHeating, setIsHeating] = useState(false);
  const [time, setTime] = useState(0); // seconds
  const [temperature, setTemperature] = useState(20);
  const [tempHistory, setTempHistory] = useState([20]);
  const rafRef = useRef();

  const substances = {
    water:   { name: '水',     c: 4.2,  color: '#4DB8FF' },   // J/(g·℃) = 4200 J/(kg·℃)
    sand:    { name: '沙子',   c: 0.92, color: '#D4A853' },
    oil:     { name: '食用油', c: 2.0,  color: '#FFB84D' },
    copper:  { name: '铜',     c: 0.39, color: '#B87333' },
  };
  const sub = substances[substance];

  // Q = P*t = c*m*ΔT
  // ΔT = P*t / (c*m)
  useEffect(() => {
    if (!isHeating) return;
    let last = performance.now();
    const tick = (now) => {
      const dt = (now - last) / 1000;
      last = now;
      const dtScaled = dt * 5; // 5x speed

      setTime(t => {
        const newT = t + dtScaled;
        const Q = power * newT; // J
        const deltaT = Q / (sub.c * mass);
        const newTemp = 20 + deltaT + (Math.random() - 0.5) * 0.3;

        setTemperature(newTemp);
        if (Math.floor(newT * 2) > Math.floor(t * 2)) {
          setTempHistory(h => [...h.slice(-100), newTemp]);
        }

        return newT;
      });

      rafRef.current = requestAnimationFrame(tick);
    };
    rafRef.current = requestAnimationFrame(tick);
    return () => cancelAnimationFrame(rafRef.current);
  }, [isHeating, power, mass, sub.c]);

  const reset = () => {
    setIsHeating(false);
    setTime(0);
    setTemperature(20);
    setTempHistory([20]);
  };

  const Q = power * time;
  const deltaT = temperature - 20;

  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 style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 6 }}>
            {Object.entries(substances).map(([k, v]) => (
              <button key={k} className={`btn sm ${substance === k ? 'primary' : ''}`}
                onClick={() => { setSubstance(k); reset(); }}>
                {v.name}
              </button>
            ))}
          </div>
        </div>
        <div className="panel-section">
          <div className="panel-section-title">参数调节</div>
          <ControlSlider label="质量" value={mass} onChange={v => { setMass(v); reset(); }} min={100} max={500} step={10} unit=" g" />
          <ControlSlider label="加热器功率" value={power} onChange={setPower} min={50} max={200} step={10} unit=" W" />
        </div>
        <div className="panel-section">
          <div className="panel-section-title">控制</div>
          <button className={`btn ${isHeating ? 'primary' : ''}`} style={{ width: '100%', justifyContent: 'center' }}
            onClick={() => setIsHeating(!isHeating)}>
            <Icon name={isHeating ? 'Pause' : 'Fire'} size={14} />
            <span>{isHeating ? '暂停' : '开始加热'}</span>
          </button>
          <button className="btn" style={{ width: '100%', justifyContent: 'center', marginTop: 8 }}
            onClick={reset}>
            ↺ 重置
          </button>
        </div>
      </>
    );
    if (tab === 'data') return (
      <>
        <div className="meter-row">
          <MeterCard label="加热时间" value={time.toFixed(1)} unit=" s" />
          <MeterCard label="温度" value={temperature.toFixed(1)} unit=" ℃" />
        </div>
        <div className="meter-row">
          <MeterCard label="吸收热量" value={(Q/1000).toFixed(2)} unit=" kJ" />
          <MeterCard label="比热容 c" value={sub.c * 1000} unit=" J/(kg·℃)" sub={sub.name} />
        </div>
        <FormulaBox
          expr="Q = c · m · Δt"
          subs={[
            `Q：吸收的热量（J）`,
            `c：比热容 = <span class="val">${sub.c * 1000} J/(kg·℃)</span>`,
            `m：质量 = <span class="val">${mass/1000} kg</span>`,
            `Δt = Q/(c·m) = ${(Q/1000).toFixed(2)} kJ ÷ (${sub.c * 1000} × ${mass/1000}) = ${deltaT.toFixed(1)} ℃`
          ]}
        />
        <div className="chart-box" style={{ height: 160 }}>
          <MiniChart data={tempHistory} yLabel="温度℃" color="#111114" yMin={20} yMax={Math.max(80, temperature + 10)} />
        </div>
      </>
    );
    if (tab === 'principle') return (
      <ExplainBox title="比热容">
        <p><strong>比热容：</strong>单位质量的某种物质温度升高1℃所吸收的热量。</p>
        <p style={{marginTop:6}}><strong>公式：</strong>Q = c·m·Δt</p>
        <p style={{marginTop:6}}><strong>单位：</strong>J/(kg·℃)</p>
        <p style={{marginTop:6}}>水的比热容是 4.2×10³ J/(kg·℃)，是常见物质中最大的。</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={650} height={440} className="sim-canvas">
          {/* table */}
          <rect x="0" y={410} width={650} height="30" fill="#A97442" />

          {/* beaker with substance */}
          <g transform="translate(220, 200)">
            <path d="M 0 0 L 0 200 Q 0 210 10 210 L 140 210 Q 150 210 150 200 L 150 0 Z"
              fill="rgba(255,255,255,0.2)" stroke="#666" strokeWidth="2" />
            {/* substance */}
            <path d="M 4 206 Q 4 210 10 210 L 140 210 Q 146 210 146 206 L 146 50 L 4 50 Z"
              fill={sub.color} opacity="0.6" />
            {substance === 'sand' && Array.from({length: 100}, (_, i) => {
              const x = 6 + (i * 17) % 140;
              const y = 60 + ((i * 7) % 145);
              return <circle key={i} cx={x} cy={y} r="1.5" fill="#8B6914" opacity="0.4" />;
            })}
            <text x="75" y="225" textAnchor="middle" fill="#5E3A18" fontSize="12">{sub.name} {mass}g</text>
          </g>

          {/* heater */}
          <g transform="translate(280, 410)">
            {isHeating && (
              <g>
                <rect x="-30" y="-20" width="60" height="6" rx="3" fill="#FF6B3D">
                  <animate attributeName="opacity" values="0.6;1;0.6" dur="0.8s" repeatCount="indefinite" />
                </rect>
              </g>
            )}
            <rect x="-28" y="-12" width="56" height="15" fill="#888" stroke="#666" strokeWidth="1" rx="3" />
            <text x="0" y="10" textAnchor="middle" fill="#5E3A18" fontSize="10">{power}W 加热器</text>
          </g>

          {/* thermometer */}
          <g transform="translate(290, 160)">
            <rect x="-3" y="0" width="6" height="50" fill="#fff" stroke="#999" strokeWidth="0.5" rx="2" />
            <rect x="-1" y={45 - (Math.min(temperature, 120) / 120) * 40} width="2"
              height={(Math.min(temperature, 120) / 120) * 40}
              fill="#FF3B30" />
            <circle cx="0" cy="47" r="4" fill="#FF3B30" />
          </g>

          {/* temp display */}
          <g transform="translate(50, 50)">
            <rect x="0" y="0" width="130" height="60" rx="6" fill="#141416" stroke="#333" strokeWidth="1" />
            <text x="10" y="22" fill="#8FA3BD" fontSize="10">温度</text>
            <text x="10" y="46" fill={catColor} fontSize="22" fontWeight="bold" fontFamily="JetBrains Mono, monospace">
              {temperature.toFixed(1)}℃
            </text>
          </g>

          {/* time display */}
          <g transform="translate(50, 120)">
            <rect x="0" y="0" width="130" height="40" rx="6" fill="#141416" stroke="#333" strokeWidth="1" />
            <text x="10" y="17" fill="#8FA3BD" fontSize="10">时间</text>
            <text x="10" y="34" fill="#52C795" fontSize="16" fontWeight="bold" fontFamily="JetBrains Mono, monospace">
              {time.toFixed(1)} s
            </text>
          </g>
        </svg>
      </div>
    </ExperimentShell>
  );
}

Object.assign(window, {
  ThermometerExp,
  MeltingExp,
  BoilingExp,
  SpecificHeatExp,
});
