'use strict';
/* ════════════════════════════════════════════════════════════════
   CellDivisionSim v2 — интерактивное деление клетки
   Митоз и мейоз · анимация · частицы · скрабинг · клик
   ════════════════════════════════════════════════════════════════ */

class CellDivisionSim {

  static MITOSIS_PHASES = [
    { id: 'interphase',  label: 'Интерфаза',  chromN: '2n = 46', dna: '2C → 4C', dur: 6000,
      desc: 'G1+S+G2: клетка растёт, ДНК удваивается в S-периоде' },
    { id: 'prophase',    label: 'Профаза',    chromN: '2n = 46', dna: '4C',       dur: 4500,
      desc: 'Хромосомы конденсируются · ядерная оболочка разрушается · формируется веретено' },
    { id: 'metaphase',   label: 'Метафаза',   chromN: '2n = 46', dna: '4C',       dur: 3500,
      desc: 'Хромосомы на метафазной пластинке · нити веретена у кинетохор' },
    { id: 'anaphase',    label: 'Анафаза',    chromN: '4n = 92', dna: '4C',       dur: 3000,
      desc: 'Хроматиды расходятся к полюсам · клетка вытягивается' },
    { id: 'telophase',   label: 'Телофаза',   chromN: '2n = 46', dna: '4C',       dur: 3000,
      desc: 'Два ядра восстанавливаются · хромосомы деконденсируются' },
    { id: 'cytokinesis', label: 'Цитокинез',  chromN: '2n = 46', dna: '2C',       dur: 3500,
      desc: 'Цитоплазма делится · 2 дочерних диплоидных клетки (2n = 46)' },
  ];

  static MEIOSIS_PHASES = [
    { id: 'interphase',  label: 'Интерфаза',   chromN: '2n = 46', dna: '2C → 4C', dur: 4000,
      desc: 'Репликация ДНК перед делением' },
    { id: 'prophase1',   label: 'Профаза I',   chromN: '2n = 46', dna: '4C',       dur: 5000,
      desc: 'Конъюгация гомологов · кроссинговер — рекомбинация генов' },
    { id: 'metaphase1',  label: 'Метафаза I',  chromN: '2n = 46', dna: '4C',       dur: 3000,
      desc: 'Биваленты (пары гомологов) выстраиваются по экватору' },
    { id: 'anaphase1',   label: 'Анафаза I',   chromN: '2n = 46', dna: '4C',       dur: 3000,
      desc: 'Гомологичные хромосомы расходятся к полюсам' },
    { id: 'telophase1',  label: 'Телофаза I',  chromN: 'n = 23',  dna: '2C',       dur: 2500,
      desc: 'Два гаплоидных ядра · хромосомы ещё с сестринскими хроматидами' },
    { id: 'prophase2',   label: 'Профаза II',  chromN: 'n = 23',  dna: '2C',       dur: 2000,
      desc: 'Без репликации ДНК · начало второго деления' },
    { id: 'metaphase2',  label: 'Метафаза II', chromN: 'n = 23',  dna: '2C',       dur: 2500,
      desc: 'Хромосомы на экваторе · нити веретена к хроматидам' },
    { id: 'anaphase2',   label: 'Анафаза II',  chromN: 'n = 23',  dna: '2C',       dur: 2500,
      desc: 'Хроматиды расходятся к полюсам' },
    { id: 'telophase2',  label: 'Телофаза II', chromN: 'n = 23',  dna: 'C',        dur: 2500,
      desc: 'Четыре гаплоидных ядра формируются' },
    { id: 'cytokinesis', label: 'Цитокинез',   chromN: 'n = 23',  dna: 'C',        dur: 3000,
      desc: '4 гаплоидные клетки — гаметы (n = 23)' },
  ];

  static C = {
    bg:       '#070711',
    cell:     'rgba(34,211,153,0.055)',
    cellStr:  '#22d399',
    nucFill:  'rgba(122,77,210,0.09)',
    nucStr:   '#9B5DE5',
    chromatin:'#06D6E0',
    ch:       ['#EF476F','#FF9F1C','#9B5DE5','#06D6E0','#F15BB5','#7BF5A4'],
    spindle:  'rgba(255,214,0,0.55)',
    pole:     '#FFD166',
    furrow:   '#22d399',
    crossing: '#FFD166',
    progress: '#22d399',
  };

  constructor(canvas) {
    this.canvas    = canvas;
    this.ctx       = canvas.getContext('2d');
    this.mode      = 'mitosis';
    this._phaseIdx = 0;
    this._phaseT   = 0;
    this._autoPlay = true;
    this._speed    = 1.0;
    this._raf      = null;
    this._last     = 0;
    this._time     = 0;
    this.W = 0; this.H = 0;
    this.onUpdate  = null;
    this._chromatinDots = [];
    this._particles     = [];
    this._draggingBar   = false;
    this._bindEvents();
    this.fit();
  }

  /* ── Lifecycle ──────────────────────────────────────────────── */

  fit() {
    const dpr = window.devicePixelRatio || 1;
    const W   = this.canvas.offsetWidth  || 700;
    const H   = this.canvas.offsetHeight || 440;
    this.canvas.width  = Math.round(W * dpr);
    this.canvas.height = Math.round(H * dpr);
    this.ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
    this.W = W; this.H = H;
    this._genChromatinDots();
    if (!this._raf) this._draw();
  }

  start() {
    if (this._raf) return;
    this._last = performance.now();
    const loop = t => { this._raf = requestAnimationFrame(loop); this._tick(t); };
    this._raf = requestAnimationFrame(loop);
  }

  stop()  { cancelAnimationFrame(this._raf); this._raf = null; }

  reset() {
    this._phaseIdx = 0;
    this._phaseT   = 0;
    this._particles = [];
    this._emitUpdate();
    if (!this._raf) this._draw();
  }

