'use strict';
/* ══════════════════════════════════════════════════════════════
   BohrAtomSim — Bohr atomic model simulation (hydrogen)
   E_n = −13.6 / n²  eV      λ = 1240 / ΔE  nm
   Orbital animation · energy diagram · spectrum bar
   ══════════════════════════════════════════════════════════════ */

class BohrAtomSim {
  constructor(canvas) {
    this.canvas = canvas;
    this.ctx    = canvas.getContext('2d');
    this.W = 0; this.H = 0;

    /* physics */
    this.level = 2;               // current energy level n (1–6)
    this._angle = 0;              // electron orbital angle
    this._lastTransition = null;  // { from, to, deltaE, wavelength, series }
    this._emittedPhotons = [];    // wavelengths emitted so far

    /* transition animation */
    this._trans      = null;  // { from, to, t, dur, photon }
    this._photons    = [];    // flying photon particles [{x,y,vx,vy,color,t,maxT}]

    /* spectrum marks */
    this._specMarks = [];     // wavelengths (nm)

    /* animation */
    this.playing = false;
    this._raf    = null;
    this._lastTs = null;

    /* interaction */
    this._hoverLevel = null;

    this.onUpdate = null;

    this._bindEvents();
    new ResizeObserver(() => { this.fit(); this.draw(); }).observe(canvas.parentElement);
  }

  /* ── public API ─────────────────────────────── */

  fit() {
    const dpr = window.devicePixelRatio || 1;
    const w = this.canvas.offsetWidth || 600;
    const h = this.canvas.offsetHeight || 400;
    this.canvas.width  = w * dpr;
    this.canvas.height = h * dpr;
    this.ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
    this.W = w; this.H = h;
  }

  getParams() { return { level: this.level }; }
  setParams({ level } = {}) {
    if (level !== undefined) {
      const n = Math.max(1, Math.min(6, Math.round(+level)));
      if (n !== this.level) this.transition(this.level, n);
    }
    this.draw();
    this._emit();
  }

  transition(from, to) {
    from = Math.max(1, Math.min(6, Math.round(+from)));
    to   = Math.max(1, Math.min(6, Math.round(+to)));
    if (from === to) return;

    const eFrom = -13.6 / (from * from);
    const eTo   = -13.6 / (to * to);
    const deltaE = Math.abs(eTo - eFrom);
    const wl     = 1240 / deltaE;
    const color  = this._wavelengthToColor(wl);
    const series = this._seriesName(from, to);

    this._lastTransition = { from, to, deltaE, wavelength: wl, series };

    const isEmission = from > to;
    if (isEmission) this._emittedPhotons.push(wl);

    /* push spectrum mark */
    if (!this._specMarks.includes(Math.round(wl))) {
      this._specMarks.push(Math.round(wl));
    }

    /* start animation */
    this._trans = {
      from, to, t: 0, dur: 0.5,
      color, wavelength: wl,
      isEmission,
    };

    if (window.LabFX) {
      LabFX.sound.play('chime', { pitch: 1.0 + (from + to) * 0.1, volume: 0.3 });
    }

    if (!this.playing) { this.playing = true; this._lastTs = null; this._tick(); }
    this._emit();
  }

  preset(name) {
    const presets = {
      lyman_alpha:  { from: 2, to: 1 },
      balmer_alpha: { from: 3, to: 2 },
      balmer_beta:  { from: 4, to: 2 },
      paschen:      { from: 4, to: 3 },
    };
    const p = presets[name];
    if (!p) return;
    this.level = p.from;
    this.transition(p.from, p.to);
  }

  reset() {
    this.pause();
    this.level = 2;
    this._angle = 0;
    this._lastTransition = null;
    this._emittedPhotons = [];
    this._specMarks = [];
    this._trans   = null;
    this._photons = [];
    this.draw();
    this._emit();
  }

  play() {
    if (this.playing) return;
    this.playing = true;
    this._lastTs = null;
    this._tick();
  }

  pause() {
    this.playing = false;
    if (this._raf) { cancelAnimationFrame(this._raf); this._raf = null; }
  }

  start() { this.play(); }
  stop()  { this.pause(); }

  info() {
    const n  = this.level;
    const en = -13.6 / (n * n);
    return {
      level: n,
      energy: +en.toFixed(4),
      lastTransition: this._lastTransition ? { ...this._lastTransition } : null,
      emittedPhotons: this._emittedPhotons.slice(),
    };
  }

