/* advelectronicslab.jsx — AdvElectronicsTutor: the advanced (circuit-laws) electronics workbench.
   Six modules: Ohm's law, voltage divider (KVL), current divider (KCL), RC charging (τ = RC),
   RC low-pass filter (f_c), series RLC resonance (f₀, Q). Built on window.BenchKit (loaded first),
   so this file is just the FIGURES + their field computations. Each computeFields() mirrors
   server/benches/advelectronics.js simulate() EXACTLY (same SI numerics + rounding), so client and
   server agree on every graded field. Design study: docs/advancedElec/electronics-bench.html. */
(function () {
  const { useState, useMemo, useEffect } = React;
  const K = window.BenchKit, C = K.C, SVG_BG = K.SVG_BG, fmt = K.fmt, clamp = K.clamp;
  const TaskStrip = K.TaskStrip, StatMeter = K.StatMeter;

  /* ════ rounding — byte-for-byte identical to server/benches/advelectronics.js ════ */
  const r1 = (x) => Math.round(x * 10) / 10;
  const r2 = (x) => Math.round(x * 100) / 100;
  const r3 = (x) => Math.round(x * 1000) / 1000;
  const r5 = (x) => Math.round(x * 1e5) / 1e5;
  const r6 = (x) => Math.round(x * 1e6) / 1e6;
  const clampN = (x, a, b) => Math.max(a, Math.min(b, x));
  const TWO_PI = 2 * Math.PI;

  /* ════ field computations — EXACTLY the server's per-module simulate() ════ */
  function fieldsOhm(s) {
    const V = s.V, R = Math.max(1, s.R), I = V / R;
    return { current: r6(I), power: r5(V * I), conductance: r6(1 / R), voltage: r2(V), resistance: Math.round(R) };
  }
  function fieldsVdiv(s) {
    const Vin = s.Vin, R1 = Math.max(1, s.R1), R2 = Math.max(1, s.R2);
    const I = Vin / (R1 + R2), v1 = I * R1, v2 = I * R2;
    return { seriesCurrent: r6(I), vR1: r3(v1), vOut: r3(v2), divRatio: r3(R2 / (R1 + R2)), kvlSum: r3(v1 + v2), vIn: r2(Vin) };
  }
  function fieldsIdiv(s) {
    const Iin = s.Iin, R1 = Math.max(1, s.R1), R2 = Math.max(1, s.R2);
    const Rp = (R1 * R2) / (R1 + R2), i1 = Iin * R2 / (R1 + R2), i2 = Iin * R1 / (R1 + R2);
    return { rParallel: r2(Rp), vShared: r3(Iin * Rp), i1: r6(i1), i2: r6(i2), kclSum: r6(i1 + i2), iIn: r5(Iin) };
  }
  function fieldsRc(s) {
    const V = s.V, R = Math.max(1, s.R), Cc = Math.max(1e-12, s.Cuf * 1e-6);
    const tau = R * Cc, tEff = clampN(s.t, 0, 5 * tau), vC = V * (1 - Math.exp(-tEff / tau));
    return { tau: r6(tau), vC: r3(vC), pctCharged: r1(100 * vC / V), iCharge: r6((V / R) * Math.exp(-tEff / tau)), t: r5(tEff) };
  }
  function fieldsLpf(s) {
    const R = Math.max(1, s.Rf), Cc = Math.max(1e-12, s.Cnf * 1e-9);
    const fc = 1 / (TWO_PI * R * Cc), f = Math.pow(10, s.fx);
    const gain = 1 / Math.sqrt(1 + (f / fc) ** 2), db = 20 * Math.log10(gain);
    return { fc: r2(fc), freq: r2(f), gainDb: r2(db), gainRatio: r5(gain) };
  }
  function fieldsRlc(s) {
    const R = Math.max(0.01, s.Rr), L = Math.max(1e-9, s.Lmh * 1e-3), Cc = Math.max(1e-12, s.Crnf * 1e-9);
    const f0 = 1 / (TWO_PI * Math.sqrt(L * Cc)), Q = (1 / R) * Math.sqrt(L / Cc);
    const f = Math.pow(10, s.fxr), w = TWO_PI * f, H = R / Math.sqrt(R * R + (w * L - 1 / (w * Cc)) ** 2);
    return { f0: r2(f0), qFactor: r3(Q), bandwidth: r2(f0 / Q), freq: r2(f), gainDb: r2(20 * Math.log10(H)) };
  }

  /* ════ engineering-unit display formatter (visualization only — never feeds a graded field) ════ */
  function eng(v, u) {
    if (!isFinite(v)) return "∞";
    if (v === 0) return "0 " + u;
    const a = Math.abs(v), tab = [[1e9, "G"], [1e6, "M"], [1e3, "k"], [1, ""], [1e-3, "m"], [1e-6, "µ"], [1e-9, "n"], [1e-12, "p"]];
    let m = 1e-12, pre = "p";
    for (const [mm, p] of tab) { if (a >= mm) { m = mm; pre = p; break; } }
    const x = v / m;
    return (Math.abs(x) >= 100 ? x.toFixed(0) : Math.abs(x) >= 10 ? x.toFixed(1) : x.toFixed(2)) + " " + pre + u;
  }

  /* ════ plot helpers (visualization only) ════ */
  const PW = 660, PH = 300, PADL = 50, PADR = 16, PADT = 16, PADB = 32;
  function XF(xdom, ydom) {
    const x0 = PADL, x1 = PW - PADR, y0 = PH - PADB, y1 = PADT;
    return { sx: (x) => x0 + (x - xdom[0]) / ((xdom[1] - xdom[0]) || 1) * (x1 - x0), sy: (y) => y0 + (y - ydom[0]) / ((ydom[1] - ydom[0]) || 1) * (y1 - y0), x0, x1, y0, y1, xdom, ydom };
  }
  function curveD(f, xf, n = 280) { let d = ""; for (let i = 0; i <= n; i++) { const x = xf.xdom[0] + (xf.xdom[1] - xf.xdom[0]) * i / n; const y = clampN(f(x), xf.ydom[0], xf.ydom[1]); d += (i ? "L" : "M") + xf.sx(x).toFixed(1) + " " + xf.sy(y).toFixed(1); } return d; }
  function Axis({ xf, ticks, fmtT, unit }) {
    return (<g>
      <line x1={xf.x0} y1={xf.y0} x2={xf.x1} y2={xf.y0} stroke={C.faint} strokeWidth="1.2" />
      <line x1={xf.x0} y1={xf.y0} x2={xf.x0} y2={xf.y1} stroke={C.faint} strokeWidth="1" />
      {ticks.map((t, i) => (<g key={i}><line x1={xf.sx(t)} y1={xf.y0} x2={xf.sx(t)} y2={xf.y0 + 5} stroke={C.faint} strokeWidth="1" /><text x={xf.sx(t)} y={xf.y0 + 18} textAnchor="middle" fill={C.mute} fontSize="10.5" fontFamily="'IBM Plex Mono',monospace">{fmtT ? fmtT(t) : t}</text></g>))}
      {unit ? <text x={xf.x1} y={xf.y1 + 4} textAnchor="end" fill={C.faint} fontSize="10" fontFamily="'IBM Plex Mono',monospace">{unit}</text> : null}
    </g>);
  }
  function Slider({ label, value, set, min, max, step, color, fmtV }) {
    return (
      <div style={{ display: "flex", alignItems: "center", gap: 10, marginTop: 8 }}>
        <span style={{ fontSize: 12, color: C.mute, width: 150 }}><K.T>{label}</K.T></span>
        <input type="range" min={min} max={max} step={step} value={value} onChange={(e) => set(parseFloat(e.target.value))} style={{ flex: 1 }} />
        <span style={{ color: color || C.amber, width: 70, textAlign: "right", fontFamily: "'IBM Plex Mono',monospace" }}>{fmtV ? fmtV(value) : value}</span>
      </div>
    );
  }
  function Frame({ children }) {
    return <svg viewBox={`0 0 ${PW} ${PH}`} style={{ width: "100%", background: SVG_BG, border: `1px solid ${C.line}`, borderRadius: 8 }}>{children}</svg>;
  }
  function Meters({ items }) {
    return <div style={{ display: "grid", gridTemplateColumns: `repeat(${items.length},1fr)`, gap: 8, marginTop: 12 }}>{items.map((m, i) => <StatMeter key={i} label={m.label} v={m.v} d={m.d} unit={m.unit} color={m.color} />)}</div>;
  }
  function Status({ ok, children }) {
    return <div style={{ marginTop: 10, padding: "8px 12px", borderRadius: 6, background: C.panel, border: `1px solid ${ok ? C.gold : C.line}`, color: ok ? C.gold : C.mute, fontSize: 12.5 }}>{children}</div>;
  }
  // a vertical / horizontal proportion bar (KVL voltage split, KCL current split)
  function StackBar({ x, y, w, h, parts, vertical }) {
    let off = 0;
    return (<g>
      {parts.map((p, i) => {
        const seg = (vertical ? h : w) * (p.frac || 0);
        const rect = vertical
          ? { x, y: y + off, width: w, height: seg }
          : { x: x + off, y, width: seg, height: h };
        const cx = vertical ? x + w / 2 : x + off + seg / 2, cy = vertical ? y + off + seg / 2 : y + h / 2;
        off += seg;
        return (<g key={i}>
          <rect {...rect} fill={p.color} fillOpacity="0.55" stroke="#22455c" strokeWidth="0.6" />
          {seg > 18 ? <text x={cx} y={cy + 4} textAnchor="middle" fill="#f0e4c4" fontSize="11" fontFamily="'IBM Plex Mono',monospace">{p.label}</text> : null}
        </g>);
      })}
      <rect x={x} y={y} width={w} height={h} fill="none" stroke={C.faint} strokeWidth="1" />
    </g>);
  }

  /* ════ Module 1: Ohm's law — the I–V line ════ */
  function OhmFig({ task, setTask, tasks, report, done }) {
    const [s, setS] = useState({ V: 9, R: 1000 });
    const fld = useMemo(() => fieldsOhm(s), [s]);
    useEffect(() => { report(fld); }, [s]); // eslint-disable-line
    const set = (k) => (v) => setS((o) => ({ ...o, [k]: v }));
    const Imax = 12 / Math.max(200, s.R) * 1.15;
    const xf = XF([0, 12], [0, Math.max(Imax, 0.002)]);
    const I = s.V / s.R, t = tasks.find((x) => x.id === task) || tasks[0];
    return (<>
      <TaskStrip tasks={tasks} cur={task} setCur={setTask} done={done} goal={t && t.goal} />
      <Frame>
        <Axis xf={xf} ticks={[0, 3, 6, 9, 12]} fmtT={(v) => v + "V"} unit="I vs V" />
        <path d={curveD((v) => v / s.R, xf)} fill="none" stroke={C.gold} strokeWidth="2.6" />
        <line x1={xf.sx(s.V)} y1={xf.y0} x2={xf.sx(s.V)} y2={xf.sy(clampN(I, xf.ydom[0], xf.ydom[1]))} stroke={C.crimson} strokeWidth="1.4" strokeDasharray="4 4" />
        <circle cx={xf.sx(s.V)} cy={xf.sy(clampN(I, xf.ydom[0], xf.ydom[1]))} r="6" fill={C.gold} stroke={SVG_BG} strokeWidth="2" />
        <text x={xf.x0 + 6} y={xf.y1 + 12} fill={C.gold} fontSize="13" fontFamily="'IBM Plex Mono',monospace">slope 1/R = {eng(1 / s.R, "S")} · I = {eng(I, "A")}</text>
      </Frame>
      <Status ok={fld.power >= 1}>V = {eng(s.V, "V")} · R = {eng(s.R, "Ω")} · I = V/R = {eng(I, "A")} · P = V·I = {eng(s.V * I, "W")} · <K.T>the line is steeper for smaller R.</K.T></Status>
      <Slider label="source V" value={s.V} set={set("V")} min={1} max={12} step={0.5} color={C.gold} fmtV={(v) => fmt(v, 1) + " V"} />
      <Slider label="resistance R" value={s.R} set={set("R")} min={100} max={10000} step={50} color={C.blue} fmtV={(v) => eng(v, "Ω")} />
      <Meters items={[{ label: "current I", v: eng(I, "A"), color: C.gold }, { label: "power P", v: eng(s.V * I, "W"), color: C.crimson }, { label: "conductance 1/R", v: eng(1 / s.R, "S"), color: C.teal }, { label: "voltage", v: eng(s.V, "V"), color: C.blue }]} />
    </>);
  }

  /* ════ Module 2: voltage divider — KVL ════ */
  function VdivFig({ task, setTask, tasks, report, done }) {
    const [s, setS] = useState({ Vin: 12, R1: 1000, R2: 1000 });
    const fld = useMemo(() => fieldsVdiv(s), [s]);
    useEffect(() => { report(fld); }, [s]); // eslint-disable-line
    const set = (k) => (v) => setS((o) => ({ ...o, [k]: v }));
    const I = s.Vin / (s.R1 + s.R2), v1 = I * s.R1, v2 = I * s.R2, t = tasks.find((x) => x.id === task) || tasks[0];
    return (<>
      <TaskStrip tasks={tasks} cur={task} setCur={setTask} done={done} goal={t && t.goal} />
      <Frame>
        <text x={PW / 2} y={28} textAnchor="middle" fill={C.faint} fontSize="12" fontFamily="'IBM Plex Mono',monospace">V_in = {eng(s.Vin, "V")} splits across R1 (top) and R2 (bottom)</text>
        <StackBar x={PW / 2 - 40} y={48} w={80} h={210} vertical parts={[{ frac: v1 / s.Vin, color: "#3f6377", label: "V_R1" }, { frac: v2 / s.Vin, color: C.gold, label: "V_out" }]} />
        <line x1={PW / 2 + 48} y1={48 + 210 * (v1 / s.Vin)} x2={PW / 2 + 96} y2={48 + 210 * (v1 / s.Vin)} stroke={C.gold} strokeWidth="1.6" />
        <text x={PW / 2 + 102} y={48 + 210 * (v1 / s.Vin) + 4} fill={C.gold} fontSize="12" fontFamily="'IBM Plex Mono',monospace">V_out = {eng(v2, "V")}</text>
      </Frame>
      <Status ok={fld.divRatio >= 0.45 && fld.divRatio <= 0.55}>I (series) = {eng(I, "A")} · V_R1 = {eng(v1, "V")} · V_out = {eng(v2, "V")} · ratio R2/(R1+R2) = {fmt(fld.divRatio, 3)} · <K.T>V_R1 + V_R2 = V_in (KVL).</K.T></Status>
      <Slider label="input V_in" value={s.Vin} set={set("Vin")} min={1} max={12} step={0.5} color={C.gold} fmtV={(v) => fmt(v, 1) + " V"} />
      <Slider label="R1 (top)" value={s.R1} set={set("R1")} min={100} max={10000} step={100} color={C.blue} fmtV={(v) => eng(v, "Ω")} />
      <Slider label="R2 (bottom)" value={s.R2} set={set("R2")} min={100} max={10000} step={100} color="#a98fd0" fmtV={(v) => eng(v, "Ω")} />
      <Meters items={[{ label: "V_out", v: eng(v2, "V"), color: C.gold }, { label: "V_R1", v: eng(v1, "V"), color: C.blue }, { label: "ratio", v: fld.divRatio, d: 3, color: C.teal }, { label: "ΣV (KVL)", v: eng(fld.kvlSum, "V"), color: C.mute }]} />
    </>);
  }

  /* ════ Module 3: current divider — KCL ════ */
  function IdivFig({ task, setTask, tasks, report, done }) {
    const [s, setS] = useState({ Iin: 0.03, R1: 200, R2: 600 });
    const fld = useMemo(() => fieldsIdiv(s), [s]);
    useEffect(() => { report(fld); }, [s]); // eslint-disable-line
    const set = (k) => (v) => setS((o) => ({ ...o, [k]: v }));
    const Rp = (s.R1 * s.R2) / (s.R1 + s.R2), i1 = s.Iin * s.R2 / (s.R1 + s.R2), i2 = s.Iin * s.R1 / (s.R1 + s.R2);
    const t = tasks.find((x) => x.id === task) || tasks[0];
    return (<>
      <TaskStrip tasks={tasks} cur={task} setCur={setTask} done={done} goal={t && t.goal} />
      <Frame>
        <text x={PW / 2} y={64} textAnchor="middle" fill={C.faint} fontSize="12" fontFamily="'IBM Plex Mono',monospace">I_in = {eng(s.Iin, "A")} splits between the two parallel branches</text>
        <StackBar x={PADL} y={120} w={PW - PADL - PADR} h={56} parts={[{ frac: i1 / s.Iin, color: C.teal, label: "I1" }, { frac: i2 / s.Iin, color: "#a98fd0", label: "I2" }]} />
        <text x={PW / 2} y={210} textAnchor="middle" fill={C.mute} fontSize="11" fontFamily="'IBM Plex Mono',monospace">current splits inversely to R — the smaller R carries more</text>
      </Frame>
      <Status ok={fld.rParallel < 200}>R_parallel = {eng(Rp, "Ω")} · V (shared) = {eng(s.Iin * Rp, "V")} · I1 = {eng(i1, "A")} · I2 = {eng(i2, "A")} · <K.T>I1 + I2 = I_in (KCL).</K.T></Status>
      <Slider label="input I_in" value={s.Iin} set={set("Iin")} min={0.005} max={0.06} step={0.001} color={C.gold} fmtV={(v) => eng(v, "A")} />
      <Slider label="R1" value={s.R1} set={set("R1")} min={100} max={5000} step={100} color={C.teal} fmtV={(v) => eng(v, "Ω")} />
      <Slider label="R2" value={s.R2} set={set("R2")} min={100} max={5000} step={100} color="#a98fd0" fmtV={(v) => eng(v, "Ω")} />
      <Meters items={[{ label: "I1", v: eng(i1, "A"), color: C.teal }, { label: "I2", v: eng(i2, "A"), color: "#a98fd0" }, { label: "R_parallel", v: eng(Rp, "Ω"), color: C.blue }, { label: "ΣI (KCL)", v: eng(fld.kclSum, "A"), color: C.mute }]} />
    </>);
  }

  /* ════ Module 4: RC charging — τ = RC ════ */
  function RcFig({ task, setTask, tasks, report, done }) {
    const [s, setS] = useState({ V: 10, R: 1000, Cuf: 100, t: 0.1 });
    const fld = useMemo(() => fieldsRc(s), [s]);
    useEffect(() => { report(fld); }, [s]); // eslint-disable-line
    const set = (k) => (v) => setS((o) => ({ ...o, [k]: v }));
    const tau = Math.max(1, s.R) * Math.max(1e-12, s.Cuf * 1e-6), tEnd = 5 * tau;
    const tEff = clampN(s.t, 0, tEnd), vC = s.V * (1 - Math.exp(-tEff / tau));
    const xf = XF([0, tEnd], [0, s.V * 1.12]), t = tasks.find((x) => x.id === task) || tasks[0];
    return (<>
      <TaskStrip tasks={tasks} cur={task} setCur={setTask} done={done} goal={t && t.goal} />
      <Frame>
        <Axis xf={xf} ticks={[0, tau, 2 * tau, 3 * tau, 4 * tau, 5 * tau]} fmtT={(v) => eng(v, "s")} unit="V_C vs t" />
        <line x1={xf.x0} y1={xf.sy(s.V)} x2={xf.x1} y2={xf.sy(s.V)} stroke={C.faint} strokeWidth="1" strokeDasharray="3 4" />
        <line x1={xf.sx(tau)} y1={xf.y0} x2={xf.sx(tau)} y2={xf.sy(s.V * 0.632)} stroke={C.teal} strokeWidth="1.3" strokeDasharray="4 4" />
        <line x1={xf.x0} y1={xf.sy(s.V * 0.632)} x2={xf.sx(tau)} y2={xf.sy(s.V * 0.632)} stroke={C.teal} strokeWidth="1" strokeDasharray="3 4" />
        <text x={xf.sx(tau) + 4} y={xf.sy(s.V * 0.632) - 6} fill={C.teal} fontSize="10.5" fontFamily="'IBM Plex Mono',monospace">63.2% @ τ</text>
        <path d={curveD((x) => s.V * (1 - Math.exp(-x / tau)), xf)} fill="none" stroke={C.gold} strokeWidth="2.6" />
        <line x1={xf.sx(tEff)} y1={xf.y0} x2={xf.sx(tEff)} y2={xf.sy(vC)} stroke={C.crimson} strokeWidth="1.4" strokeDasharray="4 4" />
        <circle cx={xf.sx(tEff)} cy={xf.sy(vC)} r="6" fill={C.crimson} stroke={SVG_BG} strokeWidth="2" />
      </Frame>
      <Status ok={fld.pctCharged >= 55 && fld.pctCharged <= 70}>τ = RC = {eng(tau, "s")} · t = {eng(tEff, "s")} · V_C = {eng(vC, "V")} · {fmt(fld.pctCharged, 1)}% charged · <K.T>63.2% at one time constant.</K.T></Status>
      <Slider label="source V" value={s.V} set={set("V")} min={5} max={12} step={0.5} color={C.gold} fmtV={(v) => fmt(v, 1) + " V"} />
      <Slider label="resistance R" value={s.R} set={set("R")} min={100} max={10000} step={100} color={C.blue} fmtV={(v) => eng(v, "Ω")} />
      <Slider label="capacitance C" value={s.Cuf} set={set("Cuf")} min={1} max={200} step={1} color="#a98fd0" fmtV={(v) => v + " µF"} />
      <Slider label="time cursor t" value={s.t} set={set("t")} min={0} max={tEnd} step={tEnd / 200} color={C.crimson} fmtV={() => eng(tEff, "s")} />
      <Meters items={[{ label: "τ = RC", v: eng(tau, "s"), color: C.teal }, { label: "V_C(t)", v: eng(vC, "V"), color: C.gold }, { label: "% charged", v: fmt(fld.pctCharged, 1) + "%", color: C.crimson }, { label: "i(t)", v: eng((s.V / Math.max(1, s.R)) * Math.exp(-tEff / tau), "A"), color: C.blue }]} />
    </>);
  }

  /* ════ Module 5: RC low-pass filter — f_c ════ */
  function LpfFig({ task, setTask, tasks, report, done }) {
    const [s, setS] = useState({ Rf: 1000, Cnf: 100, fx: 3.2 });
    const fld = useMemo(() => fieldsLpf(s), [s]);
    useEffect(() => { report(fld); }, [s]); // eslint-disable-line
    const set = (k) => (v) => setS((o) => ({ ...o, [k]: v }));
    const fc = 1 / (TWO_PI * Math.max(1, s.Rf) * Math.max(1e-12, s.Cnf * 1e-9));
    const dB = (lx) => { const f = Math.pow(10, lx); return 20 * Math.log10(1 / Math.sqrt(1 + (f / fc) ** 2)); };
    const xf = XF([1, 6], [-42, 4]), db = dB(s.fx), t = tasks.find((x) => x.id === task) || tasks[0];
    return (<>
      <TaskStrip tasks={tasks} cur={task} setCur={setTask} done={done} goal={t && t.goal} />
      <Frame>
        <Axis xf={xf} ticks={[1, 2, 3, 4, 5, 6]} fmtT={(k) => eng(Math.pow(10, k), "Hz")} unit="|H| (dB) vs f" />
        {[-3, -20, -40].map((d, i) => <line key={i} x1={xf.x0} y1={xf.sy(d)} x2={xf.x1} y2={xf.sy(d)} stroke={C.faint} strokeWidth="1" strokeDasharray="3 4" />)}
        <text x={xf.x1 - 4} y={xf.sy(-3) - 4} textAnchor="end" fill={C.faint} fontSize="10" fontFamily="'IBM Plex Mono',monospace">−3 dB</text>
        <path d={curveD((lx) => dB(lx), xf)} fill="none" stroke={C.gold} strokeWidth="2.6" />
        <line x1={xf.sx(Math.log10(fc))} y1={xf.y0} x2={xf.sx(Math.log10(fc))} y2={xf.sy(-3)} stroke={C.teal} strokeWidth="1.3" strokeDasharray="4 4" />
        <text x={xf.sx(clampN(Math.log10(fc), 1, 6))} y={xf.y0 - 4} textAnchor="middle" fill={C.teal} fontSize="11" fontFamily="'IBM Plex Mono',monospace">f_c</text>
        <line x1={xf.sx(s.fx)} y1={xf.y0} x2={xf.sx(s.fx)} y2={xf.sy(clampN(db, -42, 4))} stroke={C.crimson} strokeWidth="1.4" strokeDasharray="4 4" />
        <circle cx={xf.sx(s.fx)} cy={xf.sy(clampN(db, -42, 4))} r="6" fill={C.crimson} stroke={SVG_BG} strokeWidth="2" />
      </Frame>
      <Status ok={fld.gainDb >= -3.5 && fld.gainDb <= -2.5}>f_c = 1/(2πRC) = {eng(fc, "Hz")} · f = {eng(fld.freq, "Hz")} · |H| = {fmt(fld.gainDb, 1)} dB ({fmt(fld.gainRatio, 3)}) · <K.T>−3 dB at the corner, then −20 dB/decade.</K.T></Status>
      <Slider label="resistance R" value={s.Rf} set={set("Rf")} min={100} max={10000} step={100} color={C.blue} fmtV={(v) => eng(v, "Ω")} />
      <Slider label="capacitance C" value={s.Cnf} set={set("Cnf")} min={1} max={1000} step={1} color="#a98fd0" fmtV={(v) => v + " nF"} />
      <Slider label="frequency cursor f" value={s.fx} set={set("fx")} min={1} max={6} step={0.01} color={C.crimson} fmtV={(v) => eng(Math.pow(10, v), "Hz")} />
      <Meters items={[{ label: "f_c", v: eng(fc, "Hz"), color: C.teal }, { label: "freq", v: eng(fld.freq, "Hz"), color: C.crimson }, { label: "|H| dB", v: fld.gainDb, d: 1, color: C.gold }, { label: "|H| ratio", v: fld.gainRatio, d: 3, color: C.blue }]} />
    </>);
  }

  /* ════ Module 6: series RLC resonance — f₀, Q ════ */
  function RlcFig({ task, setTask, tasks, report, done }) {
    const [s, setS] = useState({ Rr: 50, Lmh: 10, Crnf: 100, fxr: 3.7 });
    const fld = useMemo(() => fieldsRlc(s), [s]);
    useEffect(() => { report(fld); }, [s]); // eslint-disable-line
    const set = (k) => (v) => setS((o) => ({ ...o, [k]: v }));
    const R = Math.max(0.01, s.Rr), L = Math.max(1e-9, s.Lmh * 1e-3), Cc = Math.max(1e-12, s.Crnf * 1e-9);
    const f0 = 1 / (TWO_PI * Math.sqrt(L * Cc));
    const dB = (lx) => { const w = TWO_PI * Math.pow(10, lx); return 20 * Math.log10(R / Math.sqrt(R * R + (w * L - 1 / (w * Cc)) ** 2)); };
    const xf = XF([1.5, 5.5], [-42, 4]), db = dB(s.fxr), t = tasks.find((x) => x.id === task) || tasks[0];
    return (<>
      <TaskStrip tasks={tasks} cur={task} setCur={setTask} done={done} goal={t && t.goal} />
      <Frame>
        <Axis xf={xf} ticks={[2, 3, 4, 5]} fmtT={(k) => eng(Math.pow(10, k), "Hz")} unit="|H| (dB) vs f" />
        {[0, -3, -20, -40].map((d, i) => <line key={i} x1={xf.x0} y1={xf.sy(d)} x2={xf.x1} y2={xf.sy(d)} stroke={C.faint} strokeWidth="1" strokeDasharray="3 4" />)}
        <path d={curveD((lx) => dB(lx), xf, 380)} fill="none" stroke={C.gold} strokeWidth="2.6" />
        <line x1={xf.sx(clampN(Math.log10(f0), 1.5, 5.5))} y1={xf.y0} x2={xf.sx(clampN(Math.log10(f0), 1.5, 5.5))} y2={xf.sy(0)} stroke={C.teal} strokeWidth="1.3" strokeDasharray="4 4" />
        <text x={xf.sx(clampN(Math.log10(f0), 1.5, 5.5))} y={xf.y0 - 4} textAnchor="middle" fill={C.teal} fontSize="11" fontFamily="'IBM Plex Mono',monospace">f₀</text>
        <line x1={xf.sx(s.fxr)} y1={xf.y0} x2={xf.sx(s.fxr)} y2={xf.sy(clampN(db, -42, 4))} stroke={C.crimson} strokeWidth="1.4" strokeDasharray="4 4" />
        <circle cx={xf.sx(s.fxr)} cy={xf.sy(clampN(db, -42, 4))} r="6" fill={C.crimson} stroke={SVG_BG} strokeWidth="2" />
      </Frame>
      <Status ok={fld.qFactor >= 5}>f₀ = 1/(2π√(LC)) = {eng(f0, "Hz")} · Q = (1/R)√(L/C) = {fmt(fld.qFactor, 2)} · bandwidth f₀/Q = {eng(fld.bandwidth, "Hz")} · <K.T>smaller R ⇒ sharper peak.</K.T></Status>
      <Slider label="resistance R" value={s.Rr} set={set("Rr")} min={5} max={500} step={5} color={C.blue} fmtV={(v) => eng(v, "Ω")} />
      <Slider label="inductance L" value={s.Lmh} set={set("Lmh")} min={1} max={100} step={1} color={C.teal} fmtV={(v) => v + " mH"} />
      <Slider label="capacitance C" value={s.Crnf} set={set("Crnf")} min={1} max={1000} step={1} color="#a98fd0" fmtV={(v) => v + " nF"} />
      <Slider label="frequency cursor f" value={s.fxr} set={set("fxr")} min={1.5} max={5.5} step={0.01} color={C.crimson} fmtV={(v) => eng(Math.pow(10, v), "Hz")} />
      <Meters items={[{ label: "f₀", v: eng(f0, "Hz"), color: C.teal }, { label: "Q", v: fld.qFactor, d: 2, color: C.gold }, { label: "bandwidth", v: eng(fld.bandwidth, "Hz"), color: C.blue }, { label: "|H| dB", v: fld.gainDb, d: 1, color: C.crimson }]} />
    </>);
  }

  const FIGS = { ohm: OhmFig, vdiv: VdivFig, idiv: IdivFig, rc: RcFig, lpf: LpfFig, rlc: RlcFig };
  const MODULE_LABEL = {
    ohm: "Ohm's law — V, I, R and power", vdiv: "Voltage divider (KVL)", idiv: "Current divider (KCL)",
    rc: "RC charging — τ = RC", lpf: "RC low-pass filter — f_c", rlc: "Series RLC resonance — f₀, Q",
  };

  function AdvElectronicsFig(props) {
    const module = FIGS[props.spec && props.spec.module] ? props.spec.module : "ohm";
    const Fig = FIGS[module];
    return <Fig {...props} />;
  }

  window.AdvElectronicsTutor = K.makeTutor(AdvElectronicsFig, {
    benchId: "advelectronics",
    moduleLabel: (spec) => MODULE_LABEL[spec && spec.module] || "Advanced electronics workbench",
  });
})();
