'use strict'; /* LabMeasure — измерительные инструменты поверх любой симуляции (Фаза 2). * Линейка (длина в px + ≈ метрах при PX_PER_M) и угломер (угол при вершине). * SVG-оверлей на весь экран с pointer-events:none, чтобы симуляция оставалась * интерактивной; перехватывают события только перетаскиваемые ручки. * Самодостаточно: свой DOM/CSS, не трогает симуляции. Точка входа — LabMeasure.toggle(). */ (function (global) { var NS = 'http://www.w3.org/2000/svg'; var PXM = (global.LabPalette && LabPalette.PX_PER_M) || 100; var svg = null, bar = null, mode = null, drag = null; var ruler = null, angle = null; function el(tag, attrs) { var e = document.createElementNS(NS, tag); for (var k in attrs) e.setAttribute(k, attrs[k]); return e; } function center() { return { x: global.innerWidth / 2, y: global.innerHeight / 2 }; } function ensureStyle() { if (document.getElementById('lm-style')) return; var s = document.createElement('style'); s.id = 'lm-style'; s.textContent = [ '#lm-svg{position:fixed;inset:0;z-index:60;pointer-events:none;display:none;}', '#lm-svg.on{display:block;}', '#lm-svg .lm-hit{pointer-events:auto;cursor:grab;}', '#lm-svg .lm-hit:active{cursor:grabbing;}', '#lm-bar{position:fixed;top:64px;left:50%;transform:translateX(-50%);z-index:61;display:none;gap:6px;padding:6px;border-radius:12px;', 'background:var(--surface,rgba(255,255,255,.9));backdrop-filter:var(--blur,blur(20px));border:1px solid var(--border,rgba(15,23,42,.1));box-shadow:0 8px 28px rgba(15,23,42,.18);}', '#lm-bar.on{display:flex;}', '.lm-tb{display:inline-flex;align-items:center;gap:6px;padding:7px 13px;border:none;border-radius:8px;background:transparent;', 'font:700 .8rem Manrope,sans-serif;color:var(--text-2,#475569);cursor:pointer;}', '.lm-tb:hover{background:rgba(155,93,229,.08);color:var(--violet,#9B5DE5);}', '.lm-tb.on{background:var(--violet,#9B5DE5);color:#fff;}', '.lm-tb .ic{width:15px;height:15px;}', ].join(''); document.head.appendChild(s); } function ensure() { if (svg) return; ensureStyle(); svg = el('svg', { id: 'lm-svg' }); document.body.appendChild(svg); bar = document.createElement('div'); bar.id = 'lm-bar'; bar.innerHTML = '' + '' + ''; document.body.appendChild(bar); bar.addEventListener('click', function (e) { var b = e.target.closest('.lm-tb'); if (b) setTool(b.dataset.t); }); // drag delegation svg.addEventListener('pointerdown', function (e) { var h = e.target.getAttribute && e.target.getAttribute('data-h'); if (!h) return; drag = h; e.target.setPointerCapture && e.target.setPointerCapture(e.pointerId); e.preventDefault(); }); svg.addEventListener('pointermove', function (e) { if (!drag) return; var p = (mode === 'ruler') ? ruler : angle; p[drag + 'x'] = e.clientX; p[drag + 'y'] = e.clientY; render(); }); function end() { drag = null; } svg.addEventListener('pointerup', end); svg.addEventListener('pointercancel', end); global.addEventListener('resize', function () { if (mode) render(); }); } function setTool(t) { ensure(); if (t === 'off') { mode = null; svg.classList.remove('on'); paintBar(); render(); return; } mode = t; var c = center(); if (t === 'ruler' && !ruler) ruler = { ax: c.x - 110, ay: c.y, bx: c.x + 110, by: c.y }; if (t === 'angle' && !angle) angle = { vx: c.x, vy: c.y + 70, ax: c.x - 120, ay: c.y - 40, bx: c.x + 120, by: c.y - 40 }; svg.classList.add('on'); paintBar(); render(); } function paintBar() { if (!bar) return; bar.querySelectorAll('.lm-tb').forEach(function (b) { b.classList.toggle('on', b.dataset.t === mode); }); } function lineLabel(x, y, text) { var g = el('g', {}); var pad = 5, w = text.length * 7.2 + pad * 2, h = 20; g.appendChild(el('rect', { x: x - w / 2, y: y - h / 2, width: w, height: h, rx: 6, fill: 'rgba(13,13,26,.85)' })); var t = el('text', { x: x, y: y + 4, 'text-anchor': 'middle', fill: '#fff', 'font-size': 12, 'font-family': 'Manrope,sans-serif', 'font-weight': 700 }); t.textContent = text; g.appendChild(t); return g; } function handle(hx, hy, name) { return el('circle', { cx: hx, cy: hy, r: 9, class: 'lm-hit', fill: '#9B5DE5', stroke: '#fff', 'stroke-width': 2, 'data-h': name }); } function render() { if (!svg) return; while (svg.firstChild) svg.removeChild(svg.firstChild); if (!mode) return; if (mode === 'ruler') { var r = ruler, dx = r.bx - r.ax, dy = r.by - r.ay; var dpx = Math.hypot(dx, dy), deg = Math.abs(Math.atan2(dy, dx) * 180 / Math.PI); if (deg > 90) deg = 180 - deg; svg.appendChild(el('line', { x1: r.ax, y1: r.ay, x2: r.bx, y2: r.by, stroke: '#9B5DE5', 'stroke-width': 2.5 })); svg.appendChild(handle(r.ax, r.ay, 'a')); svg.appendChild(handle(r.bx, r.by, 'b')); var lab = Math.round(dpx) + ' px · ' + (dpx / PXM).toFixed(2) + ' м · ' + deg.toFixed(1) + '°'; svg.appendChild(lineLabel((r.ax + r.bx) / 2, (r.ay + r.by) / 2 - 18, lab)); } else if (mode === 'angle') { var a = angle; var a1 = Math.atan2(a.ay - a.vy, a.ax - a.vx), a2 = Math.atan2(a.by - a.vy, a.bx - a.vx); var deg2 = Math.abs((a1 - a2) * 180 / Math.PI); if (deg2 > 180) deg2 = 360 - deg2; svg.appendChild(el('line', { x1: a.vx, y1: a.vy, x2: a.ax, y2: a.ay, stroke: '#06D6E0', 'stroke-width': 2.5 })); svg.appendChild(el('line', { x1: a.vx, y1: a.vy, x2: a.bx, y2: a.by, stroke: '#06D6E0', 'stroke-width': 2.5 })); // дуга угла var rr = 36, s = el('path', { d: 'M ' + (a.vx + rr * Math.cos(a1)) + ' ' + (a.vy + rr * Math.sin(a1)) + ' A ' + rr + ' ' + rr + ' 0 ' + (deg2 > 180 ? 1 : 0) + ' ' + ((a2 - a1 + 2 * Math.PI) % (2 * Math.PI) < Math.PI ? 1 : 0) + ' ' + (a.vx + rr * Math.cos(a2)) + ' ' + (a.vy + rr * Math.sin(a2)), fill: 'none', stroke: '#06D6E0', 'stroke-width': 2, opacity: .6 }); svg.appendChild(s); svg.appendChild(handle(a.ax, a.ay, 'a')); svg.appendChild(handle(a.bx, a.by, 'b')); svg.appendChild(handle(a.vx, a.vy, 'v')); svg.appendChild(lineLabel(a.vx, a.vy + 26, deg2.toFixed(1) + '°')); } } global.LabMeasure = { toggle: function () { ensure(); if (bar.classList.contains('on')) { this.hide(); } else { bar.classList.add('on'); if (!mode) setTool('ruler'); } }, hide: function () { if (bar) bar.classList.remove('on'); if (svg) svg.classList.remove('on'); mode = null; paintBar(); render(); }, setTool: setTool, }; })(window);