  /* ── internals ─────────────────────────────── */

  _emit() { if (this.onUpdate) this.onUpdate(this.info()); }

  _energyOf(n) { return -13.6 / (n * n); }

  _seriesName(from, to) {
    const lo = Math.min(from, to);
    if (lo === 1) return 'Lyman';
    if (lo === 2) return 'Balmer';
    if (lo === 3) return 'Paschen';
    if (lo === 4) return 'Brackett';
    if (lo === 5) return 'Pfund';
    return '';
  }

  _wavelengthToColor(nm) {
    if (nm < 380) return '#9B5DE5';
    if (nm > 780) return '#EF476F';
    /* approximate visible spectrum */
    let r = 0, g = 0, b = 0;
    if (nm < 450) {
      const t = (nm - 380) / 70;
      r = (1 - t) * 0.6; g = 0; b = 1;
    } else if (nm < 495) {
      const t = (nm - 450) / 45;
      r = 0; g = t; b = 1;
    } else if (nm < 570) {
      const t = (nm - 495) / 75;
      r = t; g = 1; b = 1 - t;
    } else if (nm < 590) {
      const t = (nm - 570) / 20;
      r = 1; g = 1 - t * 0.5; b = 0;
    } else if (nm < 620) {
      const t = (nm - 590) / 30;
      r = 1; g = 0.5 - t * 0.5; b = 0;
    } else {
      const t = Math.min((nm - 620) / 160, 1);
      r = 1; g = 0; b = 0;
    }
    const clamp = v => Math.max(0, Math.min(255, Math.round(v * 255)));
    return `rgb(${clamp(r)},${clamp(g)},${clamp(b)})`;
  }

  /* ── tick / animate ────────────────────────── */

  _tick() {
    if (!this.playing) return;
    this._raf = requestAnimationFrame(ts => {
      if (this._lastTs === null) this._lastTs = ts;
      const dt = Math.min((ts - this._lastTs) / 1000, 0.05);
      this._lastTs = ts;

      /* orbital motion — angular speed inversely proportional to n */
      const omega = (2.5 / this.level);
      this._angle += omega * dt * 2 * Math.PI;
      if (this._angle > Math.PI * 2) this._angle -= Math.PI * 2;

      /* transition animation */
      if (this._trans) {
        this._trans.t += dt;
        if (this._trans.t >= this._trans.dur) {
          this.level = this._trans.to;
          /* spawn photon */
          if (this._trans.isEmission) {
            const cx = this.W * 0.325;
            const cy = (this.H - 44) * 0.5;
            const a  = this._angle;
            const r  = this._orbitRadius(this._trans.to);
            const ex = cx + r * Math.cos(a);
            const ey = cy + r * Math.sin(a);
            const pa = Math.random() * Math.PI * 2;
            this._photons.push({
              x: ex, y: ey,
              vx: Math.cos(pa) * 120, vy: Math.sin(pa) * 120,
              color: this._trans.color, t: 0, maxT: 1.2,
            });
            if (window.LabFX) {
              LabFX.particles.emit({ ctx: this.ctx, x: ex, y: ey, count: 6,
                color: this._trans.color, speed: 35, spread: Math.PI * 2, angle: 0,
                gravity: 0, life: 600, fade: true, glow: true, shape: 'spark', size: 3, sizeFade: true });
            }
          }
          this._trans = null;
          this._emit();
        }
      }

      /* update photons */
      for (const p of this._photons) {
        p.x += p.vx * dt;
        p.y += p.vy * dt;
        p.t += dt;
      }
      this._photons = this._photons.filter(p => p.t < p.maxT);

      if (window.LabFX) LabFX.particles.update(dt * 1000);
      this.draw();
      this._tick();
    });
  }

  /* ── geometry helpers ──────────────────────── */

  _orbitRadius(n) {
    const maxR = Math.min(this.W * 0.325, (this.H - 44) * 0.5) * 0.85;
    return 18 + (n - 1) * (maxR - 18) / 5;
  }

  _diagramLevelY(n) {
    /* energy diagram in right panel; map energy <svg class="ic" viewBox="0 0 24 24"><line x1="5" y1="12" x2="19" y2="12"/><polyline points="12 5 19 12 12 19"/></svg> y */
    const panelTop = 30;
    const panelBot = this.H - 74;
    const eMin = -13.6;    // n=1
    const eMax = -0.378;   // n=6
    const en   = this._energyOf(n);
    const t = (en - eMin) / (eMax - eMin);
    return panelBot - t * (panelBot - panelTop);
  }

