waygraph 0.12.38 → 0.12.40

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.js CHANGED
@@ -808,59 +808,11 @@ async function installOverlay(page, title) {
808
808
  const focus = !!opts.focus;
809
809
  const color = opts.color || "";
810
810
  const zoom = opts.zoom != null && opts.zoom > 1 ? opts.zoom : 1;
811
- const followMouse = opts.followMouse !== false && zoom > 1;
812
811
  const camMs = opts.camMs != null ? opts.camMs : 700;
813
- let settled = zoom <= 1;
814
- const settleAt = Date.now() + camMs;
815
812
  const style = { size, weight, focus, color };
816
- // Screen Studio / Screenify: dead zone follow (see followTick).
817
- const deadFrac = 0.5;
818
- let lastPanAt = 0;
819
- const readCursorViewport = () => {
820
- const cursor = document.getElementById("wg-cursor");
821
- if (cursor && cursor.style.opacity !== "0") {
822
- const r = cursor.getBoundingClientRect();
823
- if (r.width || r.height || r.left || r.top) {
824
- return { x: r.left, y: r.top };
825
- }
826
- }
827
- if (window.__wgCursorPos) return window.__wgCursorPos;
828
- return null;
829
- };
830
- const followTick = () => {
831
- if (!followMouse || !settled || window.__wgCameraLock) return;
832
- if (!window.__wgSetCamera || !window.__wgViewportToDoc) return;
833
- const cam = window.__wgCamera;
834
- if (!cam || !(cam.scale > 1.001)) return;
835
- // Skip while the demo cursor is mid CSS travel - reading a
836
- // mid-lerp rect makes the camera chase noise and teleport.
837
- if (window.__wgCursorTravelUntil && Date.now() < window.__wgCursorTravelUntil) {
838
- return;
839
- }
840
- const pos = readCursorViewport();
841
- if (!pos) return;
842
- const vw = window.innerWidth;
843
- const vh = window.innerHeight;
844
- const dx = pos.x - vw / 2;
845
- const dy = pos.y - vh / 2;
846
- const deadX = (vw * deadFrac) / 2;
847
- const deadY = (vh * deadFrac) / 2;
848
- let ox = 0;
849
- let oy = 0;
850
- if (dx > deadX) ox = dx - deadX;
851
- else if (dx < -deadX) ox = dx + deadX;
852
- if (dy > deadY) oy = dy - deadY;
853
- else if (dy < -deadY) oy = dy + deadY;
854
- if (ox === 0 && oy === 0) return;
855
- const now = Date.now();
856
- if (now - lastPanAt < 120) return;
857
- lastPanAt = now;
858
- // Partial gain - never yank the full overflow in one pan.
859
- const gain = 0.35;
860
- const nx = cam.cx + (ox / cam.scale) * gain;
861
- const ny = cam.cy + (oy / cam.scale) * gain;
862
- window.__wgSetCamera(nx, ny, cam.scale, 280);
863
- };
813
+ // Auto-center only. Never chase the host mouse (that fights the
814
+ // camera). Playwright's real mouse is driven separately by the CLI.
815
+ window.__wgIgnoreHostMouse = true;
864
816
  const tick = () => {
865
817
  const el = document.querySelector(sel);
866
818
  if (el && window.__wgPositionRing) {
@@ -872,8 +824,6 @@ async function installOverlay(page, title) {
872
824
  style,
873
825
  );
874
826
  }
875
- if (!settled && Date.now() >= settleAt) settled = true;
876
- followTick();
877
827
  window.__wgRingRaf = requestAnimationFrame(tick);
878
828
  };
879
829
  if (zoom > 1 && window.__wgSetCamera) {
@@ -882,37 +832,25 @@ async function installOverlay(page, title) {
882
832
  const already =
883
833
  window.__wgCamera && window.__wgCamera.scale > 1.001;
884
834
  const dur = already ? Math.min(camMs, 420) : camMs;
885
- // Focus must be document-space, not raw getBoundingClientRect
886
- // (viewport) - otherwise already-zoomed pans fight the cursor.
887
835
  const pt = window.__wgElDocPoint
888
836
  ? window.__wgElDocPoint(el0)
889
837
  : {
890
838
  x: el0.getBoundingClientRect().left + el0.getBoundingClientRect().width / 2,
891
839
  y: el0.getBoundingClientRect().top + el0.getBoundingClientRect().height / 2,
892
840
  };
841
+ // Lock for the whole ring - host mouse cannot steer.
893
842
  window.__wgCameraLock = true;
894
843
  window.__wgSetCamera(pt.x, pt.y, zoom, dur);
895
- setTimeout(() => {
896
- window.__wgCameraLock = false;
897
- }, dur + 40);
898
844
  }
899
845
  }
900
846
  window.__wgRingRaf = requestAnimationFrame(tick);
901
- // Real mouse only seeds cursor pos; dead-zone follow runs in RAF.
902
- const onMove = (ev) => {
903
- window.__wgCursorPos = { x: ev.clientX, y: ev.clientY };
904
- };
905
- window.addEventListener("mousemove", onMove, { passive: true });
906
- window.__wgRingTrack = onMove;
847
+ // Do NOT listen to host mousemove - it was steering the camera.
848
+ window.__wgRingTrack = null;
907
849
  window.__wgStopHighlightTrack = () => {
908
850
  if (window.__wgRingRaf) {
909
851
  cancelAnimationFrame(window.__wgRingRaf);
910
852
  window.__wgRingRaf = null;
911
853
  }
912
- if (window.__wgRingTrack) {
913
- window.removeEventListener("mousemove", window.__wgRingTrack);
914
- window.__wgRingTrack = null;
915
- }
916
854
  window.__wgStopHighlightTrack = null;
917
855
  };
918
856
  };
