/* flow-sandbox.jsx — Falstad-style free-placement sandbox.
   M1 geometry→netlist · M2 inspect · M3 drag · M4 palette/add/delete/rotate/wire.
   M5: inspector + per-part sliders in a side rail (outside the canvas), bipolar
   battery slider (negative → charges reverse & go red), switch parts (tap to
   toggle). Names fsb*. */

const { useState: fsbUseState, useMemo: fsbUseMemo, useRef: fsbUseRef, useEffect: fsbUseEffect } = React;
// Charges stay a single colour (always yellow). The PATHWAY turns red when its
// current is reversed (negative) — so an LC/RLC oscillation visibly flips the
// path red on the back half-cycle, while the dots keep moving as plain charges.
window.CL_DIRCOLOR = true;
const FSB_GRID = 20;
const fsbSnap = (val) => Math.round(val / FSB_GRID) * FSB_GRID;
const fsbSnapPt = (p) => ({ x: fsbSnap(p.x), y: fsbSnap(p.y) });

const FSB_RVALS = [1, 2.2, 4.7, 10, 22, 47, 100, 220, 330, 470, 680, 1000, 2200, 4700, 10000, 22000, 47000, 100000];
const FSB_CVALS = [1, 2, 5, 10, 22, 47];
const FSB_LVALS = [1, 2, 5, 10, 22, 47];
const fsbNearest = (arr, val) => { let bi = 0, bd = Infinity; arr.forEach((a, i) => { const d = Math.abs(a - val); if (d < bd) { bd = d; bi = i; } }); return bi; };

const FSB_DEMO = [
  { id: "bat", type: "battery", ends: [{ x: 120, y: 180 }, { x: 120, y: 300 }], volts: 9 },
  { id: "w1",  type: "wire", ends: [{ x: 120, y: 180 }, { x: 120, y: 100 }] },
  { id: "wA",  type: "wire", ends: [{ x: 120, y: 100 }, { x: 200, y: 100 }] },
  { id: "r1",  type: "resistor", ends: [{ x: 200, y: 100 }, { x: 400, y: 100 }], value: 330 },
  { id: "wB",  type: "wire", ends: [{ x: 400, y: 100 }, { x: 480, y: 100 }] },
  { id: "wBr", type: "wire", ends: [{ x: 480, y: 100 }, { x: 480, y: 160 }] },
  { id: "r2",  type: "resistor", ends: [{ x: 480, y: 160 }, { x: 480, y: 280 }], value: 470 },
  { id: "wC",  type: "wire", ends: [{ x: 480, y: 280 }, { x: 480, y: 340 }] },
  { id: "wbot",type: "wire", ends: [{ x: 480, y: 340 }, { x: 120, y: 340 }] },
  { id: "wD",  type: "wire", ends: [{ x: 120, y: 340 }, { x: 120, y: 300 }] },
  { id: "gnd1",type: "ground", ends: [{ x: 120, y: 340 }] },
];

function fsbMakeDefault(type, id) {
  const cx = 300, cy = 220;
  switch (type) {
    case "wire":      return { id, type, ends: [{ x: cx - 40, y: cy }, { x: cx + 40, y: cy }] };
    case "resistor":  return { id, type, value: 1000, ends: [{ x: cx - 60, y: cy }, { x: cx + 60, y: cy }] };
    case "battery":   return { id, type, volts: 9, ends: [{ x: cx, y: cy - 60 }, { x: cx, y: cy + 60 }] };
    case "led":       return { id, type, ends: [{ x: cx - 40, y: cy }, { x: cx + 40, y: cy }] };
    case "diode":     return { id, type, ends: [{ x: cx - 40, y: cy }, { x: cx + 40, y: cy }] };
    case "cap":       return { id, type, value: 2, ends: [{ x: cx, y: cy - 30 }, { x: cx, y: cy + 30 }] };
    case "ind":       return { id, type, value: 10, ends: [{ x: cx, y: cy - 40 }, { x: cx, y: cy + 40 }] };
    case "pot":       return { id, type, value: 10000, pos: 0.5, ends: [{ x: cx - 60, y: cy }, { x: cx, y: cy - 44 }, { x: cx + 60, y: cy }] };
    case "npn":       return { id, type, ends: [{ x: cx - 44, y: cy }, { x: cx + 20, y: cy - 44 }, { x: cx + 20, y: cy + 44 }] };
    case "switch":    return { id, type, closed: true, ends: [{ x: cx - 40, y: cy }, { x: cx + 40, y: cy }] };
    case "ground":    return { id, type, ends: [{ x: cx, y: cy }] };
    default:          return { id, type, ends: [{ x: cx - 40, y: cy }, { x: cx + 40, y: cy }] };
  }
}

const FSB_PALETTE = [
  { type: "wire", label: "Wire", tool: true },
  { type: "resistor", label: "Resistor" },
  { type: "battery", label: "Battery" },
  { type: "switch", label: "Switch" },
  { type: "led", label: "LED" },
  { type: "diode", label: "Diode" },
  { type: "cap", label: "Capacitor" },
  { type: "ind", label: "Inductor" },
  { type: "pot", label: "Potentiometer" },
  { type: "npn", label: "Transistor" },
  { type: "ground", label: "Ground" },
];