  /* ── draw ──────────────────────────────────── */

  draw() {
    const ctx = this.ctx, W = this.W, H = this.H;
    if (!W || !H) return;

    ctx.fillStyle = '#0D0D1A';
    ctx.fillRect(0, 0, W, H);

    const atomW = W * 0.65;
    const panelX = atomW;
    const specH = 44;  // spectrum bar height at bottom

    /* divider */
    ctx.fillStyle = 'rgba(255,255,255,0.06)';
    ctx.fillRect(atomW - 1, 0, 2, H - specH);

    this._drawAtom(ctx, atomW, H - specH);
    this._drawEnergyDiagram(ctx, panelX, W, H - specH);
    this._drawSpectrumBar(ctx, W, H, specH);
    this._drawPhotons(ctx);
    if (window.LabFX) LabFX.particles.draw(ctx);
  }

  /* ── atom (left 65%) ───────────────────────── */

  _drawAtom(ctx, aW, aH) {
    const cx = aW * 0.5;
    const cy = aH * 0.5;

    /* nucleus glow */
    const ng = ctx.createRadialGradient(cx, cy, 0, cx, cy, 20);
    ng.addColorStop(0, 'rgba(255,220,80,0.9)');
    ng.addColorStop(0.3, 'rgba(255,200,60,0.3)');
    ng.addColorStop(1, 'rgba(255,200,60,0)');
    ctx.fillStyle = ng;
    ctx.beginPath(); ctx.arc(cx, cy, 20, 0, Math.PI * 2); ctx.fill();

    /* nucleus dot */
    ctx.fillStyle = '#FFD166';
    ctx.beginPath(); ctx.arc(cx, cy, 4, 0, Math.PI * 2); ctx.fill();

    /* orbitals */
    for (let n = 1; n <= 6; n++) {
      const r = this._orbitRadius(n);
      const isCurrent = n === this._currentDisplayLevel();
      const alpha = isCurrent ? 0.6 : 0.15;

      ctx.strokeStyle = isCurrent ? '#06D6E0' : `rgba(255,255,255,${alpha})`;
      ctx.lineWidth = isCurrent ? 2 : 1;
      ctx.setLineDash(isCurrent ? [] : [4, 4]);
      ctx.beginPath(); ctx.arc(cx, cy, r, 0, Math.PI * 2); ctx.stroke();
      ctx.setLineDash([]);

      /* label */
      const en = this._energyOf(n);
      ctx.font = "10px 'Manrope', system-ui, sans-serif";
      ctx.fillStyle = isCurrent ? '#06D6E0' : 'rgba(255,255,255,0.35)';
      ctx.textAlign = 'left'; ctx.textBaseline = 'middle';
      ctx.fillText(`n=${n}  ${en.toFixed(2)} eV`, cx + r + 6, cy - 4);
    }

    /* electron */
    const eLevel = this._currentDisplayLevel();
    let eAngle = this._angle;
    let eR     = this._orbitRadius(eLevel);

    /* during transition: interpolate radius */
    if (this._trans) {
      const prog = Math.min(this._trans.t / this._trans.dur, 1);
      const ease = prog * prog * (3 - 2 * prog); // smoothstep
      const rFrom = this._orbitRadius(this._trans.from);
      const rTo   = this._orbitRadius(this._trans.to);
      eR = rFrom + (rTo - rFrom) * ease;
    }

    const ex = cx + eR * Math.cos(eAngle);
    const ey = cy + eR * Math.sin(eAngle);

    /* electron glow */
    const eg = ctx.createRadialGradient(ex, ey, 0, ex, ey, 14);
    eg.addColorStop(0, 'rgba(6,214,224,0.8)');
    eg.addColorStop(0.4, 'rgba(6,214,224,0.2)');
    eg.addColorStop(1, 'rgba(6,214,224,0)');
    ctx.fillStyle = eg;
    ctx.beginPath(); ctx.arc(ex, ey, 14, 0, Math.PI * 2); ctx.fill();

    /* electron dot */
    ctx.fillStyle = '#06D6E0';
    ctx.beginPath(); ctx.arc(ex, ey, 5, 0, Math.PI * 2); ctx.fill();
    ctx.fillStyle = '#fff';
    ctx.beginPath(); ctx.arc(ex - 1.5, ey - 1.5, 1.5, 0, Math.PI * 2); ctx.fill();

    /* title */
    ctx.font = "bold 13px 'Manrope', system-ui, sans-serif";
    ctx.fillStyle = 'rgba(255,255,255,0.3)';
    ctx.textAlign = 'center'; ctx.textBaseline = 'top';
    ctx.fillText('Модель атома Бора (водород)', aW * 0.5, 10);
  }

