/* ============================================================================
   VOLTLAB · Simulador de Circuitos (estilo Falstad)
   ----------------------------------------------------------------------------
   Motor: Análise Nodal Modificada (MNA) + análise transitória (Euler implícito)
   Não-lineares (diodo/LED): iteração de Newton-Raphson com amortecimento
   Renderização: <canvas> com animação de corrente e coloração por tensão
   Expõe: window.SimuladorCircuitos (componente React)
   ============================================================================ */

(function () {
  const { useState, useRef, useEffect, useCallback } = React;

  /* ------------------------------ Constantes ------------------------------ */
  const GRID = 16;
  const GMIN = 1e-9;            // condutância mínima nó→terra (estabilidade)
  const C = {
    bg:      '#0C1018',
    panel:   '#121826',
    panel2:  '#0F1420',
    border:  'rgba(61,139,255,0.16)',
    grid:    'rgba(61,139,255,0.07)',
    text:    '#E8EDF5',
    dim:     '#8FA3BF',
    blue:    '#3D8BFF',
    cyan:    '#57D8E6',
    amber:   '#FFB84D',
    red:     '#FF6B6B',
    green:   '#5CE0A1',
    body:    '#A9BBD4',
    selGlow: 'rgba(61,139,255,0.85)',
  };
  const SCOPE_COLORS = ['#57D8E6', '#FFB84D', '#FF6B6B', '#9B8CFF', '#5CE0A1', '#FF8FD2'];

  /* ----------------------- Notação de engenharia -------------------------- */
  const ENG_TIERS = [
    [1e9, 'G'], [1e6, 'M'], [1e3, 'k'], [1, ''],
    [1e-3, 'm'], [1e-6, 'µ'], [1e-9, 'n'], [1e-12, 'p'],
  ];
  function fmtEng(v, unit) {
    if (!isFinite(v)) return '—';
    const a = Math.abs(v);
    if (a < 1e-15) return '0 ' + unit;
    for (const [f, p] of ENG_TIERS) {
      if (a >= f * 0.9999) {
        const x = v / f;
        const s = Math.abs(x) >= 100 ? x.toFixed(0) : Math.abs(x) >= 10 ? x.toFixed(1) : x.toFixed(2);
        return s.replace(/\.?0+$/, '') + ' ' + p + unit;
      }
    }
    return v.toExponential(1) + ' ' + unit;
  }
  function parseEng(str) {
    if (typeof str === 'number') return str;
    const m = String(str).trim().replace(',', '.').match(/^(-?[\d.]+)\s*([GMkKmuµnp]?)/);
    if (!m) return NaN;
    const mult = { G: 1e9, M: 1e6, k: 1e3, K: 1e3, m: 1e-3, u: 1e-6, 'µ': 1e-6, n: 1e-9, p: 1e-12, '': 1 }[m[2]];
    return parseFloat(m[1]) * mult;
  }

  /* --------------------------- Tipos de elemento --------------------------- */
  // vsCount: quantas variáveis de corrente MNA o elemento adiciona
  const TYPES = {
    wire:      { nome: 'Fio',        prefix: 'W', vs: 1 },
    resistor:  { nome: 'Resistor',   prefix: 'R', vs: 0, def: { R: 1000 } },
    capacitor: { nome: 'Capacitor',  prefix: 'C', vs: 0, def: { Cap: 100e-6 } },
    inductor:  { nome: 'Indutor',    prefix: 'L', vs: 0, def: { L: 100e-3 } },
    dc:        { nome: 'Fonte CC',   prefix: 'V', vs: 1, def: { V: 12 } },
    ac:        { nome: 'Fonte CA',   prefix: 'V', vs: 1, def: { A: 10, F: 60, Off: 0 } },
    ground:    { nome: 'Terra',      prefix: 'GND', vs: 0, single: true },
    switch:    { nome: 'Chave',      prefix: 'S', vs: 1, def: { fechada: false } },
    diode:     { nome: 'Diodo',      prefix: 'D', vs: 0, def: { Is: 1e-9, nVt: 1.5 * 0.025852 } },
    led:       { nome: 'LED',        prefix: 'D', vs: 0, def: { Is: 1e-18, nVt: 2.0 * 0.025852 } },
  };
  const PALETTE = ['wire', 'resistor', 'capacitor', 'inductor', 'dc', 'ac', 'ground', 'switch', 'diode', 'led'];

  let UID = 1;
  const counters = {};
  function newElement(type, x1, y1, x2, y2, props) {
    const t = TYPES[type];
    counters[type] = (counters[type] || 0) + 1;
    return {
      id: UID++,
      type,
      name: t.prefix + counters[type],
      p1: { x: x1, y: y1 },
      p2: t.single ? { x: x1, y: y1 } : { x: x2, y: y2 },
      props: Object.assign({}, t.def || {}, props || {}),
      scope: false,
      // estado de simulação
      nodes: [], volts: [0, 0], current: 0, vsIdx: -1,
      capV: 0, indI: 0, dV: 0, curcount: 0,
    };
  }
  function resetState(el) {
    el.capV = 0; el.indI = 0; el.dV = 0; el.current = 0;
    el.volts = [0, 0]; el.curcount = 0;
  }

  /* ------------------------------ Motor MNA ------------------------------- */
  function assignNodes(els) {
    const map = new Map();
    let n = 0;
    const key = (p) => p.x + ',' + p.y;
    const gndKeys = new Set();
    els.forEach((e) => { if (e.type === 'ground') gndKeys.add(key(e.p1)); });
    els.forEach((e) => {
      const posts = e.type === 'ground' ? [e.p1] : [e.p1, e.p2];
      e.nodes = posts.map((p) => {
        const k = key(p);
        if (gndKeys.has(k)) return -1;
        if (!map.has(k)) map.set(k, n++);
        return map.get(k);
      });
    });
    return n;
  }

/* ---- Buffer MNA pré-alocado (elimina alocação de matriz a cada passo) ---- */
  const _mna = { cap: 0, A: null, z: null, row: null, x: null };
  function _mnaEnsure(n) {
    if (n > _mna.cap) {
      const c = n + 8;
      _mna.A   = new Float64Array(c * c);
      _mna.z   = new Float64Array(c);
      _mna.row = new Float64Array(c);
      _mna.x   = new Float64Array(c);
      _mna.cap = c;
    }
  }

  // Eliminação gaussiana sobre buffer flat _mna.A (row-major, n×n)
  function solveLinear(n) {
    const A = _mna.A, z = _mna.z, row = _mna.row, x = _mna.x;
    x.fill(0, 0, n);
    for (let c = 0; c < n; c++) {
      let piv = c, pivAbs = Math.abs(A[c * n + c]);
      for (let r = c + 1; r < n; r++) { const ab = Math.abs(A[r * n + c]); if (ab > pivAbs) { pivAbs = ab; piv = r; } }
      if (pivAbs < 1e-13) return null;
      if (piv !== c) {
        const cn = c * n, pn = piv * n;
        row.set(A.subarray(cn, cn + n));
        A.copyWithin(cn, pn, pn + n);
        A.set(row.subarray(0, n), pn);
        const tz = z[piv]; z[piv] = z[c]; z[c] = tz;
      }
      const cn = c * n, inv = 1 / A[cn + c];
      for (let r = c + 1; r < n; r++) {
        const f = A[r * n + c] * inv;
        if (f === 0) continue;
        const rn = r * n;
        for (let k = c; k < n; k++) A[rn + k] -= f * A[cn + k];
        z[r] -= f * z[c];
      }
    }
    for (let r = n - 1; r >= 0; r--) {
      let s = z[r]; const rn = r * n;
      for (let k = r + 1; k < n; k++) s -= A[rn + k] * x[k];
      x[r] = s / A[rn + r];
    }
    return x;
  }

  const limExp = (x) => (x > 50 ? Math.exp(50) * (x - 49) : Math.exp(x));
  const nv = (x, n) => (n < 0 ? 0 : x[n]);

  function stampG(n, a, b, g) {
    const A = _mna.A;
    if (a >= 0) { A[a*n+a] += g; if (b >= 0) A[a*n+b] -= g; }
    if (b >= 0) { A[b*n+b] += g; if (a >= 0) A[b*n+a] -= g; }
  }
  function stampI(a, b, i) { // fonte de corrente entrando no nó a, saindo de b
    const z = _mna.z;
    if (a >= 0) z[a] += i;
    if (b >= 0) z[b] -= i;
  }
  function stampVS(n, nN, a, b, k, V) { // + em a, − em b
    const A = _mna.A, z = _mna.z, r = nN + k;
    if (a >= 0) { A[a*n+r] += 1; A[r*n+a] += 1; }
    if (b >= 0) { A[b*n+r] -= 1; A[r*n+b] -= 1; }
    z[r] = V;
  }

  function stepCircuit(els, nNodes, dt, t) {
    let m = 0;
    let hasNL = false;
    for (const e of els) {
      e.vsIdx = -1;
      const T = TYPES[e.type];
      if (T.vs && !(e.type === 'switch' && !e.props.fechada)) e.vsIdx = m++;
      if (e.type === 'diode' || e.type === 'led') hasNL = true;
    }
    const size = nNodes + m;
    if (size === 0) return { ok: true, x: null };

    _mnaEnsure(size);
    const A = _mna.A, z = _mna.z;
    let x = null;
    const maxIter = hasNL ? 50 : 1;
    for (let it = 0; it < maxIter; it++) {
      // Zera apenas as células usadas — sem alocação
      A.fill(0, 0, size * size);
      z.fill(0, 0, size);
      for (let i = 0; i < nNodes; i++) A[i * size + i] += GMIN;

      for (const e of els) {
        const a = e.nodes[0], b = e.nodes[1];
        switch (e.type) {
          case 'resistor': stampG(size, a, b, 1 / Math.max(e.props.R, 1e-6)); break;
          case 'capacitor': {
            const g = e.props.Cap / dt;
            stampG(size, a, b, g);
            stampI(a, b, g * e.capV);
            break;
          }
          case 'inductor': {
            const g = dt / Math.max(e.props.L, 1e-12);
            stampG(size, a, b, g);
            stampI(a, b, -e.indI);
            break;
          }
          case 'dc': stampVS(size, nNodes, a, b, e.vsIdx, e.props.V); break;
          case 'ac': {
            const v = e.props.Off + e.props.A * Math.sin(2 * Math.PI * e.props.F * t);
            stampVS(size, nNodes, a, b, e.vsIdx, v);
            break;
          }
          case 'wire': stampVS(size, nNodes, a, b, e.vsIdx, 0); break;
          case 'switch':
            if (e.props.fechada) stampVS(size, nNodes, a, b, e.vsIdx, 0);
            break;
          case 'diode': case 'led': {
            const { Is, nVt } = e.props;
            const ex = limExp(e.dV / nVt);
            const geq = Math.max((Is / nVt) * ex, 1e-12);
            const Id = Is * (ex - 1);
            const Ieq = Id - geq * e.dV;
            stampG(size, a, b, geq);
            stampI(a, b, -Ieq);
            break;
          }
          default: break;
        }
      }

      x = solveLinear(size);
      if (!x) return { ok: false, x: null };

      if (!hasNL) break;
      let conv = true;
      for (const e of els) {
        if (e.type !== 'diode' && e.type !== 'led') continue;
        const vd = nv(x, e.nodes[0]) - nv(x, e.nodes[1]);
        const dv = vd - e.dV;
        if (Math.abs(dv) > 1e-4) conv = false;
        e.dV += Math.max(-0.4, Math.min(0.4, dv));
      }
      if (conv) break;
    }

    // commit: tensões, correntes e estados
    for (const e of els) {
      const v0 = nv(x, e.nodes[0]);
      const v1 = e.type === 'ground' ? 0 : nv(x, e.nodes[1]);
      e.volts = [v0, v1];
      switch (e.type) {
        case 'resistor': e.current = (v0 - v1) / Math.max(e.props.R, 1e-6); break;
        case 'capacitor': {
          const g = e.props.Cap / dt;
          e.current = g * ((v0 - v1) - e.capV);
          e.capV = v0 - v1;
          break;
        }
        case 'inductor': {
          e.indI += (dt / Math.max(e.props.L, 1e-12)) * (v0 - v1);
          e.current = e.indI;
          break;
        }
        case 'dc': case 'ac': case 'wire':
          e.current = x[nNodes + e.vsIdx];
          break;
        case 'switch':
          e.current = e.props.fechada ? x[nNodes + e.vsIdx] : 0;
          break;
        case 'diode': case 'led': {
          const { Is, nVt } = e.props;
          e.current = Is * (limExp((v0 - v1) / nVt) - 1);
          break;
        }
        default: e.current = 0;
      }
    }
    return { ok: true, x };
  }

  /* ------------------------------ Desenho --------------------------------- */
  const VC_NEG = [255, 107, 107], VC_ZERO = [82, 96, 122], VC_POS = [87, 216, 230];
  function voltColor(v, maxV) {
    const t = Math.max(-1, Math.min(1, v / maxV));
    const a = t < 0 ? VC_NEG : VC_POS;
    const k = Math.abs(t);
    const r = Math.round(VC_ZERO[0] + (a[0] - VC_ZERO[0]) * k);
    const g = Math.round(VC_ZERO[1] + (a[1] - VC_ZERO[1]) * k);
    const b = Math.round(VC_ZERO[2] + (a[2] - VC_ZERO[2]) * k);
    return `rgb(${r},${g},${b})`;
  }

  function geom(el) {
    const dx = el.p2.x - el.p1.x, dy = el.p2.y - el.p1.y;
    const len = Math.hypot(dx, dy) || 1;
    const ux = dx / len, uy = dy / len;
    const cx = (el.p1.x + el.p2.x) / 2, cy = (el.p1.y + el.p2.y) / 2;
    return { dx, dy, len, ux, uy, cx, cy, px: -uy, py: ux };
  }

  function drawDots(ctx, el, g) {
    const I = el.current;
    if (Math.abs(I) < 1e-7) return;
    ctx.fillStyle = C.amber;
    const spacing = 16;
    let off = ((el.curcount % spacing) + spacing) % spacing;
    for (let d = off; d < g.len; d += spacing) {
      ctx.beginPath();
      ctx.arc(el.p1.x + g.ux * d, el.p1.y + g.uy * d, 2.1, 0, Math.PI * 2);
      ctx.fill();
    }
  }

  function lead(ctx, x1, y1, x2, y2, color, w) {
    ctx.strokeStyle = color;
    ctx.lineWidth = w || 2;
    ctx.lineCap = 'round';
    ctx.beginPath(); ctx.moveTo(x1, y1); ctx.lineTo(x2, y2); ctx.stroke();
  }

  function drawLabel(ctx, el, g, txt, extra) {
    ctx.font = '500 10px "JetBrains Mono", monospace';
    ctx.fillStyle = C.dim;
    ctx.textAlign = 'center';
    const ox = g.px * 13, oy = g.py * 13;
    ctx.fillText(txt, g.cx + ox, g.cy + oy + 3.5);
    if (extra) {
      ctx.fillStyle = 'rgba(143,163,191,0.65)';
      ctx.fillText(extra, g.cx - ox, g.cy - oy + 3.5);
    }
  }

  function drawElement(ctx, el, maxV, selected, running) {
    const g = geom(el);
    const c0 = voltColor(el.volts[0], maxV);
    const c1 = voltColor(el.volts[1], maxV);

    if (selected) {
      ctx.save();
      ctx.shadowColor = C.selGlow; ctx.shadowBlur = 9;
    }

    const BODY = Math.min(32, g.len * 0.55);
    const b1x = g.cx - g.ux * BODY / 2, b1y = g.cy - g.uy * BODY / 2;
    const b2x = g.cx + g.ux * BODY / 2, b2y = g.cy + g.uy * BODY / 2;

    switch (el.type) {
      case 'wire':
        lead(ctx, el.p1.x, el.p1.y, g.cx, g.cy, c0);
        lead(ctx, g.cx, g.cy, el.p2.x, el.p2.y, c1);
        break;

      case 'ground': {
        lead(ctx, el.p1.x, el.p1.y, el.p1.x, el.p1.y + 8, c0);
        ctx.strokeStyle = C.body; ctx.lineWidth = 2;
        const y = el.p1.y + 8;
        [[10, 0], [6.5, 4], [3, 8]].forEach(([w, dy]) => {
          ctx.beginPath(); ctx.moveTo(el.p1.x - w, y + dy); ctx.lineTo(el.p1.x + w, y + dy); ctx.stroke();
        });
        break;
      }

      case 'resistor': {
        lead(ctx, el.p1.x, el.p1.y, b1x, b1y, c0);
        lead(ctx, b2x, b2y, el.p2.x, el.p2.y, c1);
        ctx.strokeStyle = C.body; ctx.lineWidth = 2; ctx.lineJoin = 'round';
        ctx.beginPath(); ctx.moveTo(b1x, b1y);
        const segs = 6, amp = 5;
        for (let i = 1; i < segs; i++) {
          const f = i / segs, s = i % 2 ? 1 : -1;
          ctx.lineTo(b1x + (b2x - b1x) * f + g.px * amp * s, b1y + (b2y - b1y) * f + g.py * amp * s);
        }
        ctx.lineTo(b2x, b2y); ctx.stroke();
        drawLabel(ctx, el, g, fmtEng(el.props.R, 'Ω'));
        break;
      }

      case 'capacitor': {
        const gap = 7;
        const m1x = g.cx - g.ux * gap / 2, m1y = g.cy - g.uy * gap / 2;
        const m2x = g.cx + g.ux * gap / 2, m2y = g.cy + g.uy * gap / 2;
        lead(ctx, el.p1.x, el.p1.y, m1x, m1y, c0);
        lead(ctx, m2x, m2y, el.p2.x, el.p2.y, c1);
        ctx.lineWidth = 2.5;
        ctx.strokeStyle = c0;
        ctx.beginPath(); ctx.moveTo(m1x + g.px * 10, m1y + g.py * 10); ctx.lineTo(m1x - g.px * 10, m1y - g.py * 10); ctx.stroke();
        ctx.strokeStyle = c1;
        ctx.beginPath(); ctx.moveTo(m2x + g.px * 10, m2y + g.py * 10); ctx.lineTo(m2x - g.px * 10, m2y - g.py * 10); ctx.stroke();
        drawLabel(ctx, el, g, fmtEng(el.props.Cap, 'F'));
        break;
      }

      case 'inductor': {
        lead(ctx, el.p1.x, el.p1.y, b1x, b1y, c0);
        lead(ctx, b2x, b2y, el.p2.x, el.p2.y, c1);
        ctx.strokeStyle = C.body; ctx.lineWidth = 2;
        const loops = 4, step = BODY / loops, r = step / 2;
        const ang = Math.atan2(g.uy, g.ux);
        for (let i = 0; i < loops; i++) {
          const ccx = b1x + g.ux * (i * step + r), ccy = b1y + g.uy * (i * step + r);
          ctx.beginPath(); ctx.arc(ccx, ccy, r, ang + Math.PI, ang, false); ctx.stroke();
        }
        drawLabel(ctx, el, g, fmtEng(el.props.L, 'H'));
        break;
      }

      case 'dc': {
        const gap = 6;
        const m1x = g.cx - g.ux * gap / 2, m1y = g.cy - g.uy * gap / 2;
        const m2x = g.cx + g.ux * gap / 2, m2y = g.cy + g.uy * gap / 2;
        lead(ctx, el.p1.x, el.p1.y, m1x, m1y, c0);
        lead(ctx, m2x, m2y, el.p2.x, el.p2.y, c1);
        ctx.lineWidth = 2.5; ctx.strokeStyle = c0; // placa longa = +  (p1)
        ctx.beginPath(); ctx.moveTo(m1x + g.px * 12, m1y + g.py * 12); ctx.lineTo(m1x - g.px * 12, m1y - g.py * 12); ctx.stroke();
        ctx.strokeStyle = c1; // placa curta = −
        ctx.beginPath(); ctx.moveTo(m2x + g.px * 5, m2y + g.py * 5); ctx.lineTo(m2x - g.px * 5, m2y - g.py * 5); ctx.stroke();
        ctx.font = '600 9px "JetBrains Mono", monospace'; ctx.fillStyle = C.dim;
        ctx.fillText('+', m1x - g.ux * 7 + g.px * 17, m1y - g.uy * 7 + g.py * 17 + 3);
        drawLabel(ctx, el, g, fmtEng(el.props.V, 'V'));
        break;
      }

      case 'ac': {
        const r = 13;
        const e1x = g.cx - g.ux * r, e1y = g.cy - g.uy * r;
        const e2x = g.cx + g.ux * r, e2y = g.cy + g.uy * r;
        lead(ctx, el.p1.x, el.p1.y, e1x, e1y, c0);
        lead(ctx, e2x, e2y, el.p2.x, el.p2.y, c1);
        ctx.strokeStyle = C.body; ctx.lineWidth = 2;
        ctx.beginPath(); ctx.arc(g.cx, g.cy, r, 0, Math.PI * 2); ctx.stroke();
        ctx.strokeStyle = C.cyan; ctx.lineWidth = 1.6;
        ctx.beginPath();
        for (let i = -8; i <= 8; i++) {
          const f = i / 8;
          const px = g.cx + g.ux * f * 8 + g.px * Math.sin(f * Math.PI) * 5;
          const py = g.cy + g.uy * f * 8 + g.py * Math.sin(f * Math.PI) * 5;
          i === -8 ? ctx.moveTo(px, py) : ctx.lineTo(px, py);
        }
        ctx.stroke();
        drawLabel(ctx, el, g, fmtEng(el.props.A, 'V') + ' · ' + fmtEng(el.props.F, 'Hz'));
        break;
      }

      case 'switch': {
        const gap = 22;
        const m1x = g.cx - g.ux * gap / 2, m1y = g.cy - g.uy * gap / 2;
        const m2x = g.cx + g.ux * gap / 2, m2y = g.cy + g.uy * gap / 2;
        lead(ctx, el.p1.x, el.p1.y, m1x, m1y, c0);
        lead(ctx, m2x, m2y, el.p2.x, el.p2.y, c1);
        ctx.fillStyle = C.body;
        ctx.beginPath(); ctx.arc(m1x, m1y, 2.6, 0, Math.PI * 2); ctx.fill();
        ctx.beginPath(); ctx.arc(m2x, m2y, 2.6, 0, Math.PI * 2); ctx.fill();
        ctx.strokeStyle = el.props.fechada ? C.green : C.body; ctx.lineWidth = 2.2;
        ctx.beginPath(); ctx.moveTo(m1x, m1y);
        if (el.props.fechada) ctx.lineTo(m2x, m2y);
        else ctx.lineTo(m1x + g.ux * gap * 0.85 + g.px * 11, m1y + g.uy * gap * 0.85 + g.py * 11);
        ctx.stroke();
        drawLabel(ctx, el, g, el.props.fechada ? 'fechada' : 'aberta');
        break;
      }

      case 'diode': case 'led': {
        const h = 8, w = 12;
        const m1x = g.cx - g.ux * w / 2, m1y = g.cy - g.uy * w / 2; // ânodo (p1)
        const m2x = g.cx + g.ux * w / 2, m2y = g.cy + g.uy * w / 2; // cátodo
        lead(ctx, el.p1.x, el.p1.y, m1x, m1y, c0);
        lead(ctx, m2x, m2y, el.p2.x, el.p2.y, c1);
        const on = el.current > 1e-4;
        if (el.type === 'led' && on) {
          ctx.save();
          ctx.fillStyle = 'rgba(255,184,77,0.18)';
          ctx.beginPath(); ctx.arc(g.cx, g.cy, 14 + Math.min(8, el.current * 60), 0, Math.PI * 2); ctx.fill();
          ctx.restore();
        }
        ctx.fillStyle = el.type === 'led' ? (on ? C.amber : '#7d6233') : C.body;
        ctx.beginPath();
        ctx.moveTo(m1x + g.px * h, m1y + g.py * h);
        ctx.lineTo(m1x - g.px * h, m1y - g.py * h);
        ctx.lineTo(m2x, m2y);
        ctx.closePath(); ctx.fill();
        ctx.strokeStyle = C.body; ctx.lineWidth = 2.4;
        ctx.beginPath(); ctx.moveTo(m2x + g.px * h, m2y + g.py * h); ctx.lineTo(m2x - g.px * h, m2y - g.py * h); ctx.stroke();
        if (el.type === 'led') {
          ctx.strokeStyle = on ? C.amber : C.dim; ctx.lineWidth = 1.4;
          for (const o of [0, 5]) {
            const sx = g.cx + g.px * (h + 4) + g.ux * o, sy = g.cy + g.py * (h + 4) + g.uy * o;
            ctx.beginPath(); ctx.moveTo(sx, sy); ctx.lineTo(sx + g.px * 6 + g.ux * 3, sy + g.py * 6 + g.uy * 3); ctx.stroke();
          }
        }
        break;
      }
      default: break;
    }

    if (selected) ctx.restore();
    if (running && el.type !== 'ground') drawDots(ctx, el, g);
  }

  function distToSeg(px, py, x1, y1, x2, y2) {
    const dx = x2 - x1, dy = y2 - y1;
    const l2 = dx * dx + dy * dy;
    let t = l2 ? ((px - x1) * dx + (py - y1) * dy) / l2 : 0;
    t = Math.max(0, Math.min(1, t));
    return Math.hypot(px - (x1 + t * dx), py - (y1 + t * dy));
  }

  /* ------------------------------ Exemplos -------------------------------- */
  function exemplos(nome) {
    const G = GRID;
    const E = (t, x1, y1, x2, y2, p) => newElement(t, x1 * G, y1 * G, x2 * G, y2 * G, p);
    switch (nome) {
      case 'rc': {
        const els = [
          E('dc', 5, 7, 5, 15, { V: 12 }),
          E('wire', 5, 7, 9, 7),
          E('switch', 9, 7, 14, 7, { fechada: false }),
          E('resistor', 14, 7, 20, 7, { R: 1000 }),
          E('wire', 20, 7, 24, 7),
          E('capacitor', 24, 7, 24, 15, { Cap: 470e-6 }),
          E('wire', 24, 15, 5, 15),
          E('ground', 5, 15, 5, 15),
        ];
        els[5].scope = true; els[3].scope = true;
        return els;
      }
      case 'rlc': {
        const els = [
          E('dc', 5, 7, 5, 15, { V: 10 }),
          E('switch', 5, 7, 11, 7, { fechada: false }),
          E('inductor', 11, 7, 17, 7, { L: 250e-3 }),
          E('resistor', 17, 7, 23, 7, { R: 10 }),
          E('capacitor', 23, 7, 23, 15, { Cap: 22e-6 }),
          E('wire', 23, 15, 5, 15),
          E('ground', 5, 15, 5, 15),
        ];
        els[4].scope = true; els[2].scope = true;
        return els;
      }
      case 'retificador': {
        const els = [
          E('ac', 4, 7, 4, 15, { A: 12, F: 60, Off: 0 }),
          E('wire', 4, 7, 8, 7),
          E('diode', 8, 7, 13, 7),
          E('wire', 13, 7, 17, 7),
          E('resistor', 17, 7, 17, 15, { R: 470 }),
          E('wire', 17, 7, 22, 7),
          E('capacitor', 22, 7, 22, 15, { Cap: 220e-6 }),
          E('wire', 22, 15, 4, 15),
          E('wire', 17, 15, 17, 15),
          E('ground', 4, 15, 4, 15),
        ];
        // conecta R ao barramento inferior
        els[8] = E('wire', 17, 15, 22, 15);
        els[0].scope = true; els[4].scope = true;
        return els;
      }
      case 'led': {
        const els = [
          E('dc', 6, 7, 6, 14, { V: 5 }),
          E('wire', 6, 7, 11, 7),
          E('resistor', 11, 7, 17, 7, { R: 220 }),
          E('led', 17, 7, 22, 7),
          E('wire', 22, 7, 22, 14),
          E('wire', 22, 14, 6, 14),
          E('ground', 6, 14, 6, 14),
        ];
        els[3].scope = true;
        return els;
      }
      default: return [];
    }
  }

  /* ----------------------------- Componente ------------------------------- */
  function SimuladorCircuitos() {
    const canvasRef = useRef(null);
    const wrapRef = useRef(null);
    const scopeRef = useRef(null);

    const sim = useRef({
      els: exemplos('rc'),
      t: 0,
      dt: 50e-6,
      running: true,
      speed: 180,           // passos por quadro
      singular: false,
      maxV: 5,
      scopeData: new Map(), // id -> {v:[], i:[]}
      mode: 'select',
      selectedId: null,
      drag: null,
      hoverNode: null,
    }).current;

    const [, force] = useState(0);
    const rerender = useCallback(() => force((x) => x + 1), []);
    const [tick, setTick] = useState(0);

    /* ------- laço de simulação + desenho ------- */
    useEffect(() => {
      let raf;
      const loop = () => {
        const cv = canvasRef.current;
        if (!cv) { raf = requestAnimationFrame(loop); return; }
        const ctx = cv.getContext('2d');
        const dpr = window.devicePixelRatio || 1;
        const W = cv.clientWidth, H = cv.clientHeight;
        if (cv.width !== W * dpr || cv.height !== H * dpr) { cv.width = W * dpr; cv.height = H * dpr; }
        ctx.setTransform(dpr, 0, 0, dpr, 0, 0);

        // simula
        if (sim.running && sim.els.length) {
          const n = assignNodes(sim.els);
          let ok = true;
          for (let s = 0; s < sim.speed; s++) {
            const r = stepCircuit(sim.els, n, sim.dt, sim.t);
            if (!r.ok) { ok = false; break; }
            sim.t += sim.dt;
          }
          sim.singular = !ok;
          // animação dos pontos
          for (const e of sim.els) e.curcount += Math.max(-3, Math.min(3, e.current * 1.4)) * 4;
          // escala de cor
          let mv = 5;
          for (const e of sim.els) mv = Math.max(mv, Math.abs(e.volts[0]), Math.abs(e.volts[1]));
          sim.maxV = sim.maxV * 0.98 + mv * 0.02;
          // osciloscópio
          for (const e of sim.els) {
            if (!e.scope) { sim.scopeData.delete(e.id); continue; }
            let d = sim.scopeData.get(e.id);
            if (!d) { d = { v: [], i: [], name: e.name, type: e.type }; sim.scopeData.set(e.id, d); }
            d.name = e.name;
            d.v.push(e.volts[0] - e.volts[1]);
            d.i.push(e.current);
            if (d.v.length > 460) { d.v.shift(); d.i.shift(); }
          }
        }

        /* ------- canvas principal ------- */
        ctx.fillStyle = C.bg; ctx.fillRect(0, 0, W, H);
        ctx.fillStyle = C.grid;
        for (let x = 0; x < W; x += GRID)
          for (let y = 0; y < H; y += GRID) ctx.fillRect(x - 0.5, y - 0.5, 1.2, 1.2);

        for (const e of sim.els) drawElement(ctx, e, sim.maxV, e.id === sim.selectedId, sim.running);

        // pré-visualização de colocação
        if (sim.drag && sim.drag.kind === 'place') {
          ctx.strokeStyle = 'rgba(61,139,255,0.6)'; ctx.lineWidth = 2; ctx.setLineDash([5, 4]);
          ctx.beginPath(); ctx.moveTo(sim.drag.x1, sim.drag.y1); ctx.lineTo(sim.drag.x2, sim.drag.y2); ctx.stroke();
          ctx.setLineDash([]);
        }

        /* ------- osciloscópio ------- */
        const sc = scopeRef.current;
        if (sc) {
          const sctx = sc.getContext('2d');
          const SW = sc.clientWidth, SH = sc.clientHeight;
          if (sc.width !== SW * dpr || sc.height !== SH * dpr) { sc.width = SW * dpr; sc.height = SH * dpr; }
          sctx.setTransform(dpr, 0, 0, dpr, 0, 0);
          sctx.fillStyle = C.panel2; sctx.fillRect(0, 0, SW, SH);
          sctx.strokeStyle = 'rgba(61,139,255,0.12)';
          sctx.beginPath(); sctx.moveTo(0, SH / 2); sctx.lineTo(SW, SH / 2); sctx.stroke();
          let ci = 0;
          for (const [, d] of sim.scopeData) {
            const col = SCOPE_COLORS[ci % SCOPE_COLORS.length];
            let mv = 1e-9; for (let _i = 0; _i < d.v.length; _i++) { const _av = Math.abs(d.v[_i]); if (_av > mv) mv = _av; }
            sctx.strokeStyle = col; sctx.lineWidth = 1.6;
            sctx.beginPath();
            d.v.forEach((v, i) => {
              const x = (i / 459) * SW;
              const y = SH / 2 - (v / mv) * (SH / 2 - 6);
              i === 0 ? sctx.moveTo(x, y) : sctx.lineTo(x, y);
            });
            sctx.stroke();
            sctx.font = '500 10px "JetBrains Mono", monospace';
            sctx.fillStyle = col;
            const last = d.v[d.v.length - 1] || 0, lastI = d.i[d.i.length - 1] || 0;
            sctx.fillText(`${d.name}  ${fmtEng(last, 'V')}  ${fmtEng(lastI, 'A')}  (esc. ${fmtEng(mv, 'V')})`, 8, 14 + ci * 13);
            ci++;
          }
          if (ci === 0) {
            sctx.fillStyle = C.dim; sctx.font = '400 11px "IBM Plex Sans", sans-serif';
            sctx.fillText('Selecione um elemento e ative "Osciloscópio" para plotar tensão aqui.', 8, SH / 2 + 4);
          }
        }

        raf = requestAnimationFrame(loop);
      };
      raf = requestAnimationFrame(loop);
      const iv = setInterval(() => setTick((t) => t + 1), 250); // atualiza painel
      return () => { cancelAnimationFrame(raf); clearInterval(iv); };
    }, []); // eslint-disable-line

    /* ------- interação ------- */
    const snap = (v) => Math.round(v / GRID) * GRID;
    const getPos = (ev) => {
      const r = canvasRef.current.getBoundingClientRect();
      return { x: ev.clientX - r.left, y: ev.clientY - r.top };
    };
    const hitTest = (x, y) => {
      let best = null, bd = 7;
      for (let i = sim.els.length - 1; i >= 0; i--) {
        const e = sim.els[i];
        const p2 = e.type === 'ground' ? { x: e.p1.x, y: e.p1.y + 16 } : e.p2;
        const d = distToSeg(x, y, e.p1.x, e.p1.y, p2.x, p2.y);
        if (d < bd) { bd = d; best = e; }
      }
      return best;
    };

    const onDown = (ev) => {
      ev.preventDefault();
      canvasRef.current.setPointerCapture(ev.pointerId);
      const { x, y } = getPos(ev);
      if (sim.mode === 'select') {
        const e = hitTest(x, y);
        sim.selectedId = e ? e.id : null;
        sim.drag = e ? { kind: 'move', id: e.id, lx: x, ly: y, moved: false } : null;
        rerender();
      } else if (sim.mode === 'ground') {
        sim.els.push(newElement('ground', snap(x), snap(y)));
        rerender();
      } else {
        sim.drag = { kind: 'place', type: sim.mode, x1: snap(x), y1: snap(y), x2: snap(x), y2: snap(y) };
      }
    };
    const onMove = (ev) => {
      if (!sim.drag) return;
      const { x, y } = getPos(ev);
      if (sim.drag.kind === 'place') {
        sim.drag.x2 = snap(x); sim.drag.y2 = snap(y);
      } else if (sim.drag.kind === 'move') {
        const e = sim.els.find((q) => q.id === sim.drag.id);
        if (!e) return;
        const dx = snap(x) - snap(sim.drag.lx), dy = snap(y) - snap(sim.drag.ly);
        if (dx || dy) {
          e.p1.x += dx; e.p1.y += dy; e.p2.x += dx; e.p2.y += dy;
          sim.drag.lx = x; sim.drag.ly = y; sim.drag.moved = true;
        }
      }
    };
    const onUp = () => {
      const d = sim.drag;
      sim.drag = null;
      if (!d) return;
      if (d.kind === 'place') {
        const len = Math.hypot(d.x2 - d.x1, d.y2 - d.y1);
        if (len >= GRID) {
          const el = newElement(d.type, d.x1, d.y1, d.x2, d.y2);
          sim.els.push(el);
          sim.selectedId = el.id;
          rerender();
        }
      } else if (d.kind === 'move' && !d.moved) {
        const e = sim.els.find((q) => q.id === d.id);
        if (e && e.type === 'switch') { e.props.fechada = !e.props.fechada; rerender(); }
      }
    };

    useEffect(() => {
      const onKey = (ev) => {
        if (/INPUT|TEXTAREA|SELECT/.test(document.activeElement?.tagName || '')) return;
        if ((ev.key === 'Delete' || ev.key === 'Backspace') && sim.selectedId != null) {
          sim.els = sim.els.filter((e) => e.id !== sim.selectedId);
          sim.scopeData.delete(sim.selectedId);
          sim.selectedId = null;
          rerender();
        }
        if (ev.key === ' ') { ev.preventDefault(); sim.running = !sim.running; rerender(); }
      };
      window.addEventListener('keydown', onKey);
      return () => window.removeEventListener('keydown', onKey);
    }, []); // eslint-disable-line

    /* ------- ações ------- */
    const sel = sim.els.find((e) => e.id === sim.selectedId) || null;
    const resetSim = () => { sim.t = 0; sim.els.forEach(resetState); sim.scopeData.forEach((d) => { d.v.length = 0; d.i.length = 0; }); };
    const loadExample = (nome) => {
      if (!nome) return;
      sim.els = exemplos(nome);
      sim.scopeData.clear();
      sim.selectedId = null;
      resetSim();
      sim.running = true;
      rerender();
    };
    const setProp = (key, str) => {
      const v = parseEng(str);
      if (sel && isFinite(v)) { sel.props[key] = v; rerender(); }
    };

    const propFields = sel ? ({
      resistor: [['R', 'Resistência (Ω)']],
      capacitor: [['Cap', 'Capacitância (F)']],
      inductor: [['L', 'Indutância (H)']],
      dc: [['V', 'Tensão (V)']],
      ac: [['A', 'Amplitude (V)'], ['F', 'Frequência (Hz)'], ['Off', 'Offset (V)']],
    }[sel.type] || []) : [];

    const hasGround = sim.els.some((e) => e.type === 'ground');

    /* ------- UI ------- */
    return (
      <div className="vlsim">
        

        <header className="vlsim-bar">
          <div className="vlsim-controls">
            <button className={'vlsim-btn ' + (sim.running ? 'on' : 'primary')}
              onClick={() => { sim.running = !sim.running; rerender(); }}>
              {sim.running ? '⏸ Pausar' : '▶ Executar'}
            </button>
            <button className="vlsim-btn" onClick={() => { resetSim(); rerender(); }}>↺ Reiniciar</button>
            <label className="vlsim-slider">
              <span>Velocidade</span>
              <input type="range" min="10" max="1200" defaultValue={sim.speed}
                onChange={(e) => { sim.speed = +e.target.value; }} />
            </label>
            <select className="vlsim-select" defaultValue="" onChange={(e) => { loadExample(e.target.value); e.target.value = ''; }}>
              <option value="" disabled>Exemplos…</option>
              <option value="rc">Carga RC (chave + capacitor)</option>
              <option value="rlc">Oscilação RLC série</option>
              <option value="retificador">Retificador de meia onda</option>
              <option value="led">LED com resistor</option>
            </select>
            <button className="vlsim-btn danger" onClick={() => { sim.els = []; sim.scopeData.clear(); sim.selectedId = null; resetSim(); rerender(); }}>
              Limpar
            </button>
          </div>
        </header>

        <div className="vlsim-main">
          {/* paleta */}
          <aside className="vlsim-palette">
            <button className={'vlsim-tool ' + (sim.mode === 'select' ? 'active' : '')}
              onClick={() => { sim.mode = 'select'; rerender(); }} title="Selecionar / mover / acionar chave">
              <span className="vlsim-tool-ic">↖</span><span>Selecionar</span>
            </button>
            <div className="vlsim-pal-sep" />
            {PALETTE.map((t) => (
              <button key={t} className={'vlsim-tool ' + (sim.mode === t ? 'active' : '')}
                onClick={() => { sim.mode = t; sim.selectedId = null; rerender(); }}>
                <span className="vlsim-tool-ic">{
                  { wire: '─', resistor: '⌁', capacitor: '⊣⊢', inductor: '∿', dc: '⎓', ac: '~', ground: '⏚', switch: '⌥', diode: '▷|', led: '▷✦' }[t]
                }</span>
                <span>{TYPES[t].nome}</span>
              </button>
            ))}
          </aside>

          {/* canvas */}
          <div className="vlsim-canvas-wrap" ref={wrapRef}>
            <canvas ref={canvasRef}
              onPointerDown={onDown} onPointerMove={onMove} onPointerUp={onUp} onPointerCancel={onUp} />
            <div className="vlsim-status">
              <span>t = {fmtEng(sim.t, 's')}</span>
              <span>{sim.els.length} elementos</span>
              {!hasGround && sim.els.length > 0 && <span className="warn">⚠ adicione um Terra como referência</span>}
              {sim.singular && <span className="warn">⚠ circuito indeterminado (verifique conexões)</span>}
              <span className="hint">arraste para desenhar · clique para selecionar · clique na chave para acionar · Del exclui · Espaço pausa</span>
            </div>
          </div>

          {/* propriedades */}
          <aside className="vlsim-props">
            <h2>Propriedades</h2>
            {!sel && <p className="vlsim-empty">Nenhum elemento selecionado. Clique em um componente no esquema para editar valores e medir grandezas.</p>}
            {sel && (
              <div key={sel.id + ':' + tick} className="vlsim-prop-body">
                <div className="vlsim-prop-name">
                  <strong>{sel.name}</strong>
                  <span>{TYPES[sel.type].nome}</span>
                </div>

                {propFields.map(([k, label]) => (
                  <label key={k} className="vlsim-field">
                    <span>{label}</span>
                    <input defaultValue={fmtEng(sel.props[k], '').trim()}
                      onBlur={(e) => setProp(k, e.target.value)}
                      onKeyDown={(e) => { if (e.key === 'Enter') { setProp(k, e.target.value); e.target.blur(); } }} />
                  </label>
                ))}

                {sel.type === 'switch' && (
                  <button className="vlsim-btn block" onClick={() => { sel.props.fechada = !sel.props.fechada; rerender(); }}>
                    {sel.props.fechada ? 'Abrir chave' : 'Fechar chave'}
                  </button>
                )}

                <div className="vlsim-meas">
                  <div><span>Tensão</span><code>{fmtEng(sel.volts[0] - sel.volts[1], 'V')}</code></div>
                  <div><span>Corrente</span><code>{fmtEng(sel.current, 'A')}</code></div>
                  <div><span>Potência</span><code>{fmtEng((sel.volts[0] - sel.volts[1]) * sel.current, 'W')}</code></div>
                </div>

                <label className="vlsim-check">
                  <input type="checkbox" checked={sel.scope}
                    onChange={(e) => { sel.scope = e.target.checked; if (!sel.scope) sim.scopeData.delete(sel.id); rerender(); }} />
                  <span>Mostrar no osciloscópio</span>
                </label>

                <div className="vlsim-prop-actions">
                  <button className="vlsim-btn" onClick={() => {
                    const t = { ...sel.p1 }; sel.p1 = { ...sel.p2 }; sel.p2 = t; rerender();
                  }}>Inverter polaridade</button>
                  <button className="vlsim-btn danger" onClick={() => {
                    sim.els = sim.els.filter((e) => e.id !== sel.id);
                    sim.scopeData.delete(sel.id); sim.selectedId = null; rerender();
                  }}>Excluir</button>
                </div>
              </div>
            )}
            <div className="vlsim-legend">
              <h3>Legenda</h3>
              <div><i style={{ background: C.cyan }} /> potencial positivo</div>
              <div><i style={{ background: '#526078' }} /> potencial nulo</div>
              <div><i style={{ background: C.red }} /> potencial negativo</div>
              <div><i style={{ background: C.amber, borderRadius: '50%' }} /> fluxo de corrente convencional</div>
            </div>
          </aside>
        </div>

        <footer className="vlsim-scope">
          <div className="vlsim-scope-head">Osciloscópio</div>
          <canvas ref={scopeRef} />
        </footer>
      </div>
    );
  }

  /* -------------------------------- CSS ----------------------------------- */
  const CSS = `
  .vlsim{display:flex;flex-direction:column;height:calc(100vh - 200px);min-height:560px;background:${C.bg};color:${C.text};
    font-family:'IBM Plex Sans',sans-serif;border:1px solid ${C.border};border-radius:14px;overflow:hidden}
  .vlsim *{box-sizing:border-box}
  .vlsim h1,.vlsim h2,.vlsim h3{font-family:'Space Grotesk',sans-serif;margin:0}

  .vlsim-bar{display:flex;align-items:center;justify-content:flex-start;gap:10px;flex-wrap:wrap;
    padding:12px 18px;background:${C.panel};border-bottom:1px solid ${C.border}}
  .vlsim-title{display:flex;align-items:center;gap:12px}
  .vlsim-logo{width:34px;height:34px;display:grid;place-items:center;border-radius:9px;
    background:linear-gradient(135deg,rgba(61,139,255,.25),rgba(87,216,230,.15));border:1px solid ${C.border};font-size:16px}
  .vlsim-title h1{font-size:15px;font-weight:600;letter-spacing:.2px}
  .vlsim-title p{margin:1px 0 0;font-size:11px;color:${C.dim}}
  .vlsim-controls{display:flex;align-items:center;gap:8px;flex-wrap:wrap}

  .vlsim-btn{background:${C.panel2};color:${C.text};border:1px solid ${C.border};border-radius:8px;
    padding:7px 13px;font:500 12px 'IBM Plex Sans',sans-serif;cursor:pointer;transition:.15s}
  .vlsim-btn:hover{border-color:${C.blue};color:#fff}
  .vlsim-btn.primary{background:linear-gradient(135deg,${C.blue},#2f6fd6);border-color:transparent;color:#fff}
  .vlsim-btn.on{border-color:${C.cyan};color:${C.cyan}}
  .vlsim-btn.danger:hover{border-color:${C.red};color:${C.red}}
  .vlsim-btn.block{width:100%}
  .vlsim-select{background:${C.panel2};color:${C.text};border:1px solid ${C.border};border-radius:8px;
    padding:7px 10px;font:500 12px 'IBM Plex Sans',sans-serif;cursor:pointer}
  .vlsim-slider{display:flex;align-items:center;gap:8px;font-size:11px;color:${C.dim}}
  .vlsim-slider input{accent-color:${C.blue};width:110px}

  .vlsim-main{display:flex;flex:1;min-height:0}
  .vlsim-palette{width:128px;flex-shrink:0;background:${C.panel};border-right:1px solid ${C.border};
    padding:10px 8px;display:flex;flex-direction:column;gap:4px;overflow-y:auto}
  .vlsim-pal-sep{height:1px;background:${C.border};margin:5px 2px}
  .vlsim-tool{display:flex;align-items:center;gap:8px;background:transparent;border:1px solid transparent;
    color:${C.dim};border-radius:8px;padding:7px 9px;font:500 11.5px 'IBM Plex Sans',sans-serif;cursor:pointer;text-align:left;transition:.12s}
  .vlsim-tool:hover{color:${C.text};background:${C.panel2}}
  .vlsim-tool.active{color:${C.cyan};background:rgba(87,216,230,.08);border-color:rgba(87,216,230,.35)}
  .vlsim-tool-ic{width:26px;text-align:center;font-family:'JetBrains Mono',monospace;font-size:12px;flex-shrink:0}

  .vlsim-canvas-wrap{flex:1;position:relative;min-width:0}
  .vlsim-canvas-wrap canvas{width:100%;height:100%;display:block;cursor:crosshair;touch-action:none}
  .vlsim-status{position:absolute;left:0;right:0;bottom:0;display:flex;gap:16px;flex-wrap:wrap;align-items:center;
    padding:5px 12px;font:500 10.5px 'JetBrains Mono',monospace;color:${C.dim};
    background:linear-gradient(transparent,rgba(12,16,24,.92));pointer-events:none}
  .vlsim-status .warn{color:${C.amber}}
  .vlsim-status .hint{margin-left:auto;opacity:.55;font-family:'IBM Plex Sans',sans-serif}

  .vlsim-props{width:248px;flex-shrink:0;background:${C.panel};border-left:1px solid ${C.border};
    padding:14px;overflow-y:auto}
  .vlsim-props h2{font-size:12px;text-transform:uppercase;letter-spacing:1.2px;color:${C.dim};margin-bottom:12px}
  .vlsim-empty{font-size:12px;color:${C.dim};line-height:1.55}
  .vlsim-prop-name{display:flex;align-items:baseline;gap:8px;margin-bottom:12px}
  .vlsim-prop-name strong{font-family:'JetBrains Mono',monospace;font-size:15px;color:${C.cyan}}
  .vlsim-prop-name span{font-size:11px;color:${C.dim}}
  .vlsim-field{display:block;margin-bottom:10px}
  .vlsim-field span{display:block;font-size:10.5px;color:${C.dim};margin-bottom:4px}
  .vlsim-field input{width:100%;background:${C.panel2};border:1px solid ${C.border};border-radius:7px;
    color:${C.text};padding:7px 9px;font:500 12.5px 'JetBrains Mono',monospace;outline:none}
  .vlsim-field input:focus{border-color:${C.blue}}
  .vlsim-meas{margin:12px 0;border:1px solid ${C.border};border-radius:9px;overflow:hidden}
  .vlsim-meas div{display:flex;justify-content:space-between;padding:6px 10px;font-size:11px;color:${C.dim}}
  .vlsim-meas div+div{border-top:1px solid ${C.border}}
  .vlsim-meas code{font:600 11.5px 'JetBrains Mono',monospace;color:${C.text}}
  .vlsim-check{display:flex;align-items:center;gap:8px;font-size:12px;margin:10px 0;cursor:pointer}
  .vlsim-check input{accent-color:${C.cyan}}
  .vlsim-prop-actions{display:flex;gap:8px;margin-top:10px}
  .vlsim-prop-actions .vlsim-btn{flex:1;padding:7px 6px;font-size:11px}

  .vlsim-legend{margin-top:18px;padding-top:12px;border-top:1px solid ${C.border}}
  .vlsim-legend h3{font-size:10.5px;text-transform:uppercase;letter-spacing:1px;color:${C.dim};margin-bottom:8px}
  .vlsim-legend div{display:flex;align-items:center;gap:8px;font-size:11px;color:${C.dim};margin-bottom:5px}
  .vlsim-legend i{width:14px;height:3.5px;border-radius:2px;display:inline-block}

  .vlsim-scope{flex-shrink:0;border-top:1px solid ${C.border};background:${C.panel}}
  .vlsim-scope-head{padding:6px 14px 0;font:600 10.5px 'Space Grotesk',sans-serif;
    text-transform:uppercase;letter-spacing:1.2px;color:${C.dim}}
  .vlsim-scope canvas{width:100%;height:118px;display:block}

  @media (max-width:900px){
    .vlsim-main{flex-direction:column}
    .vlsim-palette{width:100%;flex-direction:row;flex-wrap:wrap;border-right:none;border-bottom:1px solid ${C.border}}
    .vlsim-pal-sep{display:none}
    .vlsim-canvas-wrap{min-height:380px}
    .vlsim-props{width:100%;border-left:none;border-top:1px solid ${C.border}}
  }`;

  /* --------------------------- Registro global ---------------------------- */
  if (!document.getElementById('vl-simulador-css')) {
    const st = document.createElement('style');
    st.id = 'vl-simulador-css';
    st.textContent = CSS;
    document.head.appendChild(st);
  }

  window.SimuladorCircuitos = SimuladorCircuitos;
  if (window.VOLTLAB_MODULES && Array.isArray(window.VOLTLAB_MODULES)) {
    window.VOLTLAB_MODULES.push({
      id: 'simulador',
      nome: 'Simulador de Circuitos',
      descricao: 'Simulação interativa de circuitos (estilo Falstad)',
      component: SimuladorCircuitos,
    });
  }
})();   