// ── example circuits (fresh copy each load) ──
const FSB_EXAMPLES = [
  { key: "series", cat: "Basics", label: "Series", make: () => FSB_DEMO.map(p => ({ ...p, ends: p.ends.map(e => ({ ...e })) })) },
  { key: "divider", cat: "Basics", label: "Voltage divider", make: () => ([
    { id: "bat", type: "battery", volts: 9, ends: [{ x: 120, y: 160 }, { x: 120, y: 300 }] },
    { id: "wu", type: "wire", ends: [{ x: 120, y: 160 }, { x: 120, y: 110 }] },
    { id: "wt", type: "wire", ends: [{ x: 120, y: 110 }, { x: 300, y: 110 }] },
    { id: "r1", type: "resistor", value: 1000, ends: [{ x: 300, y: 110 }, { x: 300, y: 210 }] },
    { id: "r2", type: "resistor", value: 1000, ends: [{ x: 300, y: 210 }, { x: 300, y: 330 }] },
    { id: "wb", type: "wire", ends: [{ x: 300, y: 330 }, { x: 120, y: 330 }] },
    { id: "wd", type: "wire", ends: [{ x: 120, y: 300 }, { x: 120, y: 330 }] },
    { id: "gnd", type: "ground", ends: [{ x: 120, y: 330 }] },
    { id: "wtap", type: "wire", ends: [{ x: 300, y: 210 }, { x: 400, y: 210 }] },
    { id: "r3", type: "resistor", value: 470, ends: [{ x: 400, y: 210 }, { x: 400, y: 260 }] },
    { id: "led", type: "led", ends: [{ x: 400, y: 260 }, { x: 400, y: 320 }] },
    { id: "wld", type: "wire", ends: [{ x: 400, y: 320 }, { x: 400, y: 330 }] },
    { id: "wld2", type: "wire", ends: [{ x: 400, y: 330 }, { x: 300, y: 330 }] },
  ]) },
  { key: "parallel", cat: "Basics", label: "3∥ → wire → 3∥", make: () => {
    const xs = [220, 330, 440], valsA = [100, 330, 1000], valsB = [220, 680, 2200];
    const cx = 330;                                   // node where the banks converge
    const topY = 110, aBot = 210, bTop = 270, botY = 350;
    const out = [
      { id: "bat", type: "battery", volts: 9, ends: [{ x: 120, y: 150 }, { x: 120, y: 310 }] },
      { id: "wu", type: "wire", ends: [{ x: 120, y: 150 }, { x: 120, y: topY }] },
      { id: "wd", type: "wire", ends: [{ x: 120, y: 310 }, { x: 120, y: botY }] },
      { id: "gnd", type: "ground", ends: [{ x: 120, y: botY }] },
      { id: "wtop0", type: "wire", ends: [{ x: 120, y: topY }, { x: xs[0], y: topY }] },
      { id: "wbot0", type: "wire", ends: [{ x: 120, y: botY }, { x: xs[0], y: botY }] },
      // the single wire joining the two parallel banks
      { id: "link", type: "wire", ends: [{ x: cx, y: aBot }, { x: cx, y: bTop }] },
    ];
    xs.forEach((x, i) => {
      // bank A: top rail → switch → resistor → converge rail (aBot)
      out.push({ id: "swA" + i, type: "switch", closed: true, ends: [{ x, y: topY }, { x, y: topY + 50 }] });
      out.push({ id: "rA" + i, type: "resistor", value: valsA[i], ends: [{ x, y: topY + 50 }, { x, y: aBot }] });
      // bank B: distribute rail (bTop) → switch → resistor → bottom rail
      out.push({ id: "swB" + i, type: "switch", closed: true, ends: [{ x, y: bTop }, { x, y: bTop + 50 }] });
      out.push({ id: "rB" + i, type: "resistor", value: valsB[i], ends: [{ x, y: bTop + 50 }, { x, y: botY }] });
      if (i < xs.length - 1) {
        out.push({ id: "wtop" + (i + 1), type: "wire", ends: [{ x, y: topY }, { x: xs[i + 1], y: topY }] });
        out.push({ id: "waBot" + (i + 1), type: "wire", ends: [{ x, y: aBot }, { x: xs[i + 1], y: aBot }] });
        out.push({ id: "wbTop" + (i + 1), type: "wire", ends: [{ x, y: bTop }, { x: xs[i + 1], y: bTop }] });
        out.push({ id: "wbot" + (i + 1), type: "wire", ends: [{ x, y: botY }, { x: xs[i + 1], y: botY }] });
      }
    });
    return out;
  } },
  { key: "twobatt", cat: "Basics", label: "Batteries in series", make: () => ([
    { id: "bat1", type: "battery", volts: 9, ends: [{ x: 120, y: 120 }, { x: 120, y: 210 }] },
    { id: "bat2", type: "battery", volts: 9, ends: [{ x: 120, y: 210 }, { x: 120, y: 300 }] },
    { id: "wu", type: "wire", ends: [{ x: 120, y: 120 }, { x: 120, y: 90 }] },
    { id: "rail", type: "wire", ends: [{ x: 120, y: 90 }, { x: 440, y: 90 }] },
    { id: "r1", type: "resistor", value: 470, ends: [{ x: 440, y: 90 }, { x: 440, y: 170 }] },
    { id: "led", type: "led", ends: [{ x: 440, y: 170 }, { x: 440, y: 250 }] },
    { id: "wb", type: "wire", ends: [{ x: 440, y: 250 }, { x: 440, y: 300 }] },
    { id: "wbot", type: "wire", ends: [{ x: 440, y: 300 }, { x: 120, y: 300 }] },
    { id: "gnd", type: "ground", ends: [{ x: 120, y: 210 }] },
  ]) },
  { key: "noresistor", cat: "Basics", label: "LED · no resistor ⚠", make: () => ([
    { id: "bat", type: "battery", volts: 9, ends: [{ x: 120, y: 150 }, { x: 120, y: 300 }] },
    { id: "wu", type: "wire", ends: [{ x: 120, y: 150 }, { x: 120, y: 120 }] },
    { id: "rail", type: "wire", ends: [{ x: 120, y: 120 }, { x: 440, y: 120 }] },
    { id: "led", type: "led", ends: [{ x: 440, y: 120 }, { x: 440, y: 250 }] },
    { id: "wb", type: "wire", ends: [{ x: 440, y: 250 }, { x: 120, y: 250 }] },
    { id: "wd", type: "wire", ends: [{ x: 120, y: 300 }, { x: 120, y: 250 }] },
    { id: "gnd", type: "ground", ends: [{ x: 120, y: 300 }] },
  ]) },
  { key: "switched", cat: "Controls", label: "Switch + LED", make: () => ([
    { id: "bat", type: "battery", volts: 9, ends: [{ x: 120, y: 180 }, { x: 120, y: 300 }] },
    { id: "wu", type: "wire", ends: [{ x: 120, y: 180 }, { x: 120, y: 120 }] },
    { id: "wt", type: "wire", ends: [{ x: 120, y: 120 }, { x: 200, y: 120 }] },
    { id: "sw", type: "switch", closed: true, ends: [{ x: 200, y: 120 }, { x: 300, y: 120 }] },
    { id: "r1", type: "resistor", value: 330, ends: [{ x: 300, y: 120 }, { x: 440, y: 120 }] },
    { id: "wr", type: "wire", ends: [{ x: 440, y: 120 }, { x: 440, y: 180 }] },
    { id: "led", type: "led", ends: [{ x: 440, y: 180 }, { x: 440, y: 280 }] },
    { id: "wc", type: "wire", ends: [{ x: 440, y: 280 }, { x: 440, y: 320 }] },
    { id: "wb", type: "wire", ends: [{ x: 440, y: 320 }, { x: 120, y: 320 }] },
    { id: "wd", type: "wire", ends: [{ x: 120, y: 300 }, { x: 120, y: 320 }] },
    { id: "gnd", type: "ground", ends: [{ x: 120, y: 320 }] },
  ]) },
  { key: "branches", cat: "Controls", label: "Two switched branches", make: () => ([
    { id: "bat", type: "battery", volts: 9, ends: [{ x: 120, y: 180 }, { x: 120, y: 300 }] },
    { id: "wu", type: "wire", ends: [{ x: 120, y: 180 }, { x: 120, y: 120 }] },
    { id: "wt1", type: "wire", ends: [{ x: 120, y: 120 }, { x: 280, y: 120 }] },
    { id: "wt2", type: "wire", ends: [{ x: 280, y: 120 }, { x: 420, y: 120 }] },
    { id: "sw1", type: "switch", closed: true, ends: [{ x: 280, y: 120 }, { x: 280, y: 200 }] },
    { id: "r1", type: "resistor", value: 330, ends: [{ x: 280, y: 200 }, { x: 280, y: 320 }] },
    { id: "sw2", type: "switch", closed: false, ends: [{ x: 420, y: 120 }, { x: 420, y: 200 }] },
    { id: "r2", type: "resistor", value: 680, ends: [{ x: 420, y: 200 }, { x: 420, y: 320 }] },
    { id: "wb1", type: "wire", ends: [{ x: 120, y: 320 }, { x: 280, y: 320 }] },
    { id: "wb2", type: "wire", ends: [{ x: 280, y: 320 }, { x: 420, y: 320 }] },
    { id: "wd", type: "wire", ends: [{ x: 120, y: 300 }, { x: 120, y: 320 }] },
    { id: "gnd", type: "ground", ends: [{ x: 120, y: 320 }] },
  ]) },
  { key: "pot", cat: "Controls", label: "Potentiometer", make: () => ([
    { id: "bat", type: "battery", volts: 9, ends: [{ x: 120, y: 150 }, { x: 120, y: 310 }] },
    { id: "wu", type: "wire", ends: [{ x: 120, y: 150 }, { x: 120, y: 110 }] },
    { id: "wt", type: "wire", ends: [{ x: 120, y: 110 }, { x: 300, y: 110 }] },
    { id: "pot", type: "pot", value: 1000, pos: 0.5, ends: [{ x: 300, y: 110 }, { x: 380, y: 220 }, { x: 300, y: 330 }] },
    { id: "wb", type: "wire", ends: [{ x: 300, y: 330 }, { x: 120, y: 330 }] },
    { id: "wd", type: "wire", ends: [{ x: 120, y: 310 }, { x: 120, y: 330 }] },
    { id: "gnd", type: "ground", ends: [{ x: 120, y: 330 }] },
    { id: "ww", type: "wire", ends: [{ x: 380, y: 220 }, { x: 460, y: 220 }] },
    { id: "r3", type: "resistor", value: 100, ends: [{ x: 460, y: 220 }, { x: 460, y: 270 }] },
    { id: "led", type: "led", ends: [{ x: 460, y: 270 }, { x: 460, y: 330 }] },
    { id: "wl2", type: "wire", ends: [{ x: 460, y: 330 }, { x: 300, y: 330 }] },
  ]) },
  { key: "npn", cat: "Controls", label: "Transistor switch", make: () => ([
    { id: "bat", type: "battery", volts: 9, ends: [{ x: 120, y: 150 }, { x: 120, y: 310 }] },
    { id: "wu", type: "wire", ends: [{ x: 120, y: 150 }, { x: 120, y: 110 }] },
    { id: "wd", type: "wire", ends: [{ x: 120, y: 310 }, { x: 120, y: 350 }] },
    { id: "gnd", type: "ground", ends: [{ x: 120, y: 350 }] },
    { id: "rail1", type: "wire", ends: [{ x: 120, y: 110 }, { x: 240, y: 110 }] },
    { id: "rail2", type: "wire", ends: [{ x: 240, y: 110 }, { x: 440, y: 110 }] },
    { id: "wcol", type: "wire", ends: [{ x: 440, y: 110 }, { x: 440, y: 150 }] },
    { id: "led", type: "led", ends: [{ x: 440, y: 150 }, { x: 440, y: 210 }] },
    { id: "npn", type: "npn", ends: [{ x: 360, y: 250 }, { x: 440, y: 210 }, { x: 440, y: 290 }] },
    { id: "wemi", type: "wire", ends: [{ x: 440, y: 290 }, { x: 440, y: 350 }] },
    { id: "wbot", type: "wire", ends: [{ x: 440, y: 350 }, { x: 120, y: 350 }] },
    { id: "wbase", type: "wire", ends: [{ x: 240, y: 110 }, { x: 240, y: 200 }] },
    { id: "swb", type: "switch", closed: true, ends: [{ x: 240, y: 200 }, { x: 240, y: 250 }] },
    { id: "rb", type: "resistor", value: 4700, ends: [{ x: 240, y: 250 }, { x: 360, y: 250 }] },
  ]) },
  { key: "inverter", cat: "Controls", label: "NOT gate (inverter)", make: () => ([
    { id: "bat", type: "battery", volts: 9, ends: [{ x: 120, y: 150 }, { x: 120, y: 340 }] },
    { id: "wu", type: "wire", ends: [{ x: 120, y: 150 }, { x: 120, y: 100 }] },
    { id: "railA", type: "wire", ends: [{ x: 120, y: 100 }, { x: 240, y: 100 }] },
    { id: "railB", type: "wire", ends: [{ x: 240, y: 100 }, { x: 440, y: 100 }] },
    { id: "wpc", type: "wire", ends: [{ x: 440, y: 100 }, { x: 440, y: 140 }] },
    { id: "rp", type: "resistor", value: 470, ends: [{ x: 440, y: 140 }, { x: 440, y: 180 }] },
    { id: "npn", type: "npn", ends: [{ x: 340, y: 240 }, { x: 440, y: 180 }, { x: 440, y: 300 }] },
    { id: "wemi", type: "wire", ends: [{ x: 440, y: 300 }, { x: 440, y: 350 }] },
    { id: "wd", type: "wire", ends: [{ x: 120, y: 340 }, { x: 120, y: 350 }] },
    { id: "brA", type: "wire", ends: [{ x: 120, y: 350 }, { x: 340, y: 350 }] },
    { id: "brB", type: "wire", ends: [{ x: 340, y: 350 }, { x: 440, y: 350 }] },
    { id: "brC", type: "wire", ends: [{ x: 440, y: 350 }, { x: 560, y: 350 }] },
    { id: "gnd", type: "ground", ends: [{ x: 120, y: 350 }] },
    { id: "wlc", type: "wire", ends: [{ x: 440, y: 180 }, { x: 560, y: 180 }] },
    { id: "led", type: "led", ends: [{ x: 560, y: 180 }, { x: 560, y: 250 }] },
    { id: "rl", type: "resistor", value: 220, ends: [{ x: 560, y: 250 }, { x: 560, y: 350 }] },
    { id: "wbr", type: "wire", ends: [{ x: 240, y: 100 }, { x: 240, y: 185 }] },
    { id: "swb", type: "switch", closed: false, ends: [{ x: 240, y: 185 }, { x: 240, y: 240 }] },
    { id: "rb", type: "resistor", value: 4700, ends: [{ x: 240, y: 240 }, { x: 340, y: 240 }] },
    { id: "rbd", type: "resistor", value: 47000, ends: [{ x: 340, y: 240 }, { x: 340, y: 350 }] },
  ]) },
  { key: "rc", cat: "Reactive", label: "Capacitor (RC)", make: () => ([
    { id: "bat", type: "battery", volts: 9, ends: [{ x: 120, y: 180 }, { x: 120, y: 300 }] },
    { id: "wu", type: "wire", ends: [{ x: 120, y: 180 }, { x: 120, y: 120 }] },
    { id: "wt", type: "wire", ends: [{ x: 120, y: 120 }, { x: 240, y: 120 }] },
    { id: "sw", type: "switch", closed: true, ends: [{ x: 240, y: 120 }, { x: 320, y: 120 }] },
    { id: "r1", type: "resistor", value: 47, ends: [{ x: 320, y: 120 }, { x: 440, y: 120 }] },
    { id: "wr", type: "wire", ends: [{ x: 440, y: 120 }, { x: 440, y: 180 }] },
    { id: "cap", type: "cap", value: 10, ends: [{ x: 440, y: 180 }, { x: 440, y: 260 }] },
    { id: "wc", type: "wire", ends: [{ x: 440, y: 260 }, { x: 440, y: 320 }] },
    { id: "wb", type: "wire", ends: [{ x: 440, y: 320 }, { x: 120, y: 320 }] },
    { id: "wd", type: "wire", ends: [{ x: 120, y: 300 }, { x: 120, y: 320 }] },
    { id: "gnd", type: "ground", ends: [{ x: 120, y: 320 }] },
  ]) },
  { key: "rl", cat: "Reactive", label: "Inductor (RL)", make: () => ([
    { id: "bat", type: "battery", volts: 9, ends: [{ x: 120, y: 180 }, { x: 120, y: 300 }] },
    { id: "wu", type: "wire", ends: [{ x: 120, y: 180 }, { x: 120, y: 120 }] },
    { id: "wt", type: "wire", ends: [{ x: 120, y: 120 }, { x: 240, y: 120 }] },
    { id: "sw", type: "switch", closed: true, ends: [{ x: 240, y: 120 }, { x: 320, y: 120 }] },
    { id: "r1", type: "resistor", value: 22, ends: [{ x: 320, y: 120 }, { x: 440, y: 120 }] },
    { id: "wr", type: "wire", ends: [{ x: 440, y: 120 }, { x: 440, y: 160 }] },
    { id: "ind", type: "ind", value: 22, ends: [{ x: 440, y: 160 }, { x: 440, y: 280 }] },
    { id: "wc", type: "wire", ends: [{ x: 440, y: 280 }, { x: 440, y: 320 }] },
    { id: "wb", type: "wire", ends: [{ x: 440, y: 320 }, { x: 120, y: 320 }] },
    { id: "wd", type: "wire", ends: [{ x: 120, y: 300 }, { x: 120, y: 320 }] },
    { id: "gnd", type: "ground", ends: [{ x: 120, y: 320 }] },
  ]) },
  { key: "flyback", cat: "Reactive", label: "Flyback diode", make: () => ([
    { id: "bat", type: "battery", volts: 9, ends: [{ x: 120, y: 150 }, { x: 120, y: 320 }] },
    { id: "wu", type: "wire", ends: [{ x: 120, y: 150 }, { x: 120, y: 110 }] },
    { id: "wt", type: "wire", ends: [{ x: 120, y: 110 }, { x: 220, y: 110 }] },
    { id: "sw", type: "switch", closed: true, ends: [{ x: 220, y: 110 }, { x: 300, y: 110 }] },
    { id: "r1", type: "resistor", value: 47, ends: [{ x: 300, y: 110 }, { x: 420, y: 110 }] },
    { id: "wa", type: "wire", ends: [{ x: 420, y: 110 }, { x: 420, y: 150 }] },
    { id: "ind", type: "ind", value: 22, ends: [{ x: 420, y: 150 }, { x: 420, y: 290 }] },
    { id: "wc", type: "wire", ends: [{ x: 420, y: 290 }, { x: 420, y: 330 }] },
    { id: "wb", type: "wire", ends: [{ x: 420, y: 330 }, { x: 120, y: 330 }] },
    { id: "wd", type: "wire", ends: [{ x: 120, y: 320 }, { x: 120, y: 330 }] },
    { id: "gnd", type: "ground", ends: [{ x: 120, y: 330 }] },
    { id: "wfa", type: "wire", ends: [{ x: 420, y: 290 }, { x: 520, y: 290 }] },
    { id: "fly", type: "diode", ends: [{ x: 520, y: 290 }, { x: 520, y: 150 }] },
    { id: "wfc", type: "wire", ends: [{ x: 520, y: 150 }, { x: 420, y: 150 }] },
  ]) },
  { key: "lc", cat: "Reactive", label: "LC oscillator", make: () => ([
    { id: "bat", type: "battery", volts: 9, ends: [{ x: 120, y: 180 }, { x: 120, y: 300 }] },
    { id: "wu", type: "wire", ends: [{ x: 120, y: 180 }, { x: 120, y: 120 }] },
    { id: "r1", type: "resistor", value: 1, ends: [{ x: 120, y: 120 }, { x: 260, y: 120 }] },
    { id: "ind", type: "ind", value: 5, ends: [{ x: 260, y: 120 }, { x: 400, y: 120 }] },
    { id: "wr", type: "wire", ends: [{ x: 400, y: 120 }, { x: 440, y: 120 }] },
    { id: "wr2", type: "wire", ends: [{ x: 440, y: 120 }, { x: 440, y: 160 }] },
    { id: "cap", type: "cap", value: 10, ends: [{ x: 440, y: 160 }, { x: 440, y: 260 }] },
    { id: "wc", type: "wire", ends: [{ x: 440, y: 260 }, { x: 440, y: 320 }] },
    { id: "wb", type: "wire", ends: [{ x: 440, y: 320 }, { x: 120, y: 320 }] },
    { id: "wd", type: "wire", ends: [{ x: 120, y: 300 }, { x: 120, y: 320 }] },
    { id: "gnd", type: "ground", ends: [{ x: 120, y: 320 }] },
  ]) },
  { key: "rlc", cat: "Reactive", label: "RLC (damped)", make: () => ([
    { id: "bat", type: "battery", volts: 9, ends: [{ x: 120, y: 180 }, { x: 120, y: 300 }] },
    { id: "wu", type: "wire", ends: [{ x: 120, y: 180 }, { x: 120, y: 120 }] },
    { id: "r1", type: "resistor", value: 10, ends: [{ x: 120, y: 120 }, { x: 260, y: 120 }] },
    { id: "ind", type: "ind", value: 5, ends: [{ x: 260, y: 120 }, { x: 400, y: 120 }] },
    { id: "wr", type: "wire", ends: [{ x: 400, y: 120 }, { x: 440, y: 120 }] },
    { id: "wr2", type: "wire", ends: [{ x: 440, y: 120 }, { x: 440, y: 160 }] },
    { id: "cap", type: "cap", value: 10, ends: [{ x: 440, y: 160 }, { x: 440, y: 260 }] },
    { id: "wc", type: "wire", ends: [{ x: 440, y: 260 }, { x: 440, y: 320 }] },
    { id: "wb", type: "wire", ends: [{ x: 440, y: 320 }, { x: 120, y: 320 }] },
    { id: "wd", type: "wire", ends: [{ x: 120, y: 300 }, { x: 120, y: 320 }] },
    { id: "gnd", type: "ground", ends: [{ x: 120, y: 320 }] },
  ]) },
  { key: "flash", cat: "Reactive", label: "Charge & flash", make: () => ([
    { id: "bat", type: "battery", volts: 9, ends: [{ x: 120, y: 150 }, { x: 120, y: 310 }] },
    { id: "wu", type: "wire", ends: [{ x: 120, y: 150 }, { x: 120, y: 110 }] },
    { id: "wd", type: "wire", ends: [{ x: 120, y: 310 }, { x: 120, y: 350 }] },
    { id: "gnd", type: "ground", ends: [{ x: 120, y: 350 }] },
    { id: "s1", type: "switch", closed: true, ends: [{ x: 120, y: 110 }, { x: 220, y: 110 }] },
    { id: "r1", type: "resistor", value: 47, ends: [{ x: 220, y: 110 }, { x: 340, y: 110 }] },
    { id: "wtc", type: "wire", ends: [{ x: 340, y: 110 }, { x: 360, y: 110 }] },
    { id: "wtc2", type: "wire", ends: [{ x: 360, y: 110 }, { x: 360, y: 160 }] },
    { id: "cap", type: "cap", value: 10, ends: [{ x: 360, y: 160 }, { x: 360, y: 280 }] },
    { id: "wcb", type: "wire", ends: [{ x: 360, y: 280 }, { x: 360, y: 350 }] },
    { id: "wbot", type: "wire", ends: [{ x: 360, y: 350 }, { x: 120, y: 350 }] },
    { id: "wdis", type: "wire", ends: [{ x: 360, y: 160 }, { x: 480, y: 160 }] },
    { id: "s2", type: "switch", closed: false, ends: [{ x: 480, y: 160 }, { x: 480, y: 200 }] },
    { id: "r2", type: "resistor", value: 100, ends: [{ x: 480, y: 200 }, { x: 480, y: 240 }] },
    { id: "led", type: "led", ends: [{ x: 480, y: 240 }, { x: 480, y: 310 }] },
    { id: "wled", type: "wire", ends: [{ x: 480, y: 310 }, { x: 480, y: 350 }] },
    { id: "wled2", type: "wire", ends: [{ x: 480, y: 350 }, { x: 360, y: 350 }] },
  ]) },
];