  setMode(mode) {
    this.mode = mode;
    if (window.LabFX) LabFX.sound.play('click', { pitch: 1.2 });
    this.reset();
  }
  setSpeed(s)   { this._speed = s; }

  nextPhase() {
    const phases = this._phases();
    this._phaseIdx = (this._phaseIdx + 1) % phases.length;
    this._phaseT   = 0;
    this._particles = [];
    if (window.LabFX) LabFX.sound.play('whoosh', { pitch: 1.0 + this._phaseIdx * 0.05, volume: 0.3 });
    this._emitUpdate();
    if (!this._raf) this._draw();
  }

  prevPhase() {
    const phases = this._phases();
    this._phaseIdx = (this._phaseIdx - 1 + phases.length) % phases.length;
    this._phaseT   = 0;
    this._particles = [];
    if (window.LabFX) LabFX.sound.play('whoosh', { pitch: 1.0 + this._phaseIdx * 0.05, volume: 0.3 });
    this._emitUpdate();
    if (!this._raf) this._draw();
  }

  jumpToPhase(idx) {
    const phases = this._phases();
    this._phaseIdx = Math.max(0, Math.min(phases.length - 1, idx));
    this._phaseT   = 0;
    this._particles = [];
    if (window.LabFX) LabFX.sound.play('whoosh', { pitch: 1.0 + this._phaseIdx * 0.05, volume: 0.3 });
    this._emitUpdate();
    if (!this._raf) this._draw();
  }

  toggleAutoPlay() {
    this._autoPlay = !this._autoPlay;
    if (this._autoPlay && !this._raf) this.start();
    return this._autoPlay;
  }

  info() {
    const phases = this._phases();
    const p = phases[this._phaseIdx];
    return { phase: p.label, chromN: p.chromN, dna: p.dna,
             index: this._phaseIdx, total: phases.length,
             progress: this._phaseT, mode: this.mode };
  }

  /* ── Events ─────────────────────────────────────────────────── */

  _bindEvents() {
    const c = this.canvas;
    const getBarT = e => {
      const rect = c.getBoundingClientRect();
      return Math.max(0, Math.min(1, (e.clientX - rect.left - 14) / (this.W - 28)));
    };

    c.addEventListener('click', e => {
      const rect = c.getBoundingClientRect();
      if (e.clientY - rect.top < this.H - 28) this.nextPhase();
    });

    c.addEventListener('mousedown', e => {
      const rect = c.getBoundingClientRect();
      if (e.clientY - rect.top >= this.H - 28) {
        this._draggingBar = true;
        this._phaseT = getBarT(e);
        if (!this._raf) this._draw();
      }
    });
    c.addEventListener('mousemove', e => {
      const rect = c.getBoundingClientRect();
      c.style.cursor = (e.clientY - rect.top >= this.H - 28) ? 'col-resize' : 'pointer';
      if (this._draggingBar) { this._phaseT = getBarT(e); if (!this._raf) this._draw(); }
    });
    c.addEventListener('mouseup',    () => { this._draggingBar = false; });
    c.addEventListener('mouseleave', () => { this._draggingBar = false; });

    c.setAttribute('tabindex', '0');
    c.addEventListener('keydown', e => {
      if (e.code === 'Space')       { e.preventDefault(); this.toggleAutoPlay(); }
      if (e.key  === 'ArrowRight')  { e.preventDefault(); this.nextPhase(); }
      if (e.key  === 'ArrowLeft')   { e.preventDefault(); this.prevPhase(); }
    });
  }

  /* ── Internals ──────────────────────────────────────────────── */

  _phases() {
    return this.mode === 'meiosis'
      ? CellDivisionSim.MEIOSIS_PHASES
      : CellDivisionSim.MITOSIS_PHASES;
  }

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

  _ease(t) {
    return t < 0.5 ? 4 * t * t * t : 1 - Math.pow(-2 * t + 2, 3) / 2;
  }

  _genChromatinDots() {
    const { W, H } = this;
    const cx = W / 2, cy = H / 2;
    const nr = Math.min(W, H) * 0.17;
    this._chromatinDots = Array.from({ length: 52 }, (_, i) => {
      // stable seeded positions
      const seed = i * 1337 + 42;
      const a  = ((seed * 9301 + 49297) % 233280) / 233280 * Math.PI * 2;
      const r  = ((seed * 4321  + 12345) % 233280) / 233280 * nr * 0.88;
      const sz = 1.4 + ((seed * 2341 + 7777) % 233280) / 233280 * 2.5;
      const ph = ((seed * 6543 + 3210) % 233280) / 233280 * Math.PI * 2;
      return { x: cx + Math.cos(a) * r, y: cy + Math.sin(a) * r * 0.85, size: sz, phase: ph };
    });
  }

  _spawnEnvelopeParticles(cx, cy, nucR) {
    for (let i = 0; i < 32; i++) {
      const a  = Math.random() * Math.PI * 2;
      const r  = nucR * (0.88 + Math.random() * 0.18);
      this._particles.push({
        x: cx + Math.cos(a) * r, y: cy + Math.sin(a) * r * 0.9,
        vx: Math.cos(a) * (0.8 + Math.random() * 1.8),
        vy: Math.sin(a) * (0.8 + Math.random() * 1.8),
        life: 1, decay: 0.016 + Math.random() * 0.018,
        size: 2 + Math.random() * 3, color: '#9B5DE5',
      });
    }
  }

  /* ── Tick ───────────────────────────────────────────────────── */

