'use strict';
/* ══════════════════════════════════════════════════════
   TriangleSim — interactive triangle geometry simulation
   Draggable vertices A / B / C, toggleable layers:
   medians, altitudes, bisectors, circumcircle, incircle
══════════════════════════════════════════════════════ */

class TriangleSim {
  constructor(canvas) {
    this.canvas = canvas;
    this.ctx    = canvas.getContext('2d');

    this.pts  = null;          // [{x,y}, {x,y}, {x,y}]
    this._dragging = null;
    this._hovered  = null;

    // visible layers
    this.layers = {
      medians     : false,
      altitudes   : false,
      bisectors   : false,
      circumcircle: false,
      incircle    : false,
      eulerLine   : false,
      sineLaw     : false,
      cosineLaw   : false,
      pythagorean : false,
      grid        : true,
    };

    this.onUpdate = null;       // cb(stats)
    this._lastHapticTs = 0;
    this._lastRafTs = 0;
    this._bindEvents();
    this._startParticleLoop();
  }

  _startParticleLoop() {
    let last = performance.now();
    let fxFrames = 0; // countdown frames to keep drawing after last emit
    const loop = (now) => {
      const dt = (now - last) / 1000;
      last = now;
      if (window.LabFX) {
        LabFX.particles.update(dt);
        if (fxFrames > 0) { fxFrames--; this.draw(); }
      }
      requestAnimationFrame(loop);
    };
    // expose a trigger so emit events bump the counter
    this._fxActivate = () => { fxFrames = 60; };
    requestAnimationFrame(loop);
  }

  /* ── sizing ── */

  fit() {
    const rect = this.canvas.getBoundingClientRect();
    const dpr  = window.devicePixelRatio || 1;
    this.canvas.width  = rect.width  * dpr;
    this.canvas.height = rect.height * dpr;
    this.ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
    this.W = rect.width;
    this.H = rect.height;
    if (!this.pts) this._initPts();
    else           this._clampPts();
    this.draw();
  }

  _initPts() {
    const cx = this.W / 2, cy = this.H / 2;
    const r  = Math.min(this.W, this.H) * 0.30;
    this.pts = [
      { x: cx,             y: cy - r        },   // A  top
      { x: cx - r * 0.88, y: cy + r * 0.62 },   // B  bottom-left
      { x: cx + r * 0.88, y: cy + r * 0.62 },   // C  bottom-right
    ];
  }

  _clampPts() {
    const pad = 48;
    for (const p of this.pts) {
      p.x = Math.max(pad, Math.min(this.W - pad, p.x));
      p.y = Math.max(pad, Math.min(this.H - pad, p.y));
    }
  }

  reset() {
    this.pts = null;
    this._initPts();
    this.draw();
    if (this.onUpdate) this.onUpdate(this.stats());
    if (window.LabFX) LabFX.sound.play('whoosh', { volume: 0.3 });
  }

  /* ── pointer events ── */

  _bindEvents() {
    const c = this.canvas;

    const pos = e => {
      const r = c.getBoundingClientRect();
      const s = e.touches ? e.touches[0] : e;
      return { x: s.clientX - r.left, y: s.clientY - r.top };
    };

    const hit = p => {
      if (!this.pts) return -1;
      for (let i = 0; i < 3; i++) {
        if (Math.hypot(p.x - this.pts[i].x, p.y - this.pts[i].y) < 20) return i;
      }
      return -1;
    };

    const drag = (p) => {
      this.pts[this._dragging].x = p.x;
      this.pts[this._dragging].y = p.y;
      this._clampPts();
      this.draw();
      if (this.onUpdate) this.onUpdate(this.stats());
      const now = performance.now();
      if (window.LabFX && now - (this._lastHapticTs || 0) > 100) {
        this._lastHapticTs = now;
        LabFX.haptic(5);
      }
    };

    c.addEventListener('mousedown', e => {
      const i = hit(pos(e));
      if (i >= 0) { this._dragging = i; c.style.cursor = 'grabbing'; }
    });

    c.addEventListener('mousemove', e => {
      const p = pos(e);
      if (this._dragging !== null) { drag(p); return; }
      const i  = hit(p);
      const was = this._hovered;
      this._hovered = i >= 0 ? i : null;
      c.style.cursor = i >= 0 ? 'grab' : 'default';
      if (was !== this._hovered) this.draw();
    });

    c.addEventListener('mouseup', () => {
      if (this._dragging !== null && this.pts) {
        const p = this.pts[this._dragging];
        if (window.LabFX) {
          LabFX.sound.play('tick', { pitch: 1.2, volume: 0.2 });
          LabFX.particles.emit({ ctx: this.ctx, x: p.x, y: p.y, count: 4, color: '#9B5DE5', shape: 'dust', life: 300, speed: 40, spread: Math.PI * 2, gravity: 0 });
          if (this._fxActivate) this._fxActivate();
        }
      }
      this._dragging = null;
      c.style.cursor = this._hovered !== null ? 'grab' : 'default';
    });

    c.addEventListener('touchstart', e => { e.preventDefault(); const i = hit(pos(e)); if (i >= 0) this._dragging = i; }, { passive: false });
    c.addEventListener('touchmove',  e => { e.preventDefault(); if (this._dragging !== null) drag(pos(e)); }, { passive: false });
    c.addEventListener('touchend',   () => { this._dragging = null; });
  }

  /* ── layer toggles ── */

  toggleLayer(name) { this.layers[name] = !this.layers[name]; this.draw(); }
  setLayer(name, v) { this.layers[name] = v; this.draw(); }

  /* ══════════════════════════════════════
     Geometry helpers (canvas coords)
     Scale: SCALE px = 1 unit for UI display
  ══════════════════════════════════════ */

  static SCALE = 50;   // px per unit

  _len(P1, P2) { return Math.hypot(P2.x - P1.x, P2.y - P1.y); }

  _sides() {
    const [A, B, C] = this.pts;
    return { a: this._len(B, C), b: this._len(A, C), c: this._len(A, B) };
  }

  _area() {
    const [A, B, C] = this.pts;
    return Math.abs((B.x - A.x) * (C.y - A.y) - (C.x - A.x) * (B.y - A.y)) / 2;
  }