const FSB_CATS = ["Basics", "Controls", "Reactive"];
const FSB_EX_META = {
  series:   { kicker: "R + R", desc: "Two resistors in a loop — Ohm's law in action.",
              teach: "Ohm's law & series resistance. One current flows through everything; the supply voltage splits across the two resistors in proportion to their size. Bigger resistor → bigger share of the voltage.",
              probes: [{ x: 200, y: 100, label: "+9V" }, { x: 480, y: 160, label: "R1│R2" }],
              probeNote: "Probe 1 sits at the supply (flat 9 V); probe 2 is the junction between the resistors — it parks partway down, showing how the voltage divides." },
  twobatt:  { kicker: "2×", desc: "Two cells stacked — their voltages add.",
              teach: "Batteries in series add their EMFs: two 9 V cells give 18 V, so the same LED branch runs brighter (more current). Stack for more push (voltage); the current path is still one loop.",
              probes: [{ x: 440, y: 130, label: "top rail" }, { x: 120, y: 255, label: "bottom" }],
              probeNote: "The mid-point between the cells is the 0 V reference, so the top rail reads +9 and the bottom −9 — 18 V across the load. Stacking cells is how you get more voltage." },
  noresistor:{ kicker: "⚠", desc: "An LED with NO limiting resistor.",
              teach: "Why an LED needs a series resistor. With nothing to limit it, the current is set only by the LED's tiny internal resistance — it shoots to hundreds of mA. In real life that burns the LED out instantly. Compare with the 'Series' and 'Divider' circuits that include a resistor.",
              probes: [{ x: 440, y: 120, label: "LED+" }],
              probeNote: "Inspect the LED: the current reads dangerously high (hundreds of mA) because nothing limits it. Add a resistor in series and watch it drop to a safe ~15–20 mA." },
  divider:  { kicker: "÷", desc: "Tap a middle voltage to drive an LED.",
              teach: "The voltage divider — the most common circuit in electronics. Two resistors set a fixed fraction of the supply at their midpoint; that tapped voltage powers the LED branch. Change either resistor to move the tap.",
              probes: [{ x: 300, y: 210, label: "tap" }],
              probeNote: "The probe reads the divider's tap voltage — the fraction R2/(R1+R2) of the supply that the LED branch runs on." },
  parallel: { kicker: "∥→∥", desc: "Three branches merge to one wire, then split again.",
              teach: "Parallel branches & current-sharing (KCL). Each branch carries its own current set by its resistance, they all sum into the single neck wire, then split again. Watch the charge speed differ branch to branch.",
              probes: [{ x: 330, y: 210, label: "neck" }],
              probeNote: "The probe sits on the neck where all branch currents combine — toggle branch switches and watch how the shared node behaves." },
  switched: { kicker: "SW", desc: "A switch gates current to an LED.",
              teach: "An open switch breaks the loop — no complete path, no current, LED dark. Close it and the whole loop conducts at once. The switch is just a controllable break in the conductor.",
              probes: [{ x: 440, y: 180, label: "LED+" }],
              probeNote: "The probe reads the LED's top node: ~supply when the switch is closed, collapses to 0 when open. Toggle the switch to see it step." },
  branches: { kicker: "SW²", desc: "Two parallel branches, each with its own switch.",
              teach: "Independent parallel paths. Each branch has its own switch and resistor, so they turn on and off without affecting each other — and each draws current set by its own resistance.",
              probes: [{ x: 280, y: 200, label: "branch 1" }, { x: 420, y: 200, label: "branch 2" }],
              probeNote: "One probe per branch — toggle either switch and only that branch's trace responds." },
  pot:      { kicker: "↻", desc: "Turn a knob to dim an LED.",
              teach: "A potentiometer is a divider you can sweep. The wiper taps anywhere between the two ends, so turning the knob smoothly changes the tapped voltage — here dimming the LED. Drag the wiper slider in the rail.",
              probes: [{ x: 380, y: 210, label: "wiper" }],
              probeNote: "The probe rides the wiper. Sweep the knob slider and watch its voltage glide from near-0 to near-supply." },
  npn:      { kicker: "NPN", desc: "A tiny base current switches a big load.",
              teach: "The transistor as a switch/amplifier. A small current into the base lets a much larger current flow collector→emitter, lighting the LED. Tiny control, big effect — the basis of every logic gate and amplifier.",
              probes: [{ x: 440, y: 210, label: "collector" }, { x: 360, y: 250, label: "base" }],
              probeNote: "Compare the base node (small) with the collector (the switched load). Toggle the base switch to turn it on/off — then raise the base resistor in the rail and watch the collector current (and the LED) throttle down: that's β·Ib in action." },
  inverter: { kicker: "¬", desc: "LED ON when the input is OFF — a NOT gate.",
              teach: "A transistor inverter. A pull-up resistor holds the collector HIGH, lighting the LED — until you drive the base HIGH, which turns the transistor on and yanks the collector LOW, stealing the current and switching the LED OFF. Output is the opposite of the input: that's a NOT gate, the seed of all digital logic.",
              probes: [{ x: 440, y: 180, label: "output (C)" }, { x: 340, y: 235, label: "input (B)" }],
              probeNote: "The switch is the INPUT, the LED is the OUTPUT. It starts open (input low) → LED lit. Close the switch (input high) → the collector collapses and the LED goes dark. Input and output are always opposite." },
  rc:       { kicker: "RC", desc: "Watch a capacitor charge through a resistor.",
              teach: "The RC time constant. A capacitor fills like a bucket through the resistor: fast at first, then ever slower as it nears the supply. The charging current tapers to zero. τ = R×C sets the pace.",
              probes: [{ x: 440, y: 180, label: "cap" }],
              probeNote: "The probe traces the classic charging curve — a steep rise that bends over toward the supply voltage. Open the switch to stop it." },
  flyback:  { kicker: "⎘", desc: "A diode catches the inductor's kick.",
              teach: "The flyback (freewheel) diode. An inductor hates having its current cut — open the switch and it would spike to a huge voltage. The diode across it gives that current a path to keep flowing and decay gently. While the switch is closed the diode is reverse-biased (blocked); the instant you open it, the diode conducts and the inductor freewheels.",
              probes: [{ x: 420, y: 150, label: "L top" }, { x: 520, y: 220, label: "diode" }],
              probeNote: "Close the switch to build current in the inductor, then OPEN it: the diode immediately conducts (its charges light up) and the inductor's current loops through it instead of spiking. Remove the diode and the kick has nowhere to go." },
  rl:       { kicker: "RL", desc: "An inductor ramps the current up slowly.",
              teach: "The inductor resists change in current. At switch-on it blocks (all the voltage appears across it), then current ramps in as it gives way. The mirror image of a capacitor.",
              probes: [{ x: 440, y: 160, label: "L top" }],
              probeNote: "The probe at the inductor's top starts near the full supply and decays as current builds and the resistor takes over the voltage." },
  lc:       { kicker: "LC", desc: "Energy sloshes back and forth — it rings.",
              teach: "An LC tank oscillates. Energy trades between the capacitor's electric field and the inductor's magnetic field, so the voltage and current ring back and forth — the seed of every radio and clock.",
              probes: [{ x: 440, y: 160, label: "cap" }],
              probeNote: "The probe shows the cap voltage swinging up and down — a real oscillation, slowly decaying as the small resistor bleeds energy." },
  rlc:      { kicker: "RLC", desc: "Add resistance and the ringing dies away.",
              teach: "Damping. The same LC tank, but a bigger resistor drains energy each cycle, so the ringing shrinks and settles. More resistance = faster decay — this is how you tune a system's 'bounce'.",
              probes: [{ x: 440, y: 160, label: "cap" }],
              probeNote: "Compare with the LC example: same swing, but each peak is smaller than the last as the resistor damps it out." },
  flash:    { kicker: "⚡", desc: "Charge a cap, then dump it through an LED.",
              teach: "A capacitor stores energy, then releases it. Charge it slowly through one path (switch 1), then open that and close switch 2 to dump the stored charge through the LED in a quick flash — like a camera strobe.",
              probes: [{ x: 360, y: 160, label: "cap" }, { x: 480, y: 240, label: "LED" }],
              probeNote: "Charge with switch 1 (watch the cap probe rise), then flip: open 1, close 2, and the cap probe falls while the LED probe pulses." },
};