  _currentDisplayLevel() {
    if (this._trans) return this._trans.from;
    return this.level;
  }

  /* ── energy diagram (right 35%) ────────────── */

  _drawEnergyDiagram(ctx, x0, W, pH) {
    const pW = W - x0;
    const pad = { l: 52, r: 16, t: 30, b: 20 };
    const lineX0 = x0 + pad.l;
    const lineX1 = W - pad.r;

    /* panel bg */
    ctx.fillStyle = 'rgba(5,5,20,0.85)';
    ctx.fillRect(x0, 0, pW, pH);

    /* title */
    ctx.font = "10px 'Manrope', system-ui, sans-serif";
    ctx.fillStyle = 'rgba(255,255,255,0.5)';
    ctx.textAlign = 'left'; ctx.textBaseline = 'top';
    ctx.fillText('Энергетические уровни', x0 + 10, 10);

    /* draw each level */
    for (let n = 1; n <= 6; n++) {
      const y  = this._diagramLevelY(n);
      const en = this._energyOf(n);
      const isCurrent = n === this.level && !this._trans;

      /* line */
      ctx.strokeStyle = isCurrent ? '#06D6E0' : 'rgba(255,255,255,0.3)';
      ctx.lineWidth = isCurrent ? 2.5 : 1.5;
      ctx.beginPath(); ctx.moveTo(lineX0, y); ctx.lineTo(lineX1, y); ctx.stroke();

      /* hover highlight */
      if (this._hoverLevel === n && n !== this.level) {
        ctx.strokeStyle = 'rgba(155,93,229,0.5)';
        ctx.lineWidth = 2;
        ctx.beginPath(); ctx.moveTo(lineX0, y); ctx.lineTo(lineX1, y); ctx.stroke();
      }

      /* n label (right) */
      ctx.font = "11px 'Manrope', system-ui, sans-serif";
      ctx.fillStyle = isCurrent ? '#06D6E0' : 'rgba(255,255,255,0.6)';
      ctx.textAlign = 'right'; ctx.textBaseline = 'middle';
      ctx.fillText(`n=${n}`, lineX0 - 4, y);

      /* energy label (left of n) */
      ctx.font = "9px 'Manrope', system-ui, sans-serif";
      ctx.fillStyle = 'rgba(255,255,255,0.35)';
      ctx.textAlign = 'right';
      ctx.fillText(`${en.toFixed(2)}`, lineX0 - 30, y);

      /* dot on current level */
      if (isCurrent) {
        ctx.fillStyle = '#06D6E0';
        ctx.beginPath(); ctx.arc(lineX0 + 8, y, 4, 0, Math.PI * 2); ctx.fill();
      }
    }

    /* transition arrow */
    if (this._lastTransition) {
      const lt = this._lastTransition;
      const y1 = this._diagramLevelY(lt.from);
      const y2 = this._diagramLevelY(lt.to);
      const ax = (lineX0 + lineX1) * 0.5 + 10;
      const col = this._wavelengthToColor(lt.wavelength);

      ctx.strokeStyle = col;
      ctx.lineWidth = 2;
      ctx.setLineDash([]);
      ctx.beginPath(); ctx.moveTo(ax, y1); ctx.lineTo(ax, y2); ctx.stroke();

      /* arrowhead */
      const dir = y2 > y1 ? 1 : -1;
      ctx.fillStyle = col;
      ctx.beginPath();
      ctx.moveTo(ax, y2);
      ctx.lineTo(ax - 5, y2 - dir * 8);
      ctx.lineTo(ax + 5, y2 - dir * 8);
      ctx.closePath(); ctx.fill();

      /* ΔE and λ labels */
      ctx.font = "10px 'Manrope', system-ui, sans-serif";
      ctx.fillStyle = col;
      ctx.textAlign = 'left'; ctx.textBaseline = 'middle';
      const midY = (y1 + y2) / 2;
      ctx.fillText(`ΔE=${lt.deltaE.toFixed(2)} eV`, ax + 8, midY - 8);
      ctx.fillText(`λ=${lt.wavelength.toFixed(1)} nm`, ax + 8, midY + 8);

      /* series name */
      if (lt.series) {
        ctx.fillStyle = 'rgba(255,255,255,0.4)';
        ctx.font = "9px 'Manrope', system-ui, sans-serif";
        ctx.fillText(lt.series, ax + 8, midY + 22);
      }
    }
  }