  _angles() {
    const { a, b, c } = this._sides();
    const cl = v => Math.max(-1, Math.min(1, v));
    const A  = Math.acos(cl((b*b + c*c - a*a) / (2*b*c)));
    const B  = Math.acos(cl((a*a + c*c - b*b) / (2*a*c)));
    const C  = Math.PI - A - B;
    return { A, B, C };
  }

  _centroid() {
    const [A, B, C] = this.pts;
    return { x: (A.x + B.x + C.x) / 3, y: (A.y + B.y + C.y) / 3 };
  }

  _circumcenter() {
    const [A, B, C] = this.pts;
    const D = 2 * (A.x*(B.y-C.y) + B.x*(C.y-A.y) + C.x*(A.y-B.y));
    if (Math.abs(D) < 1e-8) return null;
    const a2 = A.x*A.x + A.y*A.y, b2 = B.x*B.x + B.y*B.y, c2 = C.x*C.x + C.y*C.y;
    return {
      x: (a2*(B.y-C.y) + b2*(C.y-A.y) + c2*(A.y-B.y)) / D,
      y: (a2*(C.x-B.x) + b2*(A.x-C.x) + c2*(B.x-A.x)) / D,
    };
  }

  _circumR() {
    const O = this._circumcenter();
    return O ? this._len(this.pts[0], O) : 0;
  }

  _incenter() {
    const [A, B, C] = this.pts;
    const { a, b, c } = this._sides();
    const s = a + b + c;
    if (s < 1e-8) return null;
    return { x: (a*A.x + b*B.x + c*C.x)/s, y: (a*A.y + b*B.y + c*C.y)/s };
  }

  _inR() {
    const { a, b, c } = this._sides();
    const s = a + b + c;
    return s < 1e-8 ? 0 : (2 * this._area()) / s;
  }

  _orthocenter() {
    const [A, B, C] = this.pts;
    const bcDx = C.x - B.x, bcDy = C.y - B.y;
    const acDx = C.x - A.x, acDy = C.y - A.y;
    const denom = bcDy * (-acDx) - (-bcDx) * acDy;
    if (Math.abs(denom) < 1e-8) return null;
    const t = ((B.x-A.x)*(-acDy) - (B.y-A.y)*(-acDx)) / denom;
    return { x: A.x + t*bcDy, y: A.y - t*bcDx };
  }

  _foot(P, L1, L2) {
    const dx = L2.x - L1.x, dy = L2.y - L1.y;
    const l2 = dx*dx + dy*dy;
    if (l2 < 1e-10) return { ...L1 };
    const t = ((P.x-L1.x)*dx + (P.y-L1.y)*dy) / l2;
    return { x: L1.x + t*dx, y: L1.y + t*dy };
  }

  _mid(P1, P2) { return { x: (P1.x+P2.x)/2, y: (P1.y+P2.y)/2 }; }

  /* bisector foot: divides opposite side by angle-bisector theorem */
  _bisFoot(V, L1, L2) {
    const d1 = this._len(V, L1), d2 = this._len(V, L2);
    const s  = d1 + d2;
    if (s < 1e-8) return { ...L1 };
    return { x: (d2*L1.x + d1*L2.x)/s, y: (d2*L1.y + d1*L2.y)/s };
  }

  stats() {
    const S  = TriangleSim.SCALE;
    const { a, b, c } = this._sides();
    const { A, B, C } = this._angles();
    const area  = this._area();
    const perim = a + b + c;
    const R = this._circumR();
    const r = this._inR();

    const deg = rad => rad * 180 / Math.PI;
    const dA = deg(A), dB = deg(B), dC = deg(C);

    let type = '';
    const eps = 1.8;  // degrees tolerance
    const isRight = [dA, dB, dC].some(d => Math.abs(d - 90) < eps);
    const isObtuse = [dA, dB, dC].some(d => d > 90 + eps);
    const sidesArr = [a, b, c].sort((x, y) => x - y);
    const isEquil = sidesArr[2] - sidesArr[0] < 2;
    const isIsoc  = !isEquil && (
      Math.abs(a - b) < 2 || Math.abs(b - c) < 2 || Math.abs(a - c) < 2
    );

    if (isEquil)       type = 'Равносторонний';
    else if (isRight)  type = isIsoc ? 'Прямоугольный равнобедр.' : 'Прямоугольный';
    else if (isObtuse) type = isIsoc ? 'Тупоугольный равнобедр.' : 'Тупоугольный';
    else               type = isIsoc ? 'Остроугольный равнобедр.' : 'Остроугольный';

    return {
      a: a/S, b: b/S, c: c/S,
      A: dA,  B: dB,  C: dC,
      S: area / (S*S),
      perim: perim / S,
      R: R / S,
      r: r / S,
      type,
    };
  }

  /* ══════════════════════════════════════
     Drawing
  ══════════════════════════════════════ */

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

    ctx.clearRect(0, 0, W, H);

    // Background
    const bg = ctx.createLinearGradient(0, 0, W, H);
    bg.addColorStop(0, '#07071A');
    bg.addColorStop(1, '#0D1830');
    ctx.fillStyle = bg; ctx.fillRect(0, 0, W, H);

    if (this.layers.grid) this._drawGrid(ctx, W, H);

    // Layer order: circles behind <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> fill <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> construction lines <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> edges <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> vertices <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> labels
    if (this.layers.circumcircle) this._drawCircumcircle(ctx);
    if (this.layers.incircle)     this._drawIncircle(ctx);

    this._drawFill(ctx);

    if (this.layers.medians)   this._drawMedians(ctx);
    if (this.layers.altitudes) this._drawAltitudes(ctx);
    if (this.layers.bisectors) this._drawBisectors(ctx);

    if (this.layers.eulerLine) this._drawEulerLine(ctx);

    if (this.layers.pythagorean) this._drawPythagorean(ctx);
    if (this.layers.sineLaw)     this._drawSineLaw(ctx);
    if (this.layers.cosineLaw)   this._drawCosineLaw(ctx);