  _tick(t) {
    const dt = Math.min(t - this._last, 80);
    this._last = t;
    this._time += dt;

    if (this._autoPlay && !this._draggingBar) {
      const phases = this._phases();
      const phase  = phases[this._phaseIdx];
      this._phaseT += (dt / phase.dur) * this._speed;

      // nuclear envelope breakdown particles
      if ((phase.id === 'prophase' || phase.id === 'prophase1') &&
          this._phaseT > 0.34 && this._phaseT < 0.38 && this._particles.length < 5) {
        const cellR = Math.min(this.W, this.H) * 0.37;
        this._spawnEnvelopeParticles(this.W / 2, this.H / 2, cellR * 0.46);
        // dust particles from LabFX around condensing chromosomes
        if (window.LabFX) {
          const cx = this.W / 2, cy = this.H / 2;
          LabFX.particles.emit({ ctx: this.ctx, x: cx, y: cy, count: 6, color: '#9B5DE5',
            speed: 18, spread: Math.PI * 2, angle: 0, gravity: 0, life: 700, fade: true,
            glow: false, shape: 'dust', size: 3, sizeFade: true });
        }
      }

      // anaphase: tick sound at start of separation + pole sparks
      if ((phase.id === 'anaphase' || phase.id === 'anaphase1' || phase.id === 'anaphase2') &&
          this._phaseT > 0.05 && this._phaseT < 0.10 && !this._anaphaseTickDone) {
        this._anaphaseTickDone = true;
        if (window.LabFX) {
          LabFX.sound.play('tick', { pitch: 1.3 });
          const cx = this.W / 2, cellR = Math.min(this.W, this.H) * 0.37;
          const poleY = cellR * 0.7 * this._phaseT;
          for (const sy of [-1, 1]) {
            LabFX.particles.emit({ ctx: this.ctx, x: cx, y: this.H / 2 + sy * poleY,
              count: 5, color: '#FFD166', speed: 22, spread: Math.PI * 2, angle: 0,
              gravity: 0, life: 500, fade: true, glow: true, shape: 'spark', size: 3, sizeFade: true });
          }
        }
      }
      if (phase.id !== 'anaphase' && phase.id !== 'anaphase1' && phase.id !== 'anaphase2') {
        this._anaphaseTickDone = false;
      }

      if (this._phaseT >= 1) {
        // cytokinesis complete
        if (phase.id === 'cytokinesis') {
          if (window.LabFX) {
            LabFX.sound.play('chime');
            const cx = this.W / 2, cy = this.H / 2;
            const cellR = Math.min(this.W, this.H) * 0.37;
            for (const sy of [-1, 1]) {
              LabFX.particles.emit({ ctx: this.ctx, x: cx, y: cy + sy * cellR * 0.5,
                count: 10, color: '#22d399', speed: 30, spread: Math.PI * 2, angle: 0,
                gravity: 0, life: 800, fade: true, glow: true, shape: 'ring', size: 5, sizeFade: true });
            }
          }
        }
        this._phaseT   = 0;
        this._phaseIdx = (this._phaseIdx + 1) % phases.length;
        this._particles = [];
        this._emitUpdate();
      }
    }

    this._particles = this._particles.filter(p => {
      p.x += p.vx; p.y += p.vy; p.vx *= 0.94; p.vy *= 0.94;
      p.life -= p.decay; return p.life > 0;
    });

    if (window.LabFX) LabFX.particles.update(dt);
    this._emitUpdate();
    this._draw();
  }

  /* ── Drawing ────────────────────────────────────────────────── */

  _draw() {
    const { ctx, W, H } = this;
    const C  = CellDivisionSim.C;
    const t  = this._phaseT;
    const phases = this._phases();
    const phase  = phases[this._phaseIdx];
    const cx = W / 2, cy = H / 2;
    const cellR  = Math.min(W, H) * 0.37;
    const nucR   = cellR * 0.46;

    ctx.fillStyle = C.bg;
    ctx.fillRect(0, 0, W, H);

    // subtle radial bg
    const bg2 = ctx.createRadialGradient(cx, cy, 0, cx, cy, cellR * 1.5);
    bg2.addColorStop(0, 'rgba(34,211,153,0.022)');
    bg2.addColorStop(1, 'transparent');
    ctx.fillStyle = bg2;
    ctx.fillRect(0, 0, W, H);

    switch (phase.id) {
      case 'interphase':  this._drawInterphase(cx, cy, cellR, nucR, t); break;
      case 'prophase':
      case 'prophase1':   this._drawProphase(cx, cy, cellR, nucR, t, phase.id === 'prophase1'); break;
      case 'metaphase':
      case 'metaphase1':  this._drawMetaphase(cx, cy, cellR, t, phase.id === 'metaphase1', false); break;
      case 'prophase2':   this._drawProphase2(cx, cy, cellR, nucR, t); break;
      case 'metaphase2':  this._drawMetaphase(cx, cy, cellR, t, false, true); break;
      case 'anaphase':    this._drawAnaphase(cx, cy, cellR, t, false, false); break;
      case 'anaphase1':   this._drawAnaphase(cx, cy, cellR, t, true,  false); break;
      case 'anaphase2':   this._drawAnaphase(cx, cy, cellR, t, false, true);  break;
      case 'telophase':   this._drawTelophase(cx, cy, cellR, nucR, t, false, false); break;
      case 'telophase1':  this._drawTelophase(cx, cy, cellR, nucR, t, true,  false); break;
      case 'telophase2':  this._drawTelophase(cx, cy, cellR, nucR, t, false, true);  break;
      case 'cytokinesis': this._drawCytokinesis(cx, cy, cellR, nucR, t); break;
    }

    this._drawParticles();
    this._drawOverlay(phase);
    this._drawProgressBar();
    this._drawHint();
    if (window.LabFX) LabFX.particles.draw(this.ctx);
  }

  /* ── Cell / nucleus ─────────────────────────────────────────── */