  /* ── spectrum bar (bottom) ─────────────────── */

  _drawSpectrumBar(ctx, W, H, barH) {
    const y0 = H - barH;

    /* background strip */
    ctx.fillStyle = 'rgba(5,5,20,0.9)';
    ctx.fillRect(0, y0, W, barH);

    /* label */
    ctx.font = "9px 'Manrope', system-ui, sans-serif";
    ctx.fillStyle = 'rgba(255,255,255,0.35)';
    ctx.textAlign = 'left'; ctx.textBaseline = 'top';
    ctx.fillText('Спектр', 6, y0 + 2);

    /* visible spectrum gradient */
    const gradX0 = 50, gradX1 = W - 16;
    const gradW = gradX1 - gradX0;
    const gradY = y0 + 14, gradH = 16;
    const nmMin = 380, nmMax = 780;

    for (let px = 0; px < gradW; px++) {
      const nm = nmMin + (px / gradW) * (nmMax - nmMin);
      ctx.fillStyle = this._wavelengthToColor(nm);
      ctx.globalAlpha = 0.6;
      ctx.fillRect(gradX0 + px, gradY, 1, gradH);
    }
    ctx.globalAlpha = 1;

    /* border */
    ctx.strokeStyle = 'rgba(255,255,255,0.15)';
    ctx.lineWidth = 1;
    ctx.strokeRect(gradX0, gradY, gradW, gradH);

    /* nm tick labels */
    ctx.font = "8px 'Manrope', system-ui, sans-serif";
    ctx.fillStyle = 'rgba(255,255,255,0.3)';
    ctx.textAlign = 'center'; ctx.textBaseline = 'top';
    for (let nm = 400; nm <= 750; nm += 50) {
      const px = gradX0 + ((nm - nmMin) / (nmMax - nmMin)) * gradW;
      ctx.fillText(nm, px, gradY + gradH + 2);
    }

    /* UV / IR labels */
    ctx.fillStyle = '#9B5DE5'; ctx.textAlign = 'right';
    ctx.fillText('UV', gradX0 - 4, gradY + 4);
    ctx.fillStyle = '#EF476F'; ctx.textAlign = 'left';
    ctx.fillText('IR', gradX1 + 4, gradY + 4);

    /* emission marks */
    for (const wl of this._specMarks) {
      let px;
      if (wl < nmMin) {
        px = gradX0 - 6;
      } else if (wl > nmMax) {
        px = gradX1 + 6;
      } else {
        px = gradX0 + ((wl - nmMin) / (nmMax - nmMin)) * gradW;
      }

      const col = this._wavelengthToColor(wl);
      ctx.strokeStyle = col;
      ctx.lineWidth = 2;
      ctx.beginPath(); ctx.moveTo(px, gradY - 3); ctx.lineTo(px, gradY + gradH + 3); ctx.stroke();

      /* tiny wavelength label above */
      ctx.font = "7px 'Manrope', system-ui, sans-serif";
      ctx.fillStyle = col;
      ctx.textAlign = 'center'; ctx.textBaseline = 'bottom';
      ctx.fillText(wl, px, gradY - 4);
    }
  }

  /* ── flying photons ────────────────────────── */

  _drawPhotons(ctx) {
    for (const p of this._photons) {
      const alpha = 1 - p.t / p.maxT;
      const r = 4 + p.t * 6;

      /* glow */
      const g = ctx.createRadialGradient(p.x, p.y, 0, p.x, p.y, r * 2);
      g.addColorStop(0, p.color);
      g.addColorStop(1, 'rgba(0,0,0,0)');
      ctx.globalAlpha = alpha * 0.5;
      ctx.fillStyle = g;
      ctx.beginPath(); ctx.arc(p.x, p.y, r * 2, 0, Math.PI * 2); ctx.fill();

      /* core */
      ctx.globalAlpha = alpha;
      ctx.fillStyle = p.color;
      ctx.beginPath(); ctx.arc(p.x, p.y, r * 0.5, 0, Math.PI * 2); ctx.fill();

      /* wavy trail */
      ctx.strokeStyle = p.color;
      ctx.lineWidth = 1;
      ctx.globalAlpha = alpha * 0.4;
      ctx.beginPath();
      const len = 30;
      const vMag = Math.hypot(p.vx, p.vy) || 1;
      const dx = -p.vx / vMag, dy = -p.vy / vMag;
      const nx = -dy, ny = dx;
      for (let i = 0; i <= len; i++) {
        const t = i / len;
        const wx = p.x + dx * i * 1.5 + nx * Math.sin(t * 8 + p.t * 12) * 3;
        const wy = p.y + dy * i * 1.5 + ny * Math.sin(t * 8 + p.t * 12) * 3;
        i === 0 ? ctx.moveTo(wx, wy) : ctx.lineTo(wx, wy);
      }
      ctx.stroke();

      ctx.globalAlpha = 1;
    }
  }