    this._drawAngleArcs(ctx);
    this._drawEdges(ctx);
    this._drawRightAngleMark(ctx);
    this._drawVertices(ctx);
    this._drawSideLabels(ctx);
    this._drawAngleLabels(ctx);
    if (window.LabFX) LabFX.particles.draw(ctx);
  }

  /* helpers */
  _line(ctx, x1, y1, x2, y2) {
    ctx.beginPath(); ctx.moveTo(x1, y1); ctx.lineTo(x2, y2); ctx.stroke();
  }

  _dot(ctx, x, y, r, fill, shadow) {
    ctx.save();
    ctx.shadowColor = shadow || fill; ctx.shadowBlur = 12;
    ctx.beginPath(); ctx.arc(x, y, r, 0, Math.PI*2);
    ctx.fillStyle = fill; ctx.fill(); ctx.restore();
  }

  _label(ctx, text, x, y, color, size=13) {
    ctx.save();
    ctx.font = `bold ${size}px Manrope, sans-serif`;
    ctx.fillStyle = color;
    ctx.shadowColor = color; ctx.shadowBlur = 8;
    ctx.textAlign = 'center'; ctx.textBaseline = 'middle';
    ctx.fillText(text, x, y); ctx.restore();
  }

  /* ── grid ── */
  _drawGrid(ctx, W, H) {
    const step = 50;
    ctx.save();
    ctx.strokeStyle = 'rgba(255,255,255,0.045)'; ctx.lineWidth = 1;
    for (let x = 0; x <= W; x += step) this._line(ctx, x, 0, x, H);
    for (let y = 0; y <= H; y += step) this._line(ctx, 0, y, W, y);
    // axes
    ctx.strokeStyle = 'rgba(255,255,255,0.10)'; ctx.lineWidth = 1.2;
    this._line(ctx, 0, H/2, W, H/2);
    this._line(ctx, W/2, 0, W/2, H);
    ctx.restore();
  }

  /* ── triangle fill ── */
  _drawFill(ctx) {
    const [A, B, C] = this.pts;
    ctx.save();
    const g = ctx.createLinearGradient(A.x, A.y, (B.x+C.x)/2, (B.y+C.y)/2);
    g.addColorStop(0, 'rgba(155,93,229,0.20)');
    g.addColorStop(1, 'rgba(6,214,224,0.07)');
    ctx.beginPath(); ctx.moveTo(A.x,A.y); ctx.lineTo(B.x,B.y); ctx.lineTo(C.x,C.y); ctx.closePath();
    ctx.fillStyle = g; ctx.fill();
    ctx.restore();
  }

  /* ── edges ── */
  _drawEdges(ctx) {
    const [A, B, C] = this.pts;
    ctx.save();
    ctx.shadowColor = 'rgba(155,93,229,0.55)'; ctx.shadowBlur = 10;
    ctx.strokeStyle = '#9B5DE5'; ctx.lineWidth = 2.5; ctx.lineJoin = 'round';
    ctx.beginPath(); ctx.moveTo(A.x,A.y); ctx.lineTo(B.x,B.y); ctx.lineTo(C.x,C.y); ctx.closePath(); ctx.stroke();
    ctx.restore();
  }

  /* ── vertices ── */
  _drawVertices(ctx) {
    const names  = ['A', 'B', 'C'];
    const colors = ['#9B5DE5', '#06D6E0', '#F15BB5'];

    this.pts.forEach((p, i) => {
      const active = this._hovered === i || this._dragging === i;
      const col    = colors[i];
      ctx.save();
      ctx.shadowColor = col; ctx.shadowBlur = active ? 24 : 14;

      ctx.beginPath(); ctx.arc(p.x, p.y, active ? 13 : 10, 0, Math.PI*2);
      ctx.fillStyle   = active ? col : 'rgba(7,7,26,0.92)';
      ctx.fill();
      ctx.strokeStyle = col; ctx.lineWidth = 2.2; ctx.stroke();

      if (!active) {
        ctx.beginPath(); ctx.arc(p.x, p.y, 4, 0, Math.PI*2);
        ctx.fillStyle = col; ctx.shadowBlur = 0; ctx.fill();
      }
      ctx.restore();
    });
  }

  /* ── vertex name labels (outside) ── */
  _drawSideLabels(ctx) {
    const [A, B, C] = this.pts;
    // sides: a=BC, b=AC, c=AB
    const sides = [
      { from: B, to: C, label: 'a', col: '#9B5DE5' },
      { from: A, to: C, label: 'b', col: '#06D6E0' },
      { from: A, to: B, label: 'c', col: '#F15BB5' },
    ];
    ctx.save();
    sides.forEach(({ from, to, label, col }) => {
      const mx = (from.x+to.x)/2, my = (from.y+to.y)/2;
      const dx = to.x-from.x, dy = to.y-from.y;
      const len = Math.hypot(dx, dy); if (len < 1) return;
      // perpendicular outward — pick side toward centroid and invert
      const cx_ = (this.pts[0].x+this.pts[1].x+this.pts[2].x)/3;
      const cy_ = (this.pts[0].y+this.pts[1].y+this.pts[2].y)/3;
      let nx = -dy/len, ny = dx/len;
      if ((mx+nx*10-cx_)*nx + (my+ny*10-cy_)*ny < 0) { nx=-nx; ny=-ny; }
      this._label(ctx, label, mx + nx*20, my + ny*20, col, 14);
    });
    ctx.restore();
  }

  /* ── vertex A/B/C labels ── */
  _drawAngleLabels(ctx) {
    const names  = ['A', 'B', 'C'];
    const colors = ['#9B5DE5', '#06D6E0', '#F15BB5'];
    const nexts  = [this.pts[1], this.pts[2], this.pts[0]];
    const prevs  = [this.pts[2], this.pts[0], this.pts[1]];

    this.pts.forEach((V, i) => {
      const P = nexts[i], Q = prevs[i];
      // direction from vertex away from triangle (outward bisector)
      const dp = { x: P.x-V.x, y: P.y-V.y }; const lp = Math.hypot(dp.x, dp.y);
      const dq = { x: Q.x-V.x, y: Q.y-V.y }; const lq = Math.hypot(dq.x, dq.y);
      if (lp < 1 || lq < 1) return;
      // outward: negate inward bisector
      const bx = dp.x/lp + dq.x/lq, by = dp.y/lp + dq.y/lq;
      const bl = Math.hypot(bx, by) || 1;
      const ox = -bx/bl * 26, oy = -by/bl * 26;
      this._label(ctx, names[i], V.x + ox, V.y + oy, colors[i], 15);
    });
  }

  /* ── angle arcs ── */
  _drawAngleArcs(ctx) {
    const nexts = [this.pts[1], this.pts[2], this.pts[0]];
    const prevs = [this.pts[2], this.pts[0], this.pts[1]];
    const colors= ['rgba(155,93,229,0.7)','rgba(6,214,224,0.7)','rgba(241,91,181,0.7)'];
    const fills = ['rgba(155,93,229,0.12)','rgba(6,214,224,0.12)','rgba(241,91,181,0.12)'];

    ctx.save();
    this.pts.forEach((V, i) => {
      const P = nexts[i], Q = prevs[i];
      const r = 30;
      const a1 = Math.atan2(P.y-V.y, P.x-V.x);
      const a2 = Math.atan2(Q.y-V.y, Q.x-V.x);
      let diff = a2 - a1;
      while (diff >  Math.PI) diff -= 2*Math.PI;
      while (diff < -Math.PI) diff += 2*Math.PI;
      const ccw = diff < 0;

      ctx.strokeStyle = colors[i]; ctx.lineWidth = 1.5;
      ctx.fillStyle   = fills[i];

      ctx.beginPath();
      ctx.moveTo(V.x, V.y);
      ctx.arc(V.x, V.y, r, a1, a2, ccw);
      ctx.closePath();
      ctx.fill();

      ctx.beginPath();
      ctx.arc(V.x, V.y, r, a1, a2, ccw);
      ctx.stroke();
    });
    ctx.restore();
  }

  /* ── right angle mark ── */
  _drawRightAngleMark(ctx) {
    const { A, B, C } = this._angles();
    const verts  = this.pts;
    const angles = [A, B, C];
    const nexts  = [verts[1], verts[2], verts[0]];
    const prevs  = [verts[2], verts[0], verts[1]];

    ctx.save();
    ctx.strokeStyle = 'rgba(255,255,255,0.55)'; ctx.lineWidth = 1.5;

    verts.forEach((V, i) => {
      if (Math.abs(angles[i] - Math.PI/2) > 0.035) return;
      const P = nexts[i], Q = prevs[i];
      const sz = 14;
      const lp = Math.hypot(P.x-V.x, P.y-V.y), lq = Math.hypot(Q.x-V.x, Q.y-V.y);
      if (lp < 1 || lq < 1) return;
      const d1 = { x:(P.x-V.x)/lp*sz, y:(P.y-V.y)/lp*sz };
      const d2 = { x:(Q.x-V.x)/lq*sz, y:(Q.y-V.y)/lq*sz };
      ctx.beginPath();
      ctx.moveTo(V.x+d1.x, V.y+d1.y);
      ctx.lineTo(V.x+d1.x+d2.x, V.y+d1.y+d2.y);
      ctx.lineTo(V.x+d2.x, V.y+d2.y);
      ctx.stroke();
    });
    ctx.restore();
  }

  /* ── medians (green) ── */
  _drawMedians(ctx) {
    const [A, B, C] = this.pts;
    const mA = this._mid(B, C), mB = this._mid(A, C), mC = this._mid(A, B);
    const G  = this._centroid();

    ctx.save();
    ctx.strokeStyle = '#22d55e'; ctx.lineWidth = 1.8;
    ctx.setLineDash([6, 4]); ctx.shadowColor = '#22d55e'; ctx.shadowBlur = 7;

    [[A, mA],[B, mB],[C, mC]].forEach(([fr, to]) => {
      ctx.beginPath(); ctx.moveTo(fr.x, fr.y); ctx.lineTo(to.x, to.y); ctx.stroke();
    });
    ctx.setLineDash([]);

    // midpoint ticks
    [mA, mB, mC].forEach(m => this._dot(ctx, m.x, m.y, 4, '#22d55e'));

    // centroid with glow
    if (window.LabFX) {
      LabFX.glow.drawGlow(ctx, () => {
        this._dot(ctx, G.x, G.y, 7, '#22d55e');
      }, { color: '#F59E0B', intensity: 6 });
    } else {
      this._dot(ctx, G.x, G.y, 7, '#22d55e');
    }
    this._label(ctx, 'G', G.x + 14, G.y - 8, '#22d55e', 13);

    ctx.restore();
  }

  /* ── altitudes (amber) ── */
  _drawAltitudes(ctx) {
    const [A, B, C] = this.pts;
    const fA = this._foot(A, B, C);
    const fB = this._foot(B, A, C);
    const fC = this._foot(C, A, B);
    const H  = this._orthocenter();

    ctx.save();
    ctx.strokeStyle = '#f59e0b'; ctx.lineWidth = 1.8;
    ctx.setLineDash([6, 4]); ctx.shadowColor = '#f59e0b'; ctx.shadowBlur = 7;

    [[A, fA],[B, fB],[C, fC]].forEach(([fr, to]) => {
      ctx.beginPath(); ctx.moveTo(fr.x, fr.y); ctx.lineTo(to.x, to.y); ctx.stroke();
    });
    ctx.setLineDash([]);

    // foot right-angle marks
    [[A, B, C, fA],[B, A, C, fB],[C, A, B, fC]].forEach(([P, L1, L2, ft]) => {
      const lp = Math.hypot(P.x-ft.x, P.y-ft.y);
      const ll = Math.hypot(L2.x-L1.x, L2.y-L1.y);
      if (lp < 1 || ll < 1) return;
      const sz = 9;
      const d1 = { x:(P.x-ft.x)/lp*sz, y:(P.y-ft.y)/lp*sz };
      const d2 = { x:(L2.x-L1.x)/ll*sz, y:(L2.y-L1.y)/ll*sz };
      ctx.strokeStyle = 'rgba(245,158,11,0.6)'; ctx.lineWidth = 1.2;
      ctx.beginPath();
      ctx.moveTo(ft.x+d1.x, ft.y+d1.y);
      ctx.lineTo(ft.x+d1.x+d2.x, ft.y+d1.y+d2.y);
      ctx.lineTo(ft.x+d2.x, ft.y+d2.y);
      ctx.stroke();
    });

    [fA, fB, fC].forEach(f => this._dot(ctx, f.x, f.y, 4, '#f59e0b'));

    if (H) {
      if (window.LabFX) {
        LabFX.glow.drawGlow(ctx, () => {
          this._dot(ctx, H.x, H.y, 7, '#f59e0b');
        }, { color: '#F59E0B', intensity: 6 });
      } else {
        this._dot(ctx, H.x, H.y, 7, '#f59e0b');
      }
      this._label(ctx, 'H', H.x + 14, H.y - 8, '#f59e0b', 13);
    }
    ctx.restore();
  }

  /* ── bisectors (pink) ── */
  _drawBisectors(ctx) {
    const [A, B, C] = this.pts;
    const fA = this._bisFoot(A, B, C);
    const fB = this._bisFoot(B, A, C);
    const fC = this._bisFoot(C, A, B);
    const I  = this._incenter();

    ctx.save();
    ctx.strokeStyle = '#ec4899'; ctx.lineWidth = 1.8;
    ctx.setLineDash([6, 4]); ctx.shadowColor = '#ec4899'; ctx.shadowBlur = 7;

    [[A, fA],[B, fB],[C, fC]].forEach(([fr, to]) => {
      ctx.beginPath(); ctx.moveTo(fr.x, fr.y); ctx.lineTo(to.x, to.y); ctx.stroke();
    });
    ctx.setLineDash([]);

    if (I) {
      this._dot(ctx, I.x, I.y, 7, '#ec4899');
      this._label(ctx, 'I', I.x + 14, I.y - 8, '#ec4899', 13);
    }
    ctx.restore();
  }

  /* ── circumscribed circle (pink dashed) ── */
  _drawCircumcircle(ctx) {
    const O = this._circumcenter();
    if (!O) return;
    const R = this._circumR();

    ctx.save();
    ctx.strokeStyle = 'rgba(241,91,181,0.75)'; ctx.lineWidth = 1.8;
    ctx.setLineDash([7, 4]); ctx.shadowColor = '#F15BB5'; ctx.shadowBlur = 10;
    ctx.beginPath(); ctx.arc(O.x, O.y, R, 0, Math.PI*2); ctx.stroke();
    ctx.setLineDash([]);

    // center O
    this._dot(ctx, O.x, O.y, 5, '#F15BB5');
    this._label(ctx, 'O', O.x + 10, O.y - 10, '#F15BB5', 12);

    // radii (faint)
    ctx.strokeStyle = 'rgba(241,91,181,0.18)'; ctx.lineWidth = 1;
    this.pts.forEach(P => {
      ctx.beginPath(); ctx.moveTo(O.x, O.y); ctx.lineTo(P.x, P.y); ctx.stroke();
    });
    ctx.restore();
  }

  /* ── inscribed circle (cyan dashed) ── */
  _drawIncircle(ctx) {
    const I = this._incenter();
    if (!I) return;
    const r = this._inR();

    ctx.save();
    ctx.strokeStyle = 'rgba(6,214,224,0.75)'; ctx.lineWidth = 1.8;
    ctx.setLineDash([7, 4]); ctx.shadowColor = '#06D6E0'; ctx.shadowBlur = 10;
    ctx.beginPath(); ctx.arc(I.x, I.y, r, 0, Math.PI*2); ctx.stroke();
    ctx.setLineDash([]);

    ctx.beginPath(); ctx.arc(I.x, I.y, r, 0, Math.PI*2);
    ctx.fillStyle = 'rgba(6,214,224,0.05)'; ctx.fill();

    this._dot(ctx, I.x, I.y, 5, '#06D6E0');
    ctx.restore();
  }

  /* ── Euler line: O <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> G <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> H ── */
  _drawEulerLine(ctx) {
    const O = this._circumcenter();
    const G = this._centroid();
    const H = this._orthocenter();
    if (!O || !H) return;

    ctx.save();
    ctx.strokeStyle = 'rgba(255,255,100,0.5)'; ctx.lineWidth = 1.5;
    ctx.setLineDash([4, 4]); ctx.shadowColor = 'rgba(255,255,100,0.4)'; ctx.shadowBlur = 6;
    ctx.beginPath(); ctx.moveTo(O.x, O.y); ctx.lineTo(H.x, H.y); ctx.stroke();
    ctx.setLineDash([]);

    // Label
    const mx = (O.x + H.x)/2 + 16, my = (O.y + H.y)/2 - 8;
    ctx.font = '11px Manrope, sans-serif';
    ctx.fillStyle = 'rgba(255,255,100,0.6)';
    ctx.textAlign = 'left'; ctx.textBaseline = 'middle';
    ctx.fillText('прямая Эйлера', mx, my);
    ctx.restore();
  }

  /* ══════════════════════════════════════════════════════
     THEOREM VISUALIZATIONS
  ══════════════════════════════════════════════════════ */

  /* ── Law of Sines: a/sinA = b/sinB = c/sinC = 2R ── */
  _drawSineLaw(ctx) {
    const [A, B, C] = this.pts;
    const { a, b, c } = this._sides();
    const angles = this._angles();
    const S = TriangleSim.SCALE;
    const O = this._circumcenter();
    const R = this._circumR();

    if (!O || R < 1) return;

    ctx.save();

    // Draw circumscribed circle (faint, if not already enabled)
    if (!this.layers.circumcircle) {
      ctx.strokeStyle = 'rgba(96,165,250,0.3)'; ctx.lineWidth = 1.2;
      ctx.setLineDash([5, 4]);
      ctx.beginPath(); ctx.arc(O.x, O.y, R, 0, Math.PI * 2); ctx.stroke();
      ctx.setLineDash([]);
      this._dot(ctx, O.x, O.y, 3, 'rgba(96,165,250,0.5)');
    }

    // Draw radii from O to each vertex with labels
    const verts = [A, B, C];
    const sideNames = ['a', 'b', 'c'];
    const angNames  = ['A', 'B', 'C'];
    const angVals   = [angles.A, angles.B, angles.C];
    const sideVals  = [a, b, c];
    const colors    = ['#60a5fa', '#34d399', '#fbbf24'];

    // Draw radius lines from center to vertices
    ctx.strokeStyle = 'rgba(96,165,250,0.25)'; ctx.lineWidth = 1;
    verts.forEach(v => {
      ctx.beginPath(); ctx.moveTo(O.x, O.y); ctx.lineTo(v.x, v.y); ctx.stroke();
    });

    // Compute 2R
    const twoR = 2 * R / S;

    // For each side, show a/sinA value annotation near the side midpoint
    for (let i = 0; i < 3; i++) {
      const ratio = (sideVals[i] / S) / Math.sin(angVals[i]);
      const from = verts[(i + 1) % 3], to = verts[(i + 2) % 3];
      const mx = (from.x + to.x) / 2, my = (from.y + to.y) / 2;

      // offset outward from centroid
      const cx_ = (A.x + B.x + C.x) / 3, cy_ = (A.y + B.y + C.y) / 3;
      const dx = mx - cx_, dy = my - cy_;
      const dl = Math.hypot(dx, dy) || 1;
      const ox = dx / dl * 38, oy = dy / dl * 38;

      ctx.font = 'bold 11px Manrope, sans-serif';
      ctx.fillStyle = colors[i];
      ctx.textAlign = 'center'; ctx.textBaseline = 'middle';
      ctx.shadowColor = colors[i]; ctx.shadowBlur = 4;
      ctx.fillText(`${sideNames[i]}/sin${angNames[i]} = ${ratio.toFixed(2)}`, mx + ox, my + oy);
      ctx.shadowBlur = 0;
    }

    // Formula box at bottom
    this._drawFormulaBox(ctx, this.W, this.H,
      `a/sinA = b/sinB = c/sinC = 2R = ${twoR.toFixed(2)}`,
      '#60a5fa');

    ctx.restore();
  }

  /* ── Law of Cosines: c² = a² + b² − 2ab·cosC ── */
  _drawCosineLaw(ctx) {
    const [A, B, C] = this.pts;
    const sides = this._sides();
    const angles = this._angles();
    const S = TriangleSim.SCALE;

    // Pick the largest angle to demonstrate
    const angArr = [angles.A, angles.B, angles.C];
    const maxIdx = angArr.indexOf(Math.max(...angArr));
    const angVertex = this.pts[maxIdx];
    const oppFrom = this.pts[(maxIdx + 1) % 3];
    const oppTo = this.pts[(maxIdx + 2) % 3];

    const sNames = ['a', 'b', 'c'];
    const aNames = ['A', 'B', 'C'];
    const sVals  = [sides.a, sides.b, sides.c];

    // The opposite side to the chosen angle
    const oppSide = sVals[maxIdx] / S;
    const adjSide1Name = sNames[(maxIdx + 1) % 3]; // side opposite to vertex (maxIdx+1)
    const adjSide2Name = sNames[(maxIdx + 2) % 3]; // side opposite to vertex (maxIdx+2)
    const adjSide1 = sVals[(maxIdx + 1) % 3] / S;
    const adjSide2 = sVals[(maxIdx + 2) % 3] / S;
    const oppSideName = sNames[maxIdx];
    const angName = aNames[maxIdx];
    const angDeg = angArr[maxIdx] * 180 / Math.PI;

    ctx.save();

    // Highlight the two adjacent sides with thicker lines
    ctx.lineWidth = 3.5; ctx.lineCap = 'round';

    ctx.strokeStyle = 'rgba(251,191,36,0.7)';
    ctx.shadowColor = '#fbbf24'; ctx.shadowBlur = 8;
    ctx.beginPath(); ctx.moveTo(angVertex.x, angVertex.y); ctx.lineTo(oppFrom.x, oppFrom.y); ctx.stroke();

    ctx.strokeStyle = 'rgba(52,211,153,0.7)';
    ctx.shadowColor = '#34d399'; ctx.shadowBlur = 8;
    ctx.beginPath(); ctx.moveTo(angVertex.x, angVertex.y); ctx.lineTo(oppTo.x, oppTo.y); ctx.stroke();

    // Highlight the opposite side
    ctx.strokeStyle = 'rgba(239,71,111,0.7)';
    ctx.shadowColor = '#EF476F'; ctx.shadowBlur = 8;
    ctx.beginPath(); ctx.moveTo(oppFrom.x, oppFrom.y); ctx.lineTo(oppTo.x, oppTo.y); ctx.stroke();
    ctx.shadowBlur = 0;

    // Angle arc highlight at the chosen vertex
    const r = 36;
    const a1 = Math.atan2(oppFrom.y - angVertex.y, oppFrom.x - angVertex.x);
    const a2 = Math.atan2(oppTo.y - angVertex.y, oppTo.x - angVertex.x);
    let diff = a2 - a1;
    while (diff > Math.PI) diff -= 2 * Math.PI;
    while (diff < -Math.PI) diff += 2 * Math.PI;
    const ccw = diff < 0;

    ctx.fillStyle = 'rgba(251,191,36,0.15)';
    ctx.beginPath();
    ctx.moveTo(angVertex.x, angVertex.y);
    ctx.arc(angVertex.x, angVertex.y, r, a1, a2, ccw);
    ctx.closePath(); ctx.fill();

    ctx.strokeStyle = 'rgba(251,191,36,0.6)'; ctx.lineWidth = 2;
    ctx.beginPath(); ctx.arc(angVertex.x, angVertex.y, r, a1, a2, ccw); ctx.stroke();

    // Angle label
    const aMid = a1 + diff / 2;
    ctx.font = 'bold 12px Manrope, sans-serif';
    ctx.fillStyle = '#fbbf24'; ctx.textAlign = 'center'; ctx.textBaseline = 'middle';
    ctx.fillText(`${angDeg.toFixed(1)}°`, angVertex.x + Math.cos(aMid) * 50, angVertex.y + Math.sin(aMid) * 50);

    // Compute c² vs a²+b²-2ab·cosC
    const c2 = oppSide ** 2;
    const check = adjSide1 ** 2 + adjSide2 ** 2 - 2 * adjSide1 * adjSide2 * Math.cos(angArr[maxIdx]);

    // Formula
    this._drawFormulaBox(ctx, this.W, this.H,
      `${oppSideName}² = ${adjSide1Name}² + ${adjSide2Name}² \u2212 2\u00B7${adjSide1Name}\u00B7${adjSide2Name}\u00B7cos${angName}  \u2192  ${c2.toFixed(2)} = ${check.toFixed(2)}`,
      '#fbbf24');

    ctx.restore();
  }

  /* ── Pythagorean theorem: c² = a² + b² ── */
  _drawPythagorean(ctx) {
    const [A, B, C] = this.pts;
    const { a, b, c } = this._sides();
    const angles = this._angles();
    const S = TriangleSim.SCALE;

    // Find the largest angle (closest to being right)
    const angArr = [angles.A, angles.B, angles.C];
    const maxIdx = angArr.indexOf(Math.max(...angArr));
    const maxAngle = angArr[maxIdx];
    const isRight = Math.abs(maxAngle - Math.PI / 2) < 0.035;

    const hyp = this.pts[maxIdx]; // vertex at the largest angle
    const p1 = this.pts[(maxIdx + 1) % 3];
    const p2 = this.pts[(maxIdx + 2) % 3];

    // Side lengths (the side opposite the right angle is the hypotenuse)
    const sNames = ['a', 'b', 'c'];
    const sVals  = [a / S, b / S, c / S];
    const hypSide = sVals[maxIdx];
    const leg1 = sVals[(maxIdx + 1) % 3];
    const leg2 = sVals[(maxIdx + 2) % 3];
    const hypName = sNames[maxIdx];
    const leg1Name = sNames[(maxIdx + 1) % 3];
    const leg2Name = sNames[(maxIdx + 2) % 3];

    ctx.save();

    // Draw squares on each side
    this._drawSideSquare(ctx, p1, p2, '#EF476F', 0.12);  // hypotenuse
    this._drawSideSquare(ctx, hyp, p2, '#06D6E0', 0.12);  // leg 1
    this._drawSideSquare(ctx, hyp, p1, '#9B5DE5', 0.12);  // leg 2

    // Labels on squares with areas
    const hypArea = hypSide ** 2;
    const leg1Area = leg1 ** 2;
    const leg2Area = leg2 ** 2;

    this._labelSquare(ctx, p1, p2, `${hypName}² = ${hypArea.toFixed(2)}`, '#EF476F');
    this._labelSquare(ctx, hyp, p2, `${leg1Name}² = ${leg1Area.toFixed(2)}`, '#06D6E0');
    this._labelSquare(ctx, hyp, p1, `${leg2Name}² = ${leg2Area.toFixed(2)}`, '#9B5DE5');

    // Status: how close to Pythagorean
    const diff = Math.abs(hypArea - (leg1Area + leg2Area));
    const statusCol = isRight ? '#22d55e' : '#f59e0b';
    const statusText = isRight
      ? `\u2713  ${leg1Name}\u00B2 + ${leg2Name}\u00B2 = ${hypName}\u00B2  (${(leg1Area + leg2Area).toFixed(2)} = ${hypArea.toFixed(2)})`
      : `${leg1Name}² + ${leg2Name}² = ${(leg1Area + leg2Area).toFixed(2)}  ≠  ${hypName}² = ${hypArea.toFixed(2)}  (Δ = ${diff.toFixed(2)})`;

    this._drawFormulaBox(ctx, this.W, this.H, statusText, statusCol);

    // Hint if not right angle
    if (!isRight) {
      ctx.font = '11px Manrope, sans-serif';
      ctx.fillStyle = 'rgba(245,158,11,0.7)';
      ctx.textAlign = 'center';
      ctx.fillText('Перетащи вершину чтобы ∠ ≈ 90°', this.W / 2, this.H - 16);
    }

    ctx.restore();
  }

  /* ── Helpers for theorem visuals ── */

  _drawSideSquare(ctx, p1, p2, color, alpha) {
    const dx = p2.x - p1.x, dy = p2.y - p1.y;
    // perpendicular direction (outward from triangle centroid)
    const cx_ = (this.pts[0].x + this.pts[1].x + this.pts[2].x) / 3;
    const cy_ = (this.pts[0].y + this.pts[1].y + this.pts[2].y) / 3;
    let nx = -dy, ny = dx; // perpendicular
    // ensure outward
    const mx = (p1.x + p2.x) / 2, my = (p1.y + p2.y) / 2;
    if ((mx + nx * 0.1 - cx_) * nx + (my + ny * 0.1 - cy_) * ny < 0) { nx = -nx; ny = -ny; }

    const q1 = { x: p1.x + nx, y: p1.y + ny };
    const q2 = { x: p2.x + nx, y: p2.y + ny };

    ctx.save();
    ctx.globalAlpha = alpha;
    ctx.fillStyle = color;
    ctx.beginPath();
    ctx.moveTo(p1.x, p1.y);
    ctx.lineTo(p2.x, p2.y);
    ctx.lineTo(q2.x, q2.y);
    ctx.lineTo(q1.x, q1.y);
    ctx.closePath();
    ctx.fill();
    ctx.globalAlpha = 1;

    ctx.strokeStyle = color;
    ctx.lineWidth = 1.5;
    ctx.globalAlpha = 0.5;
    ctx.beginPath();
    ctx.moveTo(p1.x, p1.y);
    ctx.lineTo(p2.x, p2.y);
    ctx.lineTo(q2.x, q2.y);
    ctx.lineTo(q1.x, q1.y);
    ctx.closePath();
    ctx.stroke();
    ctx.globalAlpha = 1;
    ctx.restore();
  }

  _labelSquare(ctx, p1, p2, text, color) {
    const dx = p2.x - p1.x, dy = p2.y - p1.y;
    const cx_ = (this.pts[0].x + this.pts[1].x + this.pts[2].x) / 3;
    const cy_ = (this.pts[0].y + this.pts[1].y + this.pts[2].y) / 3;
    let nx = -dy, ny = dx;
    const mx = (p1.x + p2.x) / 2, my = (p1.y + p2.y) / 2;
    if ((mx + nx * 0.1 - cx_) * nx + (my + ny * 0.1 - cy_) * ny < 0) { nx = -nx; ny = -ny; }

    const center = { x: mx + nx * 0.5, y: my + ny * 0.5 };

    ctx.save();
    ctx.font = 'bold 11px Manrope, sans-serif';
    ctx.fillStyle = color;
    ctx.textAlign = 'center'; ctx.textBaseline = 'middle';
    ctx.shadowColor = 'rgba(0,0,0,0.7)'; ctx.shadowBlur = 4;
    ctx.fillText(text, center.x, center.y);
    ctx.restore();
  }

  _drawFormulaBox(ctx, W, H, text, color) {
    ctx.save();
    const bw = ctx.measureText(text).width || 300;
    const pad = 12;
    const boxW = Math.min(W - 40, bw + pad * 2 + 20);
    const boxH = 28;
    const bx = (W - boxW) / 2;
    const by = H - 42;

    ctx.font = 'bold 12px Manrope, monospace';

    // background
    ctx.fillStyle = 'rgba(7,7,26,0.85)';
    ctx.strokeStyle = color;
    ctx.lineWidth = 1.2;
    ctx.globalAlpha = 0.9;
    const r = 8;
    ctx.beginPath();
    ctx.moveTo(bx + r, by); ctx.lineTo(bx + boxW - r, by);
    ctx.quadraticCurveTo(bx + boxW, by, bx + boxW, by + r);
    ctx.lineTo(bx + boxW, by + boxH - r);
    ctx.quadraticCurveTo(bx + boxW, by + boxH, bx + boxW - r, by + boxH);
    ctx.lineTo(bx + r, by + boxH);
    ctx.quadraticCurveTo(bx, by + boxH, bx, by + boxH - r);
    ctx.lineTo(bx, by + r);
    ctx.quadraticCurveTo(bx, by, bx + r, by);
    ctx.closePath();
    ctx.fill(); ctx.stroke();
    ctx.globalAlpha = 1;

    // text
    ctx.fillStyle = color;
    ctx.textAlign = 'center'; ctx.textBaseline = 'middle';
    ctx.shadowColor = color; ctx.shadowBlur = 6;
    ctx.fillText(text, W / 2, by + boxH / 2);
    ctx.restore();
  }
}