  _cellPath(cx, cy, rx, ry, wobble) {
    const ctx = this.ctx, N = 48;
    ctx.beginPath();
    for (let i = 0; i <= N; i++) {
      const a = (i / N) * Math.PI * 2;
      const w = (wobble || 0) * (Math.sin(a * 3 + this._time * 0.00075) * 0.6 +
                                  Math.sin(a * 5 + this._time * 0.00055) * 0.4);
      ctx.lineTo(cx + Math.cos(a) * (rx + rx * w),
                 cy + Math.sin(a) * ((ry || rx * 0.88) + (ry || rx * 0.88) * w));
    }
    ctx.closePath();
  }

  _drawCell(cx, cy, rx, ry, wobble, alpha) {
    const ctx = this.ctx, C = CellDivisionSim.C;
    ctx.save();
    ctx.globalAlpha = alpha !== undefined ? alpha : 1;
    this._cellPath(cx, cy, rx, ry, wobble !== undefined ? wobble : 0.013);
    ctx.shadowColor = C.cellStr; ctx.shadowBlur = 20;
    ctx.fillStyle   = C.cell; ctx.fill();
    ctx.shadowBlur  = 0;
    ctx.strokeStyle = C.cellStr; ctx.lineWidth = 2.2;
    ctx.globalAlpha *= 0.65; ctx.stroke();
    ctx.restore();
  }

  _drawNucleus(cx, cy, rx, ry, alpha) {
    const ctx = this.ctx, C = CellDivisionSim.C;
    ctx.save(); ctx.globalAlpha = alpha;
    ctx.beginPath(); ctx.ellipse(cx, cy, rx, ry || rx * 0.9, 0, 0, Math.PI * 2);
    ctx.shadowColor = C.nucStr; ctx.shadowBlur = 16;
    ctx.fillStyle   = C.nucFill; ctx.fill();
    ctx.shadowBlur  = 0; ctx.setLineDash([3, 3]);
    ctx.strokeStyle = C.nucStr; ctx.lineWidth = 1.6; ctx.stroke();
    ctx.setLineDash([]); ctx.restore();
  }

  /* ── Chromosome ─────────────────────────────────────────────── */

  _drawChromosome(x, y, size, angle, color, sister, alpha) {
    const ctx = this.ctx;
    ctx.save();
    if (alpha !== undefined) ctx.globalAlpha = alpha;
    ctx.translate(x, y); ctx.rotate(angle);
    const aw = size * 0.20, ah = size * 0.50, gap = size * 0.11;
    const offsets = sister ? [-gap, gap] : [0];
    ctx.shadowColor = color; ctx.shadowBlur = 10;
    for (const ox of offsets) {
      ctx.save(); ctx.translate(ox, 0);
      ctx.beginPath();
      ctx.moveTo(0, -gap * 0.5);
      ctx.bezierCurveTo(-aw * 1.1, -ah * 0.35, -aw * 1.3, -ah * 0.75, 0, -ah);
      ctx.bezierCurveTo( aw * 1.3, -ah * 0.75,  aw * 1.1, -ah * 0.35, 0, -gap * 0.5);
      ctx.moveTo(0, gap * 0.5);
      ctx.bezierCurveTo(-aw * 1.1,  ah * 0.35, -aw * 1.3,  ah * 0.75, 0,  ah);
      ctx.bezierCurveTo( aw * 1.3,  ah * 0.75,  aw * 1.1,  ah * 0.35, 0,  gap * 0.5);
      ctx.fillStyle = color; ctx.fill();
      ctx.restore();
    }
    ctx.shadowColor = '#fff'; ctx.shadowBlur = 6;
    ctx.beginPath(); ctx.arc(0, 0, size * 0.12, 0, Math.PI * 2);
    ctx.fillStyle = '#fff'; ctx.fill();
    ctx.restore();
  }

  _chrPairs(cx, cy, r) {
    const ch = CellDivisionSim.C.ch;
    return [
      { x: cx - r * 0.50, y: cy - r * 0.28, angle: -0.20, color: ch[0] },
      { x: cx - r * 0.15, y: cy - r * 0.35, angle:  0.15, color: ch[1] },
      { x: cx + r * 0.28, y: cy - r * 0.20, angle:  0.28, color: ch[2] },
      { x: cx - r * 0.38, y: cy + r * 0.22, angle:  0.18, color: ch[3] },
      { x: cx + r * 0.12, y: cy + r * 0.32, angle: -0.22, color: ch[4] },
      { x: cx + r * 0.48, y: cy + r * 0.18, angle: -0.10, color: ch[5] },
    ];
  }

  _chrPairsHaploid(cx, cy, r) {
    const ch = CellDivisionSim.C.ch;
    return [
      { x: cx - r * 0.36, y: cy - r * 0.24, angle: -0.18, color: ch[0] },
      { x: cx + r * 0.08, y: cy - r * 0.08, angle:  0.12, color: ch[2] },
      { x: cx + r * 0.32, y: cy + r * 0.26, angle:  0.28, color: ch[4] },
    ];
  }

  /* ── Spindle ────────────────────────────────────────────────── */

  _drawSpindle(cx, cy, cellR, alpha, chrs) {
    const ctx = this.ctx, C = CellDivisionSim.C;
    ctx.save(); ctx.globalAlpha = alpha;
    const poleY = cellR * 0.72;
    const poles = [{ x: cx, y: cy - poleY }, { x: cx, y: cy + poleY }];

    // aster rays
    for (const pole of poles) {
      for (let i = 0; i < 10; i++) {
        const a = (i / 10) * Math.PI * 2;
        ctx.beginPath(); ctx.moveTo(pole.x, pole.y);
        ctx.lineTo(pole.x + Math.cos(a) * 16, pole.y + Math.sin(a) * 16);
        ctx.strokeStyle = 'rgba(255,214,0,0.28)'; ctx.lineWidth = 0.8; ctx.stroke();
      }
    }
    // fibers
    if (chrs) {
      for (const ch of chrs) {
        for (const pole of poles) {
          ctx.beginPath(); ctx.moveTo(pole.x, pole.y);
          ctx.quadraticCurveTo(cx + (ch.x - cx) * 0.18, (pole.y + ch.y) / 2, ch.x, ch.y);
          ctx.strokeStyle = C.spindle; ctx.lineWidth = 0.9; ctx.stroke();
        }
      }
    }
    // pole dots
    for (const p of poles) {
      ctx.shadowColor = C.pole; ctx.shadowBlur = 14;
      ctx.beginPath(); ctx.arc(p.x, p.y, 5, 0, Math.PI * 2);
      ctx.fillStyle = C.pole; ctx.fill();
    }
    ctx.restore();
  }

