function mount(ctx) {
  var canvas = ctx.el;
  if (!canvas || !canvas.getContext) return;
  var draw = canvas.getContext('2d');
  if (!draw) return;
  var sec = canvas.parentElement || canvas;
  var content = canvas.nextElementSibling || null;

  var width = 0, height = 0, dpr = 1;
  var zones = [];
  var reduced = false;
  try {
    reduced = !!(window.matchMedia && window.matchMedia('(prefers-reduced-motion: reduce)').matches);
  } catch (e) { reduced = false; }

  // every visible block of the hero (headline, copy, doors, the return pop-up)
  // becomes its own obstacle the rows must flow around
  function measureZones() {
    zones = [];
    if (!content || !content.children) return;
    var box = canvas.getBoundingClientRect();
    for (var i = 0; i < content.children.length; i++) {
      var el = content.children[i];
      if (el.hasAttribute && el.hasAttribute('hidden')) continue;
      var c = el.getBoundingClientRect();
      if (!c.width || !c.height) continue;
      zones.push({
        cx: (c.left + c.right) / 2 - box.left,
        cy: (c.top + c.bottom) / 2 - box.top,
        rx: c.width / 2 + 46,
        ry: c.height / 2 + 28
      });
    }
  }

  function resize() {
    var srect = sec.getBoundingClientRect();
    if (!srect.width || !srect.height) return; // screen hidden — keep last good sizes
    var docW = (document.documentElement && document.documentElement.clientWidth) || srect.width;
    canvas.style.left = (-srect.left) + 'px';
    canvas.style.width = docW + 'px';
    var box = canvas.getBoundingClientRect();
    dpr = Math.min(window.devicePixelRatio || 1, 2);
    width = Math.max(1, box.width);
    height = Math.max(1, box.height);
    canvas.width = Math.round(width * dpr);
    canvas.height = Math.round(height * dpr);
    draw.setTransform(dpr, 0, 0, dpr, 0, 0);
    measureZones();
  }
  var observer = new ResizeObserver(resize);
  observer.observe(sec);
  ctx.on(window, 'resize', resize);
  resize();

  var rows = [];
  var CAP = 300;
  function spawn(x, y, fresh) {
    return {
      x: (x == null) ? Math.random() * (width || 900) : x,
      y: (y == null) ? Math.random() * (height || 500) : y,
      len: 10 + Math.random() * 18,
      speed: 15 + Math.random() * 32,
      drift: (y == null) ? 0 : (Math.random() - 0.5) * 26,
      base: 0.22 + Math.random() * 0.32,
      blue: fresh ? 1 : (Math.random() < 0.16 ? 0.75 : 0),
      born: fresh ? 1 : 0
    };
  }
  for (var i = 0; i < 150; i++) rows.push(spawn(null, null, false));

  var px = -1e4, py = -1e4, inside = false;
  var pvx = 0, pvy = 0, lastMove = 0;
  var pulses = [];

  var doc = canvas.ownerDocument || document;

  function toLocal(ev) {
    var box = canvas.getBoundingClientRect();
    return { x: ev.clientX - box.left, y: ev.clientY - box.top };
  }
  function homeHidden() {
    return !!(sec.hasAttribute && sec.hasAttribute('hidden'));
  }
  // the canvas is full-bleed, so the whole page is its input surface —
  // listen on the document, not the centered section column
  ctx.on(doc, 'pointermove', function (ev) {
    if (homeHidden()) return;
    var p = toLocal(ev);
    var t = (typeof performance !== 'undefined' && performance.now) ? performance.now() : Date.now();
    if (lastMove && inside) {
      var dtp = Math.max(8, t - lastMove) / 1000;
      // pointer velocity drives a wake the rows ride
      pvx = Math.max(-1600, Math.min(1600, (p.x - px) / dtp));
      pvy = Math.max(-1600, Math.min(1600, (p.y - py) / dtp));
    }
    lastMove = t;
    px = p.x; py = p.y; inside = true;
  });
  ctx.on(doc, 'pointerleave', function () { inside = false; px = -1e4; py = -1e4; pvx = 0; pvy = 0; });
  ctx.on(doc, 'pointerdown', function (ev) {
    if (homeHidden()) return;
    // clicks on real controls and dialogs stay theirs — open ground writes rows
    var t = ev.target;
    while (t && t !== doc.documentElement) {
      var tag = (t.tagName || '').toLowerCase();
      if (tag === 'button' || tag === 'a' || tag === 'input' ||
          tag === 'textarea' || tag === 'select' || tag === 'label') return;
      if (t.getAttribute && t.getAttribute('role') === 'dialog') return;
      t = t.parentElement;
    }
    var p = toLocal(ev);
    var cx = Math.max(0, Math.min(width, p.x));
    var cy = Math.max(0, Math.min(height, p.y));
    pulses.push({ x: cx, y: cy, t: 0 });
    for (var b = 0; b < 14; b++) {
      if (rows.length >= CAP) rows.shift();
      rows.push(spawn(cx + (Math.random() - 0.5) * 36, cy + (Math.random() - 0.5) * 36, true));
    }
  });

  var last = 0;
  var zoneClock = 0;
  function frame(time) {
    // one bad frame must never cancel the loop forever — swallow and go on
    try {
      var dt = last ? Math.min(0.05, (time - last) / 1000) : 0.016;
      last = time;
      draw.clearRect(0, 0, width, height);
      var speedScale = reduced ? 0 : 1;

      // the return pop-up appears without a resize — re-measure twice a second
      zoneClock += dt;
      if (zoneClock > 0.5) { zoneClock = 0; measureZones(); }

      // the wake fades once the pointer rests
      pvx *= (1 - 3 * dt); pvy *= (1 - 3 * dt);

      for (var i = 0; i < rows.length; i++) {
        var r = rows[i];
        var dx = px - r.x, dy = py - r.y;
        var dist = Math.sqrt(dx * dx + dy * dy);
        var near = inside ? Math.max(0, 1 - dist / 260) : 0;

        r.x += (r.speed * (1 + near * 3) + near * near * pvx * 0.55) * dt * speedScale;
        r.y += (r.drift * dt + near * near * (dy * 3.2 + pvy * 0.55) * dt) * speedScale;
        r.drift += (Math.random() - 0.5) * 16 * dt;
        r.drift *= (1 - 0.6 * dt);
        if (r.born > 0) r.born = Math.max(0, r.born - dt * 1.4);

        // every row bends around every visible block — none pass through
        var m = 0.7, edgeGlow = 0;
        for (var z = 0; z < zones.length; z++) {
          var zo = zones[z];
          var ex = (r.x - zo.cx) / zo.rx;
          var ey = (r.y - zo.cy) / zo.ry;
          var e = ex * ex + ey * ey;
          if (e < 2.2) {
            var dir = ey >= 0 ? 1 : -1;
            r.y += dir * Math.max(0, 1.5 - e) * 170 * dt * speedScale;
            if (e < 1) {
              r.y += dir * (1 - e) * 300 * dt * speedScale;
              m = Math.min(m, 0.12 + 0.58 * e);
            } else if (e < 1.4) {
              edgeGlow = Math.max(edgeGlow, (1.4 - e) / 0.4);
            }
          }
        }

        if (r.x - r.len > width + 4) { r.x = -r.len - 4; r.y = Math.random() * height; r.born = 0; }
        if (r.y < -6) r.y = height + 4;
        if (r.y > height + 6) r.y = -4;

        var glow = Math.max(near, r.born);
        var a = Math.min(0.92, r.base * m + glow * 0.6 + edgeGlow * 0.22);
        draw.globalAlpha = a;
        draw.fillStyle = (r.blue || glow > 0.28 || edgeGlow > 0.55) ? '#0A84FF' : '#64748B';
        var h = 3 + glow * 2;
        if (draw.roundRect) {
          draw.beginPath();
          draw.roundRect(r.x, r.y, r.len + glow * 10, h, 2);
          draw.fill();
        } else {
          draw.fillRect(r.x, r.y, r.len + glow * 10, h);
        }
      }

      for (var p2 = pulses.length - 1; p2 >= 0; p2--) {
        var pu = pulses[p2];
        pu.t += dt;
        if (pu.t > 0.9) { pulses.splice(p2, 1); continue; }
        var k = pu.t / 0.9;
        draw.globalAlpha = 0.35 * (1 - k);
        draw.strokeStyle = '#0A84FF';
        draw.lineWidth = 1.5;
        draw.beginPath();
        draw.arc(pu.x, pu.y, 10 + k * 100, 0, Math.PI * 2);
        draw.stroke();
      }
      draw.globalAlpha = 1;
    } catch (e) { /* skip this frame, keep the loop alive */ }
  }
  ctx.raf(frame);

  ctx.cleanup(function () { observer.disconnect(); });
}