/* ─── lab UI init ─────────────────────────────────── */
  function _openTriangle() {
    document.getElementById('sim-topbar-title').textContent = 'Геометрия треугольника';
    _simShow('sim-tri');
    _simShow('ctrl-tri');

    requestAnimationFrame(() => requestAnimationFrame(() => {
      if (!tSim) {
        tSim = new TriangleSim(document.getElementById('tri-canvas'));
        tSim.onUpdate = _triUpdateUI;
      }
      tSim.fit();
      tSim.draw();
      _triUpdateUI(tSim.stats());
    }));
  }

  function triToggle(layer, rowEl) {
    if (!tSim) return;
    tSim.toggleLayer(layer);
    rowEl.classList.toggle('active', tSim.layers[layer]);
  }

  function _triUpdateUI(s) {
    const f2 = v => v.toFixed(2);
    const deg = v => v.toFixed(1) + '°';
    const unit = v => f2(v) + ' ед';

    // panel
    document.getElementById('ts-a').textContent = unit(s.a);
    document.getElementById('ts-b').textContent = unit(s.b);
    document.getElementById('ts-c').textContent = unit(s.c);
    document.getElementById('ts-A').textContent = deg(s.A);
    document.getElementById('ts-B').textContent = deg(s.B);
    document.getElementById('ts-C').textContent = deg(s.C);
    document.getElementById('ts-S').textContent = f2(s.S) + ' ед²';
    document.getElementById('ts-P').textContent = unit(s.perim);
    document.getElementById('ts-R').textContent = unit(s.R);
    document.getElementById('ts-r').textContent = unit(s.r);
    document.getElementById('ts-type').textContent = s.type;

    // stats bar
    document.getElementById('tbar-a').textContent = unit(s.a);
    document.getElementById('tbar-b').textContent = unit(s.b);
    document.getElementById('tbar-c').textContent = unit(s.c);
    document.getElementById('tbar-S').textContent = f2(s.S) + ' ед²';
    document.getElementById('tbar-P').textContent = unit(s.perim);
    document.getElementById('tbar-Rr').textContent = f2(s.R) + ' / ' + f2(s.r);
  }

  /* ── geometry (planimetry) ── */

  const _GEO_HINTS = {
    select:       'Клик — выбрать объект, перетащи точку для перемещения',
    point:        'Клик — поставить точку',
    segment:      'Кликни 2 точки для отрезка',
    line:         'Кликни 2 точки для прямой',
    ray:          'Кликни: начало, затем направление',
    circle:       'Клик — центр; второй клик — радиус',
    triangle:     'Кликни 3 точки для треугольника',
    quad:         'Кликни 4 точки для четырёхугольника',
    polygon:      'Кликай точки; двойной клик или Enter — завершить',
    midpoint:     'Кликни 2 точки — получи середину отрезка',
    perpbisect:   'Кликни 2 точки — получи серединный перпендикуляр',
    anglebisect:  'Кликни: точку A, затем вершину угла, затем точку B',
    parallel:     'Сначала кликни на прямую/отрезок, затем на точку',
    perpendicular:'Сначала кликни на прямую/отрезок, затем на точку',
    intersect:    'Кликни на первую прямую, затем на вторую',
    foot:         'Сначала кликни на прямую/отрезок',
    circumcircle: 'Кликни 3 точки треугольника — получи описанную окружность',
    incircle:     'Кликни 3 точки треугольника — получи вписанную окружность',
    reflect:      'Сначала кликни на ось симметрии (прямую/отрезок)',
    ngon:         'Клик — центр правильного многоугольника; второй клик — вершина',
    tangent:      'Кликни на окружность — построим касательные',
    translate:    'Кликни начало вектора A',
    tick:         'Кликни на отрезок или сторону — добавить штрих (1–3; ещё раз — убрать)',
    arcmark:      'Кликни на вершину полигона — добавить дугу (1–3; ещё раз — убрать)',
    parallelmark: 'Кликни на отрезок или сторону — добавить метку параллельности (1–2; ещё раз — убрать)',
    altitude:     'Кликни на вершину треугольника — построим высоту из неё',
    median:       'Кликни на вершину треугольника — построим медиану из неё',
    centroid:     'Кликни на треугольник или внутри него — построим все 3 медианы и центроид G',
    orthocenter:  'Кликни на треугольник или внутри него — построим все 3 высоты и ортоцентр H',
    thales:       'Кликни центр подобия O (начало лучей)',
    midline:      'Кликни вершину A треугольника',
    parallelogram:'Кликни вершину A параллелограмма',
    diagonal:     'Кликни внутри четырёхугольника — построим диагонали',
    scale:          'Кликни центр подобия O',
    measure_length:    'Кликни на отрезок — прикрепит живой чип с длиной',
    measure_angle:     'Кликни первую точку на стороне угла',
    measure_area:      'Кликни на многоугольник — прикрепит живой чип с площадью',
    locus:             'Кликни точку-мовер (должна быть on_segment или on_circle)',
    point_on_segment:  'Кликни на отрезок — создаст скользящую точку для ГМТ',
    point_on_circle:   'Кликни на окружность — создаст скользящую точку для ГМТ',
  };