  /* ── Phase renderers ────────────────────────────────────────── */

  _drawInterphase(cx, cy, cellR, nucR, t) {
    const ctx = this.ctx, C = CellDivisionSim.C;
    const te = this._ease(t);
    this._drawCell(cx, cy, cellR);
    this._drawNucleus(cx, cy, nucR, nucR * 0.9, 1);

    const dots = this._chromatinDots;
    ctx.save();
    for (let i = 0; i < dots.length; i++) {
      const d = dots[i];
      const pulse = 0.35 + 0.25 * Math.sin(d.phase + this._time * 0.0015);
      ctx.globalAlpha = pulse;
      ctx.beginPath(); ctx.arc(d.x, d.y, d.size * 0.72, 0, Math.PI * 2);
      ctx.shadowColor = C.chromatin; ctx.shadowBlur = 5;
      ctx.fillStyle = C.chromatin; ctx.fill();
      // replication copies
      if (te > 0.45 && i < Math.floor(((te - 0.45) / 0.55) * dots.length)) {
        ctx.globalAlpha = ((te - 0.45) / 0.55) * 0.5;
        ctx.beginPath(); ctx.arc(d.x + 3.5, d.y + 2, d.size * 0.62, 0, Math.PI * 2);
        ctx.shadowColor = '#FFD166'; ctx.fillStyle = '#FFD166'; ctx.fill();
      }
    }
    ctx.restore();
    if (t > 0.42) {
      const a = Math.min(1, (t - 0.42) * 7);
      ctx.save(); ctx.globalAlpha = a;
      ctx.font = 'bold 11px Manrope,sans-serif';
      ctx.shadowColor = '#FFD166'; ctx.shadowBlur = 8;
      ctx.fillStyle = '#FFD166'; ctx.textAlign = 'center';
      ctx.fillText('S-период: репликация ДНК', cx, cy + nucR + 26);
      ctx.restore();
    }
  }

  _drawProphase(cx, cy, cellR, nucR, t, isMeiosis1) {
    const ctx = this.ctx, C = CellDivisionSim.C;
    const te = this._ease(t);
    this._drawCell(cx, cy, cellR);
    this._drawNucleus(cx, cy, nucR, nucR * 0.9, 1 - te * 0.95);
    if (te > 0.28) {
      this._drawSpindle(cx, cy, cellR, (te - 0.28) / 0.72 * 0.6);
    }
    const chrs = this._chrPairs(cx, cy, cellR * 0.27);
    const size  = 11 + te * 15;
    if (isMeiosis1) {
      for (let i = 0; i < chrs.length; i++) {
        const ch = chrs[i];
        const off = (1 - te) * 9 * (i % 2 === 0 ? 1 : -1);
        this._drawChromosome(ch.x + off, ch.y, size, ch.angle, ch.color, true);
        if (te > 0.52) {
          const ca = (te - 0.52) / 0.48;
          ctx.save(); ctx.globalAlpha = ca * 0.88;
          ctx.shadowColor = C.crossing; ctx.shadowBlur = 10;
          ctx.beginPath(); ctx.arc(ch.x, ch.y, size * 0.55, 0, Math.PI * 2);
          ctx.strokeStyle = C.crossing; ctx.lineWidth = 2; ctx.stroke();
          ctx.restore();
        }
      }
      if (te > 0.52) {
        const ca = (te - 0.52) / 0.48;
        ctx.save(); ctx.globalAlpha = ca;
        ctx.shadowColor = '#FFD166'; ctx.shadowBlur = 8;
        ctx.font = 'bold 11px Manrope,sans-serif';
        ctx.fillStyle = '#FFD166'; ctx.textAlign = 'center';
        ctx.fillText('Кроссинговер — рекомбинация', cx, cy + cellR * 0.72);
        ctx.restore();
      }
    } else {
      for (const ch of chrs) this._drawChromosome(ch.x, ch.y, size, ch.angle, ch.color, true);
    }
  }

  _drawProphase2(cx, cy, cellR, nucR, t) {
    const te = this._ease(t);
    this._drawCell(cx, cy, cellR * 0.9);
    this._drawNucleus(cx, cy, nucR * 0.75, nucR * 0.67, 1 - te * 0.85);
    const chrs = this._chrPairsHaploid(cx, cy, cellR * 0.22);
    const size = 14 + te * 9;
    if (te > 0.3) this._drawSpindle(cx, cy, cellR * 0.9, (te - 0.3) / 0.7 * 0.55);
    for (const ch of chrs) this._drawChromosome(ch.x, ch.y, size, ch.angle, ch.color, true);
  }