function fsbBtn(active, accent) {
  const ac = accent || "#8be0ff";
  return {
    fontFamily: "IBM Plex Mono, monospace", fontSize: 12.5, padding: "6px 12px",
    borderRadius: 7, cursor: "pointer", border: "1px solid " + (active ? ac : "#33404c"),
    background: active ? "rgba(139,224,255,0.14)" : "#161b22", color: active ? ac : "#cdd4da",
    lineHeight: 1, whiteSpace: "nowrap",
  };
}
const fsbAdjustable = (p) => p.type === "resistor" || p.type === "battery" || p.type === "cap" || p.type === "ind" || p.type === "pot" || p.type === "switch";

// a labelled slider/toggle row for one part
function FsbSliderRow({ part, selected, onValue, onToggle, onHover, onSelect }) {
  const hl = selected ? "#8be0ff" : "#2c3540";
  const name = { resistor: "Resistor", battery: "Battery", cap: "Capacitor", ind: "Inductor", pot: "Potentiometer", switch: "Switch" }[part.type];
  let control, valTxt;
  if (part.type === "resistor") {
    const idx = fsbNearest(FSB_RVALS, part.value);
    valTxt = window.fmtOhm(FSB_RVALS[idx]);
    control = <input type="range" min="0" max={FSB_RVALS.length - 1} step="1" value={idx}
                     onChange={(e) => onValue(part.id, { value: FSB_RVALS[+e.target.value] })}
                     style={{ width: "100%", accentColor: "#8be0ff" }} />;
  } else if (part.type === "cap") {
    const idx = fsbNearest(FSB_CVALS, part.value);
    valTxt = FSB_CVALS[idx] + " µF*";
    control = <input type="range" min="0" max={FSB_CVALS.length - 1} step="1" value={idx}
                     onChange={(e) => onValue(part.id, { value: FSB_CVALS[+e.target.value] })}
                     style={{ width: "100%", accentColor: "#8be0ff" }} />;
  } else if (part.type === "ind") {
    const idx = fsbNearest(FSB_LVALS, part.value);
    valTxt = FSB_LVALS[idx] + " H*";
    control = <input type="range" min="0" max={FSB_LVALS.length - 1} step="1" value={idx}
                     onChange={(e) => onValue(part.id, { value: FSB_LVALS[+e.target.value] })}
                     style={{ width: "100%", accentColor: "#8be0ff" }} />;
  } else if (part.type === "pot") {
    valTxt = Math.round((part.pos != null ? part.pos : 0.5) * 100) + "%";
    control = <input type="range" min="0" max="1" step="0.01" value={part.pos != null ? part.pos : 0.5}
                     onChange={(e) => onValue(part.id, { pos: +e.target.value })}
                     style={{ width: "100%", accentColor: "#8be0ff" }} />;
  } else if (part.type === "battery") {
    valTxt = window.fmtV(part.volts);
    control = <input type="range" min="-12" max="12" step="0.5" value={part.volts}
                     onChange={(e) => onValue(part.id, { volts: +e.target.value })}
                     style={{ width: "100%", accentColor: part.volts < 0 ? "#ff5a72" : "#39ff14" }} />;
  } else if (part.type === "switch") {
    valTxt = part.closed ? "● closed" : "○ open";
    control = <button style={{ ...fsbBtn(part.closed, "#39ff14"), width: "100%", padding: "5px 0" }}
                      onClick={() => onToggle(part.id)}>{part.closed ? "● Closed — tap to open" : "○ Open — tap to close"}</button>;
  }
  return (
    <div onMouseEnter={() => onHover(part.id)} onMouseLeave={() => onHover(null)} onClick={() => onSelect(part.id)}
         style={{ borderTop: "1px solid #1c232b", padding: "8px 2px", cursor: "pointer" }}>
      <div style={{ display: "flex", justifyContent: "space-between", alignItems: "baseline", gap: 8, marginBottom: 5 }}>
        <span style={{ fontFamily: "IBM Plex Mono, monospace", fontSize: 11.5, color: selected ? "#8be0ff" : "#9aa6b1", whiteSpace: "nowrap", overflow: "hidden", textOverflow: "ellipsis" }}>{name}</span>
        <span style={{ fontFamily: "IBM Plex Mono, monospace", fontSize: 11.5, color: part.type === "battery" && part.volts < 0 ? "#ff8aa0" : "#dfe6ec", whiteSpace: "nowrap", flexShrink: 0 }}>{valTxt}</span>
      </div>
      {control}
    </div>
  );
}

