afterbefore 0.2.4 → 0.2.5

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.
@@ -1,7 +1,7 @@
1
1
  "use client";
2
2
 
3
3
  // src/overlay/index.tsx
4
- import { useState as useState6, useCallback as useCallback7, useRef as useRef6, useEffect as useEffect6 } from "react";
4
+ import { useState as useState6, useCallback as useCallback6, useRef as useRef5, useEffect as useEffect5 } from "react";
5
5
 
6
6
  // src/overlay/state.ts
7
7
  import { useState, useCallback } from "react";
@@ -33,99 +33,67 @@ function useOverlayState() {
33
33
  }
34
34
 
35
35
  // src/overlay/capture.ts
36
- var cachedStream = null;
37
- async function getTabStream() {
38
- if (cachedStream && cachedStream.active) {
39
- return cachedStream;
36
+ import { domToPng } from "modern-screenshot";
37
+ var DEV_UI_SELECTORS = [
38
+ // Afterbefore overlay
39
+ "[data-afterbefore]",
40
+ // Next.js dev indicators
41
+ "[data-nextjs-toast]",
42
+ "[data-nextjs-dev-overlay]",
43
+ "[data-nextjs-dialog]",
44
+ "[data-nextjs-dialog-backdrop]",
45
+ "[data-next-badge]",
46
+ "[data-next-mark]"
47
+ ].join(",");
48
+ function filterDevUI(node) {
49
+ if (node instanceof HTMLElement) {
50
+ return !node.matches(DEV_UI_SELECTORS);
40
51
  }
41
- const stream = await navigator.mediaDevices.getDisplayMedia({
42
- video: { displaySurface: "browser" },
43
- preferCurrentTab: true
44
- });
45
- cachedStream = stream;
46
- stream.getVideoTracks()[0].addEventListener("ended", () => {
47
- cachedStream = null;
48
- });
49
- return stream;
52
+ return true;
50
53
  }
51
- async function hideOverlay() {
52
- const elements = document.querySelectorAll(
53
- "[data-afterbefore]"
54
- );
54
+ function hideDevUI() {
55
+ const elements = document.querySelectorAll(DEV_UI_SELECTORS);
55
56
  const saved = /* @__PURE__ */ new Map();
56
57
  elements.forEach((el) => {
57
- saved.set(el, el.style.visibility);
58
- el.style.visibility = "hidden";
59
- });
60
- await new Promise((resolve) => {
61
- requestAnimationFrame(() => {
62
- requestAnimationFrame(() => {
63
- setTimeout(resolve, 50);
64
- });
65
- });
58
+ saved.set(el, el.style.display);
59
+ el.style.display = "none";
66
60
  });
67
61
  return () => {
68
62
  saved.forEach((v, el) => {
69
- el.style.visibility = v;
63
+ el.style.display = v;
70
64
  });
71
65
  };
72
66
  }
73
- async function grabFrame() {
74
- const stream = await getTabStream();
75
- const track = stream.getVideoTracks()[0];
76
- const imageCapture = new window.ImageCapture(track);
77
- return imageCapture.grabFrame();
78
- }
79
- function bitmapToDataUrl(bitmap, crop) {
80
- const canvas = document.createElement("canvas");
81
- const ctx = canvas.getContext("2d");
82
- if (crop) {
83
- const scaleX = bitmap.width / window.innerWidth;
84
- const scaleY = bitmap.height / window.innerHeight;
85
- const sx = crop.x * scaleX;
86
- const sy = crop.y * scaleY;
87
- const sw = crop.width * scaleX;
88
- const sh = crop.height * scaleY;
89
- canvas.width = sw;
90
- canvas.height = sh;
91
- ctx.drawImage(bitmap, sx, sy, sw, sh, 0, 0, sw, sh);
92
- } else {
93
- canvas.width = bitmap.width;
94
- canvas.height = bitmap.height;
95
- ctx.drawImage(bitmap, 0, 0);
96
- }
97
- return canvas.toDataURL("image/png");
98
- }
99
67
  async function capture(options) {
100
68
  const { mode, area, element } = options;
101
- if (mode === "fullpage") {
102
- return captureFullPage();
103
- }
104
- const restoreOverlay = await hideOverlay();
69
+ const restore = hideDevUI();
105
70
  try {
106
- const bitmap = await grabFrame();
107
71
  if (mode === "viewport") {
108
- return bitmapToDataUrl(bitmap);
72
+ return await captureViewport();
73
+ }
74
+ if (mode === "fullpage") {
75
+ return await captureFullPage();
109
76
  }
110
77
  if (mode === "area" && area) {
111
- return bitmapToDataUrl(bitmap, area);
78
+ return await captureArea(area);
112
79
  }
113
80
  if (mode === "component" && element) {
114
- const rect = element.getBoundingClientRect();
115
- return bitmapToDataUrl(bitmap, {
116
- x: rect.x,
117
- y: rect.y,
118
- width: rect.width,
119
- height: rect.height
120
- });
81
+ return await captureComponent(element);
121
82
  }
122
83
  throw new Error(`Invalid capture mode: ${mode}`);
123
84
  } finally {
124
- restoreOverlay();
85
+ restore();
125
86
  }
126
87
  }
88
+ async function captureViewport() {
89
+ return domToPng(document.documentElement, {
90
+ width: window.innerWidth,
91
+ height: window.innerHeight,
92
+ style: { overflow: "hidden" },
93
+ filter: filterDevUI
94
+ });
95
+ }
127
96
  async function captureFullPage() {
128
- const { toPng } = await import("html-to-image");
129
97
  const scrollY = window.scrollY;
130
98
  const body = document.body;
131
99
  const html = document.documentElement;
@@ -136,18 +104,52 @@ async function captureFullPage() {
136
104
  html.scrollHeight,
137
105
  html.offsetHeight
138
106
  );
139
- const dataUrl = await toPng(document.documentElement, {
107
+ const dataUrl = await domToPng(document.documentElement, {
140
108
  width: window.innerWidth,
141
109
  height: fullHeight,
142
110
  style: {
143
111
  overflow: "visible",
144
112
  height: `${fullHeight}px`
145
113
  },
146
- filter: (node) => !node.dataset?.afterbefore
114
+ filter: filterDevUI
147
115
  });
148
116
  window.scrollTo(0, scrollY);
149
117
  return dataUrl;
150
118
  }
119
+ async function captureArea(area) {
120
+ const fullDataUrl = await captureViewport();
121
+ const img = await loadImage(fullDataUrl);
122
+ const dpr = window.devicePixelRatio || 1;
123
+ const canvas = document.createElement("canvas");
124
+ canvas.width = area.width * dpr;
125
+ canvas.height = area.height * dpr;
126
+ const ctx = canvas.getContext("2d");
127
+ ctx.drawImage(
128
+ img,
129
+ area.x * dpr,
130
+ area.y * dpr,
131
+ area.width * dpr,
132
+ area.height * dpr,
133
+ 0,
134
+ 0,
135
+ area.width * dpr,
136
+ area.height * dpr
137
+ );
138
+ return canvas.toDataURL("image/png");
139
+ }
140
+ async function captureComponent(element) {
141
+ return domToPng(element, {
142
+ filter: filterDevUI
143
+ });
144
+ }
145
+ function loadImage(src) {
146
+ return new Promise((resolve, reject) => {
147
+ const img = new Image();
148
+ img.onload = () => resolve(img);
149
+ img.onerror = reject;
150
+ img.src = src;
151
+ });
152
+ }
151
153
 
152
154
  // src/overlay/ui/icon.tsx
153
155
  import { useRef, useCallback as useCallback2, useEffect, useState as useState2 } from "react";
@@ -366,327 +368,14 @@ function Icon({ phase, onClick, loading, onPositionChange }) {
366
368
  );
367
369
  }
368
370
 
369
- // src/overlay/ui/menu.tsx
370
- import { useEffect as useEffect2, useRef as useRef2, useCallback as useCallback3 } from "react";
371
- import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
372
- var modes = [
373
- {
374
- mode: "viewport",
375
- label: "Viewport",
376
- icon: /* @__PURE__ */ jsxs2("svg", { width: "18", height: "18", viewBox: "0 0 16 16", children: [
377
- /* @__PURE__ */ jsx2(
378
- "rect",
379
- {
380
- x: "1",
381
- y: "2",
382
- width: "14",
383
- height: "11",
384
- rx: "1.5",
385
- fill: "none",
386
- stroke: "currentColor",
387
- strokeWidth: "1.5"
388
- }
389
- ),
390
- /* @__PURE__ */ jsx2(
391
- "line",
392
- {
393
- x1: "1",
394
- y1: "14",
395
- x2: "15",
396
- y2: "14",
397
- stroke: "currentColor",
398
- strokeWidth: "1.5",
399
- strokeLinecap: "round"
400
- }
401
- )
402
- ] })
403
- },
404
- {
405
- mode: "fullpage",
406
- label: "Full Page",
407
- icon: /* @__PURE__ */ jsxs2("svg", { width: "18", height: "18", viewBox: "0 0 16 16", children: [
408
- /* @__PURE__ */ jsx2(
409
- "rect",
410
- {
411
- x: "3",
412
- y: "1",
413
- width: "10",
414
- height: "14",
415
- rx: "1.5",
416
- fill: "none",
417
- stroke: "currentColor",
418
- strokeWidth: "1.5"
419
- }
420
- ),
421
- /* @__PURE__ */ jsx2(
422
- "line",
423
- {
424
- x1: "5.5",
425
- y1: "4",
426
- x2: "10.5",
427
- y2: "4",
428
- stroke: "currentColor",
429
- strokeWidth: "1",
430
- strokeLinecap: "round"
431
- }
432
- ),
433
- /* @__PURE__ */ jsx2(
434
- "line",
435
- {
436
- x1: "5.5",
437
- y1: "6.5",
438
- x2: "10.5",
439
- y2: "6.5",
440
- stroke: "currentColor",
441
- strokeWidth: "1",
442
- strokeLinecap: "round"
443
- }
444
- ),
445
- /* @__PURE__ */ jsx2(
446
- "line",
447
- {
448
- x1: "5.5",
449
- y1: "9",
450
- x2: "10.5",
451
- y2: "9",
452
- stroke: "currentColor",
453
- strokeWidth: "1",
454
- strokeLinecap: "round"
455
- }
456
- )
457
- ] })
458
- },
459
- {
460
- mode: "area",
461
- label: "Select Area",
462
- icon: /* @__PURE__ */ jsxs2("svg", { width: "18", height: "18", viewBox: "0 0 16 16", children: [
463
- /* @__PURE__ */ jsx2(
464
- "path",
465
- {
466
- d: "M1 5V2.5A1.5 1.5 0 012.5 1H5",
467
- fill: "none",
468
- stroke: "currentColor",
469
- strokeWidth: "1.5",
470
- strokeLinecap: "round"
471
- }
472
- ),
473
- /* @__PURE__ */ jsx2(
474
- "path",
475
- {
476
- d: "M11 1h2.5A1.5 1.5 0 0115 2.5V5",
477
- fill: "none",
478
- stroke: "currentColor",
479
- strokeWidth: "1.5",
480
- strokeLinecap: "round"
481
- }
482
- ),
483
- /* @__PURE__ */ jsx2(
484
- "path",
485
- {
486
- d: "M15 11v2.5a1.5 1.5 0 01-1.5 1.5H11",
487
- fill: "none",
488
- stroke: "currentColor",
489
- strokeWidth: "1.5",
490
- strokeLinecap: "round"
491
- }
492
- ),
493
- /* @__PURE__ */ jsx2(
494
- "path",
495
- {
496
- d: "M5 15H2.5A1.5 1.5 0 011 13.5V11",
497
- fill: "none",
498
- stroke: "currentColor",
499
- strokeWidth: "1.5",
500
- strokeLinecap: "round"
501
- }
502
- )
503
- ] })
504
- },
505
- {
506
- mode: "component",
507
- label: "Component",
508
- icon: /* @__PURE__ */ jsxs2("svg", { width: "18", height: "18", viewBox: "0 0 16 16", children: [
509
- /* @__PURE__ */ jsx2(
510
- "path",
511
- {
512
- d: "M3 2l2.5 10 2-3.5L11 11 3 2z",
513
- fill: "none",
514
- stroke: "currentColor",
515
- strokeWidth: "1.3",
516
- strokeLinejoin: "round",
517
- strokeLinecap: "round"
518
- }
519
- ),
520
- /* @__PURE__ */ jsx2(
521
- "rect",
522
- {
523
- x: "8",
524
- y: "5",
525
- width: "6.5",
526
- height: "6.5",
527
- rx: "1",
528
- fill: "none",
529
- stroke: "currentColor",
530
- strokeWidth: "1.2",
531
- strokeDasharray: "2 1.5"
532
- }
533
- )
534
- ] })
535
- }
536
- ];
537
- var PILL_WIDTH = 270;
538
- var BTN_SIZE = 40;
539
- function Menu({ onSelect, onClose, position }) {
540
- const menuRef = useRef2(null);
541
- const handleClickOutside = useCallback3(
542
- (e) => {
543
- if (menuRef.current && !menuRef.current.contains(e.target)) {
544
- onClose();
545
- }
546
- },
547
- [onClose]
548
- );
549
- const handleKeyDown = useCallback3(
550
- (e) => {
551
- if (e.key === "Escape") {
552
- onClose();
553
- }
554
- },
555
- [onClose]
556
- );
557
- useEffect2(() => {
558
- document.addEventListener("mousedown", handleClickOutside);
559
- document.addEventListener("keydown", handleKeyDown);
560
- return () => {
561
- document.removeEventListener("mousedown", handleClickOutside);
562
- document.removeEventListener("keydown", handleKeyDown);
563
- };
564
- }, [handleClickOutside, handleKeyDown]);
565
- const menuLeft = Math.max(
566
- 8,
567
- Math.min(position.x - PILL_WIDTH / 2 + 20, window.innerWidth - PILL_WIDTH - 8)
568
- );
569
- const menuBottom = window.innerHeight - position.y + 8;
570
- return /* @__PURE__ */ jsxs2(
571
- "div",
572
- {
573
- ref: menuRef,
574
- "data-afterbefore": "true",
575
- style: {
576
- position: "fixed",
577
- left: menuLeft,
578
- bottom: menuBottom,
579
- display: "flex",
580
- alignItems: "center",
581
- gap: 4,
582
- background: "rgba(24, 24, 27, 0.95)",
583
- borderRadius: 22,
584
- padding: "5px 6px",
585
- boxShadow: "0 4px 20px rgba(0,0,0,0.4), 0 0 0 1px rgba(255,255,255,0.08)",
586
- zIndex: 2147483647,
587
- fontFamily: "system-ui, -apple-system, sans-serif",
588
- backdropFilter: "blur(12px)"
589
- },
590
- children: [
591
- modes.map(({ mode, label, icon }) => /* @__PURE__ */ jsx2(
592
- "button",
593
- {
594
- title: label,
595
- onClick: () => onSelect(mode),
596
- style: {
597
- display: "flex",
598
- alignItems: "center",
599
- justifyContent: "center",
600
- width: BTN_SIZE,
601
- height: BTN_SIZE,
602
- border: "none",
603
- background: "transparent",
604
- color: "rgba(255,255,255,0.7)",
605
- borderRadius: "50%",
606
- cursor: "pointer",
607
- padding: 0,
608
- transition: "background 0.1s, color 0.1s",
609
- flexShrink: 0
610
- },
611
- onMouseEnter: (e) => {
612
- const btn = e.currentTarget;
613
- btn.style.background = "rgba(255,255,255,0.12)";
614
- btn.style.color = "rgba(255,255,255,1)";
615
- },
616
- onMouseLeave: (e) => {
617
- const btn = e.currentTarget;
618
- btn.style.background = "transparent";
619
- btn.style.color = "rgba(255,255,255,0.7)";
620
- },
621
- children: icon
622
- },
623
- mode
624
- )),
625
- /* @__PURE__ */ jsx2(
626
- "div",
627
- {
628
- style: {
629
- width: 1,
630
- height: 24,
631
- background: "rgba(255,255,255,0.12)",
632
- flexShrink: 0,
633
- margin: "0 2px"
634
- }
635
- }
636
- ),
637
- /* @__PURE__ */ jsx2(
638
- "button",
639
- {
640
- title: "Close",
641
- onClick: onClose,
642
- style: {
643
- display: "flex",
644
- alignItems: "center",
645
- justifyContent: "center",
646
- width: BTN_SIZE,
647
- height: BTN_SIZE,
648
- border: "none",
649
- background: "transparent",
650
- color: "rgba(255,255,255,0.45)",
651
- borderRadius: "50%",
652
- cursor: "pointer",
653
- padding: 0,
654
- transition: "background 0.1s, color 0.1s",
655
- flexShrink: 0
656
- },
657
- onMouseEnter: (e) => {
658
- const btn = e.currentTarget;
659
- btn.style.background = "rgba(255,255,255,0.12)";
660
- btn.style.color = "rgba(255,255,255,0.9)";
661
- },
662
- onMouseLeave: (e) => {
663
- const btn = e.currentTarget;
664
- btn.style.background = "transparent";
665
- btn.style.color = "rgba(255,255,255,0.45)";
666
- },
667
- children: /* @__PURE__ */ jsx2("svg", { width: "16", height: "16", viewBox: "0 0 14 14", children: /* @__PURE__ */ jsx2(
668
- "path",
669
- {
670
- d: "M3 3l8 8M11 3l-8 8",
671
- stroke: "currentColor",
672
- strokeWidth: "1.5",
673
- strokeLinecap: "round"
674
- }
675
- ) })
676
- }
677
- )
678
- ]
679
- }
680
- );
681
- }
682
-
683
371
  // src/overlay/ui/selector.tsx
684
- import React3, { useState as useState3, useRef as useRef3, useCallback as useCallback4, useEffect as useEffect3 } from "react";
685
- import { Fragment, jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
372
+ import React2, { useState as useState3, useRef as useRef2, useCallback as useCallback3, useEffect as useEffect2 } from "react";
373
+ import { Fragment, jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
686
374
  var HANDLE_R = 6;
687
375
  var HANDLE_HIT = 16;
688
376
  var MIN_SIZE = 20;
689
377
  var PANEL_HEIGHT_EST = 140;
378
+ var SNAP_THRESHOLD = 8;
690
379
  var ASPECT_RATIOS = [
691
380
  { label: "Free", value: 0 },
692
381
  { label: "16:9", value: 16 / 9 },
@@ -695,7 +384,7 @@ var ASPECT_RATIOS = [
695
384
  { label: "3:2", value: 3 / 2 },
696
385
  { label: "21:9", value: 21 / 9 }
697
386
  ];
698
- function Selector({ onSelect, onCancel }) {
387
+ function Selector({ onSelect, onCancel, onViewport, onFullPage, onComponent }) {
699
388
  const [rect, setRect] = useState3(null);
700
389
  const [placed, setPlaced] = useState3(false);
701
390
  const [aspect, setAspect] = useState3("Free");
@@ -712,13 +401,39 @@ function Selector({ onSelect, onCancel }) {
712
401
  }
713
402
  );
714
403
  const [cursor, setCursor] = useState3("crosshair");
715
- const mode = useRef3("none");
716
- const start = useRef3({ x: 0, y: 0 });
717
- const snap = useRef3({ x: 0, y: 0, w: 0, h: 0 });
718
- const corner = useRef3("br");
719
- const panelRef = useRef3(null);
404
+ const [magnet, setMagnet] = useState3(() => {
405
+ try {
406
+ return localStorage.getItem("ab-magnet") !== "false";
407
+ } catch {
408
+ return true;
409
+ }
410
+ });
411
+ const [snappedX, setSnappedX] = useState3(false);
412
+ const mode = useRef2("none");
413
+ const start = useRef2({ x: 0, y: 0 });
414
+ const snapRef = useRef2({ x: 0, y: 0, w: 0, h: 0 });
415
+ const corner = useRef2("br");
416
+ const panelRef = useRef2(null);
720
417
  const ratio = ASPECT_RATIOS.find((a) => a.label === aspect)?.value ?? 0;
721
- useEffect3(() => {
418
+ const applySnap = useCallback3(
419
+ (r) => {
420
+ if (!magnet) {
421
+ setSnappedX(false);
422
+ return r;
423
+ }
424
+ const vw = window.innerWidth;
425
+ const centerX = r.x + r.w / 2;
426
+ const viewCenterX = vw / 2;
427
+ if (Math.abs(centerX - viewCenterX) < SNAP_THRESHOLD) {
428
+ setSnappedX(true);
429
+ return { ...r, x: viewCenterX - r.w / 2 };
430
+ }
431
+ setSnappedX(false);
432
+ return r;
433
+ },
434
+ [magnet]
435
+ );
436
+ useEffect2(() => {
722
437
  const onKey = (e) => {
723
438
  if (e.target?.tagName === "INPUT") {
724
439
  if (e.key === "Escape") e.target.blur();
@@ -743,7 +458,7 @@ function Selector({ onSelect, onCancel }) {
743
458
  document.addEventListener("keydown", onKey);
744
459
  return () => document.removeEventListener("keydown", onKey);
745
460
  }, [placed, rect, onSelect, onCancel]);
746
- const hitCorner = useCallback4(
461
+ const hitCorner = useCallback3(
747
462
  (mx, my, r) => {
748
463
  const cs = [
749
464
  ["tl", r.x, r.y],
@@ -759,11 +474,11 @@ function Selector({ onSelect, onCancel }) {
759
474
  },
760
475
  []
761
476
  );
762
- const hitInside = useCallback4(
477
+ const hitInside = useCallback3(
763
478
  (mx, my, r) => mx >= r.x && mx <= r.x + r.w && my >= r.y && my <= r.y + r.h,
764
479
  []
765
480
  );
766
- const onDown = useCallback4(
481
+ const onDown = useCallback3(
767
482
  (e) => {
768
483
  if (panelRef.current?.contains(e.target)) return;
769
484
  e.preventDefault();
@@ -776,13 +491,13 @@ function Selector({ onSelect, onCancel }) {
776
491
  mode.current = "resizing";
777
492
  corner.current = c;
778
493
  start.current = { x: mx, y: my };
779
- snap.current = { ...rect };
494
+ snapRef.current = { ...rect };
780
495
  return;
781
496
  }
782
497
  if (hitInside(mx, my, rect)) {
783
498
  mode.current = "moving";
784
499
  start.current = { x: mx, y: my };
785
- snap.current = { ...rect };
500
+ snapRef.current = { ...rect };
786
501
  return;
787
502
  }
788
503
  setPlaced(false);
@@ -793,7 +508,7 @@ function Selector({ onSelect, onCancel }) {
793
508
  },
794
509
  [placed, rect, hitCorner, hitInside]
795
510
  );
796
- const onMove = useCallback4(
511
+ const onMove = useCallback3(
797
512
  (e) => {
798
513
  const mx = e.clientX, my = e.clientY;
799
514
  if (mode.current === "drawing") {
@@ -804,16 +519,18 @@ function Selector({ onSelect, onCancel }) {
804
519
  h = w / ratio;
805
520
  if (my < sy) y = sy - h;
806
521
  }
807
- setRect({ x, y, w, h });
522
+ setRect(applySnap({ x, y, w, h }));
808
523
  } else if (mode.current === "moving") {
809
524
  const dx = mx - start.current.x, dy = my - start.current.y;
810
- setRect({
811
- ...snap.current,
812
- x: snap.current.x + dx,
813
- y: snap.current.y + dy
814
- });
525
+ setRect(
526
+ applySnap({
527
+ ...snapRef.current,
528
+ x: snapRef.current.x + dx,
529
+ y: snapRef.current.y + dy
530
+ })
531
+ );
815
532
  } else if (mode.current === "resizing") {
816
- const o = snap.current;
533
+ const o = snapRef.current;
817
534
  const c = corner.current;
818
535
  const nr = { ...o };
819
536
  if (c === "br") {
@@ -833,7 +550,7 @@ function Selector({ onSelect, onCancel }) {
833
550
  nr.x = o.x + o.w - nr.w;
834
551
  nr.y = o.y + o.h - nr.h;
835
552
  }
836
- setRect(nr);
553
+ setRect(applySnap(nr));
837
554
  } else if (placed && rect) {
838
555
  const c = hitCorner(mx, my, rect);
839
556
  if (c === "tl" || c === "br") setCursor("nwse-resize");
@@ -842,9 +559,9 @@ function Selector({ onSelect, onCancel }) {
842
559
  else setCursor("crosshair");
843
560
  }
844
561
  },
845
- [ratio, placed, rect, hitCorner, hitInside]
562
+ [ratio, placed, rect, hitCorner, hitInside, applySnap]
846
563
  );
847
- const onUp = useCallback4(
564
+ const onUp = useCallback3(
848
565
  (e) => {
849
566
  const prevMode = mode.current;
850
567
  if (prevMode === "drawing" && rect) {
@@ -870,7 +587,7 @@ function Selector({ onSelect, onCancel }) {
870
587
  },
871
588
  [rect, placed, onSelect]
872
589
  );
873
- const setSize = useCallback4(
590
+ const setSize = useCallback3(
874
591
  (field, v) => {
875
592
  const n = parseInt(v, 10);
876
593
  if (isNaN(n) || n < MIN_SIZE || !rect) return;
@@ -882,7 +599,7 @@ function Selector({ onSelect, onCancel }) {
882
599
  },
883
600
  [rect, ratio]
884
601
  );
885
- const setPos = useCallback4(
602
+ const setPos = useCallback3(
886
603
  (field, v) => {
887
604
  const n = parseInt(v, 10);
888
605
  if (isNaN(n) || !rect) return;
@@ -890,7 +607,7 @@ function Selector({ onSelect, onCancel }) {
890
607
  },
891
608
  [rect]
892
609
  );
893
- const savePreset = useCallback4(() => {
610
+ const savePreset = useCallback3(() => {
894
611
  if (!rect) return;
895
612
  const label = `${Math.round(rect.w)}\xD7${Math.round(rect.h)}`;
896
613
  const next = [
@@ -904,7 +621,7 @@ function Selector({ onSelect, onCancel }) {
904
621
  }
905
622
  setSavedOpen(false);
906
623
  }, [rect, presets]);
907
- const loadPreset = useCallback4((p) => {
624
+ const loadPreset = useCallback3((p) => {
908
625
  setRect({ ...p.rect });
909
626
  setPlaced(true);
910
627
  setSavedOpen(false);
@@ -912,7 +629,7 @@ function Selector({ onSelect, onCancel }) {
912
629
  const activeCursor = mode.current === "moving" ? "move" : mode.current === "resizing" ? "nwse-resize" : mode.current === "drawing" ? "crosshair" : cursor;
913
630
  const panelAbove = rect && rect.y + rect.h + PANEL_HEIGHT_EST > window.innerHeight;
914
631
  const hasRect = rect && rect.w > 0 && rect.h > 0;
915
- return /* @__PURE__ */ jsxs3(
632
+ return /* @__PURE__ */ jsxs2(
916
633
  "div",
917
634
  {
918
635
  "data-afterbefore": "true",
@@ -926,7 +643,22 @@ function Selector({ onSelect, onCancel }) {
926
643
  cursor: activeCursor
927
644
  },
928
645
  children: [
929
- /* @__PURE__ */ jsx3(
646
+ snappedX && /* @__PURE__ */ jsx2(
647
+ "div",
648
+ {
649
+ style: {
650
+ position: "absolute",
651
+ left: "50%",
652
+ top: 0,
653
+ bottom: 0,
654
+ width: 0,
655
+ borderLeft: "1px solid rgba(147, 130, 220, 0.5)",
656
+ pointerEvents: "none",
657
+ zIndex: 2
658
+ }
659
+ }
660
+ ),
661
+ /* @__PURE__ */ jsx2(
930
662
  "div",
931
663
  {
932
664
  style: {
@@ -947,8 +679,8 @@ function Selector({ onSelect, onCancel }) {
947
679
  }
948
680
  }
949
681
  ),
950
- hasRect && /* @__PURE__ */ jsxs3(Fragment, { children: [
951
- /* @__PURE__ */ jsx3(
682
+ hasRect && /* @__PURE__ */ jsxs2(Fragment, { children: [
683
+ /* @__PURE__ */ jsx2(
952
684
  "div",
953
685
  {
954
686
  style: {
@@ -962,8 +694,8 @@ function Selector({ onSelect, onCancel }) {
962
694
  }
963
695
  }
964
696
  ),
965
- placed && [1, 2].map((i) => /* @__PURE__ */ jsxs3(React3.Fragment, { children: [
966
- /* @__PURE__ */ jsx3(
697
+ placed && [1, 2].map((i) => /* @__PURE__ */ jsxs2(React2.Fragment, { children: [
698
+ /* @__PURE__ */ jsx2(
967
699
  "div",
968
700
  {
969
701
  style: {
@@ -977,7 +709,7 @@ function Selector({ onSelect, onCancel }) {
977
709
  }
978
710
  }
979
711
  ),
980
- /* @__PURE__ */ jsx3(
712
+ /* @__PURE__ */ jsx2(
981
713
  "div",
982
714
  {
983
715
  style: {
@@ -997,7 +729,7 @@ function Selector({ onSelect, onCancel }) {
997
729
  [rect.x + rect.w, rect.y],
998
730
  [rect.x, rect.y + rect.h],
999
731
  [rect.x + rect.w, rect.y + rect.h]
1000
- ].map(([cx, cy], i) => /* @__PURE__ */ jsx3(
732
+ ].map(([cx, cy], i) => /* @__PURE__ */ jsx2(
1001
733
  "div",
1002
734
  {
1003
735
  style: {
@@ -1014,7 +746,7 @@ function Selector({ onSelect, onCancel }) {
1014
746
  },
1015
747
  i
1016
748
  )),
1017
- placed && /* @__PURE__ */ jsxs3(
749
+ placed && /* @__PURE__ */ jsxs2(
1018
750
  "div",
1019
751
  {
1020
752
  ref: panelRef,
@@ -1040,43 +772,43 @@ function Selector({ onSelect, onCancel }) {
1040
772
  zIndex: 1
1041
773
  },
1042
774
  children: [
1043
- /* @__PURE__ */ jsxs3(Row, { label: "Size", children: [
1044
- /* @__PURE__ */ jsx3(
775
+ /* @__PURE__ */ jsxs2(Row, { label: "Size", children: [
776
+ /* @__PURE__ */ jsx2(
1045
777
  NumInput,
1046
778
  {
1047
779
  value: Math.round(rect.w),
1048
780
  onChange: (v) => setSize("w", v)
1049
781
  }
1050
782
  ),
1051
- /* @__PURE__ */ jsx3(Sep, { children: "\xD7" }),
1052
- /* @__PURE__ */ jsx3(
783
+ /* @__PURE__ */ jsx2(Sep, { children: "\xD7" }),
784
+ /* @__PURE__ */ jsx2(
1053
785
  NumInput,
1054
786
  {
1055
787
  value: Math.round(rect.h),
1056
788
  onChange: (v) => setSize("h", v)
1057
789
  }
1058
790
  ),
1059
- /* @__PURE__ */ jsx3(Unit, { children: "px" })
791
+ /* @__PURE__ */ jsx2(Unit, { children: "px" })
1060
792
  ] }),
1061
- /* @__PURE__ */ jsxs3(Row, { label: "Position", children: [
1062
- /* @__PURE__ */ jsx3(
793
+ /* @__PURE__ */ jsxs2(Row, { label: "Position", children: [
794
+ /* @__PURE__ */ jsx2(
1063
795
  NumInput,
1064
796
  {
1065
797
  value: Math.round(rect.x),
1066
798
  onChange: (v) => setPos("x", v)
1067
799
  }
1068
800
  ),
1069
- /* @__PURE__ */ jsx3(Sep, {}),
1070
- /* @__PURE__ */ jsx3(
801
+ /* @__PURE__ */ jsx2(Sep, {}),
802
+ /* @__PURE__ */ jsx2(
1071
803
  NumInput,
1072
804
  {
1073
805
  value: Math.round(rect.y),
1074
806
  onChange: (v) => setPos("y", v)
1075
807
  }
1076
808
  ),
1077
- /* @__PURE__ */ jsx3(Unit, { children: "px" })
809
+ /* @__PURE__ */ jsx2(Unit, { children: "px" })
1078
810
  ] }),
1079
- /* @__PURE__ */ jsx3(
811
+ /* @__PURE__ */ jsx2(
1080
812
  "div",
1081
813
  {
1082
814
  style: {
@@ -1086,13 +818,13 @@ function Selector({ onSelect, onCancel }) {
1086
818
  }
1087
819
  }
1088
820
  ),
1089
- /* @__PURE__ */ jsxs3(
821
+ /* @__PURE__ */ jsxs2(
1090
822
  "div",
1091
823
  {
1092
824
  style: { display: "flex", alignItems: "center", gap: 12 },
1093
825
  children: [
1094
- /* @__PURE__ */ jsxs3("div", { style: { position: "relative" }, children: [
1095
- /* @__PURE__ */ jsxs3(
826
+ /* @__PURE__ */ jsxs2("div", { style: { position: "relative" }, children: [
827
+ /* @__PURE__ */ jsxs2(
1096
828
  DropBtn,
1097
829
  {
1098
830
  active: aspect !== "Free",
@@ -1101,13 +833,13 @@ function Selector({ onSelect, onCancel }) {
1101
833
  setSavedOpen(false);
1102
834
  },
1103
835
  children: [
1104
- /* @__PURE__ */ jsx3(AspectIcon, {}),
836
+ /* @__PURE__ */ jsx2(AspectIcon, {}),
1105
837
  aspect === "Free" ? "Free" : aspect,
1106
- /* @__PURE__ */ jsx3(Chevron, {})
838
+ /* @__PURE__ */ jsx2(Chevron, {})
1107
839
  ]
1108
840
  }
1109
841
  ),
1110
- aspectOpen && /* @__PURE__ */ jsx3(DropMenu, { children: ASPECT_RATIOS.map((ar) => /* @__PURE__ */ jsx3(
842
+ aspectOpen && /* @__PURE__ */ jsx2(DropMenu, { children: ASPECT_RATIOS.map((ar) => /* @__PURE__ */ jsx2(
1111
843
  DropItem,
1112
844
  {
1113
845
  active: ar.label === aspect,
@@ -1123,8 +855,8 @@ function Selector({ onSelect, onCancel }) {
1123
855
  ar.label
1124
856
  )) })
1125
857
  ] }),
1126
- /* @__PURE__ */ jsxs3("div", { style: { position: "relative" }, children: [
1127
- /* @__PURE__ */ jsxs3(
858
+ /* @__PURE__ */ jsxs2("div", { style: { position: "relative" }, children: [
859
+ /* @__PURE__ */ jsxs2(
1128
860
  DropBtn,
1129
861
  {
1130
862
  onClick: () => {
@@ -1132,14 +864,14 @@ function Selector({ onSelect, onCancel }) {
1132
864
  setAspectOpen(false);
1133
865
  },
1134
866
  children: [
1135
- /* @__PURE__ */ jsx3(SavedIcon, {}),
867
+ /* @__PURE__ */ jsx2(SavedIcon, {}),
1136
868
  "Saved",
1137
- /* @__PURE__ */ jsx3(Chevron, {})
869
+ /* @__PURE__ */ jsx2(Chevron, {})
1138
870
  ]
1139
871
  }
1140
872
  ),
1141
- savedOpen && /* @__PURE__ */ jsxs3(DropMenu, { children: [
1142
- /* @__PURE__ */ jsx3(
873
+ savedOpen && /* @__PURE__ */ jsxs2(DropMenu, { children: [
874
+ /* @__PURE__ */ jsx2(
1143
875
  DropItem,
1144
876
  {
1145
877
  accent: true,
@@ -1147,7 +879,7 @@ function Selector({ onSelect, onCancel }) {
1147
879
  children: "Save current"
1148
880
  }
1149
881
  ),
1150
- presets.length > 0 && /* @__PURE__ */ jsx3(
882
+ presets.length > 0 && /* @__PURE__ */ jsx2(
1151
883
  "div",
1152
884
  {
1153
885
  style: {
@@ -1157,7 +889,7 @@ function Selector({ onSelect, onCancel }) {
1157
889
  }
1158
890
  }
1159
891
  ),
1160
- presets.map((p) => /* @__PURE__ */ jsx3(
892
+ presets.map((p) => /* @__PURE__ */ jsx2(
1161
893
  DropItem,
1162
894
  {
1163
895
  onClick: () => loadPreset(p),
@@ -1165,7 +897,7 @@ function Selector({ onSelect, onCancel }) {
1165
897
  },
1166
898
  p.label
1167
899
  )),
1168
- presets.length === 0 && /* @__PURE__ */ jsx3(
900
+ presets.length === 0 && /* @__PURE__ */ jsx2(
1169
901
  "div",
1170
902
  {
1171
903
  style: {
@@ -1181,7 +913,63 @@ function Selector({ onSelect, onCancel }) {
1181
913
  ]
1182
914
  }
1183
915
  ),
1184
- /* @__PURE__ */ jsx3(
916
+ (onViewport || onFullPage || onComponent) && /* @__PURE__ */ jsxs2(Fragment, { children: [
917
+ /* @__PURE__ */ jsx2(
918
+ "div",
919
+ {
920
+ style: {
921
+ height: 1,
922
+ background: "rgba(255, 255, 255, 0.08)",
923
+ margin: "1px 0"
924
+ }
925
+ }
926
+ ),
927
+ /* @__PURE__ */ jsxs2(
928
+ "div",
929
+ {
930
+ style: { display: "flex", alignItems: "center", gap: 4 },
931
+ children: [
932
+ onViewport && /* @__PURE__ */ jsxs2(ModeBtn, { onClick: onViewport, children: [
933
+ /* @__PURE__ */ jsx2(ViewportIcon, {}),
934
+ "Viewport"
935
+ ] }),
936
+ onFullPage && /* @__PURE__ */ jsxs2(ModeBtn, { onClick: onFullPage, children: [
937
+ /* @__PURE__ */ jsx2(FullPageIcon, {}),
938
+ "Full Page"
939
+ ] }),
940
+ onComponent && /* @__PURE__ */ jsxs2(ModeBtn, { onClick: onComponent, children: [
941
+ /* @__PURE__ */ jsx2(ComponentIcon, {}),
942
+ "Component"
943
+ ] })
944
+ ]
945
+ }
946
+ )
947
+ ] }),
948
+ /* @__PURE__ */ jsx2(
949
+ "div",
950
+ {
951
+ style: {
952
+ display: "flex",
953
+ alignItems: "center",
954
+ gap: 6
955
+ },
956
+ children: /* @__PURE__ */ jsx2(
957
+ MagnetToggle,
958
+ {
959
+ enabled: magnet,
960
+ onToggle: () => {
961
+ const next = !magnet;
962
+ setMagnet(next);
963
+ try {
964
+ localStorage.setItem("ab-magnet", String(next));
965
+ } catch {
966
+ }
967
+ }
968
+ }
969
+ )
970
+ }
971
+ ),
972
+ /* @__PURE__ */ jsx2(
1185
973
  "div",
1186
974
  {
1187
975
  style: {
@@ -1195,7 +983,7 @@ function Selector({ onSelect, onCancel }) {
1195
983
  ]
1196
984
  }
1197
985
  ),
1198
- !placed && /* @__PURE__ */ jsxs3(
986
+ !placed && /* @__PURE__ */ jsxs2(
1199
987
  "div",
1200
988
  {
1201
989
  style: {
@@ -1220,7 +1008,7 @@ function Selector({ onSelect, onCancel }) {
1220
1008
  }
1221
1009
  )
1222
1010
  ] }),
1223
- !rect && /* @__PURE__ */ jsx3(
1011
+ !rect && /* @__PURE__ */ jsxs2(
1224
1012
  "div",
1225
1013
  {
1226
1014
  style: {
@@ -1228,13 +1016,57 @@ function Selector({ onSelect, onCancel }) {
1228
1016
  top: "50%",
1229
1017
  left: "50%",
1230
1018
  transform: "translate(-50%, -50%)",
1231
- color: "rgba(255, 255, 255, 0.7)",
1232
- fontSize: 14,
1233
- fontFamily: "system-ui, -apple-system, sans-serif",
1234
- pointerEvents: "none",
1235
- textShadow: "0 1px 4px rgba(0, 0, 0, 0.5)"
1019
+ display: "flex",
1020
+ flexDirection: "column",
1021
+ alignItems: "center",
1022
+ gap: 16,
1023
+ fontFamily: "system-ui, -apple-system, sans-serif"
1236
1024
  },
1237
- children: "Drag to select an area \xB7 Esc to cancel"
1025
+ children: [
1026
+ /* @__PURE__ */ jsx2(
1027
+ "div",
1028
+ {
1029
+ style: {
1030
+ color: "rgba(255, 255, 255, 0.7)",
1031
+ fontSize: 14,
1032
+ pointerEvents: "none",
1033
+ textShadow: "0 1px 4px rgba(0, 0, 0, 0.5)"
1034
+ },
1035
+ children: "Drag to select an area \xB7 Esc to cancel"
1036
+ }
1037
+ ),
1038
+ (onViewport || onFullPage || onComponent) && /* @__PURE__ */ jsxs2(
1039
+ "div",
1040
+ {
1041
+ onMouseDown: (e) => e.stopPropagation(),
1042
+ style: {
1043
+ display: "flex",
1044
+ alignItems: "center",
1045
+ gap: 4,
1046
+ background: "rgba(32, 32, 36, 0.92)",
1047
+ backdropFilter: "blur(20px)",
1048
+ WebkitBackdropFilter: "blur(20px)",
1049
+ border: "1px solid rgba(255, 255, 255, 0.1)",
1050
+ borderRadius: 8,
1051
+ padding: "6px 8px"
1052
+ },
1053
+ children: [
1054
+ onViewport && /* @__PURE__ */ jsxs2(ModeBtn, { onClick: onViewport, children: [
1055
+ /* @__PURE__ */ jsx2(ViewportIcon, {}),
1056
+ "Viewport"
1057
+ ] }),
1058
+ onFullPage && /* @__PURE__ */ jsxs2(ModeBtn, { onClick: onFullPage, children: [
1059
+ /* @__PURE__ */ jsx2(FullPageIcon, {}),
1060
+ "Full Page"
1061
+ ] }),
1062
+ onComponent && /* @__PURE__ */ jsxs2(ModeBtn, { onClick: onComponent, children: [
1063
+ /* @__PURE__ */ jsx2(ComponentIcon, {}),
1064
+ "Component"
1065
+ ] })
1066
+ ]
1067
+ }
1068
+ )
1069
+ ]
1238
1070
  }
1239
1071
  )
1240
1072
  ]
@@ -1245,8 +1077,8 @@ function Row({
1245
1077
  label,
1246
1078
  children
1247
1079
  }) {
1248
- return /* @__PURE__ */ jsxs3("div", { style: { display: "flex", alignItems: "center", gap: 6 }, children: [
1249
- /* @__PURE__ */ jsx3(
1080
+ return /* @__PURE__ */ jsxs2("div", { style: { display: "flex", alignItems: "center", gap: 6 }, children: [
1081
+ /* @__PURE__ */ jsx2(
1250
1082
  "span",
1251
1083
  {
1252
1084
  style: {
@@ -1263,7 +1095,7 @@ function Row({
1263
1095
  ] });
1264
1096
  }
1265
1097
  function Sep({ children }) {
1266
- return /* @__PURE__ */ jsx3(
1098
+ return /* @__PURE__ */ jsx2(
1267
1099
  "span",
1268
1100
  {
1269
1101
  style: {
@@ -1279,7 +1111,7 @@ function Sep({ children }) {
1279
1111
  );
1280
1112
  }
1281
1113
  function Unit({ children }) {
1282
- return /* @__PURE__ */ jsx3(
1114
+ return /* @__PURE__ */ jsx2(
1283
1115
  "span",
1284
1116
  {
1285
1117
  style: {
@@ -1297,10 +1129,10 @@ function NumInput({
1297
1129
  }) {
1298
1130
  const [editing, setEditing] = useState3(false);
1299
1131
  const [text, setText] = useState3(String(value));
1300
- useEffect3(() => {
1132
+ useEffect2(() => {
1301
1133
  if (!editing) setText(String(value));
1302
1134
  }, [value, editing]);
1303
- return /* @__PURE__ */ jsx3(
1135
+ return /* @__PURE__ */ jsx2(
1304
1136
  "input",
1305
1137
  {
1306
1138
  type: "text",
@@ -1337,7 +1169,7 @@ function DropBtn({
1337
1169
  onClick,
1338
1170
  active
1339
1171
  }) {
1340
- return /* @__PURE__ */ jsx3(
1172
+ return /* @__PURE__ */ jsx2(
1341
1173
  "button",
1342
1174
  {
1343
1175
  onClick,
@@ -1358,7 +1190,7 @@ function DropBtn({
1358
1190
  );
1359
1191
  }
1360
1192
  function DropMenu({ children }) {
1361
- return /* @__PURE__ */ jsx3(
1193
+ return /* @__PURE__ */ jsx2(
1362
1194
  "div",
1363
1195
  {
1364
1196
  style: {
@@ -1385,7 +1217,7 @@ function DropItem({
1385
1217
  active,
1386
1218
  accent
1387
1219
  }) {
1388
- return /* @__PURE__ */ jsx3(
1220
+ return /* @__PURE__ */ jsx2(
1389
1221
  "button",
1390
1222
  {
1391
1223
  onClick,
@@ -1406,7 +1238,7 @@ function DropItem({
1406
1238
  );
1407
1239
  }
1408
1240
  function Chevron() {
1409
- return /* @__PURE__ */ jsx3("svg", { width: "10", height: "10", viewBox: "0 0 10 10", fill: "none", children: /* @__PURE__ */ jsx3(
1241
+ return /* @__PURE__ */ jsx2("svg", { width: "10", height: "10", viewBox: "0 0 10 10", fill: "none", children: /* @__PURE__ */ jsx2(
1410
1242
  "path",
1411
1243
  {
1412
1244
  d: "M3 4l2 2.5L7 4",
@@ -1416,8 +1248,8 @@ function Chevron() {
1416
1248
  ) });
1417
1249
  }
1418
1250
  function AspectIcon() {
1419
- return /* @__PURE__ */ jsxs3("svg", { width: "16", height: "16", viewBox: "0 0 16 16", fill: "none", children: [
1420
- /* @__PURE__ */ jsx3(
1251
+ return /* @__PURE__ */ jsxs2("svg", { width: "16", height: "16", viewBox: "0 0 16 16", fill: "none", children: [
1252
+ /* @__PURE__ */ jsx2(
1421
1253
  "rect",
1422
1254
  {
1423
1255
  x: "2",
@@ -1430,7 +1262,7 @@ function AspectIcon() {
1430
1262
  strokeDasharray: "2 1.5"
1431
1263
  }
1432
1264
  ),
1433
- /* @__PURE__ */ jsx3(
1265
+ /* @__PURE__ */ jsx2(
1434
1266
  "line",
1435
1267
  {
1436
1268
  x1: "6.33",
@@ -1442,7 +1274,7 @@ function AspectIcon() {
1442
1274
  strokeDasharray: "1.5 1"
1443
1275
  }
1444
1276
  ),
1445
- /* @__PURE__ */ jsx3(
1277
+ /* @__PURE__ */ jsx2(
1446
1278
  "line",
1447
1279
  {
1448
1280
  x1: "9.67",
@@ -1457,7 +1289,7 @@ function AspectIcon() {
1457
1289
  ] });
1458
1290
  }
1459
1291
  function SavedIcon() {
1460
- return /* @__PURE__ */ jsx3("svg", { width: "16", height: "16", viewBox: "0 0 16 16", fill: "none", children: /* @__PURE__ */ jsx3(
1292
+ return /* @__PURE__ */ jsx2("svg", { width: "16", height: "16", viewBox: "0 0 16 16", fill: "none", children: /* @__PURE__ */ jsx2(
1461
1293
  "rect",
1462
1294
  {
1463
1295
  x: "2",
@@ -1471,15 +1303,200 @@ function SavedIcon() {
1471
1303
  }
1472
1304
  ) });
1473
1305
  }
1306
+ function ModeBtn({
1307
+ children,
1308
+ onClick
1309
+ }) {
1310
+ return /* @__PURE__ */ jsx2(
1311
+ "button",
1312
+ {
1313
+ onClick,
1314
+ style: {
1315
+ display: "flex",
1316
+ alignItems: "center",
1317
+ gap: 5,
1318
+ background: "rgba(255, 255, 255, 0.06)",
1319
+ border: "1px solid rgba(255, 255, 255, 0.08)",
1320
+ borderRadius: 6,
1321
+ color: "rgba(255, 255, 255, 0.55)",
1322
+ cursor: "pointer",
1323
+ fontSize: 11,
1324
+ fontFamily: "inherit",
1325
+ padding: "4px 8px",
1326
+ transition: "background 0.1s, color 0.1s"
1327
+ },
1328
+ onMouseEnter: (e) => {
1329
+ const btn = e.currentTarget;
1330
+ btn.style.background = "rgba(255, 255, 255, 0.12)";
1331
+ btn.style.color = "rgba(255, 255, 255, 0.85)";
1332
+ },
1333
+ onMouseLeave: (e) => {
1334
+ const btn = e.currentTarget;
1335
+ btn.style.background = "rgba(255, 255, 255, 0.06)";
1336
+ btn.style.color = "rgba(255, 255, 255, 0.55)";
1337
+ },
1338
+ children
1339
+ }
1340
+ );
1341
+ }
1342
+ function MagnetToggle({
1343
+ enabled,
1344
+ onToggle
1345
+ }) {
1346
+ return /* @__PURE__ */ jsxs2(
1347
+ "button",
1348
+ {
1349
+ onClick: onToggle,
1350
+ style: {
1351
+ display: "flex",
1352
+ alignItems: "center",
1353
+ gap: 5,
1354
+ background: "none",
1355
+ border: "none",
1356
+ color: enabled ? "rgba(147, 130, 220, 0.9)" : "rgba(255, 255, 255, 0.35)",
1357
+ cursor: "pointer",
1358
+ fontSize: 11,
1359
+ fontFamily: "inherit",
1360
+ padding: "2px 0"
1361
+ },
1362
+ children: [
1363
+ /* @__PURE__ */ jsx2(MagnetIcon, {}),
1364
+ "Magnet"
1365
+ ]
1366
+ }
1367
+ );
1368
+ }
1369
+ function MagnetIcon() {
1370
+ return /* @__PURE__ */ jsxs2("svg", { width: "14", height: "14", viewBox: "0 0 16 16", fill: "none", children: [
1371
+ /* @__PURE__ */ jsx2(
1372
+ "path",
1373
+ {
1374
+ d: "M4 2v4a4 4 0 008 0V2",
1375
+ stroke: "currentColor",
1376
+ strokeWidth: "1.3",
1377
+ strokeLinecap: "round"
1378
+ }
1379
+ ),
1380
+ /* @__PURE__ */ jsx2("line", { x1: "4", y1: "2", x2: "4", y2: "5", stroke: "currentColor", strokeWidth: "2.5", strokeLinecap: "round" }),
1381
+ /* @__PURE__ */ jsx2("line", { x1: "12", y1: "2", x2: "12", y2: "5", stroke: "currentColor", strokeWidth: "2.5", strokeLinecap: "round" })
1382
+ ] });
1383
+ }
1384
+ function ViewportIcon() {
1385
+ return /* @__PURE__ */ jsxs2("svg", { width: "14", height: "14", viewBox: "0 0 16 16", fill: "none", children: [
1386
+ /* @__PURE__ */ jsx2(
1387
+ "rect",
1388
+ {
1389
+ x: "1",
1390
+ y: "2",
1391
+ width: "14",
1392
+ height: "11",
1393
+ rx: "1.5",
1394
+ stroke: "currentColor",
1395
+ strokeWidth: "1.3"
1396
+ }
1397
+ ),
1398
+ /* @__PURE__ */ jsx2(
1399
+ "line",
1400
+ {
1401
+ x1: "1",
1402
+ y1: "14",
1403
+ x2: "15",
1404
+ y2: "14",
1405
+ stroke: "currentColor",
1406
+ strokeWidth: "1.3",
1407
+ strokeLinecap: "round"
1408
+ }
1409
+ )
1410
+ ] });
1411
+ }
1412
+ function FullPageIcon() {
1413
+ return /* @__PURE__ */ jsxs2("svg", { width: "14", height: "14", viewBox: "0 0 16 16", fill: "none", children: [
1414
+ /* @__PURE__ */ jsx2(
1415
+ "rect",
1416
+ {
1417
+ x: "3",
1418
+ y: "1",
1419
+ width: "10",
1420
+ height: "14",
1421
+ rx: "1.5",
1422
+ stroke: "currentColor",
1423
+ strokeWidth: "1.3"
1424
+ }
1425
+ ),
1426
+ /* @__PURE__ */ jsx2(
1427
+ "line",
1428
+ {
1429
+ x1: "5.5",
1430
+ y1: "4",
1431
+ x2: "10.5",
1432
+ y2: "4",
1433
+ stroke: "currentColor",
1434
+ strokeWidth: "1",
1435
+ strokeLinecap: "round"
1436
+ }
1437
+ ),
1438
+ /* @__PURE__ */ jsx2(
1439
+ "line",
1440
+ {
1441
+ x1: "5.5",
1442
+ y1: "6.5",
1443
+ x2: "10.5",
1444
+ y2: "6.5",
1445
+ stroke: "currentColor",
1446
+ strokeWidth: "1",
1447
+ strokeLinecap: "round"
1448
+ }
1449
+ ),
1450
+ /* @__PURE__ */ jsx2(
1451
+ "line",
1452
+ {
1453
+ x1: "5.5",
1454
+ y1: "9",
1455
+ x2: "10.5",
1456
+ y2: "9",
1457
+ stroke: "currentColor",
1458
+ strokeWidth: "1",
1459
+ strokeLinecap: "round"
1460
+ }
1461
+ )
1462
+ ] });
1463
+ }
1464
+ function ComponentIcon() {
1465
+ return /* @__PURE__ */ jsxs2("svg", { width: "14", height: "14", viewBox: "0 0 16 16", fill: "none", children: [
1466
+ /* @__PURE__ */ jsx2(
1467
+ "path",
1468
+ {
1469
+ d: "M3 2l2.5 10 2-3.5L11 11 3 2z",
1470
+ stroke: "currentColor",
1471
+ strokeWidth: "1.2",
1472
+ strokeLinejoin: "round",
1473
+ strokeLinecap: "round"
1474
+ }
1475
+ ),
1476
+ /* @__PURE__ */ jsx2(
1477
+ "rect",
1478
+ {
1479
+ x: "8",
1480
+ y: "5",
1481
+ width: "6.5",
1482
+ height: "6.5",
1483
+ rx: "1",
1484
+ stroke: "currentColor",
1485
+ strokeWidth: "1.1",
1486
+ strokeDasharray: "2 1.5"
1487
+ }
1488
+ )
1489
+ ] });
1490
+ }
1474
1491
 
1475
1492
  // src/overlay/ui/inspector.tsx
1476
- import { useEffect as useEffect4, useRef as useRef4, useCallback as useCallback5, useState as useState4 } from "react";
1477
- import { Fragment as Fragment2, jsx as jsx4, jsxs as jsxs4 } from "react/jsx-runtime";
1493
+ import { useEffect as useEffect3, useRef as useRef3, useCallback as useCallback4, useState as useState4 } from "react";
1494
+ import { Fragment as Fragment2, jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
1478
1495
  function Inspector({ onSelect, onCancel }) {
1479
1496
  const [highlight, setHighlight] = useState4(null);
1480
- const hoveredEl = useRef4(null);
1481
- const styleEl = useRef4(null);
1482
- useEffect4(() => {
1497
+ const hoveredEl = useRef3(null);
1498
+ const styleEl = useRef3(null);
1499
+ useEffect3(() => {
1483
1500
  const style = document.createElement("style");
1484
1501
  style.setAttribute("data-afterbefore", "true");
1485
1502
  style.textContent = "*, *::before, *::after { cursor: crosshair !important; }";
@@ -1489,7 +1506,7 @@ function Inspector({ onSelect, onCancel }) {
1489
1506
  style.remove();
1490
1507
  };
1491
1508
  }, []);
1492
- const isOverlayElement = useCallback5((el) => {
1509
+ const isOverlayElement = useCallback4((el) => {
1493
1510
  let node = el;
1494
1511
  while (node) {
1495
1512
  if (node instanceof HTMLElement && node.dataset.afterbefore) return true;
@@ -1497,7 +1514,7 @@ function Inspector({ onSelect, onCancel }) {
1497
1514
  }
1498
1515
  return false;
1499
1516
  }, []);
1500
- const handleMouseMove = useCallback5(
1517
+ const handleMouseMove = useCallback4(
1501
1518
  (e) => {
1502
1519
  const el = document.elementFromPoint(e.clientX, e.clientY);
1503
1520
  if (!el || !(el instanceof HTMLElement) || isOverlayElement(el)) {
@@ -1517,7 +1534,7 @@ function Inspector({ onSelect, onCancel }) {
1517
1534
  },
1518
1535
  [isOverlayElement]
1519
1536
  );
1520
- const handleClick = useCallback5(
1537
+ const handleClick = useCallback4(
1521
1538
  (e) => {
1522
1539
  e.preventDefault();
1523
1540
  e.stopPropagation();
@@ -1528,7 +1545,7 @@ function Inspector({ onSelect, onCancel }) {
1528
1545
  },
1529
1546
  [onSelect]
1530
1547
  );
1531
- const handleKeyDown = useCallback5(
1548
+ const handleKeyDown = useCallback4(
1532
1549
  (e) => {
1533
1550
  if (e.key === "Escape") {
1534
1551
  onCancel();
@@ -1536,7 +1553,7 @@ function Inspector({ onSelect, onCancel }) {
1536
1553
  },
1537
1554
  [onCancel]
1538
1555
  );
1539
- useEffect4(() => {
1556
+ useEffect3(() => {
1540
1557
  document.addEventListener("mousemove", handleMouseMove, true);
1541
1558
  document.addEventListener("click", handleClick, true);
1542
1559
  document.addEventListener("keydown", handleKeyDown);
@@ -1546,9 +1563,9 @@ function Inspector({ onSelect, onCancel }) {
1546
1563
  document.removeEventListener("keydown", handleKeyDown);
1547
1564
  };
1548
1565
  }, [handleMouseMove, handleClick, handleKeyDown]);
1549
- return /* @__PURE__ */ jsxs4("div", { "data-afterbefore": "true", style: { position: "fixed", inset: 0, zIndex: 2147483646, pointerEvents: "none" }, children: [
1550
- highlight && /* @__PURE__ */ jsxs4(Fragment2, { children: [
1551
- /* @__PURE__ */ jsx4(
1566
+ return /* @__PURE__ */ jsxs3("div", { "data-afterbefore": "true", style: { position: "fixed", inset: 0, zIndex: 2147483646, pointerEvents: "none" }, children: [
1567
+ highlight && /* @__PURE__ */ jsxs3(Fragment2, { children: [
1568
+ /* @__PURE__ */ jsx3(
1552
1569
  "div",
1553
1570
  {
1554
1571
  style: {
@@ -1564,7 +1581,7 @@ function Inspector({ onSelect, onCancel }) {
1564
1581
  }
1565
1582
  }
1566
1583
  ),
1567
- /* @__PURE__ */ jsx4(
1584
+ /* @__PURE__ */ jsx3(
1568
1585
  "div",
1569
1586
  {
1570
1587
  style: {
@@ -1585,7 +1602,7 @@ function Inspector({ onSelect, onCancel }) {
1585
1602
  }
1586
1603
  )
1587
1604
  ] }),
1588
- !highlight && /* @__PURE__ */ jsx4(
1605
+ !highlight && /* @__PURE__ */ jsx3(
1589
1606
  "div",
1590
1607
  {
1591
1608
  style: {
@@ -1609,18 +1626,18 @@ function Inspector({ onSelect, onCancel }) {
1609
1626
  }
1610
1627
 
1611
1628
  // src/overlay/ui/status.tsx
1612
- import { useState as useState5, useRef as useRef5, useEffect as useEffect5, useCallback as useCallback6 } from "react";
1613
- import { jsx as jsx5, jsxs as jsxs5 } from "react/jsx-runtime";
1629
+ import { useState as useState5, useRef as useRef4, useEffect as useEffect4, useCallback as useCallback5 } from "react";
1630
+ import { jsx as jsx4, jsxs as jsxs4 } from "react/jsx-runtime";
1614
1631
  var PANEL_WIDTH = 220;
1615
1632
  function Status({ onReset, position, onClose }) {
1616
- const panelRef = useRef5(null);
1633
+ const panelRef = useRef4(null);
1617
1634
  const [toast, setToast] = useState5(null);
1618
1635
  const [pushing, setPushing] = useState5(false);
1619
- const showToast = useCallback6((message, type) => {
1636
+ const showToast = useCallback5((message, type) => {
1620
1637
  setToast({ message, type });
1621
1638
  setTimeout(() => setToast(null), 3e3);
1622
1639
  }, []);
1623
- useEffect5(() => {
1640
+ useEffect4(() => {
1624
1641
  const handler = (e) => {
1625
1642
  if (panelRef.current && !panelRef.current.contains(e.target)) {
1626
1643
  onClose();
@@ -1629,7 +1646,7 @@ function Status({ onReset, position, onClose }) {
1629
1646
  document.addEventListener("mousedown", handler);
1630
1647
  return () => document.removeEventListener("mousedown", handler);
1631
1648
  }, [onClose]);
1632
- useEffect5(() => {
1649
+ useEffect4(() => {
1633
1650
  const handler = (e) => {
1634
1651
  if (e.key === "Escape") onClose();
1635
1652
  };
@@ -1705,7 +1722,7 @@ function Status({ onReset, position, onClose }) {
1705
1722
  const onLeave = (e) => {
1706
1723
  e.currentTarget.style.background = "transparent";
1707
1724
  };
1708
- return /* @__PURE__ */ jsxs5(
1725
+ return /* @__PURE__ */ jsxs4(
1709
1726
  "div",
1710
1727
  {
1711
1728
  ref: panelRef,
@@ -1724,7 +1741,7 @@ function Status({ onReset, position, onClose }) {
1724
1741
  overflow: "hidden"
1725
1742
  },
1726
1743
  children: [
1727
- /* @__PURE__ */ jsx5(
1744
+ /* @__PURE__ */ jsx4(
1728
1745
  "div",
1729
1746
  {
1730
1747
  style: {
@@ -1737,8 +1754,8 @@ function Status({ onReset, position, onClose }) {
1737
1754
  children: "Before & After captured"
1738
1755
  }
1739
1756
  ),
1740
- /* @__PURE__ */ jsxs5("div", { style: { padding: "0 4px 4px" }, children: [
1741
- /* @__PURE__ */ jsxs5(
1757
+ /* @__PURE__ */ jsxs4("div", { style: { padding: "0 4px 4px" }, children: [
1758
+ /* @__PURE__ */ jsxs4(
1742
1759
  "button",
1743
1760
  {
1744
1761
  style: buttonStyle,
@@ -1746,12 +1763,12 @@ function Status({ onReset, position, onClose }) {
1746
1763
  onMouseEnter: onEnter,
1747
1764
  onMouseLeave: onLeave,
1748
1765
  children: [
1749
- /* @__PURE__ */ jsx5(FolderIcon, {}),
1766
+ /* @__PURE__ */ jsx4(FolderIcon, {}),
1750
1767
  "Open Folder"
1751
1768
  ]
1752
1769
  }
1753
1770
  ),
1754
- /* @__PURE__ */ jsxs5(
1771
+ /* @__PURE__ */ jsxs4(
1755
1772
  "button",
1756
1773
  {
1757
1774
  style: buttonStyle,
@@ -1759,12 +1776,12 @@ function Status({ onReset, position, onClose }) {
1759
1776
  onMouseEnter: onEnter,
1760
1777
  onMouseLeave: onLeave,
1761
1778
  children: [
1762
- /* @__PURE__ */ jsx5(CopyIcon, {}),
1779
+ /* @__PURE__ */ jsx4(CopyIcon, {}),
1763
1780
  "Copy Markdown"
1764
1781
  ]
1765
1782
  }
1766
1783
  ),
1767
- /* @__PURE__ */ jsxs5(
1784
+ /* @__PURE__ */ jsxs4(
1768
1785
  "button",
1769
1786
  {
1770
1787
  style: buttonStyle,
@@ -1773,12 +1790,12 @@ function Status({ onReset, position, onClose }) {
1773
1790
  onMouseEnter: onEnter,
1774
1791
  onMouseLeave: onLeave,
1775
1792
  children: [
1776
- /* @__PURE__ */ jsx5(PushIcon, {}),
1793
+ /* @__PURE__ */ jsx4(PushIcon, {}),
1777
1794
  pushing ? "Pushing..." : "Push to PR"
1778
1795
  ]
1779
1796
  }
1780
1797
  ),
1781
- /* @__PURE__ */ jsx5(
1798
+ /* @__PURE__ */ jsx4(
1782
1799
  "div",
1783
1800
  {
1784
1801
  style: {
@@ -1788,7 +1805,7 @@ function Status({ onReset, position, onClose }) {
1788
1805
  }
1789
1806
  }
1790
1807
  ),
1791
- /* @__PURE__ */ jsxs5(
1808
+ /* @__PURE__ */ jsxs4(
1792
1809
  "button",
1793
1810
  {
1794
1811
  style: { ...buttonStyle, color: "rgba(255,255,255,0.5)" },
@@ -1796,13 +1813,13 @@ function Status({ onReset, position, onClose }) {
1796
1813
  onMouseEnter: onEnter,
1797
1814
  onMouseLeave: onLeave,
1798
1815
  children: [
1799
- /* @__PURE__ */ jsx5(ResetIcon, {}),
1816
+ /* @__PURE__ */ jsx4(ResetIcon, {}),
1800
1817
  "Reset"
1801
1818
  ]
1802
1819
  }
1803
1820
  )
1804
1821
  ] }),
1805
- toast && /* @__PURE__ */ jsx5(
1822
+ toast && /* @__PURE__ */ jsx4(
1806
1823
  "div",
1807
1824
  {
1808
1825
  style: {
@@ -1828,14 +1845,14 @@ function Status({ onReset, position, onClose }) {
1828
1845
  );
1829
1846
  }
1830
1847
  function FolderIcon() {
1831
- return /* @__PURE__ */ jsx5(
1848
+ return /* @__PURE__ */ jsx4(
1832
1849
  "svg",
1833
1850
  {
1834
1851
  width: "14",
1835
1852
  height: "14",
1836
1853
  viewBox: "0 0 14 14",
1837
1854
  style: { color: "rgba(255,255,255,0.5)" },
1838
- children: /* @__PURE__ */ jsx5(
1855
+ children: /* @__PURE__ */ jsx4(
1839
1856
  "path",
1840
1857
  {
1841
1858
  d: "M1.5 3A1.5 1.5 0 013 1.5h2.38a1 1 0 01.72.3L7 2.72a1 1 0 00.72.3H11A1.5 1.5 0 0112.5 4.5v6A1.5 1.5 0 0111 12H3A1.5 1.5 0 011.5 10.5V3z",
@@ -1848,7 +1865,7 @@ function FolderIcon() {
1848
1865
  );
1849
1866
  }
1850
1867
  function CopyIcon() {
1851
- return /* @__PURE__ */ jsxs5(
1868
+ return /* @__PURE__ */ jsxs4(
1852
1869
  "svg",
1853
1870
  {
1854
1871
  width: "14",
@@ -1856,7 +1873,7 @@ function CopyIcon() {
1856
1873
  viewBox: "0 0 14 14",
1857
1874
  style: { color: "rgba(255,255,255,0.5)" },
1858
1875
  children: [
1859
- /* @__PURE__ */ jsx5(
1876
+ /* @__PURE__ */ jsx4(
1860
1877
  "rect",
1861
1878
  {
1862
1879
  x: "4",
@@ -1869,7 +1886,7 @@ function CopyIcon() {
1869
1886
  strokeWidth: "1.3"
1870
1887
  }
1871
1888
  ),
1872
- /* @__PURE__ */ jsx5(
1889
+ /* @__PURE__ */ jsx4(
1873
1890
  "path",
1874
1891
  {
1875
1892
  d: "M10 4V2.5A1.5 1.5 0 008.5 1h-6A1.5 1.5 0 001 2.5v6A1.5 1.5 0 002.5 10H4",
@@ -1883,14 +1900,14 @@ function CopyIcon() {
1883
1900
  );
1884
1901
  }
1885
1902
  function PushIcon() {
1886
- return /* @__PURE__ */ jsx5(
1903
+ return /* @__PURE__ */ jsx4(
1887
1904
  "svg",
1888
1905
  {
1889
1906
  width: "14",
1890
1907
  height: "14",
1891
1908
  viewBox: "0 0 14 14",
1892
1909
  style: { color: "rgba(255,255,255,0.5)" },
1893
- children: /* @__PURE__ */ jsx5(
1910
+ children: /* @__PURE__ */ jsx4(
1894
1911
  "path",
1895
1912
  {
1896
1913
  d: "M7 11V3m0 0L4 6m3-3l3 3",
@@ -1905,7 +1922,7 @@ function PushIcon() {
1905
1922
  );
1906
1923
  }
1907
1924
  function ResetIcon() {
1908
- return /* @__PURE__ */ jsxs5(
1925
+ return /* @__PURE__ */ jsxs4(
1909
1926
  "svg",
1910
1927
  {
1911
1928
  width: "14",
@@ -1913,7 +1930,7 @@ function ResetIcon() {
1913
1930
  viewBox: "0 0 14 14",
1914
1931
  style: { color: "rgba(255,255,255,0.4)" },
1915
1932
  children: [
1916
- /* @__PURE__ */ jsx5(
1933
+ /* @__PURE__ */ jsx4(
1917
1934
  "path",
1918
1935
  {
1919
1936
  d: "M2.5 7a4.5 4.5 0 118 2.5",
@@ -1923,7 +1940,7 @@ function ResetIcon() {
1923
1940
  strokeLinecap: "round"
1924
1941
  }
1925
1942
  ),
1926
- /* @__PURE__ */ jsx5(
1943
+ /* @__PURE__ */ jsx4(
1927
1944
  "path",
1928
1945
  {
1929
1946
  d: "M2.5 3v4h4",
@@ -1940,7 +1957,7 @@ function ResetIcon() {
1940
1957
  }
1941
1958
 
1942
1959
  // src/overlay/index.tsx
1943
- import { jsx as jsx6, jsxs as jsxs6 } from "react/jsx-runtime";
1960
+ import { jsx as jsx5, jsxs as jsxs5 } from "react/jsx-runtime";
1944
1961
  async function saveCapture(type, mode, dataUrl) {
1945
1962
  try {
1946
1963
  const res = await fetch("/__afterbefore/save", {
@@ -1958,39 +1975,36 @@ async function saveCapture(type, mode, dataUrl) {
1958
1975
  }
1959
1976
  function AfterBefore() {
1960
1977
  const { state, captureComplete, reset } = useOverlayState();
1961
- const [menuOpen, setMenuOpen] = useState6(false);
1962
1978
  const [statusOpen, setStatusOpen] = useState6(false);
1963
1979
  const [selectorActive, setSelectorActive] = useState6(false);
1964
1980
  const [inspectorActive, setInspectorActive] = useState6(false);
1965
1981
  const [loading, setLoading] = useState6(false);
1966
- const iconPos = useRef6({ x: 24, y: 0 });
1967
- useEffect6(() => {
1982
+ const iconPos = useRef5({ x: 24, y: 0 });
1983
+ useEffect5(() => {
1968
1984
  if (state.phase === "ready") {
1969
1985
  const timer = setTimeout(() => {
1970
1986
  reset();
1971
1987
  setStatusOpen(false);
1972
- setMenuOpen(false);
1973
1988
  }, 1500);
1974
1989
  return () => clearTimeout(timer);
1975
1990
  }
1976
1991
  }, [state.phase, reset]);
1977
- const handlePositionChange = useCallback7(
1992
+ const handlePositionChange = useCallback6(
1978
1993
  (pos) => {
1979
1994
  iconPos.current = pos;
1980
1995
  },
1981
1996
  []
1982
1997
  );
1983
- const handleIconClick = useCallback7(() => {
1998
+ const handleIconClick = useCallback6(() => {
1984
1999
  if (loading) return;
1985
2000
  if (state.phase === "ready") {
1986
2001
  setStatusOpen((prev) => !prev);
1987
- setMenuOpen(false);
1988
2002
  } else {
1989
- setMenuOpen((prev) => !prev);
2003
+ setSelectorActive((prev) => !prev);
1990
2004
  setStatusOpen(false);
1991
2005
  }
1992
2006
  }, [state.phase, loading]);
1993
- const performCapture = useCallback7(
2007
+ const performCapture = useCallback6(
1994
2008
  async (mode, area, element) => {
1995
2009
  setLoading(true);
1996
2010
  try {
@@ -2010,52 +2024,47 @@ function AfterBefore() {
2010
2024
  },
2011
2025
  [state.phase, captureComplete]
2012
2026
  );
2013
- const handleModeSelect = useCallback7(
2014
- (mode) => {
2015
- setMenuOpen(false);
2016
- if (mode === "area") {
2017
- setSelectorActive(true);
2018
- } else if (mode === "component") {
2019
- setInspectorActive(true);
2020
- } else {
2021
- performCapture(mode);
2022
- }
2023
- },
2024
- [performCapture]
2025
- );
2026
- const handleComponentSelect = useCallback7(
2027
+ const handleViewportCapture = useCallback6(() => {
2028
+ setSelectorActive(false);
2029
+ performCapture("viewport");
2030
+ }, [performCapture]);
2031
+ const handleFullPageCapture = useCallback6(() => {
2032
+ setSelectorActive(false);
2033
+ performCapture("fullpage");
2034
+ }, [performCapture]);
2035
+ const handleComponentMode = useCallback6(() => {
2036
+ setSelectorActive(false);
2037
+ setInspectorActive(true);
2038
+ }, []);
2039
+ const handleComponentSelect = useCallback6(
2027
2040
  (element) => {
2028
2041
  setInspectorActive(false);
2029
2042
  performCapture("component", void 0, element);
2030
2043
  },
2031
2044
  [performCapture]
2032
2045
  );
2033
- const handleComponentCancel = useCallback7(() => {
2046
+ const handleComponentCancel = useCallback6(() => {
2034
2047
  setInspectorActive(false);
2035
2048
  }, []);
2036
- const handleAreaSelect = useCallback7(
2049
+ const handleAreaSelect = useCallback6(
2037
2050
  (area) => {
2038
2051
  setSelectorActive(false);
2039
2052
  performCapture("area", area);
2040
2053
  },
2041
2054
  [performCapture]
2042
2055
  );
2043
- const handleAreaCancel = useCallback7(() => {
2056
+ const handleAreaCancel = useCallback6(() => {
2044
2057
  setSelectorActive(false);
2045
2058
  }, []);
2046
- const handleReset = useCallback7(() => {
2059
+ const handleReset = useCallback6(() => {
2047
2060
  reset();
2048
2061
  setStatusOpen(false);
2049
- setMenuOpen(false);
2050
2062
  }, [reset]);
2051
- const handleMenuClose = useCallback7(() => {
2052
- setMenuOpen(false);
2053
- }, []);
2054
- const handleStatusClose = useCallback7(() => {
2063
+ const handleStatusClose = useCallback6(() => {
2055
2064
  setStatusOpen(false);
2056
2065
  }, []);
2057
- return /* @__PURE__ */ jsxs6("div", { "data-afterbefore": "true", children: [
2058
- /* @__PURE__ */ jsx6(
2066
+ return /* @__PURE__ */ jsxs5("div", { "data-afterbefore": "true", children: [
2067
+ /* @__PURE__ */ jsx5(
2059
2068
  Icon,
2060
2069
  {
2061
2070
  phase: state.phase,
@@ -2064,17 +2073,18 @@ function AfterBefore() {
2064
2073
  onPositionChange: handlePositionChange
2065
2074
  }
2066
2075
  ),
2067
- menuOpen && (state.phase === "idle" || state.phase === "captured-before") && /* @__PURE__ */ jsx6(
2068
- Menu,
2076
+ selectorActive && /* @__PURE__ */ jsx5(
2077
+ Selector,
2069
2078
  {
2070
- onSelect: handleModeSelect,
2071
- onClose: handleMenuClose,
2072
- position: iconPos.current
2079
+ onSelect: handleAreaSelect,
2080
+ onCancel: handleAreaCancel,
2081
+ onViewport: handleViewportCapture,
2082
+ onFullPage: handleFullPageCapture,
2083
+ onComponent: handleComponentMode
2073
2084
  }
2074
2085
  ),
2075
- selectorActive && /* @__PURE__ */ jsx6(Selector, { onSelect: handleAreaSelect, onCancel: handleAreaCancel }),
2076
- inspectorActive && /* @__PURE__ */ jsx6(Inspector, { onSelect: handleComponentSelect, onCancel: handleComponentCancel }),
2077
- statusOpen && state.phase === "ready" && /* @__PURE__ */ jsx6(
2086
+ inspectorActive && /* @__PURE__ */ jsx5(Inspector, { onSelect: handleComponentSelect, onCancel: handleComponentCancel }),
2087
+ statusOpen && state.phase === "ready" && /* @__PURE__ */ jsx5(
2078
2088
  Status,
2079
2089
  {
2080
2090
  onReset: handleReset,