@@ -1418,6 +1356,25 @@ async function renderBeforeStep(page, info) {
1418
1356
 
1419
1357
  async function ensureSelectorInView(page, selector) {
1420
1358
  try {
1359
+ // Body-scale camera + scrollIntoView = teleport / "outside viewport" loop.
1360
+ // When zoomed, pan the camera to the target instead of scrolling the document.
1361
+ const zoomed = await page
1362
+ .evaluate(() => !!(window.__wgCamera && window.__wgCamera.scale > 1.001))
1363
+ .catch(() => false);
1364
+ if (zoomed) {
1365
+ await page
1366
+ .evaluate((sel) => {
1367
+ const el = document.querySelector(sel);
1368
+ if (!el || !window.__wgSetCamera || !window.__wgElDocPoint) return;
1369
+ const cam = window.__wgCamera;
1370
+ const pt = window.__wgElDocPoint(el);
1371
+ window.__wgCameraLock = true;
1372
+ window.__wgSetCamera(pt.x, pt.y, cam.scale > 1 ? cam.scale : 1.4, 320);
1373
+ }, selector)
1374
+ .catch(() => {});
1375
+ await new Promise((res) => setTimeout(res, 340));
1376
+ return;
1377
+ }
1421
1378
  const loc = page.locator(selector).first();
1422
1379
  await loc.scrollIntoViewIfNeeded().catch(() => {});
1423
1380
  await page
@@ -1630,18 +1587,10 @@ async function cycleHighlightRings(page, highlights, gatesFast, opts) {
1630
1587
  }
1631
1588
  const z = h.zoom != null && Number(h.zoom) > 1 ? Number(h.zoom) : 1;
1632
1589
  const camMs = 700;
1633
- const followMouse =
1634
- h.followMouse !== undefined
1635
- ? !!h.followMouse
1636
- : h.follow !== undefined
1637
- ? !!h.follow
1638
- : z > 1;
1639
- // Track/zoom first, then park the demo cursor on the LIVE rect.
1640
- // Placing the cursor on a pre-zoom boundingBox leaves it stranded after
1641
- // body scale (html cursor is outside the transform).
1590
+ // Camera auto-centers on the ring target. Host mouse never steers.
1642
1591
  await page
1643
1592
  .evaluate(
1644
- ({ sel, label, tone, size, weight, focus, color, zoom, followMouse, camMs }) => {
1593
+ ({ sel, label, tone, size, weight, focus, color, zoom, camMs }) => {
1645
1594
  if (window.__wgStartHighlightTrack) {
1646
1595
  window.__wgStartHighlightTrack({
1647
1596
  sel,
@@ -1652,7 +1601,6 @@ async function cycleHighlightRings(page, highlights, gatesFast, opts) {
1652
1601
  focus,
1653
1602
  color,
1654
1603
  zoom,
1655
- followMouse,
1656
1604
  camMs,
1657
1605
  });
1658
1606
  }
@@ -1666,7 +1614,6 @@ async function cycleHighlightRings(page, highlights, gatesFast, opts) {
1666
1614
  focus: !!h.focus,
1667
1615
  color: h.color || h.labelColor || h.textColor || "",
1668
1616
  zoom: z,
1669
- followMouse,
1670
1617
  camMs,
1671
1618
  },
1672
1619
  )
@@ -1677,19 +1624,18 @@ async function cycleHighlightRings(page, highlights, gatesFast, opts) {
1677
1624
  const waitMs = alreadyZoomed ? Math.min(camMs, 420) : camMs;
1678
1625
  await new Promise((res) => setTimeout(res, waitMs));
1679
1626
  if (h.cursor !== false) {
1680
- await page
1681
- .evaluate(({ sel, ms }) => {
1627
+ // Park Playwright real mouse + demo cursor on the LIVE centered target.
1628
+ const live = await page
1629
+ .evaluate((sel) => {
1682
1630
  const el = document.querySelector(sel);
1683
- if (!el || !window.__wgMoveCursorTo) return;
1684
- window.__wgCameraLock = true;
1631
+ if (!el) return null;
1685
1632
  const r = el.getBoundingClientRect();
1686
- window.__wgMoveCursorTo(r.left + r.width / 2, r.top + r.height / 2, ms, false);
1687
- setTimeout(() => {
1688
- window.__wgCameraLock = false;
1689
- }, ms + 40);
1690
- }, { sel: h.selector, ms: Math.min(280, camMs) })
1691
- .catch(() => {});
1692
- await new Promise((res) => setTimeout(res, Math.min(280, camMs)));
1633
+ return { x: r.left, y: r.top, width: r.width, height: r.height };
1634
+ }, h.selector)
1635
+ .catch(() => null);
1636
+ if (live) {
1637
+ await moveCursorTo(page, live, Math.min(280, camMs), { keepLock: true });
1638
+ }
1693
1639
  }
1694
1640
  // Appear settle
1695
1641
  await new Promise((res) => setTimeout(res, fadeMs));
@@ -2462,19 +2408,27 @@ async function moveCursorTo(page, box, ms, opts) {
2462
2408
  const y = box.y + box.height / 2;
2463
2409
  const travel = Math.max(0, Number(ms) || 0);
2464
2410
  const keepLock = !!(opts && opts.keepLock);
2465
- // Freeze camera follow while the demo cursor travels - otherwise dead-zone
2466
- // pans fight the click target (esp. right-edge cart chrome).
2411
+ // Camera stays locked; demo cursor + Playwright real mouse move together.
2412
+ // Host OS mouse is ignored (no mousemove camera steer).
2467
2413
  await page
2468
2414
  .evaluate(({ x, y, ms }) => {
2469
2415
  window.__wgCameraLock = true;
2416
+ window.__wgIgnoreHostMouse = true;
2470
2417
  if (window.__wgMoveCursorTo) window.__wgMoveCursorTo(x, y, ms);
2471
2418
  }, { x, y, ms: travel })
2472
2419
  .catch(() => {});
2473
- // CSS transition is async - wait the full travel so the cursor is ON the
2474
- // target before ring/pulse/real click (NavBlock click nav was skipping
2475
- // this and looked like a teleport).
2420
+ try {
2421
+ if (travel > 0) {
2422
+ const steps = Math.max(1, Math.min(30, Math.round(travel / 20)));
2423
+ await page.mouse.move(x, y, { steps });
2424
+ } else {
2425
+ await page.mouse.move(x, y);
2426
+ }
2427
+ } catch {
2428
+ /* headed / detached - demo cursor still moved */
2429
+ }
2476
2430
  if (travel > 0) {
2477
- await new Promise((res) => setTimeout(res, travel));
2431
+ await new Promise((res) => setTimeout(res, Math.min(80, travel)));
2478
2432
  }
2479
2433
  if (!keepLock) {
2480
2434
  await page
@@ -2486,6 +2440,16 @@ async function moveCursorTo(page, box, ms, opts) {
2486
2440
  return { x, y };
2487
2441
  }
2488
2442
 
2443
+ /** Live viewport center of a locator (after camera transform) - no scroll. */
2444
+ async function liveCenterOf(locator) {
2445
+ return locator
2446
+ .evaluate((el) => {
2447
+ const r = el.getBoundingClientRect();
2448
+ return { x: r.left + r.width / 2, y: r.top + r.height / 2, w: r.width, h: r.height };
2449
+ })
2450
+ .catch(() => null);
2451
+ }
2452
+
2489
2453
  async function clickPulseAt(page, x, y, tone) {
2490
2454
  await page
2491
2455
  .evaluate(({ x, y, tone }) => {
@@ -2613,29 +2577,44 @@ function instrumentInteractionHighlighting(page, mem, slowMo, pacing, stubBefore
2613
2577
  let keptCameraLock = false;
2614
2578
  try {
2615
2579
  await installOverlay(page);
2616
- // Wait until the target is visible BEFORE measuring - otherwise
2617
- // defineNavBlock({ click }) (and any slow-to-appear control) skips
2618
- // the cursor entirely: boundingBox was null, then originalClick
2619
- // waited and clicked with no demo animation.
2620
2580
  await this.waitFor({
2621
2581
  state: "visible",
2622
2582
  timeout: (options && options.timeout) || 30000,
2623
2583
  }).catch(() => {});
2624
- // Body-scale camera + Playwright scrollIntoView = teleport storm.
2625
- // Lock the camera for the whole click when zoomed; force-click so
2626
- // Playwright does not try to scroll the transformed page.
2627
2584
  const zoomed = await page
2628
2585
  .evaluate(() => !!(window.__wgCamera && window.__wgCamera.scale > 1.001))
2629
2586
  .catch(() => false);
2630
2587
  if (zoomed) {
2588
+ // Re-auto-center on this target, then lock - no host mouse, no scroll.
2631
2589
  await page
2632
2590
  .evaluate(() => {
2633
2591
  window.__wgCameraLock = true;
2592
+ window.__wgIgnoreHostMouse = true;
2634
2593
  })
2635
2594
  .catch(() => {});
2636
2595
  keptCameraLock = true;
2596
+ const handle = await this.elementHandle().catch(() => null);
2597
+ if (handle) {
2598
+ await page
2599
+ .evaluate(
2600
+ (el) => {
2601
+ if (!el || !window.__wgSetCamera || !window.__wgElDocPoint) return;
2602
+ const cam = window.__wgCamera || { scale: 1.4 };
2603
+ const pt = window.__wgElDocPoint(el);
2604
+ const s = cam.scale > 1.001 ? cam.scale : 1.4;
2605
+ window.__wgSetCamera(pt.x, pt.y, s, 280);
2606
+ },
2607
+ handle,
2608
+ )
2609
+ .catch(() => {});
2610
+ await handle.dispose().catch(() => {});
2611
+ await new Promise((res) => setTimeout(res, 300));
2612
+ }
2637
2613
  }
2638
- const box = await this.boundingBox();
2614
+ const live = await liveCenterOf(this);
2615
+ const box = live
2616
+ ? { x: live.x - live.w / 2, y: live.y - live.h / 2, width: live.w, height: live.h }
2617
+ : await this.boundingBox();
2639
2618
  const narrated = box ? await wasJustNarrated(page) : false;
2640
2619
  if (box && !narrated) {
2641
2620
  let fallback = "click";
@@ -2643,10 +2622,8 @@ function instrumentInteractionHighlighting(page, mem, slowMo, pacing, stubBefore
2643
2622
  const text = (await this.textContent())?.trim();
2644
2623
  if (text && text.length > 0 && text.length <= 30) fallback = text;
2645
2624
  } catch {
2646
- // element has no simple text (an icon button, say) - generic label is fine
2625
+ /* ignore */
2647
2626
  }
2648
- // Prefer "nav: …" when this locator is a NavBlock click target
2649
- // (set on page by runStepMode just before act).
2650
2627
  try {
2651
2628
  const navLabel = await page.evaluate(() => window.__wgPendingNavClickLabel || null);
2652
2629
  if (navLabel) fallback = String(navLabel);
@@ -2656,13 +2633,13 @@ function instrumentInteractionHighlighting(page, mem, slowMo, pacing, stubBefore
2656
2633
  const stubs = (stubBeforeRef && stubBeforeRef.current) || [];
2657
2634
  const cap = await captionForLocator(page, this, stubs, fallback);
2658
2635
  clickTone = cap.tone;
2659
- // Already centered from stubBefore zoom: short cursor, keep lock.
2660
- const travel = zoomed ? cursorMs(160) : cursorMs(600);
2661
- clickPoint = await moveCursorTo(page, box, travel, {
2662
- keepLock: zoomed,
2663
- });
2664
- // Re-measure after cursor settle - stale pre-travel box teleports the ring.
2665
- const liveBox = (await this.boundingBox()) || box;
2636
+ const travel = zoomed ? cursorMs(200) : cursorMs(600);
2637
+ const again = (await liveCenterOf(this)) || live;
2638
+ const aim = again
2639
+ ? { x: again.x - again.w / 2, y: again.y - again.h / 2, width: again.w, height: again.h }
2640
+ : box;
2641
+ clickPoint = await moveCursorTo(page, aim, travel, { keepLock: zoomed });
2642
+ const liveBox = aim;
2666
2643
  clickPoint = {
2667
2644
  x: liveBox.x + liveBox.width / 2,
2668
2645
  y: liveBox.y + liveBox.height / 2,
@@ -2672,41 +2649,52 @@ function instrumentInteractionHighlighting(page, mem, slowMo, pacing, stubBefore
2672
2649
  weight: cap.weight,
2673
2650
  });
2674
2651
  await dwellMatchedStub(page, this, stubs, pacing);
2675
- // "pop for a few seconds" - Dan's own phrase, matching the
2676
- // zsign demo-engine's ring-before-click pattern in
2677
- // services/help-center-clip-engine's video-pipeline.
2678
2652
  await new Promise((res) => setTimeout(res, clickPrePop()));
2679
2653
  } else if (box) {
2680
- // Ring/caption already handled by narrate() - still move the
2681
- // cursor + pulse the click point, just skip re-showing the ring.
2682
2654
  const travel = zoomed ? cursorMs(120) : cursorMs(200);
2683
- clickPoint = await moveCursorTo(page, box, travel, {
2684
- keepLock: zoomed,
2685
- });
2655
+ clickPoint = await moveCursorTo(page, box, travel, { keepLock: zoomed });
2686
2656
  clickTone = "planned";
2687
2657
  }
2688
2658
  } catch {
2689
- // best-effort - the real click below still runs either way
2659
+ /* best-effort */
2690
2660
  }
2691
2661
  if (clickPoint) {
2692
2662
  await clickPulseAt(page, clickPoint.x, clickPoint.y, clickTone);
2693
- // clickPulseAt only triggers the CSS animation class - it doesn't
2694
- // wait for it. Without a pause here, the real click (and any
2695
- // resulting navigation/DOM change) fires while the ripple is still
2696
- // mid-animation, sometimes cutting it off before it's even visible.
2697
- // Wait out the same .5s the "@keyframes wg-pulse" rule uses, so the
2698
- // mock click visually completes before the real one fires.
2699
2663
  await new Promise((res) => setTimeout(res, skipTheater() ? 0 : 500));
2700
2664
  }
2701
2665
  const zoomedClick = await page
2702
2666
  .evaluate(() => !!(window.__wgCamera && window.__wgCamera.scale > 1.001))
2703
2667
  .catch(() => false);
2704
- const clickOpts =
2705
- zoomedClick || keptCameraLock
2706
- ? { ...(options || {}), force: true }
2707
- : options;
2708
2668
  try {
2709
- const result = await originalClick.call(this, clickOpts);
2669
+ let result;
2670
+ if (zoomedClick || keptCameraLock) {
2671
+ // Real Playwright mouse at live visual center - never scrollIntoView.
2672
+ const pt =
2673
+ clickPoint ||
2674
+ (await liveCenterOf(this)) ||
2675
+ null;
2676
+ if (pt && Number.isFinite(pt.x) && Number.isFinite(pt.y)) {
2677
+ await page.mouse.click(pt.x, pt.y);
2678
+ result = undefined;
2679
+ } else {
2680
+ const handle = await this.elementHandle().catch(() => null);
2681
+ if (handle) {
2682
+ await handle.evaluate((el) => {
2683
+ if (el && typeof el.click === "function") el.click();
2684
+ });
2685
+ await handle.dispose().catch(() => {});
2686
+ result = undefined;
2687
+ } else {
2688
+ result = await originalClick.call(this, {
2689
+ ...(options || {}),
2690
+ force: true,
2691
+ timeout: (options && options.timeout) || 5000,
2692
+ });
2693
+ }
2694
+ }
2695
+ } else {
2696
+ result = await originalClick.call(this, options);
2697
+ }
2710
2698
  await new Promise((res) => setTimeout(res, clickPostPop()));
2711
2699
  await hideRing(page);
2712
2700
  return result;
@@ -97,12 +97,11 @@ export interface WaygraphHighlightStub extends FixtureDurationFields {
97
97
  */
98
98
  focus?: boolean;
99
99
  /**
100
- * After camera zoom settles, pan with the real mouse pointer (Screen Studio).
101
- * Default: on when `zoom` > 1. Set false to lock the camera on the target.
102
- * Alias: `follow`.
100
+ * Deprecated no-op. Camera auto-centers on the ring target; host mouse is
101
+ * ignored. Playwright's real mouse is driven to the centered target instead.
103
102
  */
104
103
  followMouse?: boolean;
105
- /** Alias for {@link WaygraphHighlightStub.followMouse}. */
104
+ /** Alias for {@link WaygraphHighlightStub.followMouse} (no-op). */
106
105
  follow?: boolean;
107
106
  /**
108
107
  * Caption text color (CSS color). Overrides tone label color when set.
@@ -30,7 +30,7 @@ export const NavCartBlock = defineNavClickBlock<CartPage>({
30
30
  color: "#c9a6ff",
31
31
  zoom: 1.55,
32
32
  zoomOut: false,
33
- followMouse: true,
33
+ followMouse: false,
34
34
  weight: "bold",
35
35
  },
36
36
  });
@@ -45,7 +45,7 @@ export const NavCartBlock = defineNavClickBlock<CartPage>({
45
45
  focus: true,
46
46
  color: "#86efac",
47
47
  zoom: 1.4,
48
- followMouse: true,
48
+ followMouse: false,
49
49
  duration: true,
50
50
  });
51
51
  },
@@ -49,7 +49,6 @@ export const AddToCartBlock = defineEffectBlock<Checkpoint<string>, ItemInCart>(
49
49
  color: "#c9a6ff",
50
50
  zoom: 1.45,
51
51
  zoomOut: false,
52
- followMouse: true,
53
52
  },
54
53
  add: {
55
54
  selector: InventorySel.addBtn(DEMO_PRODUCT_ID),
@@ -75,7 +74,6 @@ export const AddToCartBlock = defineEffectBlock<Checkpoint<string>, ItemInCart>(
75
74
  color: "#86efac",
76
75
  zoom: 1.6,
77
76
  zoomOut: false,
78
- followMouse: true,
79
77
  duration: true,
80
78
  });
81
79
  },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "waygraph",
3
- "version": "0.12.38",
3
+ "version": "0.12.40",
4
4
  "type": "module",
5
5
  "description": "A typed graph of reusable Blocks for driving a browser through E2E flows.",
6
6
  "license": "MIT",
@@ -30,7 +30,7 @@ export const NavCartBlock = defineNavClickBlock<CartPage>({
30
30
  color: "#c9a6ff",
31
31
  zoom: 1.55,
32
32
  zoomOut: false,
33
- followMouse: true,
33
+ followMouse: false,
34
34
  weight: "bold",
35
35
  },
36
36
  });
@@ -45,7 +45,7 @@ export const NavCartBlock = defineNavClickBlock<CartPage>({
45
45
  focus: true,
46
46
  color: "#86efac",
47
47
  zoom: 1.4,
48
- followMouse: true,
48
+ followMouse: false,
49
49
  duration: true,
50
50
  });
51
51
  },
@@ -49,7 +49,6 @@ export const AddToCartBlock = defineEffectBlock<Checkpoint<string>, ItemInCart>(
49
49
  color: "#c9a6ff",
50
50
  zoom: 1.45,
51
51
  zoomOut: false,
52
- followMouse: true,
53
52
  },
54
53
  add: {
55
54
  selector: InventorySel.addBtn(DEMO_PRODUCT_ID),
@@ -75,7 +74,6 @@ export const AddToCartBlock = defineEffectBlock<Checkpoint<string>, ItemInCart>(
75
74
  color: "#86efac",
76
75
  zoom: 1.6,
77
76
  zoomOut: false,
78
- followMouse: true,
79
77
  duration: true,
80
78
  });
81
79
  },