const FSB_PROBE_COLORS = ["#39ff14", "#8be0ff", "#ffd166", "#ff5a72"];
const FSB_STORE = "hte-sandbox-v1";

function FlowSandbox() {
  const [t, setTweak] = useTweaks({ theme: "paper", audience: "adult" });
  useCrossChapterPersistence(t, setTweak);
  fsbUseEffect(() => { document.body.setAttribute("data-theme", t.theme); }, [t.theme]);
  const [flow, setFlow] = fsbUseState(1);
  const [hoverId, setHoverId] = fsbUseState(null);
  const [selId, setSelId] = fsbUseState(null);
  const [parts, setParts] = fsbUseState(() => {
    try {
      const exq = new URLSearchParams(location.search).get("ex");
      if (exq) { const ex = FSB_EXAMPLES.find(e => e.key === exq); if (ex) return ex.make(); }
    } catch (_) {}
    try { const s = JSON.parse(localStorage.getItem(FSB_STORE)); if (s && Array.isArray(s.parts) && s.parts.length) return s.parts; } catch (_) {}
    return FSB_DEMO;
  });
  const [dragging, setDragging] = fsbUseState(false);
  const [tool, setTool] = fsbUseState("select");
  const [exCat, setExCat] = fsbUseState(() => {
    try { const k = new URLSearchParams(location.search).get("ex"); const ex = FSB_EXAMPLES.find(e => e.key === k); if (ex) return ex.cat; } catch (_) {}
    return "Basics";
  });
  const [exKey, setExKey] = fsbUseState(() => {
    try {
      const k = new URLSearchParams(location.search).get("ex");
      if (FSB_EXAMPLES.some(e => e.key === k)) return k;          // explicit deep link
      const s = JSON.parse(localStorage.getItem(FSB_STORE));
      if (s && Array.isArray(s.parts) && s.parts.length) return null;  // restored custom circuit
    } catch (_) {}
    return "series";                                              // fresh → demo is series
  });
  const [probes, setProbes] = fsbUseState([]);
  const svgRef = fsbUseRef(null);
  const dragRef = fsbUseRef(null);
  const probeDragRef = fsbUseRef(null);
  const movedRef = fsbUseRef(false);
  const idRef = fsbUseRef(1);
  const selRef = fsbUseRef(null);
  selRef.current = selId;
  const newId = () => "p" + (idRef.current++);

  // start id counter past any restored part ids
  fsbUseEffect(() => {
    let mx = 0; parts.forEach(p => { const m = /^p(\d+)$/.exec(p.id); if (m) mx = Math.max(mx, +m[1]); });
    if (mx >= idRef.current) idRef.current = mx + 1;
    // place the starting example's key probes (deep link or fresh demo)
    const meta = exKey ? FSB_EX_META[exKey] : null;
    if (meta && meta.probes) {
      const placed = meta.probes.slice(0, 4).map((pr, i) => {
        const id = "probe_init_" + i;
        bufsRef.current[id] = [];
        return { id, x: pr.x, y: pr.y, color: FSB_PROBE_COLORS[i], label: pr.label || null };
      });
      setProbes(placed);
    }
  }, []);
  // persist circuit (debounced via rAF-ish microtask)
  fsbUseEffect(() => {
    try { localStorage.setItem(FSB_STORE, JSON.stringify({ parts })); } catch (_) {}
  }, [parts]);

  // ── probes (scope) ──
  const probesRef = fsbUseRef([]);
  probesRef.current = probes;
  const bufsRef = fsbUseRef({});
  const addProbe = (pt) => {
    setProbes(prev => {
      if (prev.length >= 4) return prev;
      if (prev.some(p => p.x === pt.x && p.y === pt.y)) return prev;
      const used = prev.map(p => p.color);
      const color = FSB_PROBE_COLORS.find(c => !used.includes(c)) || FSB_PROBE_COLORS[prev.length % 4];
      const id = "probe" + Date.now() + Math.round(Math.random() * 999);
      bufsRef.current[id] = [];
      return [...prev, { id, x: pt.x, y: pt.y, color, label: pt.label || null }];
    });
  };
  const removeProbe = (id) => { setProbes(prev => prev.filter(p => p.id !== id)); delete bufsRef.current[id]; };
  const clearProbes = () => { setProbes([]); bufsRef.current = {}; };
  // start dragging an existing probe (works in any tool mode)
  const onProbeDown = (e, pr) => {
    e.preventDefault(); e.stopPropagation();
    probeDragRef.current = pr.id;
    movedRef.current = false;
    setDragging(true);
    try { svgRef.current.setPointerCapture(e.pointerId); } catch (_) {}
  };
  // nearest part endpoint to a click (for probe placement) — bind to the EXACT
  // endpoint coord (no grid snap; example geometry isn't on the 20px grid, and
  // flrVoltAt matches the endpoint's exact key).
  const nearestNode = (p) => {
    let best = null, bd = 26;
    parts.forEach(pt => pt.ends.forEach(e => { const d = Math.hypot(e.x - p.x, e.y - p.y); if (d < bd) { bd = d; best = { x: e.x, y: e.y }; } }));
    return best;
  };

  const toSvg = (e) => {
    const svg = svgRef.current;
    const pt = svg.createSVGPoint();
    pt.x = e.clientX; pt.y = e.clientY;
    const sp = pt.matrixTransform(svg.getScreenCTM().inverse());
    return { x: sp.x, y: sp.y };
  };

  const addPart = (type) => { const id = newId(); setParts(prev => [...prev, fsbMakeDefault(type, id)]); setSelId(id); setTool("select"); };
  const loadExample = (ex) => {
    setParts(ex.make());
    setSelId(null); setTool("select"); setExKey(ex.key);
    bufsRef.current = {};
    // start the example FRESH — drop any carried-over reactive state (a charged
    // cap / inductor current) so RC ramps and LC ringing always restart cleanly.
    engRef.current = null; linkRef.current = null; vR.current = [];
    // auto-place the example's key probes
    const meta = FSB_EX_META[ex.key];
    if (meta && meta.probes) {
      const placed = meta.probes.slice(0, 4).map((pr, i) => {
        const id = "probe" + Date.now() + "_" + i;
        bufsRef.current[id] = [];
        return { id, x: pr.x, y: pr.y, color: FSB_PROBE_COLORS[i], label: pr.label || null };
      });
      setProbes(placed);
    } else {
      setProbes([]);
    }
  };
  const deletePart = (id) => { setParts(prev => prev.filter(p => p.id !== id)); setSelId(s => s === id ? null : s); };
  const rotatePart = (id) => setParts(prev => prev.map(pt => {
    if (pt.id !== id || pt.ends.length < 2) return pt;
    const mx = (pt.ends[0].x + pt.ends[1].x) / 2, my = (pt.ends[0].y + pt.ends[1].y) / 2;
    return { ...pt, ends: pt.ends.map(en => fsbSnapPt({ x: mx - (en.y - my), y: my + (en.x - mx) })) };
  }));
  const setValue = (id, patch) => setParts(prev => prev.map(pt => pt.id === id ? { ...pt, ...patch } : pt));
  const toggleSwitch = (id) => setParts(prev => prev.map(pt => pt.id === id ? { ...pt, closed: !pt.closed } : pt));

  const onPartDown = (e, pt) => {
    if (tool === "probe") { const n = nearestNode(toSvg(e)); if (n) addProbe(n); e.preventDefault(); return; }
    if (tool === "wire") return;
    e.preventDefault();
    const p = toSvg(e);
    let mode = "body", endIndex = -1, best = 13;
    pt.ends.forEach((en, i) => { const d = Math.hypot(en.x - p.x, en.y - p.y); if (d < best) { best = d; mode = "end"; endIndex = i; } });
    dragRef.current = { id: pt.id, mode, endIndex, start: p, orig: pt.ends.map(en => ({ ...en })), type: pt.type };
    movedRef.current = false;
    setSelId(pt.id); setDragging(true);
    try { svgRef.current.setPointerCapture(e.pointerId); } catch (_) {}
  };
  const onBgDown = (e) => {
    if (tool === "probe") { const n = nearestNode(toSvg(e)); if (n) addProbe(n); e.preventDefault(); return; }
    if (tool !== "wire") return;
    e.preventDefault();
    const p = fsbSnapPt(toSvg(e));
    const id = newId();
    setParts(prev => [...prev, { id, type: "wire", ends: [{ ...p }, { ...p }] }]);
    dragRef.current = { id, mode: "end", endIndex: 1, start: p, orig: [{ ...p }, { ...p }], wireDraw: true };
    movedRef.current = false;
    setSelId(id); setDragging(true);
    try { svgRef.current.setPointerCapture(e.pointerId); } catch (_) {}
  };
  const onSvgMove = (e) => {
    // dragging a probe — follow cursor, stick to the nearest node when in range
    if (probeDragRef.current) {
      const p = toSvg(e);
      const n = nearestNode(p);
      const pos = n || { x: p.x, y: p.y };
      movedRef.current = true;
      setProbes(prev => prev.map(pr => pr.id === probeDragRef.current ? { ...pr, x: pos.x, y: pos.y, snapped: !!n } : pr));
      return;
    }
    const d = dragRef.current; if (!d) return;
    const p = toSvg(e);
    const dx = p.x - d.start.x, dy = p.y - d.start.y;
    if (Math.hypot(dx, dy) > 5) movedRef.current = true;
    setParts(prev => prev.map(pt => {
      if (pt.id !== d.id) return pt;
      if (d.mode === "end") {
        const ends = pt.ends.map((en, i) => i === d.endIndex ? { x: fsbSnap(d.orig[i].x + dx), y: fsbSnap(d.orig[i].y + dy) } : en);
        return { ...pt, ends };
      }
      const sdx = fsbSnap(dx), sdy = fsbSnap(dy);
      return { ...pt, ends: d.orig.map(en => ({ x: en.x + sdx, y: en.y + sdy })) };
    }));
  };
  const onSvgUp = (e) => {
    // finish a probe drag: clear its buffer so the scope reads fresh from the new node
    if (probeDragRef.current) {
      const id = probeDragRef.current;
      if (movedRef.current) { bufsRef.current[id] = []; setProbes(prev => prev.map(pr => pr.id === id ? { ...pr, label: null } : pr)); }
      try { svgRef.current.releasePointerCapture(e.pointerId); } catch (_) {}
      probeDragRef.current = null; setDragging(false);
      return;
    }
    const d = dragRef.current;
    if (d) { try { svgRef.current.releasePointerCapture(e.pointerId); } catch (_) {} }
    if (d && d.wireDraw) {
      setParts(prev => prev.filter(pt => !(pt.id === d.id && pt.ends[0].x === pt.ends[1].x && pt.ends[0].y === pt.ends[1].y)));
      setTool("select");
    } else if (d && !movedRef.current && d.type === "switch") {
      toggleSwitch(d.id);   // tap a switch (no drag) → toggle
    }
    dragRef.current = null; setDragging(false);
  };

  fsbUseEffect(() => {
    const onKey = (e) => {
      if (/input|textarea/i.test((e.target.tagName || ""))) return;
      const id = selRef.current; if (!id && e.key !== "Escape") return;
      if (e.key === "r" || e.key === "R") rotatePart(id);
      else if (e.key === "Delete" || e.key === "Backspace") { e.preventDefault(); deletePart(id); }
      else if (e.key === "Escape") { setSelId(null); setTool("select"); }
    };
    window.addEventListener("keydown", onKey);
    return () => window.removeEventListener("keydown", onKey);
  }, []);

  // ── real-time engine: refs persist; rebuild on TOPOLOGY change, sync values live ──
  const engRef = fsbUseRef(null);
  const linkRef = fsbUseRef(null);
  const vR = fsbUseRef([]);
  const [, fsbForce] = fsbUseState(0);

  // topology key = structure only (geometry + switch state), NOT values — so
  // dragging a value slider doesn't reset the cap/inductor dynamics.
  const topoKey = fsbUseMemo(() =>
    parts.map(p => p.type + "@" + p.ends.map(e => fsbSnap(e.x) + "," + fsbSnap(e.y)).join("|") + (p.type === "switch" ? (p.closed ? "C" : "O") : "")).join(";"),
    [parts]);
  const link = fsbUseMemo(() => window.flBuildNetlist(parts), [topoKey]);

  // rebuild the engine SYNCHRONOUSLY when topology (link) changes, so eng always
  // matches the current link within the same render (no stale-index crash).
  if (linkRef.current !== link) {
    const oldLink = linkRef.current, oldEng = engRef.current;
    // snapshot reactive state (cap voltage / inductor current) by part id, so a
    // switch toggle or drag doesn't wipe a charged capacitor — only Reset / a new
    // example (fresh ids) starts from zero.
    const snap = {};
    if (oldLink && oldEng) parts.forEach(p => {
      if (p.type !== "cap" && p.type !== "ind") return;
      const oi = oldLink.partElem.get(p.id); if (oi == null) return;
      snap[p.id] = p.type === "cap" ? oldEng.getCapV(oi) : oldEng.getIndI(oi);
    });
    const neweng = window.mnaCreate(link.netlist);
    parts.forEach(p => {
      if (snap[p.id] == null) return;
      const ni = link.partElem.get(p.id); if (ni == null) return;
      if (p.type === "cap") neweng.setCapV(ni, snap[p.id]);
      else if (p.type === "ind") neweng.setIndI(ni, snap[p.id]);
    });
    linkRef.current = link;
    engRef.current = neweng;
    vR.current = [];
  }
  const eng = engRef.current;
  // sync element values every render (cheap; eng matches link here)
  if (eng) parts.forEach(p => {
    const ei = link.partElem.get(p.id); if (ei == null) return;
    const el = eng.els[ei]; if (!el) return;
    if (p.type === "resistor") el.ohms = p.value;
    else if (p.type === "battery") el.volts = p.volts;
    else if (p.type === "cap") el.farads = p.value / 330;
    else if (p.type === "ind") el.henries = p.value;
    else if (p.type === "pot") {
      const total = p.value != null ? p.value : 10000;
      const pos = p.pos != null ? p.pos : 0.5;
      el.ohms = Math.max(1, total * (1 - pos));            // a → wiper
      const ei2 = link.partElem2.get(p.id);
      const el2 = ei2 != null ? eng.els[ei2] : null;
      if (el2) el2.ohms = Math.max(1, total * pos);        // wiper → b
    }
  });

  fsbUseEffect(() => {
    let raf, last = performance.now(), acc = 0;
    const loop = (now) => {
      const e = engRef.current;
      if (e) {
        const simT = Math.min(0.05, (now - last) / 1000);   // 1 sim-second : 1 real-second
        const dt = 1 / 240, n = Math.max(1, Math.min(40, Math.round(simT / dt)));
        let vv; for (let i = 0; i < n; i++) vv = e.step(dt);
        if (vv) vR.current = vv;
        acc += (now - last);
        if (acc >= 33) {
          acc = 0;
          // sample probes into ring buffers
          const lk = linkRef.current, vv2 = vR.current;
          if (lk && vv2 && vv2.length && probesRef.current.length) {
            probesRef.current.forEach(pr => {
              const buf = bufsRef.current[pr.id] || (bufsRef.current[pr.id] = []);
              buf.push(window.flrVoltAt({ x: pr.x, y: pr.y }, lk, vv2));
              if (buf.length > 240) buf.shift();
            });
          }
          fsbForce(t => t + 1);
        }
      }
      last = now;
      raf = requestAnimationFrame(loop);
    };
    raf = requestAnimationFrame(loop);
    return () => cancelAnimationFrame(raf);
  }, []);

  const v = vR.current;
  const vref = Math.max(1, ...(v || []).map(x => Math.abs(x)));
  const wireCur = (eng && v && v.length) ? window.flWireCurrents(parts, link, eng, v) : new Map();

  // inspector locks to the SELECTED part; hover only previews when nothing is selected
  const selPart = parts.find(p => p.id === selId) || null;
  const displayPart = selPart || parts.find(p => p.id === hoverId) || null;
  const readout = (displayPart && eng && v && v.length) ? window.flPartReadout(displayPart, parts, link, v, eng, wireCur) : null;
  const adjustables = parts.filter(fsbAdjustable);
  const activeMeta = FSB_EX_META[exKey] || null;
  const customCircuit = !FSB_EXAMPLES.some(e => e.key === exKey) || activeMeta == null;

  return (
    <>
    <ProgressBar />
    <TopBar chapterLabel="The Sandbox" currentN="flow-sandbox" audience={t.audience}
            setAudience={(a) => setTweak("audience", a)} />
    <div style={{ maxWidth: 1120, margin: "0 auto", padding: "calc(var(--topbar-h, 60px) + 26px) 24px 60px" }}>
      <div className="eyebrow" style={{ marginBottom: 10 }}>the sandbox · see how it works</div>
      <h1 className="serif" style={{ fontFamily: "Newsreader, serif", fontWeight: 500, fontSize: "clamp(30px, 4.4vw, 44px)", margin: "0 0 6px" }}>The Sandbox</h1>
      <p style={{ color: "var(--ink-soft)", fontSize: 15, margin: "0 0 10px", maxWidth: 720 }}>
        <b>See how a circuit works.</b> Wire anything, probe anything, and watch the charges, voltages, and currents move.
        Add parts, drag them into place, draw wires to connect — every adjustable part gets a
        <b> slider in the rail on the right</b>; drag a battery <b>below zero</b> to reverse the current. Tap a switch to open/close it.
      </p>
      <div style={{ display: "flex", gap: 8, alignItems: "center", flexWrap: "wrap", marginBottom: 18, fontFamily: "IBM Plex Mono, monospace", fontSize: 12 }}>
        <span style={{ background: "var(--bg-deeper)", border: "1px solid var(--rule)", borderRadius: 6, padding: "3px 9px", color: "var(--ink-soft)" }}>use this to <b style={{ color: "var(--ink)" }}>understand</b> a circuit</span>
        <span style={{ color: "var(--ink-faint)" }}>— ready to build one for real?</span>
        <a href="sandbox.html" style={{ color: "var(--water)", textDecoration: "none" }}>Go to The Workbench →</a>
        <a href="circuit-lab.html" style={{ color: "var(--water)", textDecoration: "none" }}>Circuit Lab →</a>
      </div>

      {/* examples — grouped panel with category tabs + descriptive cards */}
      <div style={{ background: "var(--bg-card)", border: "1px solid var(--rule)", borderRadius: 12, padding: "14px 16px 16px", marginBottom: 16 }}>
        <div style={{ display: "flex", alignItems: "center", justifyContent: "space-between", flexWrap: "wrap", gap: 10, marginBottom: 12 }}>
          <span style={{ fontFamily: "Newsreader, serif", fontSize: 18, color: "var(--ink)" }}>Example circuits</span>
          <div style={{ display: "inline-flex", background: "var(--bg-deeper)", border: "1px solid var(--rule)", borderRadius: 8, padding: 3, gap: 2 }}>
            {FSB_CATS.map(cat => (
              <button key={cat} onClick={() => setExCat(cat)}
                      style={{ fontFamily: "IBM Plex Mono, monospace", fontSize: 12, padding: "5px 14px", borderRadius: 6, border: "none", cursor: "pointer",
                               background: exCat === cat ? "var(--water)" : "transparent", color: exCat === cat ? "#fff" : "var(--ink-soft)", fontWeight: exCat === cat ? 600 : 400 }}>
                {cat}
              </button>
            ))}
          </div>
        </div>
        <div style={{ display: "grid", gridTemplateColumns: "repeat(auto-fill, minmax(196px, 1fr))", gap: 9 }}>
          {FSB_EXAMPLES.filter(ex => ex.cat === exCat).map(ex => {
            const m = FSB_EX_META[ex.key] || {}; const on = exKey === ex.key;
            return (
              <button key={ex.key} onClick={() => loadExample(ex)}
                      style={{ textAlign: "left", display: "flex", gap: 11, alignItems: "flex-start", padding: "11px 12px", cursor: "pointer",
                               background: on ? "color-mix(in oklab, var(--water) 12%, var(--bg-card))" : "var(--bg-card)",
                               border: "1px solid " + (on ? "var(--water)" : "var(--rule)"), borderRadius: 9, transition: "border-color .12s" }}>
                <span style={{ flexShrink: 0, minWidth: 38, height: 38, display: "flex", alignItems: "center", justifyContent: "center",
                               fontFamily: "IBM Plex Mono, monospace", fontSize: 12, fontWeight: 600, borderRadius: 7,
                               background: "var(--bg-deeper)", color: on ? "var(--water)" : "var(--ink-soft)", padding: "0 6px" }}>{m.kicker}</span>
                <span style={{ display: "flex", flexDirection: "column", gap: 2, minWidth: 0 }}>
                  <span style={{ fontFamily: "Geist, sans-serif", fontSize: 13.5, fontWeight: 500, color: "var(--ink)" }}>{ex.label}</span>
                  <span style={{ fontFamily: "Geist, sans-serif", fontSize: 11.5, color: "var(--ink-faint)", lineHeight: 1.35 }}>{m.desc}</span>
                </span>
              </button>
            );
          })}
        </div>
      </div>

      {/* palette */}
      <div style={{ display: "flex", gap: 8, alignItems: "center", marginBottom: 10, flexWrap: "wrap" }}>
        <span className="eyebrow" style={{ marginRight: 2 }}>add</span>
        {FSB_PALETTE.map(it => (
          <button key={it.type} style={fsbBtn(it.tool && tool === "wire")}
                  onClick={() => it.tool ? setTool(t => t === "wire" ? "select" : "wire") : addPart(it.type)}>
            {it.tool ? (tool === "wire" ? "✎ Wire (drawing…)" : "✎ Wire") : "+ " + it.label}
          </button>
        ))}
      </div>
      {/* canvas tools — own row so nothing crowds the palette */}
      <div style={{ display: "flex", gap: 10, alignItems: "center", marginBottom: 12, flexWrap: "wrap" }}>
        <button style={fsbBtn(tool === "probe", "#ffd166")} onClick={() => setTool(t => t === "probe" ? "select" : "probe")}>{tool === "probe" ? "⊙ Probe (tap a node)" : "⊙ Probe"}</button>
        <span style={{ display: "flex", alignItems: "center", gap: 8, fontFamily: "IBM Plex Mono, monospace", fontSize: 11.5, color: "var(--ink-soft)" }}>
          <span className="eyebrow">flow speed</span>
          <input type="range" min="0" max="3" step="0.1" value={flow}
                 onChange={(e) => setFlow(+e.target.value)}
                 style={{ width: 110, accentColor: "#8be0ff" }} />
          <span style={{ width: 34, textAlign: "right", color: "var(--ink)" }}>{flow.toFixed(1)}×</span>
        </span>
        <span style={{ flex: 1, minWidth: 8 }}></span>
        <button style={fsbBtn(false)} onClick={() => loadExample(FSB_EXAMPLES[0])}>↺ Reset</button>
      </div>

      {activeMeta && (
        <div style={{ display: "flex", gap: 13, alignItems: "flex-start", background: "var(--bg-card)", border: "1px solid var(--rule)", borderLeft: "3px solid var(--water)", borderRadius: 10, padding: "13px 16px", marginBottom: 14 }}>
          <div style={{ flexShrink: 0, minWidth: 40, height: 40, display: "flex", alignItems: "center", justifyContent: "center", fontFamily: "IBM Plex Mono, monospace", fontSize: 13, fontWeight: 600, borderRadius: 8, background: "var(--bg-deeper)", color: "var(--water)", padding: "0 7px" }}>{activeMeta.kicker}</div>
          <div style={{ display: "flex", flexDirection: "column", gap: 5, minWidth: 0 }}>
            <p style={{ margin: 0, fontFamily: "Geist, sans-serif", fontSize: 13.5, color: "var(--ink)", lineHeight: 1.5 }}>{activeMeta.teach}</p>
            {activeMeta.probeNote && (
              <p style={{ margin: 0, fontFamily: "Geist, sans-serif", fontSize: 12.5, color: "var(--ink-faint)", lineHeight: 1.5 }}>
                <span style={{ fontFamily: "IBM Plex Mono, monospace", fontSize: 10.5, textTransform: "uppercase", letterSpacing: "0.05em", color: "var(--water)", marginRight: 7 }}>scope</span>
                {activeMeta.probeNote}
              </p>
            )}
          </div>
        </div>
      )}

      <div className="fsb-main">
        {/* canvas */}
        <div className="fsb-canvas">
        <div className="fsb-stage" style={{ background: "#0b0d11", border: "1px solid #2a2f37", borderRadius: 12, padding: 10, "--ink": "#f2efe8", "--ink-soft": "#aab2ba", "--ink-faint": "#7c858e" }}>
          <svg ref={svgRef} viewBox="44 70 496 320" width="100%" style={{ display: "block", touchAction: "none", cursor: tool === "wire" ? "crosshair" : "default" }}
               onPointerMove={onSvgMove} onPointerUp={onSvgUp} onPointerLeave={onSvgUp}
               onClick={(e) => { if (e.target === e.currentTarget || e.target.tagName === "rect") setSelId(null); }}>
            <defs>
              <pattern id="fsbGrid" width={FSB_GRID} height={FSB_GRID} patternUnits="userSpaceOnUse">
                <circle cx={FSB_GRID} cy={FSB_GRID} r="1" fill="#222a33" />
              </pattern>
            </defs>
            <rect x="0" y="0" width="600" height="480" fill="url(#fsbGrid)" onPointerDown={onBgDown} />
            {eng && v && v.length ? parts.filter(p => p.id === hoverId || p.id === selId).map(pt =>
              <window.FlHalo key={"halo" + pt.id} part={pt} hot={pt.id === selId ? "sel" : "hov"} />) : null}
            {eng && v && v.length ? parts.map(pt =>
              <window.FlPart key={pt.id} part={pt} parts={parts} link={link} v={v} eng={eng} vref={vref} flowMul={flow} wireCur={wireCur} />) : null}
            {parts.map(pt => {
              const hp = window.flPartHitPts(pt);
              return (
                <polyline key={"hit" + pt.id} points={hp.map(p => `${p.x},${p.y}`).join(" ")}
                          fill="none" stroke="transparent" strokeWidth="18" strokeLinecap="round"
                          style={{ cursor: tool === "wire" ? "crosshair" : tool === "probe" ? "cell" : (dragging ? "grabbing" : "grab") }}
                          onPointerDown={(e) => onPartDown(e, pt)}
                          onMouseEnter={() => { if (!dragRef.current) setHoverId(pt.id); }}
                          onMouseLeave={() => setHoverId(h => h === pt.id ? null : h)} />
              );
            })}
            {/* probe markers */}
            {probes.map((pr, i) => (
              <g key={pr.id}>
                <circle cx={pr.x} cy={pr.y} r="8" fill="none" stroke={pr.color} strokeWidth="2.5" style={{ pointerEvents: "none" }} />
                <circle cx={pr.x} cy={pr.y} r="3" fill={pr.color} style={{ pointerEvents: "none" }} />
                <text x={pr.x + 11} y={pr.y - 7} fontFamily="IBM Plex Mono, monospace" fontSize="11.5" fill={pr.color} stroke="#0b0d11" strokeWidth="0.5" paintOrder="stroke" style={{ pointerEvents: "none" }}>{(i + 1) + (pr.label ? " " + pr.label : "")}</text>
                {/* drag handle */}
                <circle cx={pr.x} cy={pr.y} r="13" fill="transparent"
                        style={{ cursor: dragging ? "grabbing" : "grab" }}
                        onPointerDown={(e) => onProbeDown(e, pr)} />
              </g>
            ))}
          </svg>
        </div>
        {/* scope (appears when probes are placed) */}
        {probes.length > 0 && window.BBScope && (
          <div style={{ marginTop: 12, background: "#0b0d11", border: "1px solid #2a2f37", borderRadius: 12, padding: "10px 12px 6px" }}>
            <div style={{ display: "flex", justifyContent: "space-between", alignItems: "center", marginBottom: 6 }}>
              <span className="eyebrow" style={{ color: "#7c858e" }}>scope · voltage over time · drag a probe to move it</span>
              <div style={{ display: "flex", gap: 8, alignItems: "center" }}>
                {probes.map((pr, i) => (
                  <button key={pr.id} onClick={() => removeProbe(pr.id)} title="remove probe"
                          style={{ display: "flex", alignItems: "center", gap: 5, background: "transparent", border: "1px solid #2a3038", borderRadius: 6, padding: "2px 7px", cursor: "pointer", color: "#aab2ba", fontFamily: "IBM Plex Mono, monospace", fontSize: 11 }}>
                    <span style={{ width: 9, height: 9, borderRadius: "50%", background: pr.color }}></span>{i + 1} ✕
                  </button>
                ))}
                <button onClick={clearProbes} style={{ ...fsbBtn(false), padding: "3px 9px" }}>clear</button>
              </div>
            </div>
            <window.BBScope height={250}
              traces={probes.map((pr, i) => ({ label: (i + 1) + (pr.label ? " " + pr.label : ""), color: pr.color, samples: bufsRef.current[pr.id] || [] }))} />
          </div>
        )}
        </div>

        {/* side rail: inspector + per-part sliders (OUTSIDE the canvas) */}
        <div className="fsb-rail">
          <div className="fsb-railgrid">
          {/* inspector */}
          <div style={{ background: "#11161d", border: "1px solid " + (selPart ? "#3a5566" : "#222a32"), borderRadius: 10, padding: "12px 13px" }}>
            {readout ? (
              <React.Fragment>
                <div style={{ display: "flex", justifyContent: "space-between", alignItems: "baseline", marginBottom: 9 }}>
                  <span style={{ fontFamily: "Newsreader, serif", fontSize: 17, color: "#f2efe8" }}>{readout.name}</span>
                  {selPart && <span style={{ fontFamily: "IBM Plex Mono, monospace", fontSize: 9.5, color: "#8be0ff", letterSpacing: "0.05em" }}>SELECTED</span>}
                </div>
                <div style={{ display: "grid", gridTemplateColumns: "auto 1fr", gap: "5px 12px" }}>
                  {readout.rows.map(([k, val], i) => (
                    <React.Fragment key={i}>
                      <span style={{ fontFamily: "IBM Plex Mono, monospace", fontSize: 12, color: "#7c858e" }}>{k}</span>
                      <span style={{ fontFamily: "IBM Plex Mono, monospace", fontSize: 12.5, color: "#dfe6ec", textAlign: "right" }}>{val}</span>
                    </React.Fragment>
                  ))}
                </div>
                {selPart && (
                  <div style={{ marginTop: 11, borderTop: "1px solid #222a32", paddingTop: 10, display: "flex", gap: 6 }}>
                    {selPart.ends.length >= 2 && <button style={{ ...fsbBtn(false), flex: 1, padding: "6px 0" }} onClick={() => rotatePart(selId)}>⟳ Rotate</button>}
                    <button style={{ ...fsbBtn(false), flex: 1, padding: "6px 0", borderColor: "#5c3340", color: "#ff8aa0" }} onClick={() => deletePart(selId)}>🗑 Delete</button>
                  </div>
                )}
              </React.Fragment>
            ) : (
              <div style={{ fontFamily: "IBM Plex Mono, monospace", fontSize: 11.5, color: "#6c757e", lineHeight: 1.55 }}>
                {tool === "wire" ? "Wire tool: drag on the canvas to draw a connection." : "Hover a part to inspect it. Click to select, then rotate / delete here."}
              </div>
            )}
          </div>

          {/* per-part sliders */}
          <div style={{ background: "#11161d", border: "1px solid #222a32", borderRadius: 10, padding: "10px 13px 8px" }}>
            <div style={{ display: "flex", justifyContent: "space-between", alignItems: "baseline" }}>
              <span className="eyebrow" style={{ color: "#7c858e" }}>part controls</span>
              <span style={{ fontFamily: "IBM Plex Mono, monospace", fontSize: 10.5, color: "#4f5862" }}>{adjustables.length}</span>
            </div>
            {adjustables.length === 0 ? (
              <div style={{ fontFamily: "IBM Plex Mono, monospace", fontSize: 11, color: "#5a636c", padding: "10px 0 4px" }}>Add a resistor, battery, capacitor or switch to get a slider here.</div>
            ) : adjustables.map(pt => (
              <FsbSliderRow key={pt.id} part={pt} selected={pt.id === selId}
                            onValue={setValue} onToggle={toggleSwitch}
                            onHover={(id) => { if (!dragRef.current) setHoverId(id); }} onSelect={setSelId} />
            ))}
          </div>
          </div>
        </div>
      </div>

      <p style={{ fontSize: 12.5, color: "var(--ink-faint)", marginTop: 10, lineHeight: 1.5 }}>
        Endpoints sharing a grid point connect automatically. A circuit needs one <b>battery</b> and a <b>ground</b> (0&nbsp;V reference) to solve.
        <span style={{ opacity: 0.7 }}> *capacitor µF is illustrative — the timescale is compressed for watching.</span>
      </p>
      </div>
      <TweaksPanel title="Tweaks">
        <CommonTweaks t={t} setTweak={setTweak} />
      </TweaksPanel>
      <GlossaryFab />
    </>
  );
}

window.__fsbRoot = window.__fsbRoot || ReactDOM.createRoot(document.getElementById("root"));
window.__fsbRoot.render(<FlowSandbox />);
