
(function () {
  'use strict';

  var SVGNS = 'http://www.w3.org/2000/svg';
  var _uid = 0;

  function ready(fn) {
    if (document.readyState === 'loading') document.addEventListener('DOMContentLoaded', fn);
    else fn();
  }
  function num(v, d) { var n = parseFloat(v); return isNaN(n) ? d : n; }
  function escapeHtml(s) {
    return String(s).replace(/[&<>"']/g, function (c) {
      return { '&': '&amp;', '<': '&lt;', '>': '&gt;', '"': '&quot;', "'": '&#39;' }[c];
    });
  }

  function segCells(p, q) {
    var out = [];
    if (p[0] === q[0]) { for (var r = Math.min(p[1], q[1]); r <= Math.max(p[1], q[1]); r++) out.push(p[0] + ',' + r); }
    else { for (var c = Math.min(p[0], q[0]); c <= Math.max(p[0], q[0]); c++) out.push(c + ',' + p[1]); }
    return out;
  }
  
  function elbowClear(a, corner, b, occ) {
    var ak = a.col + ',' + a.row, bk = b.col + ',' + b.row;
    var cells = segCells([a.col, a.row], corner).concat(segCells(corner, [b.col, b.row]));
    for (var i = 0; i < cells.length; i++) {
      if (cells[i] === ak || cells[i] === bk) continue;
      if (occ[cells[i]]) return false;
    }
    return true;
  }

  
  function cornerPt(a, b, aC, bC, occ) {
    if (a.row === b.row || a.col === b.col) return null;
    if (elbowClear(a, [b.col, a.row], b, occ)) return [bC[0], aC[1]];
    if (elbowClear(a, [a.col, b.row], b, occ)) return [aC[0], bC[1]];
    if (!occ[b.col + ',' + a.row]) return [bC[0], aC[1]];
    if (!occ[a.col + ',' + b.row]) return [aC[0], bC[1]];
    return [bC[0], aC[1]];
  }

  
  function roundedPath(v, r) {
    if (v.length < 2) return '';
    var d = 'M' + v[0][0].toFixed(1) + ' ' + v[0][1].toFixed(1);
    for (var i = 1; i < v.length - 1; i++) {
      var p = v[i - 1], c = v[i], n = v[i + 1];
      var d1 = Math.hypot(c[0] - p[0], c[1] - p[1]);
      var d2 = Math.hypot(n[0] - c[0], n[1] - c[1]);
      if (d1 < 2 || d2 < 2) { d += ' L' + c[0].toFixed(1) + ' ' + c[1].toFixed(1); continue; }
      var r1 = Math.min(r, d1 / 2), r2 = Math.min(r, d2 / 2);
      var ax = c[0] - (c[0] - p[0]) / d1 * r1, ay = c[1] - (c[1] - p[1]) / d1 * r1;
      var bx = c[0] + (n[0] - c[0]) / d2 * r2, by = c[1] + (n[1] - c[1]) / d2 * r2;
      d += ' L' + ax.toFixed(1) + ' ' + ay.toFixed(1) + ' Q' + c[0].toFixed(1) + ' ' + c[1].toFixed(1) + ' ' + bx.toFixed(1) + ' ' + by.toFixed(1);
    }
    var L = v[v.length - 1];
    d += ' L' + L[0].toFixed(1) + ' ' + L[1].toFixed(1);
    return d;
  }

  function setup(nw) {
    nw.setAttribute('data-net-init', '1');

    var cards = [].slice.call(nw.querySelectorAll('.impulse-network-card'));
    if (!cards.length) return;

    var GAP      = num(nw.getAttribute('data-gap'), 16);
    var CELL     = parseInt(nw.getAttribute('data-cell'), 10) || 84;
    var ROT      = num(nw.getAttribute('data-rotation'), 5);   
    var TRAVEL   = num(nw.getAttribute('data-travel'), 300);   
    var HOLD     = num(nw.getAttribute('data-hold'), 950);     
    var idleMode = nw.getAttribute('data-idle') || 'cycle';
    var pro      = nw.getAttribute('data-pro') !== '0';

    nw.style.setProperty('--net-rot', ROT + 's');

    var editor = nw.getAttribute('data-editor') === '1';
    var elId   = nw.getAttribute('data-el-id') || '';
    function clamp(v, a, b) { return Math.max(a, Math.min(b, v)); }

    

    
    
    function parseMap(attr) { try { return JSON.parse(nw.getAttribute(attr) || '{}') || {}; } catch (e) { return {}; } }
    var devicePos = { tablet: parseMap('data-pos-tablet'), mobile: parseMap('data-pos-mobile') };
    function curDevice() {
      var rd = nw.getAttribute('data-render-device');
      if (rd) return rd;
      var w = window.innerWidth || 1200;
      return w < 768 ? 'mobile' : (w < 1025 ? 'tablet' : 'desktop');
    }
    var DEV = 'desktop', COLS = 4, ROWS = 2, autoMap = null;

    

    function updateDims() {
      DEV = curDevice();
      autoMap = null;

      if (DEV !== 'desktop' && cards.length) {
        var pos = devicePos[DEV] || {};
        if (cards.every(function (c) { return pos[c.getAttribute('data-id')]; })) {
          COLS = parseInt(nw.getAttribute('data-cols-' + DEV) || nw.getAttribute('data-cols'), 10) || 4;
          autoMap = {};
          var maxRow = 0;
          cards.forEach(function (c) {
            var id = c.getAttribute('data-id'), p = pos[id];
            var col = Math.max(0, Math.min(COLS - 1, parseInt(p[0], 10) || 0));
            var row = Math.max(0, parseInt(p[1], 10) || 0);
            autoMap[id] = [col, row]; if (row > maxRow) maxRow = row;
          });
          ROWS = Math.max(1, maxRow + 1);
          return;
        }
      }
      COLS = parseInt(nw.getAttribute('data-cols'), 10) || 4;
      ROWS = parseInt(nw.getAttribute('data-rows'), 10) || 2;
    }
    updateDims();

    
    function cardCell(c) {
      var id = c.getAttribute('data-id');
      if (DEV !== 'desktop' && autoMap && autoMap[id]) return autoMap[id];
      return [parseInt(c.getAttribute('data-col'), 10) || 0, parseInt(c.getAttribute('data-row'), 10) || 0];
    }

    function setCardCell(id, col, row) {
      meta[id] = { col: col, row: row };
      if (DEV === 'desktop') {
        byId[id].setAttribute('data-col', col);
        byId[id].setAttribute('data-row', row);
      } else {
        if (!devicePos[DEV]) devicePos[DEV] = {};
        devicePos[DEV][id] = [col, row];
        updateDims(); 
      }
    }

    var byId = {}, meta = {}, occ = {};
    cards.forEach(function (c) { byId[c.getAttribute('data-id')] = c; });

    
    function syncPositions() {
      for (var k in occ) { if (occ.hasOwnProperty(k)) delete occ[k]; }
      cards.forEach(function (c) {
        var rc = cardCell(c);
        var col = clamp(rc[0], 0, COLS - 1), row = clamp(rc[1], 0, ROWS - 1);
        meta[c.getAttribute('data-id')] = { col: col, row: row };
        occ[col + ',' + row] = 1;
      });
    }
    syncPositions();

    var svg = nw.querySelector('.impulse-network-lines');
    var gradId = 'impnetg' + (++_uid);

    

    
    
    var routes = [];
    function loadRoutes() {
      var raw = null;
      if (DEV !== 'desktop') {
        var attr = nw.getAttribute('data-routes-' + DEV);
        if (attr) { try { var p = JSON.parse(attr); if (Array.isArray(p) && p.length) raw = p; } catch (e) {} }
      }
      if (raw === null) { try { raw = JSON.parse(nw.getAttribute('data-routes') || '[]'); } catch (e) { raw = []; } }
      routes = (Array.isArray(raw) ? raw : []).map(function (r) {
        if (!Array.isArray(r)) r = (r && r.cards) || [];
        var lines = (r.length && Array.isArray(r[0])) ? r : [r];
        return lines.filter(function (L) {
          return Array.isArray(L) && L.length >= 2 && L.every(function (id) { return byId[id]; });
        });
      }).filter(function (lines) { return lines.length; });
    }
    loadRoutes();

    var cc = { ox: 0, oy: 0, cell: CELL, gap: GAP };
    function center(col, row) {
      return [cc.ox + col * (cc.cell + cc.gap) + cc.cell / 2, cc.oy + row * (cc.cell + cc.gap) + cc.cell / 2];
    }
    function ctr(id) { var m = meta[id]; return center(m.col, m.row); }

    var rp = [];

    
    
    function fitLabel(card) {
      var label = card.querySelector('.impulse-network-label');
      if (!label || !(label.textContent || '').trim()) return;
      var avail = Math.max(24, cc.cell - 14);
      label.style.width = avail + 'px';
      var size = Math.min(13, Math.max(9, Math.round(cc.cell * 0.12)));
      label.style.fontSize = size + 'px';
      var guard = 0;
      while (size > 7 && guard++ < 14 &&
             (label.scrollHeight > size * 1.25 * 2 + 1 || label.scrollWidth > avail + 1)) {
        label.style.fontSize = (--size) + 'px';
      }
    }

    function layout() {
      var W = nw.clientWidth || nw.offsetWidth || 680;
      var gridW0 = COLS * CELL + (COLS - 1) * GAP;
      var scale = gridW0 > (W - 16) ? (W - 16) / gridW0 : 1;

      

      

      try {
        var par = nw.parentElement;
        if (par && par.clientHeight) {
          var room = par.clientHeight;
          for (var _i = 0; _i < par.children.length; _i++) {
            if (par.children[_i] !== nw) room -= par.children[_i].offsetHeight;
          }
          room -= 16;
          if (room > 140) {
            var _cW = Math.max(34, Math.round(CELL * scale));
            var _gW = Math.round(GAP * scale);
            var _Hh = ROWS * _cW + (ROWS - 1) * _gW + Math.round(_cW * 0.5) * 2;
            if (_Hh > room) scale = scale * (room / _Hh);
          }
        }
      } catch (_e) {}
      cc.cell = Math.max(34, Math.round(CELL * scale));
      cc.gap = Math.round(GAP * scale);
      var gridW = COLS * cc.cell + (COLS - 1) * cc.gap;
      var gridH = ROWS * cc.cell + (ROWS - 1) * cc.gap;
      var padV = Math.round(cc.cell * 0.5);
      var H = gridH + padV * 2;
      nw.style.height = H + 'px';
      cc.ox = Math.max(0, (W - gridW) / 2);
      cc.oy = padV;
      cards.forEach(function (c) {
        c.style.width = cc.cell + 'px';
        c.style.height = cc.cell + 'px';
        var m = meta[c.getAttribute('data-id')];
        var p = center(m.col, m.row);
        c.style.left = p[0] + 'px';
        c.style.top = p[1] + 'px';
      });
      cards.forEach(fitLabel);
      cc.W = W; cc.H = H;
      if (svg) svg.setAttribute('viewBox', '0 0 ' + W + ' ' + H);
    }

    function buildLines() {
      if (!svg) return;
      syncPositions();
      while (svg.firstChild) svg.removeChild(svg.firstChild);
      var cs = getComputedStyle(nw);
      var stops = ['--net-c1', '--net-c2', '--net-c3', '--net-c4']
        .map(function (v) { return (cs.getPropertyValue(v) || '').trim(); })
        .filter(Boolean);
      if (stops.length < 2) stops = ['#22d3ee', '#8b5cf6'];

      
      
      var lc1 = cs.getPropertyValue('--net-line-c1').trim();
      var lc2 = cs.getPropertyValue('--net-line-c2').trim();
      if (lc1 || lc2) stops = [lc1 || stops[0], lc2 || stops[stops.length - 1]];
      var defs = document.createElementNS(SVGNS, 'defs');
      var grad = document.createElementNS(SVGNS, 'linearGradient');
      grad.setAttribute('id', gradId);

      
      grad.setAttribute('gradientUnits', 'userSpaceOnUse');
      grad.setAttribute('x1', '0'); grad.setAttribute('y1', '0');
      grad.setAttribute('x2', String(cc.W || nw.clientWidth || 680));
      grad.setAttribute('y2', String(cc.H || 480));
      stops.forEach(function (col, i) {
        var s = document.createElementNS(SVGNS, 'stop');
        s.setAttribute('offset', (i / (stops.length - 1)).toFixed(3));
        s.setAttribute('stop-color', col);
        grad.appendChild(s);
      });
      defs.appendChild(grad);
      svg.appendChild(defs);

      rp = [];

      
      function computeVerts(line) {
        var c = line.map(ctr), verts = [c[0]], cardVertIdx = [0];
        for (var i = 1; i < line.length; i++) {
          var cn = cornerPt(meta[line[i - 1]], meta[line[i]], c[i - 1], c[i], occ);
          if (cn) verts.push(cn);
          verts.push(c[i]);
          cardVertIdx.push(verts.length - 1);
        }
        return { boxes: line, verts: verts, cardVertIdx: cardVertIdx };
      }

      function axisOf(p, q) { return Math.abs(p[1] - q[1]) <= Math.abs(p[0] - q[0]) ? 'H' : 'V'; }
      function computeRuns(verts) {
        var runs = [], i = 0;
        while (i < verts.length - 1) {
          var axis = axisOf(verts[i], verts[i + 1]);
          var fixed = axis === 'H' ? verts[i][1] : verts[i][0];
          var idxs = [i], j = i;
          while (j < verts.length - 1 && axisOf(verts[j], verts[j + 1]) === axis &&
                 Math.abs((axis === 'H' ? verts[j][1] : verts[j][0]) - fixed) < 1) { idxs.push(++j); }
          var vary = idxs.map(function (k) { return axis === 'H' ? verts[k][0] : verts[k][1]; });
          runs.push({ axis: axis, fixed: fixed, lo: Math.min.apply(null, vary), hi: Math.max.apply(null, vary), idxs: idxs, offset: 0 });
          i = j;
        }
        return runs;
      }

      var lineDatas = [];
      routes.forEach(function (route, ri) {
        route.forEach(function (line, li) {
          var vd = computeVerts(line);
          vd.runs = computeRuns(vd.verts);
          vd.ri = ri; vd.li = li;
          lineDatas.push(vd);
        });
      });

      
      var LANE = 12, buckets = {};
      lineDatas.forEach(function (vd) {
        vd.runs.forEach(function (r) {
          var key = r.axis + ':' + Math.round(r.fixed);
          (buckets[key] = buckets[key] || []).push(r);
        });
      });
      Object.keys(buckets).forEach(function (key) {
        var grp = buckets[key];
        if (grp.length < 2) return;
        grp.sort(function (a, b) { return a.lo - b.lo; });
        var i = 0;
        while (i < grp.length) {
          var cluster = [grp[i]], hi = grp[i].hi, j = i + 1;
          while (j < grp.length && grp[j].lo < hi - 4) { cluster.push(grp[j]); hi = Math.max(hi, grp[j].hi); j++; }
          if (cluster.length > 1) {
            for (var k = 0; k < cluster.length; k++) cluster[k].offset = (k - (cluster.length - 1) / 2) * LANE;
          }
          i = j;
        }
      });

      
      
      lineDatas.forEach(function (vd) {
        vd.runs.forEach(function (r) {
          if (!r.offset) return;
          r.idxs.forEach(function (vi) { vd.verts[vi][r.axis === 'H' ? 1 : 0] += r.offset; });
        });
      });

      var _flowSeq = 0;
      function createPath(vd) {
        var verts = vd.verts;
        var dStr = roundedPath(verts, 11);
        var path = document.createElementNS(SVGNS, 'path');
        path.setAttribute('d', dStr);
        path.setAttribute('class', 'impulse-network-line');
        path.setAttribute('fill', 'none');
        path.setAttribute('stroke', 'url(#' + gradId + ')');
        svg.appendChild(path);

        
        
        var flow = document.createElementNS(SVGNS, 'path');
        flow.setAttribute('d', dStr);
        flow.setAttribute('class', 'impulse-network-flow');
        flow.setAttribute('fill', 'none');
        flow.style.animationDelay = (-(_flowSeq++ * 0.37)).toFixed(2) + 's';
        svg.appendChild(flow);
        var len = path.getTotalLength();
        path.style.strokeDasharray = len; path.style.strokeDashoffset = len;

        

        
        var cardDist;
        if (len > 0) {
          var N = Math.max(40, Math.min(400, Math.round(len / 6))), sx = [], sy = [], sl = [];
          for (var si = 0; si <= N; si++) { var sL = len * si / N, pt = path.getPointAtLength(sL); sx.push(pt.x); sy.push(pt.y); sl.push(sL); }
          cardDist = vd.cardVertIdx.map(function (vi) {
            var px = verts[vi][0], py = verts[vi][1], best = 0, bestD = Infinity;
            for (var s = 0; s < sl.length; s++) { var dx = sx[s] - px, dy = sy[s] - py, d = dx * dx + dy * dy; if (d < bestD) { bestD = d; best = sl[s]; } }
            return best;
          });
        } else {
          cardDist = vd.cardVertIdx.map(function () { return 0; });
        }
        return { el: path, flow: flow, boxes: vd.boxes, cardDist: cardDist, len: len };
      }
      var byRoute = {};
      lineDatas.forEach(function (vd) {
        (byRoute[vd.ri] = byRoute[vd.ri] || { lines: [] }).lines[vd.li] = createPath(vd);
      });
      routes.forEach(function (route, ri) { rp.push(byRoute[ri] || { lines: [] }); });
    }

    function setLit(id, on) { var t = byId[id]; if (!t) return; if (on) t.classList.add('is-lit'); else t.classList.remove('is-lit'); }
    function resetLines() {
      rp.forEach(function (r) { r.lines.forEach(function (L) { L.el.classList.remove('is-on'); L.el.style.strokeDashoffset = L.len; if (L.flow) L.flow.classList.remove('is-flowing'); }); });
    }
    function resetAll() {
      resetLines();
      cards.forEach(function (t) { t.classList.remove('is-lit'); });
    }

    
    function easeInOut(t) { t = t < 0 ? 0 : (t > 1 ? 1 : t); return t < 0.5 ? 2 * t * t : 1 - Math.pow(-2 * t + 2, 2) / 2; }

    var animId = 0, curRoute = 0, running = false, hovering = false, leaveTimer = null;

    

    
    function startPipeline() {

      var allLines = [];
      rp.forEach(function (r) { r.lines.forEach(function (L) { if (L) allLines.push(L); }); });
      if (!allLines.length) return;
      animId++; var my = animId;
      var STAGGER   = num(nw.getAttribute('data-stagger'), 760); 
      var MAXFLIGHT = num(nw.getAttribute('data-flight'), 3);    
      var active = [], nextIdx = 0, lastSpawn = -1e9, margin = cc.cell / 2;
      function frame(now) {
        if (my !== animId || !running) return;

        
        
        if (active.length < MAXFLIGHT && now - lastSpawn >= STAGGER) {
          var NL = null, tries = 0;
          while (tries < allLines.length) {
            var cand = allLines[nextIdx % allLines.length]; nextIdx++; tries++;
            var dup = false;
            for (var a = 0; a < active.length; a++) { if (active[a].L === cand) { dup = true; break; } }
            if (!dup) { NL = cand; break; }
          }
          if (NL) {
            lastSpawn = now;
            NL.el.classList.add('is-on');
            NL.el.style.strokeDashoffset = NL.len; 
            var nDin = Math.max(700, NL.len / TRAVEL * 1000);
            active.push({ L: NL, t0: now, Din: nDin, Dout: Math.max(200, nDin * 0.32), flowing: false });
          }
        }

        

        
        var lit = {};
        for (var i = active.length - 1; i >= 0; i--) {
          var s = active[i], L = s.L, e = now - s.t0;
          if (e < s.Din) {                                   
            var head = (e / s.Din) * L.len;
            L.el.style.strokeDashoffset = L.len - head;
            L.boxes.forEach(function (b, k) { if (head >= L.cardDist[k]) lit[b] = 1; });
          } else if (e < s.Din + HOLD) {                     
            L.el.style.strokeDashoffset = 0;
            if (L.flow && !s.flowing) { L.flow.classList.add('is-flowing'); s.flowing = true; }
            L.boxes.forEach(function (b) { lit[b] = 1; });
          } else if (e < s.Din + HOLD + s.Dout) {            
            if (L.flow && s.flowing) { L.flow.classList.remove('is-flowing'); s.flowing = false; }
            var erase = easeInOut((e - s.Din - HOLD) / s.Dout) * L.len;
            L.el.style.strokeDashoffset = -erase;
            L.boxes.forEach(function (b, k) { if (erase < L.cardDist[k] + margin) lit[b] = 1; });
          } else {                                           
            L.el.style.strokeDashoffset = L.len;
            L.el.classList.remove('is-on');
            if (L.flow) L.flow.classList.remove('is-flowing');
            active.splice(i, 1);
          }
        }

        

        if (!hovering && active.length) cards.forEach(function (c) { var id = c.getAttribute('data-id'); setLit(id, !!lit[id]); });
        requestAnimationFrame(frame);
      }
      requestAnimationFrame(frame);
    }

    function startCycle() {

      
      
      if (!pro || !rp.length || !screenActive() || hovering) return;
      running = true;
      if (idleMode === 'static') { resetAll(); return; }

      

      
      resetLines(); startPipeline();
    }
    function stopCycle() { running = false; animId++; resetAll(); }

    var qc = null, hasHover = !(window.matchMedia && window.matchMedia('(hover:hover)').matches) ? false : true;
    function quickCard() {
      if (qc) return qc;
      qc = document.createElement('div');
      qc.className = 'impulse-network-pop';
      nw.appendChild(qc);
      return qc;
    }
    function focusStatic(t) {
      if (!pro) return;
      animId++; running = false;
      var id = t.getAttribute('data-id'), lit = {};
      lit[id] = 1;

      

      
      
      var onSet = (typeof Set === 'function') ? new Set() : null;
      var onList = onSet ? null : [];
      rp.forEach(function (r) {
        r.lines.forEach(function (L) {
          if (L.boxes.indexOf(id) < 0) return;
          if (onSet) onSet.add(L); else onList.push(L);
          L.boxes.forEach(function (b) { lit[b] = 1; });
        });
      });
      var wantsLine = function (L) { return onSet ? onSet.has(L) : onList.indexOf(L) >= 0; };
      rp.forEach(function (r) {
        r.lines.forEach(function (L) {
          if (wantsLine(L)) {
            if (!L.el.classList.contains('is-on')) L.el.classList.add('is-on');
            L.el.style.strokeDashoffset = 0;
          } else {
            if (L.el.classList.contains('is-on')) { L.el.classList.remove('is-on'); L.el.style.strokeDashoffset = L.len; }
            if (L.flow) L.flow.classList.remove('is-flowing');
          }
        });
      });
      cards.forEach(function (c) {
        var want = !!lit[c.getAttribute('data-id')];
        if (want) { if (!c.classList.contains('is-lit')) c.classList.add('is-lit'); }
        else if (c.classList.contains('is-lit')) c.classList.remove('is-lit');
      });
      showQuick(t);
    }
    function showQuick(t) {

      var desc = t.getAttribute('data-desc') || '';
      if (!desc) return;
      var card = quickCard();
      card.innerHTML = '<span>' + escapeHtml(desc) + '</span>';
      card.classList.add('is-shown');
      var W = nw.clientWidth, H = nw.clientHeight, cw = card.offsetWidth, ch = card.offsetHeight;
      var tl = parseFloat(t.style.left), tt = parseFloat(t.style.top);
      var left = Math.max(8, Math.min(W - cw - 8, tl - cw / 2));
      var top = (tt > H * 0.5) ? (tt - cc.cell / 2 - ch - 12) : (tt + cc.cell / 2 + 12);
      top = Math.max(8, Math.min(H - ch - 8, top));
      card.style.left = left + 'px'; card.style.top = top + 'px';
    }
    function hideQuick() { if (qc) qc.classList.remove('is-shown'); }

    function openModal(t) {
      var label = (t.querySelector('.impulse-network-label') || {}).textContent || '';
      var desc = t.getAttribute('data-desc') || '';
      var link = t.getAttribute('data-link') || '';
      var ov = document.createElement('div');
      ov.className = 'impulse-network-modal';

      var mcs = getComputedStyle(nw);
      ['--net-card-bg', '--net-c1'].forEach(function (v) {
        var val = (mcs.getPropertyValue(v) || '').trim();
        if (val) ov.style.setProperty(v, val);
      });
      ov.innerHTML = '<div class="impulse-network-modal-card" role="dialog" aria-modal="true">'
        + '<button class="impulse-network-modal-x" aria-label="Close">&times;</button>'
        + '<h3>' + escapeHtml(label) + '</h3>'
        + (desc ? '<p>' + escapeHtml(desc) + '</p>' : '')
        + (link ? '<a class="impulse-network-modal-cta" href="' + escapeHtml(link) + '">Learn more</a>' : '')
        + '</div>';
      document.body.appendChild(ov);
      requestAnimationFrame(function () { ov.classList.add('is-shown'); });
      function close() {
        ov.classList.remove('is-shown');
        setTimeout(function () { if (ov.parentNode) ov.parentNode.removeChild(ov); }, 250);
        document.removeEventListener('keydown', onKey);
      }
      function onKey(e) { if (e.key === 'Escape' || e.keyCode === 27) close(); }
      ov.addEventListener('click', function (e) {
        if (e.target === ov || (e.target.classList && e.target.classList.contains('impulse-network-modal-x'))) { e.preventDefault(); close(); }
      });
      document.addEventListener('keydown', onKey);
    }

    
    
    cards.forEach(function (t) {
      t.addEventListener('mouseenter', function () {
        if (!hasHover || t.classList.contains('is-dragging')) return;
        if (leaveTimer) { clearTimeout(leaveTimer); leaveTimer = null; }
        hovering = true; focusStatic(t);
      });
      t.addEventListener('mouseleave', function () {
        if (!hasHover) return;

        
        if (leaveTimer) clearTimeout(leaveTimer);
        leaveTimer = setTimeout(function () {
          leaveTimer = null; hovering = false; hideQuick();
          if (idleMode === 'cycle') startCycle(); else resetAll();
        }, 300);
      });
      t.addEventListener('click', function (e) {
        if (t._netDragged) { e.preventDefault(); return; }
        var link = t.getAttribute('data-link') || '', desc = t.getAttribute('data-desc') || '';
        if (desc) { e.preventDefault(); openModal(t); }
        else if (link) { window.location.href = link; }
      });
    });

    

    
    
    function cellAt(px, py) {
      return [
        clamp(Math.round((px - cc.ox - cc.cell / 2) / (cc.cell + cc.gap)), 0, COLS - 1),
        clamp(Math.round((py - cc.oy - cc.cell / 2) / (cc.cell + cc.gap)), 0, ROWS - 1)
      ];
    }
    function cardInCell(col, row, except) {
      for (var i = 0; i < cards.length; i++) {
        if (cards[i] === except) continue;
        var m = meta[cards[i].getAttribute('data-id')];
        if (m && m.col === col && m.row === row) return cards[i];
      }
      return null;
    }

    function nearestFreeCell(col, row, except) {
      var best = null, bestD = Infinity;
      for (var r = 0; r < ROWS; r++) for (var c = 0; c < COLS; c++) {
        if (cardInCell(c, r, except)) continue;
        var d = (c - col) * (c - col) + (r - row) * (r - row);
        if (d < bestD) { bestD = d; best = [c, r]; }
      }
      return best;
    }

    
    function resolveDropCell(px, py, card) {
      var cell = cellAt(px, py), col = cell[0], row = cell[1];
      if (cardInCell(col, row, card)) {
        var free = nearestFreeCell(col, row, card);
        if (free) return free;
        var m = meta[card.getAttribute('data-id')];
        return [m.col, m.row];
      }
      return [col, row];
    }
    var dropHint = null;
    function showDropHint(col, row) {
      if (!dropHint) { dropHint = document.createElement('div'); dropHint.className = 'impulse-network-drophint'; nw.appendChild(dropHint); }
      var p = center(col, row);
      dropHint.style.width = cc.cell + 'px'; dropHint.style.height = cc.cell + 'px';
      dropHint.style.left = p[0] + 'px'; dropHint.style.top = p[1] + 'px';
      dropHint.style.display = 'block';
    }
    function hideDropHint() { if (dropHint) dropHint.style.display = 'none'; }
    function postLayout() {
      if (!window.parent) return;
      var msg = { type: 'impulse-net-layout', id: elId, device: DEV };
      if (DEV === 'desktop') {
        msg.positions = cards.map(function (c) {
          var m = meta[c.getAttribute('data-id')];
          return { id: c.getAttribute('data-id'), col: m.col, row: m.row };
        });
      } else {
        msg.posMap = devicePos[DEV] || {};
      }
      window.parent.postMessage(msg, '*');
    }
    function enableDrag() {
      nw.classList.add('impulse-network-editing');
      cards.forEach(function (card) {
        var sx = 0, sy = 0, ol = 0, ot = 0, pid = null, down = false, moved = false;
        card.addEventListener('pointerdown', function (ev) {
          if (ev.button != null && ev.button !== 0) return;
          down = true; moved = false; card._netDragged = false;
          sx = ev.clientX; sy = ev.clientY; pid = ev.pointerId;
          ol = parseFloat(card.style.left) || 0; ot = parseFloat(card.style.top) || 0;
        });
        card.addEventListener('pointermove', function (e2) {
          if (!down) return;
          var dx = e2.clientX - sx, dy = e2.clientY - sy;
          if (!moved && (dx * dx + dy * dy) < 16) return;   
          if (!moved) {
            moved = true;
            stopCycle();
            hideQuick();
            card.classList.add('is-dragging');
            try { card.setPointerCapture(pid); } catch (e) {}
          }
          e2.preventDefault();
          var px = ol + dx, py = ot + dy;
          card.style.left = px + 'px'; card.style.top = py + 'px';
          var drop = resolveDropCell(px, py, card); showDropHint(drop[0], drop[1]);
        });
        function finish() {
          if (!down) return;
          down = false;
          if (!moved) return;                         
          try { card.releasePointerCapture(pid); } catch (e) {}
          card.classList.remove('is-dragging');
          hideDropHint();
          var drop = resolveDropCell(parseFloat(card.style.left) || 0, parseFloat(card.style.top) || 0, card);
          var col = drop[0], row = drop[1];
          var id = card.getAttribute('data-id'), m = meta[id], fc = m.col, fr = m.row;
          if (col !== fc || row !== fr) {
            setCardCell(id, col, row);
            postLayout();
          }
          layout(); buildLines();

          
          if (hovering) focusStatic(card); else if (idleMode === 'cycle') startCycle();
          card._netDragged = true;                    
          setTimeout(function () { card._netDragged = false; }, 60);
        }
        card.addEventListener('pointerup', finish);
        card.addEventListener('pointercancel', finish);
      });
    }

    

    var wasActive = false, lineStartTimer = null;

    var REVEAL_HOLD = num(nw.getAttribute('data-reveal-hold'), 1800);

    function screenHost() {
      return (nw.closest && (nw.closest('.impulse-screen') || nw.closest('.impulse-el[data-type="section"]') || nw.closest('section'))) || null;
    }
    function screenActive() {

      
      
      var body = document.body;
      if (!body) return true;

      

      

      

      

      

      
      
      if (body.classList.contains('impulse-scroll-classic')) return true;
      if (body.getAttribute('data-screen-mode') !== '1') return true;
      var h = screenHost(); if (!h) return true;
      return h.classList.contains('impulse-screen-active');
    }
    function evaluate() {
      if (hovering) return; 
      var active = screenActive(), justEntered = active && !wasActive;
      wasActive = active;
      if (!active) { if (running) stopCycle(); clearTimeout(lineStartTimer); lineStartTimer = null; return; }
      if (justEntered) {

        stopCycle();
        clearTimeout(lineStartTimer);
        lineStartTimer = setTimeout(function () { lineStartTimer = null; if (screenActive() && !hovering) startCycle(); }, REVEAL_HOLD);
      } else if (!running && !lineStartTimer) {
        startCycle();
      }
    }

    layout();
    buildLines();

    if (!pro) return; 

    if (editor) enableDrag(); 

    evaluate();
    var host = screenHost();
    if (host && typeof MutationObserver !== 'undefined') {
      new MutationObserver(evaluate).observe(host, { attributes: true, attributeFilter: ['class'] });
    } else if (!running) {
      startCycle();
    }

    var rt;
    window.addEventListener('resize', function () {
      clearTimeout(rt);
      rt = setTimeout(function () {
        var prev = DEV;
        updateDims();                        
        if (DEV !== prev) { syncPositions(); loadRoutes(); } 
        stopCycle(); layout(); buildLines(); evaluate();
      }, 180);
    });
  }

  function initAll(root) {
    var nodes = (root || document).querySelectorAll('.impulse-network:not([data-net-init])');
    [].forEach.call(nodes, function (nw) { try { setup(nw); } catch (e) {} });
  }

  ready(function () { initAll(document); });
  window.ImpulseNetwork = { init: initAll };
})();