  _drawMetaphase(cx, cy, cellR, t, isMeiosis1, isHaploid) {
    const ctx = this.ctx, C = CellDivisionSim.C;
    const te = this._ease(t);
    this._drawCell(cx, cy, cellR);

    ctx.save();
    ctx.setLineDash([5, 5]);
    ctx.strokeStyle = `rgba(255,255,255,${0.14 + te * 0.12})`;
    ctx.lineWidth = 1.5;
    ctx.beginPath();
    ctx.moveTo(cx - cellR * 0.82, cy); ctx.lineTo(cx + cellR * 0.82, cy);
    ctx.stroke(); ctx.setLineDash([]); ctx.restore();

    const ch = C.ch;
    const sp = cellR * 0.55;

    if (isHaploid) {
      const pos = [
        { x: cx - sp * 0.42, y: cy, angle: -0.05 },
        { x: cx,              y: cy, angle:  0.0  },
        { x: cx + sp * 0.42, y: cy, angle:  0.05 },
      ];
      this._drawSpindle(cx, cy, cellR, 0.75 + te * 0.25, pos);
      for (let i = 0; i < pos.length; i++)
        this._drawChromosome(pos[i].x, pos[i].y, 21, pos[i].angle, ch[i * 2], true);
    } else if (isMeiosis1) {
      const pos = [
        { x: cx - sp * 0.54, y: cy, angle: -0.08 }, { x: cx - sp * 0.17, y: cy, angle:  0.04 },
        { x: cx + sp * 0.17, y: cy, angle: -0.04 }, { x: cx + sp * 0.54, y: cy, angle:  0.08 },
        { x: cx - sp * 0.35, y: cy, angle:  0.12 }, { x: cx + sp * 0.35, y: cy, angle: -0.12 },
      ];
      this._drawSpindle(cx, cy, cellR, 0.8 + te * 0.2, pos);
      for (let i = 0; i < pos.length; i++) {
        this._drawChromosome(pos[i].x - 5, pos[i].y, 19, pos[i].angle,       ch[i % 6],       true);
        this._drawChromosome(pos[i].x + 5, pos[i].y, 19, pos[i].angle + 0.25, ch[(i + 3) % 6], true);
      }
    } else {
      const pos = [
        { x: cx - sp * 0.54, y: cy, angle: -0.08 }, { x: cx - sp * 0.28, y: cy, angle:  0.04 },
        { x: cx - sp * 0.04, y: cy, angle: -0.02 }, { x: cx + sp * 0.04, y: cy, angle:  0.02 },
        { x: cx + sp * 0.28, y: cy, angle: -0.04 }, { x: cx + sp * 0.54, y: cy, angle:  0.08 },
      ];
      this._drawSpindle(cx, cy, cellR, 0.8 + te * 0.2, pos);
      for (let i = 0; i < 6; i++)
        this._drawChromosome(pos[i].x, pos[i].y, 22, pos[i].angle, ch[i], true);
    }
  }

  _drawAnaphase(cx, cy, cellR, t, isMeiosis1, isHaploid) {
    const ctx = this.ctx, C = CellDivisionSim.C;
    const te = this._ease(t);
    const stretchY = 1 + te * 0.38;
    ctx.save();
    ctx.translate(cx, cy); ctx.scale(1, stretchY); ctx.translate(-cx, -cy);
    this._drawCell(cx, cy, cellR * (1 - te * 0.04));
    ctx.restore();

    const poleOffset = cellR * 0.7 * te;
    const topY = cy - poleOffset, botY = cy + poleOffset;
    this._drawSpindle(cx, cy, cellR, 1 - te * 0.3);

    const ch = C.ch, sp = cellR * (isHaploid ? 0.32 : 0.44);
    const n = isHaploid ? 3 : 6;
    for (let i = 0; i < n; i++) {
      const ox = (i - (n - 1) / 2) * sp * (isHaploid ? 0.5 : 0.34);
      const ang = (i - (n - 1) / 2) * 0.07;
      if (isMeiosis1) {
        this._drawChromosome(cx + ox, topY, 20, ang, ch[i], true);
        this._drawChromosome(cx + ox, botY, 20, ang, ch[(i + 3) % 6], true);
      } else {
        const ci = isHaploid ? i * 2 : i;
        this._drawChromosome(cx + ox, topY, isHaploid ? 17 : 18, ang, ch[ci % 6], false);
        this._drawChromosome(cx + ox, botY, isHaploid ? 17 : 18, ang, ch[ci % 6], false);
      }
    }
  }

  _drawTelophase(cx, cy, cellR, nucR, t, isMeiosis1, isHaploid) {
    const ctx = this.ctx, C = CellDivisionSim.C;
    const te = this._ease(t);
    const sep = cellR * 0.44;

    ctx.save();
    ctx.translate(cx, cy); ctx.scale(1, 1.30 - te * 0.12); ctx.translate(-cx, -cy);
    this._drawCell(cx, cy, cellR);
    ctx.restore();

    for (const s of [-1, 1])
      this._drawNucleus(cx, cy + s * sep, nucR * 0.72, nucR * 0.65, te);

    const ch = C.ch, size = 20 * (1 - te * 0.78);
    const alpha = 1 - te * 0.88, sp = cellR * (isHaploid ? 0.22 : 0.34);
    const n = isHaploid ? 3 : 6;
    for (let i = 0; i < n; i++) {
      const ox = (i - (n - 1) / 2) * sp * (isHaploid ? 0.5 : 0.26);
      const ci = isHaploid ? i * 2 : i;
      ctx.save(); ctx.globalAlpha = alpha;
      if (isMeiosis1) {
        this._drawChromosome(cx + ox, cy - sep, size, 0, ch[ci % 6], true);
        this._drawChromosome(cx + ox, cy + sep, size, 0, ch[(ci + 3) % 6], true);
      } else {
        this._drawChromosome(cx + ox, cy - sep, size, 0, ch[ci % 6], false);
        this._drawChromosome(cx + ox, cy + sep, size, 0, ch[ci % 6], false);
      }
      ctx.restore();
    }

    if (te > 0.45) {
      const fa = (te - 0.45) / 0.55;
      ctx.save(); ctx.globalAlpha = fa * 0.6;
      ctx.setLineDash([7, 5]);
      ctx.shadowColor = C.furrow; ctx.shadowBlur = 10;
      ctx.strokeStyle = C.furrow; ctx.lineWidth = 2;
      ctx.beginPath();
      ctx.moveTo(cx - cellR * 0.7, cy); ctx.lineTo(cx + cellR * 0.7, cy);
      ctx.stroke(); ctx.setLineDash([]); ctx.restore();
    }
  }