  /* ── events ─────────────────────────────────── */

  _bindEvents() {
    const cv = this.canvas;

    const getPos = (e) => {
      const r = cv.getBoundingClientRect();
      const t = e.touches ? e.touches[0] : e;
      return {
        mx: (t.clientX - r.left) * (this.W / r.width),
        my: (t.clientY - r.top)  * (this.H / r.height),
      };
    };

    const hitLevel = (mx, my) => {
      /* check energy diagram area */
      const panelX = this.W * 0.65;
      if (mx < panelX) return null;

      const pH = this.H - 44;
      const pad = { l: 52, r: 16 };
      const lineX0 = panelX + pad.l;
      const lineX1 = this.W - pad.r;

      for (let n = 1; n <= 6; n++) {
        const y = this._diagramLevelY(n);
        if (mx >= lineX0 - 10 && mx <= lineX1 + 10 && Math.abs(my - y) < 10) {
          return n;
        }
      }
      return null;
    };

    /* click on level <svg class="ic" viewBox="0 0 24 24"><line x1="5" y1="12" x2="19" y2="12"/><polyline points="12 5 19 12 12 19"/></svg> transition */
    cv.addEventListener('click', e => {
      const { mx, my } = getPos(e);
      const n = hitLevel(mx, my);
      if (n !== null && n !== this.level && !this._trans) {
        this.transition(this.level, n);
      }
    });

    /* hover cursor */
    cv.addEventListener('mousemove', e => {
      const { mx, my } = getPos(e);
      const n = hitLevel(mx, my);
      this._hoverLevel = n;
      cv.style.cursor = (n !== null && n !== this.level) ? 'pointer' : 'default';
    });

    cv.addEventListener('mouseleave', () => {
      this._hoverLevel = null;
    });

    /* touch tap */
    cv.addEventListener('touchend', e => {
      if (e.changedTouches.length !== 1) return;
      const r = cv.getBoundingClientRect();
      const mx = (e.changedTouches[0].clientX - r.left) * (this.W / r.width);
      const my = (e.changedTouches[0].clientY - r.top)  * (this.H / r.height);
      const n = hitLevel(mx, my);
      if (n !== null && n !== this.level && !this._trans) {
        this.transition(this.level, n);
      }
    });
  }
}

/* ─── lab UI init ─────────────────────────────────── */
  function _openBohrAtom() {
    document.getElementById('sim-topbar-title').textContent = 'Атом Бора';
    _simShow('sim-bohratom');
    _registerSimState('bohratom', () => bohrSim?.getParams(), st => bohrSim?.setParams(st));
    if (_embedMode) _startStateEmit('bohratom');
    requestAnimationFrame(() => requestAnimationFrame(() => {
      if (!bohrSim) {
        bohrSim = new BohrAtomSim(document.getElementById('bohratom-canvas'));
        bohrSim.onUpdate = _bohrUpdateUI;
      }
      bohrSim.fit();
      bohrSim.play();
    }));
  }

  function bohrLevel(n) {
    if (bohrSim) {
      const from = bohrSim.info().level;
      if (from !== n) bohrSim.transition(from, n);
    }
  }

  function bohrTransition(from, to) {
    if (bohrSim) bohrSim.transition(from, to);
  }

  function _bohrUpdateUI(info) {
    const v = (id, val) => { const el = document.getElementById(id); if (el) el.textContent = val; };
    v('bohrbar-v1', info.level);
    v('bohrbar-v2', info.energy.toFixed(2));
    if (info.lastTransition) {
      v('bohrbar-v3', info.lastTransition.wavelength.toFixed(0));
      v('bohrbar-v4', info.lastTransition.series || '—');
    }
  }

  /* ── electrolysis ── */