  _drawCytokinesis(cx, cy, cellR, nucR, t) {
    const ctx = this.ctx, C = CellDivisionSim.C;
    const te = this._ease(t);

    if (te < 0.80) {
      const pinch = te;
      ctx.save();
      ctx.beginPath();
      for (let i = 0; i <= 48; i++) {
        const a = (i / 48) * Math.PI * 2;
        const s2 = Math.abs(Math.cos(a));
        const waist = 1 - pinch * Math.max(0, 1 - s2 * 2.2) * 0.90;
        ctx.lineTo(cx + Math.cos(a) * cellR * waist,
                   cy + Math.sin(a) * cellR * 0.88 * (1 + pinch * 0.09));
      }
      ctx.closePath();
      ctx.shadowColor = C.cellStr; ctx.shadowBlur = 16;
      ctx.fillStyle = C.cell; ctx.fill(); ctx.shadowBlur = 0;
      ctx.strokeStyle = C.cellStr; ctx.lineWidth = 2.2;
      ctx.globalAlpha = 0.68; ctx.stroke(); ctx.restore();

      ctx.save(); ctx.globalAlpha = pinch * 0.85;
      ctx.shadowColor = C.furrow; ctx.shadowBlur = 14;
      ctx.strokeStyle = C.furrow; ctx.lineWidth = 2.5;
      ctx.beginPath();
      ctx.moveTo(cx - cellR * (1 - pinch * 0.92), cy);
      ctx.lineTo(cx + cellR * (1 - pinch * 0.92), cy);
      ctx.stroke(); ctx.restore();
    } else {
      const appear = (te - 0.80) / 0.20;
      const sepY = cellR * 0.52;
      for (const s of [-1, 1])
        this._drawCell(cx, cy + s * sepY * 0.54, cellR * 0.65, cellR * 0.57, 0.01, 0.5 + appear * 0.5);
    }

    const sep = cellR * 0.50;
    for (const s of [-1, 1])
      this._drawNucleus(cx, cy + s * sep * 0.52, nucR * 0.68, nucR * 0.61, Math.min(1, te * 1.5));

    if (te > 0.72) {
      const a = (te - 0.72) / 0.28;
      ctx.save(); ctx.globalAlpha = a;
      ctx.shadowColor = '#22d399'; ctx.shadowBlur = 12;
      ctx.font = 'bold 12px Manrope,sans-serif';
      ctx.fillStyle = '#22d399'; ctx.textAlign = 'center';
      ctx.fillText(
        this.mode === 'meiosis' ? '4 гаплоидные клетки (n = 23)' : '2 диплоидные клетки (2n = 46)',
        cx, cy + cellR * 0.88);
      ctx.restore();
    }
  }

  /* ── Particles ──────────────────────────────────────────────── */

  _drawParticles() {
    const ctx = this.ctx;
    for (const p of this._particles) {
      ctx.save();
      ctx.globalAlpha = p.life * 0.82;
      ctx.shadowColor = p.color; ctx.shadowBlur = 8;
      ctx.beginPath(); ctx.arc(p.x, p.y, p.size, 0, Math.PI * 2);
      ctx.fillStyle = p.color; ctx.fill();
      ctx.restore();
    }
  }

  /* ── UI overlays ────────────────────────────────────────────── */

  _drawOverlay(phase) {
    const ctx = this.ctx;
    const { W, H } = this;

    // phase name pill — top right
    ctx.save();
    ctx.font = 'bold 14px Manrope,sans-serif';
    const tw = ctx.measureText(phase.label).width;
    _cdRRect(ctx, W - tw - 30, 12, tw + 22, 28, 8);
    ctx.shadowColor = '#22d399'; ctx.shadowBlur = 14;
    ctx.fillStyle = 'rgba(34,211,153,0.12)'; ctx.fill(); ctx.shadowBlur = 0;
    ctx.strokeStyle = 'rgba(34,211,153,0.38)'; ctx.lineWidth = 1; ctx.stroke();
    ctx.fillStyle = '#22d399'; ctx.textAlign = 'left';
    ctx.fillText(phase.label, W - tw - 19, 30);
    ctx.restore();

    // chromN + DNA — bottom left
    ctx.save();
    ctx.font = '11px Manrope,sans-serif'; ctx.textAlign = 'left';
    ctx.fillStyle = 'rgba(6,214,224,0.78)';
    ctx.fillText('n: ' + phase.chromN, 14, H - 46);
    ctx.fillStyle = 'rgba(255,214,0,0.78)';
    ctx.fillText('ДНК: ' + phase.dna, 14, H - 32);
    ctx.restore();

    // description — bottom right
    ctx.save();
    ctx.font = '10.5px Manrope,sans-serif';
    ctx.fillStyle = 'rgba(255,255,255,0.36)';
    ctx.textAlign = 'right';
    const maxW = W * 0.5;
    const words = phase.desc.split(' ');
    let line = '', lines = [];
    for (const w of words) {
      const test = line + (line ? ' ' : '') + w;
      if (ctx.measureText(test).width > maxW && line) { lines.push(line); line = w; }
      else line = test;
    }
    if (line) lines.push(line);
    lines.forEach((l, i) => ctx.fillText(l, W - 14, H - 46 + i * 14));
    ctx.restore();
  }

  _drawProgressBar() {
    const ctx = this.ctx;
    const { W, H } = this;
    const C = CellDivisionSim.C;
    const phases = this._phases();
    const bx = 14, bw = W - 28, by = H - 14, bh = 4;
    const total = (this._phaseIdx + this._phaseT) / phases.length;

    _cdRRect(ctx, bx, by - bh / 2, bw, bh, 2);
    ctx.fillStyle = 'rgba(255,255,255,0.07)'; ctx.fill();

    if (total > 0) {
      _cdRRect(ctx, bx, by - bh / 2, bw * total, bh, 2);
      ctx.shadowColor = C.progress; ctx.shadowBlur = 8;
      ctx.fillStyle = C.progress; ctx.fill(); ctx.shadowBlur = 0;
    }

    // phase tick marks
    for (let i = 1; i < phases.length; i++) {
      const tx = bx + bw * (i / phases.length);
      ctx.fillStyle = 'rgba(255,255,255,0.22)';
      ctx.fillRect(tx - 0.5, by - bh, 1, bh * 2);
    }

    // status
    ctx.save();
    ctx.font = '10px Manrope,sans-serif';
    ctx.fillStyle = 'rgba(255,255,255,0.3)';
    ctx.textAlign = 'left';
    ctx.fillText(this._autoPlay ? '> авто' : '|| пауза', bx, H - 22);
    ctx.restore();
  }

  _drawHint() {
    if (this._time >= 4500) return;
    const a = Math.min(1, this._time / 600) * Math.max(0, 1 - (this._time - 3200) / 1300);
    const ctx = this.ctx;
    ctx.save();
    ctx.globalAlpha = a * 0.42;
    ctx.font = '10.5px Manrope,sans-serif';
    ctx.fillStyle = '#fff'; ctx.textAlign = 'center';
    ctx.fillText('Клик — следующая фаза  ·  тяни полосу внизу  ·  Space — пауза', this.W / 2, this.H - 26);
    ctx.restore();
  }
}

function _cdRRect(ctx, x, y, w, h, r) {
  if (w <= 0 || h <= 0) return;
  r = Math.min(r, w / 2, h / 2);
  ctx.beginPath();
  ctx.moveTo(x + r, y);
  ctx.arcTo(x + w, y, x + w, y + h, r);
  ctx.arcTo(x + w, y + h, x, y + h, r);
  ctx.arcTo(x, y + h, x, y, r);
  ctx.arcTo(x, y, x + w, y, r);
  ctx.closePath();
}

/* ─── lab UI init ─────────────────────────────────── */
  function _openCellDivision(mode) {
    document.getElementById('sim-topbar-title').textContent = 'Деление клетки';
    _simShow('sim-celldivision');
    _simShow('ctrl-celldivision');
    requestAnimationFrame(() => requestAnimationFrame(() => {
      const canvas = document.getElementById('celldiv-canvas');
      if (!cellDivSim) {
        cellDivSim = new CellDivisionSim(canvas);
        cellDivSim.onUpdate = _cdUpdateUI;
      }
      cellDivSim.fit();
      cellDivSim.setMode(mode || 'mitosis');
      cellDivSim.start();
      _cdBuildDots(cellDivSim._phaseIdx);
      // sync auto button state
      const autoBtn = document.getElementById('cd-auto-btn');
      if (autoBtn) { autoBtn.innerHTML = cellDivSim._autoPlay ? '<svg class="ic" viewBox="0 0 24 24"><rect x="6" y="4" width="4" height="16"/><rect x="14" y="4" width="4" height="16"/></svg> Пауза' : '<svg class="ic" viewBox="0 0 24 24"><polygon points="5 3 19 12 5 21 5 3"/></svg> Авто'; }
      _cdUpdateUI(cellDivSim.info());
    }));
  }

  function _cdBuildDots(activeIdx) {
    const box = document.getElementById('cd-phase-dots');
    if (!box || !cellDivSim) return;
    const phases = cellDivSim._phases();
    box.innerHTML = phases.map((p, i) =>
      `<div class="cd-phase-dot${i === activeIdx ? ' active' : ''}" onclick="cdJumpPhase(${i})" title="${p.label}"></div>`
    ).join('');
  }

  function cdSetMode(mode, btn) {
    document.querySelectorAll('.cd-mode-btn').forEach(b => b.classList.remove('active'));
    if (btn) btn.classList.add('active');
    if (!cellDivSim) return;
    cellDivSim.setMode(mode);
    _cdBuildDots(cellDivSim._phaseIdx);
    _cdUpdateUI(cellDivSim.info());
  }

  function cdAutoPlay(btn) {
    if (!cellDivSim) return;
    cellDivSim.toggleAutoPlay();
    btn.classList.toggle('active', cellDivSim._autoPlay);
    btn.innerHTML = cellDivSim._autoPlay ? '<svg class="ic" viewBox="0 0 24 24"><rect x="6" y="4" width="4" height="16"/><rect x="14" y="4" width="4" height="16"/></svg> Пауза' : '<svg class="ic" viewBox="0 0 24 24"><polygon points="5 3 19 12 5 21 5 3"/></svg> Авто';
  }

  function cdPrevPhase() {
    if (!cellDivSim) return;
    cellDivSim.prevPhase();
    _cdBuildDots(cellDivSim._phaseIdx);
  }

  function cdNextPhase() {
    if (!cellDivSim) return;
    cellDivSim.nextPhase();
    _cdBuildDots(cellDivSim._phaseIdx);
  }

  function cdJumpPhase(idx) {
    if (!cellDivSim) return;
    cellDivSim.jumpToPhase(idx);
    _cdBuildDots(idx);
  }

  function _cdUpdateUI(info) {
    const v = (id, val) => { const el = document.getElementById(id); if (el) el.textContent = val; };
    v('cdbar-v1', info.phase || '—');
    v('cdbar-v2', info.chromN || '—');
    v('cdbar-v3', info.dna || '—');
    v('cdbar-v4', (info.index + 1) + ' / ' + info.total);
    v('cdbar-v5', info.mode === 'mitosis' ? 'Митоз' : 'Мейоз');
    _cdBuildDots(info.index);
  }

  /* ── Photosynthesis / Respiration ── */
