afterbefore 0.2.2 → 0.2.3

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 useState5, useCallback as useCallback6, useRef as useRef5 } from "react";
4
+ import { useState as useState6, useCallback as useCallback7, useRef as useRef6 } from "react";
5
5
 
6
6
  // src/overlay/state.ts
7
7
  import { useState, useCallback } from "react";
@@ -35,7 +35,7 @@ function useOverlayState() {
35
35
  // src/overlay/capture.ts
36
36
  import { toPng } from "html-to-image";
37
37
  async function capture(options) {
38
- const { mode, area } = options;
38
+ const { mode, area, element } = options;
39
39
  if (mode === "viewport") {
40
40
  return captureViewport();
41
41
  }
@@ -45,6 +45,9 @@ async function capture(options) {
45
45
  if (mode === "area" && area) {
46
46
  return captureArea(area);
47
47
  }
48
+ if (mode === "component" && element) {
49
+ return captureComponent(element);
50
+ }
48
51
  throw new Error(`Invalid capture mode: ${mode}`);
49
52
  }
50
53
  async function captureViewport() {
@@ -102,6 +105,12 @@ async function captureArea(area) {
102
105
  );
103
106
  return canvas.toDataURL("image/png");
104
107
  }
108
+ async function captureComponent(element) {
109
+ const dataUrl = await toPng(element, {
110
+ filter: filterOverlay
111
+ });
112
+ return dataUrl;
113
+ }
105
114
  function loadImage(src) {
106
115
  return new Promise((resolve, reject) => {
107
116
  const img = new Image();
@@ -466,9 +475,41 @@ var modes = [
466
475
  }
467
476
  )
468
477
  ] })
478
+ },
479
+ {
480
+ mode: "component",
481
+ label: "Component",
482
+ icon: /* @__PURE__ */ jsxs2("svg", { width: "16", height: "16", viewBox: "0 0 16 16", children: [
483
+ /* @__PURE__ */ jsx2(
484
+ "path",
485
+ {
486
+ d: "M3 2l2.5 10 2-3.5L11 11 3 2z",
487
+ fill: "none",
488
+ stroke: "currentColor",
489
+ strokeWidth: "1.3",
490
+ strokeLinejoin: "round",
491
+ strokeLinecap: "round"
492
+ }
493
+ ),
494
+ /* @__PURE__ */ jsx2(
495
+ "rect",
496
+ {
497
+ x: "8",
498
+ y: "5",
499
+ width: "6.5",
500
+ height: "6.5",
501
+ rx: "1",
502
+ fill: "none",
503
+ stroke: "currentColor",
504
+ strokeWidth: "1.2",
505
+ strokeDasharray: "2 1.5"
506
+ }
507
+ )
508
+ ] })
469
509
  }
470
510
  ];
471
- var MENU_WIDTH = 160;
511
+ var PILL_WIDTH = 220;
512
+ var BTN_SIZE = 36;
472
513
  function Menu({ onSelect, onClose, position }) {
473
514
  const menuRef = useRef2(null);
474
515
  const handleClickOutside = useCallback3(
@@ -497,10 +538,10 @@ function Menu({ onSelect, onClose, position }) {
497
538
  }, [handleClickOutside, handleKeyDown]);
498
539
  const menuLeft = Math.max(
499
540
  8,
500
- Math.min(position.x - MENU_WIDTH / 2 + 20, window.innerWidth - MENU_WIDTH - 8)
541
+ Math.min(position.x - PILL_WIDTH / 2 + 20, window.innerWidth - PILL_WIDTH - 8)
501
542
  );
502
543
  const menuBottom = window.innerHeight - position.y + 8;
503
- return /* @__PURE__ */ jsx2(
544
+ return /* @__PURE__ */ jsxs2(
504
545
  "div",
505
546
  {
506
547
  ref: menuRef,
@@ -509,128 +550,338 @@ function Menu({ onSelect, onClose, position }) {
509
550
  position: "fixed",
510
551
  left: menuLeft,
511
552
  bottom: menuBottom,
512
- width: MENU_WIDTH,
553
+ display: "flex",
554
+ alignItems: "center",
555
+ gap: 2,
513
556
  background: "rgba(24, 24, 27, 0.95)",
514
- borderRadius: 10,
515
- padding: 4,
557
+ borderRadius: 22,
558
+ padding: "4px 4px",
516
559
  boxShadow: "0 4px 20px rgba(0,0,0,0.4), 0 0 0 1px rgba(255,255,255,0.08)",
517
560
  zIndex: 2147483647,
518
561
  fontFamily: "system-ui, -apple-system, sans-serif",
519
562
  backdropFilter: "blur(12px)"
520
563
  },
521
- children: modes.map(({ mode, label, icon }) => /* @__PURE__ */ jsxs2(
522
- "button",
523
- {
524
- onClick: () => onSelect(mode),
525
- style: {
526
- display: "flex",
527
- alignItems: "center",
528
- gap: 8,
529
- width: "100%",
530
- padding: "8px 10px",
531
- border: "none",
532
- background: "transparent",
533
- color: "rgba(255,255,255,0.9)",
534
- fontSize: 13,
535
- borderRadius: 6,
536
- cursor: "pointer",
537
- textAlign: "left",
538
- transition: "background 0.1s"
539
- },
540
- onMouseEnter: (e) => {
541
- e.currentTarget.style.background = "rgba(255,255,255,0.1)";
542
- },
543
- onMouseLeave: (e) => {
544
- e.currentTarget.style.background = "transparent";
564
+ children: [
565
+ modes.map(({ mode, label, icon }) => /* @__PURE__ */ jsx2(
566
+ "button",
567
+ {
568
+ title: label,
569
+ onClick: () => onSelect(mode),
570
+ style: {
571
+ display: "flex",
572
+ alignItems: "center",
573
+ justifyContent: "center",
574
+ width: BTN_SIZE,
575
+ height: BTN_SIZE,
576
+ border: "none",
577
+ background: "transparent",
578
+ color: "rgba(255,255,255,0.7)",
579
+ borderRadius: "50%",
580
+ cursor: "pointer",
581
+ padding: 0,
582
+ transition: "background 0.1s, color 0.1s",
583
+ flexShrink: 0
584
+ },
585
+ onMouseEnter: (e) => {
586
+ const btn = e.currentTarget;
587
+ btn.style.background = "rgba(255,255,255,0.12)";
588
+ btn.style.color = "rgba(255,255,255,1)";
589
+ },
590
+ onMouseLeave: (e) => {
591
+ const btn = e.currentTarget;
592
+ btn.style.background = "transparent";
593
+ btn.style.color = "rgba(255,255,255,0.7)";
594
+ },
595
+ children: icon
545
596
  },
546
- children: [
547
- /* @__PURE__ */ jsx2(
548
- "span",
597
+ mode
598
+ )),
599
+ /* @__PURE__ */ jsx2(
600
+ "div",
601
+ {
602
+ style: {
603
+ width: 1,
604
+ height: 20,
605
+ background: "rgba(255,255,255,0.12)",
606
+ flexShrink: 0,
607
+ margin: "0 2px"
608
+ }
609
+ }
610
+ ),
611
+ /* @__PURE__ */ jsx2(
612
+ "button",
613
+ {
614
+ title: "Close",
615
+ onClick: onClose,
616
+ style: {
617
+ display: "flex",
618
+ alignItems: "center",
619
+ justifyContent: "center",
620
+ width: BTN_SIZE,
621
+ height: BTN_SIZE,
622
+ border: "none",
623
+ background: "transparent",
624
+ color: "rgba(255,255,255,0.45)",
625
+ borderRadius: "50%",
626
+ cursor: "pointer",
627
+ padding: 0,
628
+ transition: "background 0.1s, color 0.1s",
629
+ flexShrink: 0
630
+ },
631
+ onMouseEnter: (e) => {
632
+ const btn = e.currentTarget;
633
+ btn.style.background = "rgba(255,255,255,0.12)";
634
+ btn.style.color = "rgba(255,255,255,0.9)";
635
+ },
636
+ onMouseLeave: (e) => {
637
+ const btn = e.currentTarget;
638
+ btn.style.background = "transparent";
639
+ btn.style.color = "rgba(255,255,255,0.45)";
640
+ },
641
+ children: /* @__PURE__ */ jsx2("svg", { width: "14", height: "14", viewBox: "0 0 14 14", children: /* @__PURE__ */ jsx2(
642
+ "path",
549
643
  {
550
- style: {
551
- display: "flex",
552
- alignItems: "center",
553
- color: "rgba(255,255,255,0.6)"
554
- },
555
- children: icon
644
+ d: "M3 3l8 8M11 3l-8 8",
645
+ stroke: "currentColor",
646
+ strokeWidth: "1.5",
647
+ strokeLinecap: "round"
556
648
  }
557
- ),
558
- label
559
- ]
560
- },
561
- mode
562
- ))
649
+ ) })
650
+ }
651
+ )
652
+ ]
563
653
  }
564
654
  );
565
655
  }
566
656
 
567
657
  // src/overlay/ui/selector.tsx
568
- import { useState as useState3, useRef as useRef3, useCallback as useCallback4, useEffect as useEffect3 } from "react";
658
+ import React3, { useState as useState3, useRef as useRef3, useCallback as useCallback4, useEffect as useEffect3 } from "react";
569
659
  import { Fragment, jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
570
- var MIN_SIZE = 10;
660
+ var HANDLE_R = 6;
661
+ var HANDLE_HIT = 16;
662
+ var MIN_SIZE = 20;
663
+ var PANEL_HEIGHT_EST = 200;
664
+ var ASPECT_RATIOS = [
665
+ { label: "Free", value: 0 },
666
+ { label: "16:9", value: 16 / 9 },
667
+ { label: "4:3", value: 4 / 3 },
668
+ { label: "1:1", value: 1 },
669
+ { label: "3:2", value: 3 / 2 },
670
+ { label: "21:9", value: 21 / 9 }
671
+ ];
571
672
  function Selector({ onSelect, onCancel }) {
572
- const [selection, setSelection] = useState3(null);
573
- const dragging = useRef3(false);
574
- const handleKeyDown = useCallback4(
575
- (e) => {
673
+ const [rect, setRect] = useState3(null);
674
+ const [placed, setPlaced] = useState3(false);
675
+ const [aspect, setAspect] = useState3("Free");
676
+ const [aspectOpen, setAspectOpen] = useState3(false);
677
+ const [savedOpen, setSavedOpen] = useState3(false);
678
+ const [presets, setPresets] = useState3(
679
+ () => {
680
+ try {
681
+ const s = localStorage.getItem("ab-area-presets");
682
+ return s ? JSON.parse(s) : [];
683
+ } catch {
684
+ return [];
685
+ }
686
+ }
687
+ );
688
+ const [cursor, setCursor] = useState3("crosshair");
689
+ const mode = useRef3("none");
690
+ const start = useRef3({ x: 0, y: 0 });
691
+ const snap = useRef3({ x: 0, y: 0, w: 0, h: 0 });
692
+ const corner = useRef3("br");
693
+ const panelRef = useRef3(null);
694
+ const ratio = ASPECT_RATIOS.find((a) => a.label === aspect)?.value ?? 0;
695
+ useEffect3(() => {
696
+ const onKey = (e) => {
697
+ if (e.target?.tagName === "INPUT") {
698
+ if (e.key === "Escape") e.target.blur();
699
+ return;
700
+ }
576
701
  if (e.key === "Escape") {
577
- onCancel();
702
+ if (placed) {
703
+ setPlaced(false);
704
+ setRect(null);
705
+ } else {
706
+ onCancel();
707
+ }
708
+ } else if (e.key === "Enter" && placed && rect) {
709
+ onSelect({
710
+ x: Math.round(rect.x),
711
+ y: Math.round(rect.y),
712
+ width: Math.round(rect.w),
713
+ height: Math.round(rect.h)
714
+ });
715
+ }
716
+ };
717
+ document.addEventListener("keydown", onKey);
718
+ return () => document.removeEventListener("keydown", onKey);
719
+ }, [placed, rect, onSelect, onCancel]);
720
+ const hitCorner = useCallback4(
721
+ (mx, my, r) => {
722
+ const cs = [
723
+ ["tl", r.x, r.y],
724
+ ["tr", r.x + r.w, r.y],
725
+ ["bl", r.x, r.y + r.h],
726
+ ["br", r.x + r.w, r.y + r.h]
727
+ ];
728
+ for (const [c, cx, cy] of cs) {
729
+ if (Math.abs(mx - cx) <= HANDLE_HIT && Math.abs(my - cy) <= HANDLE_HIT)
730
+ return c;
578
731
  }
732
+ return null;
579
733
  },
580
- [onCancel]
734
+ []
581
735
  );
582
- useEffect3(() => {
583
- document.addEventListener("keydown", handleKeyDown);
584
- return () => document.removeEventListener("keydown", handleKeyDown);
585
- }, [handleKeyDown]);
586
- const handleMouseDown = useCallback4((e) => {
587
- e.preventDefault();
588
- dragging.current = true;
589
- setSelection({
590
- startX: e.clientX,
591
- startY: e.clientY,
592
- endX: e.clientX,
593
- endY: e.clientY
594
- });
595
- }, []);
596
- const handleMouseMove = useCallback4((e) => {
597
- if (!dragging.current) return;
598
- setSelection((prev) => {
599
- if (!prev) return prev;
600
- return { ...prev, endX: e.clientX, endY: e.clientY };
601
- });
602
- }, []);
603
- const handleMouseUp = useCallback4(() => {
604
- if (!dragging.current || !selection) return;
605
- dragging.current = false;
606
- const x = Math.min(selection.startX, selection.endX);
607
- const y = Math.min(selection.startY, selection.endY);
608
- const width = Math.abs(selection.endX - selection.startX);
609
- const height = Math.abs(selection.endY - selection.startY);
610
- if (width >= MIN_SIZE && height >= MIN_SIZE) {
611
- onSelect({ x, y, width, height });
612
- } else {
613
- setSelection(null);
736
+ const hitInside = useCallback4(
737
+ (mx, my, r) => mx >= r.x && mx <= r.x + r.w && my >= r.y && my <= r.y + r.h,
738
+ []
739
+ );
740
+ const onDown = useCallback4(
741
+ (e) => {
742
+ if (panelRef.current?.contains(e.target)) return;
743
+ e.preventDefault();
744
+ setAspectOpen(false);
745
+ setSavedOpen(false);
746
+ const mx = e.clientX, my = e.clientY;
747
+ if (placed && rect) {
748
+ const c = hitCorner(mx, my, rect);
749
+ if (c) {
750
+ mode.current = "resizing";
751
+ corner.current = c;
752
+ start.current = { x: mx, y: my };
753
+ snap.current = { ...rect };
754
+ return;
755
+ }
756
+ if (hitInside(mx, my, rect)) {
757
+ mode.current = "moving";
758
+ start.current = { x: mx, y: my };
759
+ snap.current = { ...rect };
760
+ return;
761
+ }
762
+ setPlaced(false);
763
+ }
764
+ mode.current = "drawing";
765
+ start.current = { x: mx, y: my };
766
+ setRect({ x: mx, y: my, w: 0, h: 0 });
767
+ },
768
+ [placed, rect, hitCorner, hitInside]
769
+ );
770
+ const onMove = useCallback4(
771
+ (e) => {
772
+ const mx = e.clientX, my = e.clientY;
773
+ if (mode.current === "drawing") {
774
+ const sx = start.current.x, sy = start.current.y;
775
+ let x = Math.min(sx, mx), y = Math.min(sy, my);
776
+ let w = Math.abs(mx - sx), h = Math.abs(my - sy);
777
+ if (ratio > 0) {
778
+ h = w / ratio;
779
+ if (my < sy) y = sy - h;
780
+ }
781
+ setRect({ x, y, w, h });
782
+ } else if (mode.current === "moving") {
783
+ const dx = mx - start.current.x, dy = my - start.current.y;
784
+ setRect({
785
+ ...snap.current,
786
+ x: snap.current.x + dx,
787
+ y: snap.current.y + dy
788
+ });
789
+ } else if (mode.current === "resizing") {
790
+ const o = snap.current;
791
+ const c = corner.current;
792
+ const nr = { ...o };
793
+ if (c === "br") {
794
+ nr.w = Math.max(MIN_SIZE, mx - o.x);
795
+ nr.h = ratio > 0 ? nr.w / ratio : Math.max(MIN_SIZE, my - o.y);
796
+ } else if (c === "bl") {
797
+ nr.w = Math.max(MIN_SIZE, o.x + o.w - mx);
798
+ nr.x = o.x + o.w - nr.w;
799
+ nr.h = ratio > 0 ? nr.w / ratio : Math.max(MIN_SIZE, my - o.y);
800
+ } else if (c === "tr") {
801
+ nr.w = Math.max(MIN_SIZE, mx - o.x);
802
+ nr.h = ratio > 0 ? nr.w / ratio : Math.max(MIN_SIZE, o.y + o.h - my);
803
+ nr.y = o.y + o.h - nr.h;
804
+ } else {
805
+ nr.w = Math.max(MIN_SIZE, o.x + o.w - mx);
806
+ nr.h = ratio > 0 ? nr.w / ratio : Math.max(MIN_SIZE, o.y + o.h - my);
807
+ nr.x = o.x + o.w - nr.w;
808
+ nr.y = o.y + o.h - nr.h;
809
+ }
810
+ setRect(nr);
811
+ } else if (placed && rect) {
812
+ const c = hitCorner(mx, my, rect);
813
+ if (c === "tl" || c === "br") setCursor("nwse-resize");
814
+ else if (c === "tr" || c === "bl") setCursor("nesw-resize");
815
+ else if (hitInside(mx, my, rect)) setCursor("move");
816
+ else setCursor("crosshair");
817
+ }
818
+ },
819
+ [ratio, placed, rect, hitCorner, hitInside]
820
+ );
821
+ const onUp = useCallback4(() => {
822
+ if (mode.current === "drawing" && rect) {
823
+ if (rect.w >= MIN_SIZE && rect.h >= MIN_SIZE) {
824
+ setPlaced(true);
825
+ } else {
826
+ setRect(null);
827
+ }
614
828
  }
615
- }, [selection, onSelect]);
616
- const rect = selection ? {
617
- x: Math.min(selection.startX, selection.endX),
618
- y: Math.min(selection.startY, selection.endY),
619
- w: Math.abs(selection.endX - selection.startX),
620
- h: Math.abs(selection.endY - selection.startY)
621
- } : null;
829
+ mode.current = "none";
830
+ }, [rect]);
831
+ const setSize = useCallback4(
832
+ (field, v) => {
833
+ const n = parseInt(v, 10);
834
+ if (isNaN(n) || n < MIN_SIZE || !rect) return;
835
+ if (field === "w") {
836
+ setRect({ ...rect, w: n, h: ratio > 0 ? n / ratio : rect.h });
837
+ } else {
838
+ setRect({ ...rect, h: n, w: ratio > 0 ? n * ratio : rect.w });
839
+ }
840
+ },
841
+ [rect, ratio]
842
+ );
843
+ const setPos = useCallback4(
844
+ (field, v) => {
845
+ const n = parseInt(v, 10);
846
+ if (isNaN(n) || !rect) return;
847
+ setRect({ ...rect, [field]: n });
848
+ },
849
+ [rect]
850
+ );
851
+ const savePreset = useCallback4(() => {
852
+ if (!rect) return;
853
+ const label = `${Math.round(rect.w)}\xD7${Math.round(rect.h)}`;
854
+ const next = [
855
+ ...presets.filter((p) => p.label !== label),
856
+ { label, rect: { ...rect } }
857
+ ];
858
+ setPresets(next);
859
+ try {
860
+ localStorage.setItem("ab-area-presets", JSON.stringify(next));
861
+ } catch {
862
+ }
863
+ setSavedOpen(false);
864
+ }, [rect, presets]);
865
+ const loadPreset = useCallback4((p) => {
866
+ setRect({ ...p.rect });
867
+ setPlaced(true);
868
+ setSavedOpen(false);
869
+ }, []);
870
+ const activeCursor = mode.current === "moving" ? "move" : mode.current === "resizing" ? "nwse-resize" : mode.current === "drawing" ? "crosshair" : cursor;
871
+ const panelAbove = rect && rect.y + rect.h + PANEL_HEIGHT_EST > window.innerHeight;
872
+ const hasRect = rect && rect.w > 0 && rect.h > 0;
622
873
  return /* @__PURE__ */ jsxs3(
623
874
  "div",
624
875
  {
625
876
  "data-afterbefore": "true",
626
- onMouseDown: handleMouseDown,
627
- onMouseMove: handleMouseMove,
628
- onMouseUp: handleMouseUp,
877
+ onMouseDown: onDown,
878
+ onMouseMove: onMove,
879
+ onMouseUp: onUp,
629
880
  style: {
630
881
  position: "fixed",
631
882
  inset: 0,
632
883
  zIndex: 2147483647,
633
- cursor: "crosshair"
884
+ cursor: activeCursor
634
885
  },
635
886
  children: [
636
887
  /* @__PURE__ */ jsx3(
@@ -639,9 +890,9 @@ function Selector({ onSelect, onCancel }) {
639
890
  style: {
640
891
  position: "absolute",
641
892
  inset: 0,
642
- background: "rgba(0, 0, 0, 0.4)",
893
+ background: "rgba(0, 0, 0, 0.5)",
643
894
  pointerEvents: "none",
644
- ...rect && rect.w > 0 && rect.h > 0 ? {
895
+ ...hasRect ? {
645
896
  clipPath: `polygon(
646
897
  0% 0%, 0% 100%, 100% 100%, 100% 0%, 0% 0%,
647
898
  ${rect.x}px ${rect.y}px,
@@ -654,7 +905,7 @@ function Selector({ onSelect, onCancel }) {
654
905
  }
655
906
  }
656
907
  ),
657
- rect && rect.w > 0 && rect.h > 0 && /* @__PURE__ */ jsxs3(Fragment, { children: [
908
+ hasRect && /* @__PURE__ */ jsxs3(Fragment, { children: [
658
909
  /* @__PURE__ */ jsx3(
659
910
  "div",
660
911
  {
@@ -664,14 +915,245 @@ function Selector({ onSelect, onCancel }) {
664
915
  top: rect.y,
665
916
  width: rect.w,
666
917
  height: rect.h,
667
- border: "2px solid rgba(59, 130, 246, 0.8)",
668
- borderRadius: 2,
669
- pointerEvents: "none",
670
- boxShadow: "0 0 0 1px rgba(0,0,0,0.3)"
918
+ border: "1.5px dashed rgba(255, 255, 255, 0.45)",
919
+ pointerEvents: "none"
920
+ }
921
+ }
922
+ ),
923
+ placed && [1, 2].map((i) => /* @__PURE__ */ jsxs3(React3.Fragment, { children: [
924
+ /* @__PURE__ */ jsx3(
925
+ "div",
926
+ {
927
+ style: {
928
+ position: "absolute",
929
+ left: rect.x + rect.w * i / 3,
930
+ top: rect.y,
931
+ width: 0,
932
+ height: rect.h,
933
+ borderLeft: "1px dashed rgba(255, 255, 255, 0.18)",
934
+ pointerEvents: "none"
935
+ }
671
936
  }
937
+ ),
938
+ /* @__PURE__ */ jsx3(
939
+ "div",
940
+ {
941
+ style: {
942
+ position: "absolute",
943
+ left: rect.x,
944
+ top: rect.y + rect.h * i / 3,
945
+ width: rect.w,
946
+ height: 0,
947
+ borderTop: "1px dashed rgba(255, 255, 255, 0.18)",
948
+ pointerEvents: "none"
949
+ }
950
+ }
951
+ )
952
+ ] }, i)),
953
+ placed && [
954
+ [rect.x, rect.y],
955
+ [rect.x + rect.w, rect.y],
956
+ [rect.x, rect.y + rect.h],
957
+ [rect.x + rect.w, rect.y + rect.h]
958
+ ].map(([cx, cy], i) => /* @__PURE__ */ jsx3(
959
+ "div",
960
+ {
961
+ style: {
962
+ position: "absolute",
963
+ left: cx - HANDLE_R,
964
+ top: cy - HANDLE_R,
965
+ width: HANDLE_R * 2,
966
+ height: HANDLE_R * 2,
967
+ borderRadius: "50%",
968
+ border: "2px solid rgba(255, 255, 255, 0.8)",
969
+ background: "rgba(0, 0, 0, 0.25)",
970
+ pointerEvents: "none"
971
+ }
972
+ },
973
+ i
974
+ )),
975
+ placed && /* @__PURE__ */ jsxs3(
976
+ "div",
977
+ {
978
+ ref: panelRef,
979
+ onMouseDown: (e) => e.stopPropagation(),
980
+ style: {
981
+ position: "absolute",
982
+ left: rect.x + rect.w / 2,
983
+ ...panelAbove ? { bottom: window.innerHeight - rect.y + 16 } : { top: rect.y + rect.h + 16 },
984
+ transform: "translateX(-50%)",
985
+ background: "rgba(32, 32, 36, 0.92)",
986
+ backdropFilter: "blur(20px)",
987
+ WebkitBackdropFilter: "blur(20px)",
988
+ border: "1px solid rgba(255, 255, 255, 0.1)",
989
+ borderRadius: 14,
990
+ padding: "16px 24px",
991
+ display: "flex",
992
+ flexDirection: "column",
993
+ gap: 12,
994
+ minWidth: 340,
995
+ fontFamily: "system-ui, -apple-system, sans-serif",
996
+ color: "rgba(255, 255, 255, 0.9)",
997
+ boxShadow: "0 8px 32px rgba(0, 0, 0, 0.4)",
998
+ zIndex: 1
999
+ },
1000
+ children: [
1001
+ /* @__PURE__ */ jsxs3(Row, { label: "Size", children: [
1002
+ /* @__PURE__ */ jsx3(
1003
+ NumInput,
1004
+ {
1005
+ value: Math.round(rect.w),
1006
+ onChange: (v) => setSize("w", v)
1007
+ }
1008
+ ),
1009
+ /* @__PURE__ */ jsx3(Sep, { children: "\\u00d7" }),
1010
+ /* @__PURE__ */ jsx3(
1011
+ NumInput,
1012
+ {
1013
+ value: Math.round(rect.h),
1014
+ onChange: (v) => setSize("h", v)
1015
+ }
1016
+ ),
1017
+ /* @__PURE__ */ jsx3(Unit, { children: "px" })
1018
+ ] }),
1019
+ /* @__PURE__ */ jsxs3(Row, { label: "Position", children: [
1020
+ /* @__PURE__ */ jsx3(
1021
+ NumInput,
1022
+ {
1023
+ value: Math.round(rect.x),
1024
+ onChange: (v) => setPos("x", v)
1025
+ }
1026
+ ),
1027
+ /* @__PURE__ */ jsx3(Sep, {}),
1028
+ /* @__PURE__ */ jsx3(
1029
+ NumInput,
1030
+ {
1031
+ value: Math.round(rect.y),
1032
+ onChange: (v) => setPos("y", v)
1033
+ }
1034
+ ),
1035
+ /* @__PURE__ */ jsx3(Unit, { children: "px" })
1036
+ ] }),
1037
+ /* @__PURE__ */ jsx3(
1038
+ "div",
1039
+ {
1040
+ style: {
1041
+ height: 1,
1042
+ background: "rgba(255, 255, 255, 0.08)",
1043
+ margin: "2px 0"
1044
+ }
1045
+ }
1046
+ ),
1047
+ /* @__PURE__ */ jsxs3(
1048
+ "div",
1049
+ {
1050
+ style: { display: "flex", alignItems: "center", gap: 20 },
1051
+ children: [
1052
+ /* @__PURE__ */ jsxs3("div", { style: { position: "relative" }, children: [
1053
+ /* @__PURE__ */ jsxs3(
1054
+ DropBtn,
1055
+ {
1056
+ active: aspect !== "Free",
1057
+ onClick: () => {
1058
+ setAspectOpen(!aspectOpen);
1059
+ setSavedOpen(false);
1060
+ },
1061
+ children: [
1062
+ /* @__PURE__ */ jsx3(AspectIcon, {}),
1063
+ aspect === "Free" ? "Free" : aspect,
1064
+ /* @__PURE__ */ jsx3(Chevron, {})
1065
+ ]
1066
+ }
1067
+ ),
1068
+ aspectOpen && /* @__PURE__ */ jsx3(DropMenu, { children: ASPECT_RATIOS.map((ar) => /* @__PURE__ */ jsx3(
1069
+ DropItem,
1070
+ {
1071
+ active: ar.label === aspect,
1072
+ onClick: () => {
1073
+ setAspect(ar.label);
1074
+ setAspectOpen(false);
1075
+ if (ar.value > 0 && rect) {
1076
+ setRect({ ...rect, h: rect.w / ar.value });
1077
+ }
1078
+ },
1079
+ children: ar.label
1080
+ },
1081
+ ar.label
1082
+ )) })
1083
+ ] }),
1084
+ /* @__PURE__ */ jsxs3("div", { style: { position: "relative" }, children: [
1085
+ /* @__PURE__ */ jsxs3(
1086
+ DropBtn,
1087
+ {
1088
+ onClick: () => {
1089
+ setSavedOpen(!savedOpen);
1090
+ setAspectOpen(false);
1091
+ },
1092
+ children: [
1093
+ /* @__PURE__ */ jsx3(SavedIcon, {}),
1094
+ "Saved",
1095
+ /* @__PURE__ */ jsx3(Chevron, {})
1096
+ ]
1097
+ }
1098
+ ),
1099
+ savedOpen && /* @__PURE__ */ jsxs3(DropMenu, { children: [
1100
+ /* @__PURE__ */ jsx3(
1101
+ DropItem,
1102
+ {
1103
+ accent: true,
1104
+ onClick: savePreset,
1105
+ children: "Save current"
1106
+ }
1107
+ ),
1108
+ presets.length > 0 && /* @__PURE__ */ jsx3(
1109
+ "div",
1110
+ {
1111
+ style: {
1112
+ height: 1,
1113
+ background: "rgba(255,255,255,0.08)",
1114
+ margin: "4px 0"
1115
+ }
1116
+ }
1117
+ ),
1118
+ presets.map((p) => /* @__PURE__ */ jsx3(
1119
+ DropItem,
1120
+ {
1121
+ onClick: () => loadPreset(p),
1122
+ children: p.label
1123
+ },
1124
+ p.label
1125
+ )),
1126
+ presets.length === 0 && /* @__PURE__ */ jsx3(
1127
+ "div",
1128
+ {
1129
+ style: {
1130
+ padding: "6px 12px",
1131
+ color: "rgba(255,255,255,0.3)",
1132
+ fontSize: 12
1133
+ },
1134
+ children: "No saved areas"
1135
+ }
1136
+ )
1137
+ ] })
1138
+ ] })
1139
+ ]
1140
+ }
1141
+ ),
1142
+ /* @__PURE__ */ jsx3(
1143
+ "div",
1144
+ {
1145
+ style: {
1146
+ fontSize: 11,
1147
+ color: "rgba(255, 255, 255, 0.25)",
1148
+ textAlign: "center"
1149
+ },
1150
+ children: "Enter to capture \xB7 Esc to cancel"
1151
+ }
1152
+ )
1153
+ ]
672
1154
  }
673
1155
  ),
674
- /* @__PURE__ */ jsxs3(
1156
+ !placed && /* @__PURE__ */ jsxs3(
675
1157
  "div",
676
1158
  {
677
1159
  style: {
@@ -680,7 +1162,7 @@ function Selector({ onSelect, onCancel }) {
680
1162
  top: rect.y + rect.h + 8,
681
1163
  transform: "translateX(-50%)",
682
1164
  background: "rgba(24, 24, 27, 0.9)",
683
- color: "rgba(255,255,255,0.9)",
1165
+ color: "rgba(255, 255, 255, 0.9)",
684
1166
  fontSize: 11,
685
1167
  fontFamily: "system-ui, -apple-system, monospace",
686
1168
  padding: "2px 8px",
@@ -696,7 +1178,7 @@ function Selector({ onSelect, onCancel }) {
696
1178
  }
697
1179
  )
698
1180
  ] }),
699
- !selection && /* @__PURE__ */ jsx3(
1181
+ !rect && /* @__PURE__ */ jsx3(
700
1182
  "div",
701
1183
  {
702
1184
  style: {
@@ -704,11 +1186,11 @@ function Selector({ onSelect, onCancel }) {
704
1186
  top: "50%",
705
1187
  left: "50%",
706
1188
  transform: "translate(-50%, -50%)",
707
- color: "rgba(255,255,255,0.7)",
1189
+ color: "rgba(255, 255, 255, 0.7)",
708
1190
  fontSize: 14,
709
1191
  fontFamily: "system-ui, -apple-system, sans-serif",
710
1192
  pointerEvents: "none",
711
- textShadow: "0 1px 4px rgba(0,0,0,0.5)"
1193
+ textShadow: "0 1px 4px rgba(0, 0, 0, 0.5)"
712
1194
  },
713
1195
  children: "Drag to select an area \xB7 Esc to cancel"
714
1196
  }
@@ -717,20 +1199,386 @@ function Selector({ onSelect, onCancel }) {
717
1199
  }
718
1200
  );
719
1201
  }
1202
+ function Row({
1203
+ label,
1204
+ children
1205
+ }) {
1206
+ return /* @__PURE__ */ jsxs3("div", { style: { display: "flex", alignItems: "center", gap: 12 }, children: [
1207
+ /* @__PURE__ */ jsx3(
1208
+ "span",
1209
+ {
1210
+ style: {
1211
+ width: 64,
1212
+ fontSize: 13,
1213
+ color: "rgba(255, 255, 255, 0.45)",
1214
+ fontWeight: 400,
1215
+ flexShrink: 0
1216
+ },
1217
+ children: label
1218
+ }
1219
+ ),
1220
+ children
1221
+ ] });
1222
+ }
1223
+ function Sep({ children }) {
1224
+ return /* @__PURE__ */ jsx3(
1225
+ "span",
1226
+ {
1227
+ style: {
1228
+ fontSize: 13,
1229
+ color: "rgba(255, 255, 255, 0.35)",
1230
+ width: 14,
1231
+ textAlign: "center",
1232
+ flexShrink: 0,
1233
+ visibility: children ? "visible" : "hidden"
1234
+ },
1235
+ children: children || "\xD7"
1236
+ }
1237
+ );
1238
+ }
1239
+ function Unit({ children }) {
1240
+ return /* @__PURE__ */ jsx3(
1241
+ "span",
1242
+ {
1243
+ style: {
1244
+ fontSize: 12,
1245
+ color: "rgba(255, 255, 255, 0.3)",
1246
+ flexShrink: 0
1247
+ },
1248
+ children
1249
+ }
1250
+ );
1251
+ }
1252
+ function NumInput({
1253
+ value,
1254
+ onChange
1255
+ }) {
1256
+ const [editing, setEditing] = useState3(false);
1257
+ const [text, setText] = useState3(String(value));
1258
+ useEffect3(() => {
1259
+ if (!editing) setText(String(value));
1260
+ }, [value, editing]);
1261
+ return /* @__PURE__ */ jsx3(
1262
+ "input",
1263
+ {
1264
+ type: "text",
1265
+ value: editing ? text : String(value),
1266
+ onFocus: () => {
1267
+ setEditing(true);
1268
+ setText(String(value));
1269
+ },
1270
+ onBlur: () => {
1271
+ setEditing(false);
1272
+ onChange(text);
1273
+ },
1274
+ onChange: (e) => setText(e.target.value),
1275
+ onKeyDown: (e) => {
1276
+ if (e.key === "Enter") e.target.blur();
1277
+ },
1278
+ style: {
1279
+ width: 72,
1280
+ padding: "6px 10px",
1281
+ background: "rgba(255, 255, 255, 0.07)",
1282
+ border: "1px solid rgba(255, 255, 255, 0.1)",
1283
+ borderRadius: 6,
1284
+ color: "rgba(255, 255, 255, 0.9)",
1285
+ fontSize: 14,
1286
+ fontFamily: "system-ui, -apple-system, sans-serif",
1287
+ textAlign: "center",
1288
+ outline: "none"
1289
+ }
1290
+ }
1291
+ );
1292
+ }
1293
+ function DropBtn({
1294
+ children,
1295
+ onClick,
1296
+ active
1297
+ }) {
1298
+ return /* @__PURE__ */ jsx3(
1299
+ "button",
1300
+ {
1301
+ onClick,
1302
+ style: {
1303
+ display: "flex",
1304
+ alignItems: "center",
1305
+ gap: 6,
1306
+ background: "none",
1307
+ border: "none",
1308
+ color: active ? "rgba(147, 130, 220, 0.9)" : "rgba(255, 255, 255, 0.5)",
1309
+ cursor: "pointer",
1310
+ fontSize: 13,
1311
+ fontFamily: "inherit",
1312
+ padding: "4px 2px"
1313
+ },
1314
+ children
1315
+ }
1316
+ );
1317
+ }
1318
+ function DropMenu({ children }) {
1319
+ return /* @__PURE__ */ jsx3(
1320
+ "div",
1321
+ {
1322
+ style: {
1323
+ position: "absolute",
1324
+ bottom: "100%",
1325
+ left: 0,
1326
+ marginBottom: 4,
1327
+ background: "rgba(32, 32, 36, 0.95)",
1328
+ border: "1px solid rgba(255, 255, 255, 0.1)",
1329
+ borderRadius: 8,
1330
+ padding: "4px 0",
1331
+ minWidth: 110,
1332
+ boxShadow: "0 4px 16px rgba(0, 0, 0, 0.3)",
1333
+ backdropFilter: "blur(20px)",
1334
+ WebkitBackdropFilter: "blur(20px)"
1335
+ },
1336
+ children
1337
+ }
1338
+ );
1339
+ }
1340
+ function DropItem({
1341
+ children,
1342
+ onClick,
1343
+ active,
1344
+ accent
1345
+ }) {
1346
+ return /* @__PURE__ */ jsx3(
1347
+ "button",
1348
+ {
1349
+ onClick,
1350
+ style: {
1351
+ display: "block",
1352
+ width: "100%",
1353
+ padding: "6px 12px",
1354
+ background: active ? "rgba(255, 255, 255, 0.08)" : "none",
1355
+ border: "none",
1356
+ color: accent ? "rgba(147, 130, 220, 0.9)" : "rgba(255, 255, 255, 0.8)",
1357
+ textAlign: "left",
1358
+ cursor: "pointer",
1359
+ fontSize: 13,
1360
+ fontFamily: "inherit"
1361
+ },
1362
+ children
1363
+ }
1364
+ );
1365
+ }
1366
+ function Chevron() {
1367
+ return /* @__PURE__ */ jsx3("svg", { width: "10", height: "10", viewBox: "0 0 10 10", fill: "none", children: /* @__PURE__ */ jsx3(
1368
+ "path",
1369
+ {
1370
+ d: "M3 4l2 2.5L7 4",
1371
+ stroke: "currentColor",
1372
+ strokeWidth: "1.2"
1373
+ }
1374
+ ) });
1375
+ }
1376
+ function AspectIcon() {
1377
+ return /* @__PURE__ */ jsxs3("svg", { width: "16", height: "16", viewBox: "0 0 16 16", fill: "none", children: [
1378
+ /* @__PURE__ */ jsx3(
1379
+ "rect",
1380
+ {
1381
+ x: "2",
1382
+ y: "4",
1383
+ width: "12",
1384
+ height: "8",
1385
+ rx: "1.5",
1386
+ stroke: "currentColor",
1387
+ strokeWidth: "1.2",
1388
+ strokeDasharray: "2 1.5"
1389
+ }
1390
+ ),
1391
+ /* @__PURE__ */ jsx3(
1392
+ "line",
1393
+ {
1394
+ x1: "6.33",
1395
+ y1: "4",
1396
+ x2: "6.33",
1397
+ y2: "12",
1398
+ stroke: "currentColor",
1399
+ strokeWidth: "0.8",
1400
+ strokeDasharray: "1.5 1"
1401
+ }
1402
+ ),
1403
+ /* @__PURE__ */ jsx3(
1404
+ "line",
1405
+ {
1406
+ x1: "9.67",
1407
+ y1: "4",
1408
+ x2: "9.67",
1409
+ y2: "12",
1410
+ stroke: "currentColor",
1411
+ strokeWidth: "0.8",
1412
+ strokeDasharray: "1.5 1"
1413
+ }
1414
+ )
1415
+ ] });
1416
+ }
1417
+ function SavedIcon() {
1418
+ return /* @__PURE__ */ jsx3("svg", { width: "16", height: "16", viewBox: "0 0 16 16", fill: "none", children: /* @__PURE__ */ jsx3(
1419
+ "rect",
1420
+ {
1421
+ x: "2",
1422
+ y: "3",
1423
+ width: "12",
1424
+ height: "10",
1425
+ rx: "1.5",
1426
+ stroke: "currentColor",
1427
+ strokeWidth: "1.2",
1428
+ strokeDasharray: "2 1.5"
1429
+ }
1430
+ ) });
1431
+ }
1432
+
1433
+ // src/overlay/ui/inspector.tsx
1434
+ import { useEffect as useEffect4, useRef as useRef4, useCallback as useCallback5, useState as useState4 } from "react";
1435
+ import { Fragment as Fragment2, jsx as jsx4, jsxs as jsxs4 } from "react/jsx-runtime";
1436
+ function Inspector({ onSelect, onCancel }) {
1437
+ const [highlight, setHighlight] = useState4(null);
1438
+ const hoveredEl = useRef4(null);
1439
+ const styleEl = useRef4(null);
1440
+ useEffect4(() => {
1441
+ const style = document.createElement("style");
1442
+ style.setAttribute("data-afterbefore", "true");
1443
+ style.textContent = "*, *::before, *::after { cursor: crosshair !important; }";
1444
+ document.head.appendChild(style);
1445
+ styleEl.current = style;
1446
+ return () => {
1447
+ style.remove();
1448
+ };
1449
+ }, []);
1450
+ const isOverlayElement = useCallback5((el) => {
1451
+ let node = el;
1452
+ while (node) {
1453
+ if (node instanceof HTMLElement && node.dataset.afterbefore) return true;
1454
+ node = node.parentElement;
1455
+ }
1456
+ return false;
1457
+ }, []);
1458
+ const handleMouseMove = useCallback5(
1459
+ (e) => {
1460
+ const el = document.elementFromPoint(e.clientX, e.clientY);
1461
+ if (!el || !(el instanceof HTMLElement) || isOverlayElement(el)) {
1462
+ setHighlight(null);
1463
+ hoveredEl.current = null;
1464
+ return;
1465
+ }
1466
+ hoveredEl.current = el;
1467
+ const rect = el.getBoundingClientRect();
1468
+ setHighlight({
1469
+ x: rect.x,
1470
+ y: rect.y,
1471
+ width: rect.width,
1472
+ height: rect.height,
1473
+ tag: el.tagName.toLowerCase() + (el.className && typeof el.className === "string" ? `.${el.className.split(" ")[0]}` : "")
1474
+ });
1475
+ },
1476
+ [isOverlayElement]
1477
+ );
1478
+ const handleClick = useCallback5(
1479
+ (e) => {
1480
+ e.preventDefault();
1481
+ e.stopPropagation();
1482
+ e.stopImmediatePropagation();
1483
+ if (hoveredEl.current) {
1484
+ onSelect(hoveredEl.current);
1485
+ }
1486
+ },
1487
+ [onSelect]
1488
+ );
1489
+ const handleKeyDown = useCallback5(
1490
+ (e) => {
1491
+ if (e.key === "Escape") {
1492
+ onCancel();
1493
+ }
1494
+ },
1495
+ [onCancel]
1496
+ );
1497
+ useEffect4(() => {
1498
+ document.addEventListener("mousemove", handleMouseMove, true);
1499
+ document.addEventListener("click", handleClick, true);
1500
+ document.addEventListener("keydown", handleKeyDown);
1501
+ return () => {
1502
+ document.removeEventListener("mousemove", handleMouseMove, true);
1503
+ document.removeEventListener("click", handleClick, true);
1504
+ document.removeEventListener("keydown", handleKeyDown);
1505
+ };
1506
+ }, [handleMouseMove, handleClick, handleKeyDown]);
1507
+ return /* @__PURE__ */ jsxs4("div", { "data-afterbefore": "true", style: { position: "fixed", inset: 0, zIndex: 2147483646, pointerEvents: "none" }, children: [
1508
+ highlight && /* @__PURE__ */ jsxs4(Fragment2, { children: [
1509
+ /* @__PURE__ */ jsx4(
1510
+ "div",
1511
+ {
1512
+ style: {
1513
+ position: "fixed",
1514
+ left: highlight.x,
1515
+ top: highlight.y,
1516
+ width: highlight.width,
1517
+ height: highlight.height,
1518
+ background: "rgba(59, 130, 246, 0.15)",
1519
+ border: "2px solid rgba(59, 130, 246, 0.7)",
1520
+ borderRadius: 2,
1521
+ pointerEvents: "none"
1522
+ }
1523
+ }
1524
+ ),
1525
+ /* @__PURE__ */ jsx4(
1526
+ "div",
1527
+ {
1528
+ style: {
1529
+ position: "fixed",
1530
+ left: highlight.x,
1531
+ top: Math.max(0, highlight.y - 24),
1532
+ background: "rgba(59, 130, 246, 0.9)",
1533
+ color: "#fff",
1534
+ fontSize: 11,
1535
+ fontFamily: "system-ui, -apple-system, monospace",
1536
+ padding: "2px 6px",
1537
+ borderRadius: 3,
1538
+ pointerEvents: "none",
1539
+ whiteSpace: "nowrap",
1540
+ lineHeight: "18px"
1541
+ },
1542
+ children: highlight.tag
1543
+ }
1544
+ )
1545
+ ] }),
1546
+ !highlight && /* @__PURE__ */ jsx4(
1547
+ "div",
1548
+ {
1549
+ style: {
1550
+ position: "fixed",
1551
+ top: "50%",
1552
+ left: "50%",
1553
+ transform: "translate(-50%, -50%)",
1554
+ color: "rgba(255, 255, 255, 0.7)",
1555
+ fontSize: 14,
1556
+ fontFamily: "system-ui, -apple-system, sans-serif",
1557
+ pointerEvents: "none",
1558
+ textShadow: "0 1px 4px rgba(0, 0, 0, 0.5)",
1559
+ background: "rgba(0, 0, 0, 0.5)",
1560
+ padding: "8px 16px",
1561
+ borderRadius: 8
1562
+ },
1563
+ children: "Hover to inspect \xB7 Click to capture \xB7 Esc to cancel"
1564
+ }
1565
+ )
1566
+ ] });
1567
+ }
720
1568
 
721
1569
  // src/overlay/ui/status.tsx
722
- import { useState as useState4, useRef as useRef4, useEffect as useEffect4, useCallback as useCallback5 } from "react";
723
- import { jsx as jsx4, jsxs as jsxs4 } from "react/jsx-runtime";
1570
+ import { useState as useState5, useRef as useRef5, useEffect as useEffect5, useCallback as useCallback6 } from "react";
1571
+ import { jsx as jsx5, jsxs as jsxs5 } from "react/jsx-runtime";
724
1572
  var PANEL_WIDTH = 220;
725
1573
  function Status({ onReset, position, onClose }) {
726
- const panelRef = useRef4(null);
727
- const [toast, setToast] = useState4(null);
728
- const [pushing, setPushing] = useState4(false);
729
- const showToast = useCallback5((message, type) => {
1574
+ const panelRef = useRef5(null);
1575
+ const [toast, setToast] = useState5(null);
1576
+ const [pushing, setPushing] = useState5(false);
1577
+ const showToast = useCallback6((message, type) => {
730
1578
  setToast({ message, type });
731
1579
  setTimeout(() => setToast(null), 3e3);
732
1580
  }, []);
733
- useEffect4(() => {
1581
+ useEffect5(() => {
734
1582
  const handler = (e) => {
735
1583
  if (panelRef.current && !panelRef.current.contains(e.target)) {
736
1584
  onClose();
@@ -739,7 +1587,7 @@ function Status({ onReset, position, onClose }) {
739
1587
  document.addEventListener("mousedown", handler);
740
1588
  return () => document.removeEventListener("mousedown", handler);
741
1589
  }, [onClose]);
742
- useEffect4(() => {
1590
+ useEffect5(() => {
743
1591
  const handler = (e) => {
744
1592
  if (e.key === "Escape") onClose();
745
1593
  };
@@ -815,7 +1663,7 @@ function Status({ onReset, position, onClose }) {
815
1663
  const onLeave = (e) => {
816
1664
  e.currentTarget.style.background = "transparent";
817
1665
  };
818
- return /* @__PURE__ */ jsxs4(
1666
+ return /* @__PURE__ */ jsxs5(
819
1667
  "div",
820
1668
  {
821
1669
  ref: panelRef,
@@ -834,7 +1682,7 @@ function Status({ onReset, position, onClose }) {
834
1682
  overflow: "hidden"
835
1683
  },
836
1684
  children: [
837
- /* @__PURE__ */ jsx4(
1685
+ /* @__PURE__ */ jsx5(
838
1686
  "div",
839
1687
  {
840
1688
  style: {
@@ -847,8 +1695,8 @@ function Status({ onReset, position, onClose }) {
847
1695
  children: "Before & After captured"
848
1696
  }
849
1697
  ),
850
- /* @__PURE__ */ jsxs4("div", { style: { padding: "0 4px 4px" }, children: [
851
- /* @__PURE__ */ jsxs4(
1698
+ /* @__PURE__ */ jsxs5("div", { style: { padding: "0 4px 4px" }, children: [
1699
+ /* @__PURE__ */ jsxs5(
852
1700
  "button",
853
1701
  {
854
1702
  style: buttonStyle,
@@ -856,12 +1704,12 @@ function Status({ onReset, position, onClose }) {
856
1704
  onMouseEnter: onEnter,
857
1705
  onMouseLeave: onLeave,
858
1706
  children: [
859
- /* @__PURE__ */ jsx4(FolderIcon, {}),
1707
+ /* @__PURE__ */ jsx5(FolderIcon, {}),
860
1708
  "Open Folder"
861
1709
  ]
862
1710
  }
863
1711
  ),
864
- /* @__PURE__ */ jsxs4(
1712
+ /* @__PURE__ */ jsxs5(
865
1713
  "button",
866
1714
  {
867
1715
  style: buttonStyle,
@@ -869,12 +1717,12 @@ function Status({ onReset, position, onClose }) {
869
1717
  onMouseEnter: onEnter,
870
1718
  onMouseLeave: onLeave,
871
1719
  children: [
872
- /* @__PURE__ */ jsx4(CopyIcon, {}),
1720
+ /* @__PURE__ */ jsx5(CopyIcon, {}),
873
1721
  "Copy Markdown"
874
1722
  ]
875
1723
  }
876
1724
  ),
877
- /* @__PURE__ */ jsxs4(
1725
+ /* @__PURE__ */ jsxs5(
878
1726
  "button",
879
1727
  {
880
1728
  style: buttonStyle,
@@ -883,12 +1731,12 @@ function Status({ onReset, position, onClose }) {
883
1731
  onMouseEnter: onEnter,
884
1732
  onMouseLeave: onLeave,
885
1733
  children: [
886
- /* @__PURE__ */ jsx4(PushIcon, {}),
1734
+ /* @__PURE__ */ jsx5(PushIcon, {}),
887
1735
  pushing ? "Pushing..." : "Push to PR"
888
1736
  ]
889
1737
  }
890
1738
  ),
891
- /* @__PURE__ */ jsx4(
1739
+ /* @__PURE__ */ jsx5(
892
1740
  "div",
893
1741
  {
894
1742
  style: {
@@ -898,7 +1746,7 @@ function Status({ onReset, position, onClose }) {
898
1746
  }
899
1747
  }
900
1748
  ),
901
- /* @__PURE__ */ jsxs4(
1749
+ /* @__PURE__ */ jsxs5(
902
1750
  "button",
903
1751
  {
904
1752
  style: { ...buttonStyle, color: "rgba(255,255,255,0.5)" },
@@ -906,13 +1754,13 @@ function Status({ onReset, position, onClose }) {
906
1754
  onMouseEnter: onEnter,
907
1755
  onMouseLeave: onLeave,
908
1756
  children: [
909
- /* @__PURE__ */ jsx4(ResetIcon, {}),
1757
+ /* @__PURE__ */ jsx5(ResetIcon, {}),
910
1758
  "Reset"
911
1759
  ]
912
1760
  }
913
1761
  )
914
1762
  ] }),
915
- toast && /* @__PURE__ */ jsx4(
1763
+ toast && /* @__PURE__ */ jsx5(
916
1764
  "div",
917
1765
  {
918
1766
  style: {
@@ -938,14 +1786,14 @@ function Status({ onReset, position, onClose }) {
938
1786
  );
939
1787
  }
940
1788
  function FolderIcon() {
941
- return /* @__PURE__ */ jsx4(
1789
+ return /* @__PURE__ */ jsx5(
942
1790
  "svg",
943
1791
  {
944
1792
  width: "14",
945
1793
  height: "14",
946
1794
  viewBox: "0 0 14 14",
947
1795
  style: { color: "rgba(255,255,255,0.5)" },
948
- children: /* @__PURE__ */ jsx4(
1796
+ children: /* @__PURE__ */ jsx5(
949
1797
  "path",
950
1798
  {
951
1799
  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",
@@ -958,7 +1806,7 @@ function FolderIcon() {
958
1806
  );
959
1807
  }
960
1808
  function CopyIcon() {
961
- return /* @__PURE__ */ jsxs4(
1809
+ return /* @__PURE__ */ jsxs5(
962
1810
  "svg",
963
1811
  {
964
1812
  width: "14",
@@ -966,7 +1814,7 @@ function CopyIcon() {
966
1814
  viewBox: "0 0 14 14",
967
1815
  style: { color: "rgba(255,255,255,0.5)" },
968
1816
  children: [
969
- /* @__PURE__ */ jsx4(
1817
+ /* @__PURE__ */ jsx5(
970
1818
  "rect",
971
1819
  {
972
1820
  x: "4",
@@ -979,7 +1827,7 @@ function CopyIcon() {
979
1827
  strokeWidth: "1.3"
980
1828
  }
981
1829
  ),
982
- /* @__PURE__ */ jsx4(
1830
+ /* @__PURE__ */ jsx5(
983
1831
  "path",
984
1832
  {
985
1833
  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",
@@ -993,14 +1841,14 @@ function CopyIcon() {
993
1841
  );
994
1842
  }
995
1843
  function PushIcon() {
996
- return /* @__PURE__ */ jsx4(
1844
+ return /* @__PURE__ */ jsx5(
997
1845
  "svg",
998
1846
  {
999
1847
  width: "14",
1000
1848
  height: "14",
1001
1849
  viewBox: "0 0 14 14",
1002
1850
  style: { color: "rgba(255,255,255,0.5)" },
1003
- children: /* @__PURE__ */ jsx4(
1851
+ children: /* @__PURE__ */ jsx5(
1004
1852
  "path",
1005
1853
  {
1006
1854
  d: "M7 11V3m0 0L4 6m3-3l3 3",
@@ -1015,7 +1863,7 @@ function PushIcon() {
1015
1863
  );
1016
1864
  }
1017
1865
  function ResetIcon() {
1018
- return /* @__PURE__ */ jsxs4(
1866
+ return /* @__PURE__ */ jsxs5(
1019
1867
  "svg",
1020
1868
  {
1021
1869
  width: "14",
@@ -1023,7 +1871,7 @@ function ResetIcon() {
1023
1871
  viewBox: "0 0 14 14",
1024
1872
  style: { color: "rgba(255,255,255,0.4)" },
1025
1873
  children: [
1026
- /* @__PURE__ */ jsx4(
1874
+ /* @__PURE__ */ jsx5(
1027
1875
  "path",
1028
1876
  {
1029
1877
  d: "M2.5 7a4.5 4.5 0 118 2.5",
@@ -1033,7 +1881,7 @@ function ResetIcon() {
1033
1881
  strokeLinecap: "round"
1034
1882
  }
1035
1883
  ),
1036
- /* @__PURE__ */ jsx4(
1884
+ /* @__PURE__ */ jsx5(
1037
1885
  "path",
1038
1886
  {
1039
1887
  d: "M2.5 3v4h4",
@@ -1050,7 +1898,7 @@ function ResetIcon() {
1050
1898
  }
1051
1899
 
1052
1900
  // src/overlay/index.tsx
1053
- import { jsx as jsx5, jsxs as jsxs5 } from "react/jsx-runtime";
1901
+ import { jsx as jsx6, jsxs as jsxs6 } from "react/jsx-runtime";
1054
1902
  async function saveCapture(type, mode, dataUrl) {
1055
1903
  try {
1056
1904
  const res = await fetch("/__afterbefore/save", {
@@ -1068,18 +1916,19 @@ async function saveCapture(type, mode, dataUrl) {
1068
1916
  }
1069
1917
  function AfterBefore() {
1070
1918
  const { state, captureComplete, reset } = useOverlayState();
1071
- const [menuOpen, setMenuOpen] = useState5(false);
1072
- const [statusOpen, setStatusOpen] = useState5(false);
1073
- const [selectorActive, setSelectorActive] = useState5(false);
1074
- const [loading, setLoading] = useState5(false);
1075
- const iconPos = useRef5({ x: 24, y: 0 });
1076
- const handlePositionChange = useCallback6(
1919
+ const [menuOpen, setMenuOpen] = useState6(false);
1920
+ const [statusOpen, setStatusOpen] = useState6(false);
1921
+ const [selectorActive, setSelectorActive] = useState6(false);
1922
+ const [inspectorActive, setInspectorActive] = useState6(false);
1923
+ const [loading, setLoading] = useState6(false);
1924
+ const iconPos = useRef6({ x: 24, y: 0 });
1925
+ const handlePositionChange = useCallback7(
1077
1926
  (pos) => {
1078
1927
  iconPos.current = pos;
1079
1928
  },
1080
1929
  []
1081
1930
  );
1082
- const handleIconClick = useCallback6(() => {
1931
+ const handleIconClick = useCallback7(() => {
1083
1932
  if (loading) return;
1084
1933
  if (state.phase === "ready") {
1085
1934
  setStatusOpen((prev) => !prev);
@@ -1089,11 +1938,11 @@ function AfterBefore() {
1089
1938
  setStatusOpen(false);
1090
1939
  }
1091
1940
  }, [state.phase, loading]);
1092
- const performCapture = useCallback6(
1093
- async (mode, area) => {
1941
+ const performCapture = useCallback7(
1942
+ async (mode, area, element) => {
1094
1943
  setLoading(true);
1095
1944
  try {
1096
- const dataUrl = await capture({ mode, area });
1945
+ const dataUrl = await capture({ mode, area, element });
1097
1946
  const type = state.phase === "idle" ? "before" : "after";
1098
1947
  await saveCapture(type, mode, dataUrl);
1099
1948
  captureComplete({
@@ -1109,40 +1958,52 @@ function AfterBefore() {
1109
1958
  },
1110
1959
  [state.phase, captureComplete]
1111
1960
  );
1112
- const handleModeSelect = useCallback6(
1961
+ const handleModeSelect = useCallback7(
1113
1962
  (mode) => {
1114
1963
  setMenuOpen(false);
1115
1964
  if (mode === "area") {
1116
1965
  setSelectorActive(true);
1966
+ } else if (mode === "component") {
1967
+ setInspectorActive(true);
1117
1968
  } else {
1118
1969
  performCapture(mode);
1119
1970
  }
1120
1971
  },
1121
1972
  [performCapture]
1122
1973
  );
1123
- const handleAreaSelect = useCallback6(
1974
+ const handleComponentSelect = useCallback7(
1975
+ (element) => {
1976
+ setInspectorActive(false);
1977
+ performCapture("component", void 0, element);
1978
+ },
1979
+ [performCapture]
1980
+ );
1981
+ const handleComponentCancel = useCallback7(() => {
1982
+ setInspectorActive(false);
1983
+ }, []);
1984
+ const handleAreaSelect = useCallback7(
1124
1985
  (area) => {
1125
1986
  setSelectorActive(false);
1126
1987
  performCapture("area", area);
1127
1988
  },
1128
1989
  [performCapture]
1129
1990
  );
1130
- const handleAreaCancel = useCallback6(() => {
1991
+ const handleAreaCancel = useCallback7(() => {
1131
1992
  setSelectorActive(false);
1132
1993
  }, []);
1133
- const handleReset = useCallback6(() => {
1994
+ const handleReset = useCallback7(() => {
1134
1995
  reset();
1135
1996
  setStatusOpen(false);
1136
1997
  setMenuOpen(false);
1137
1998
  }, [reset]);
1138
- const handleMenuClose = useCallback6(() => {
1999
+ const handleMenuClose = useCallback7(() => {
1139
2000
  setMenuOpen(false);
1140
2001
  }, []);
1141
- const handleStatusClose = useCallback6(() => {
2002
+ const handleStatusClose = useCallback7(() => {
1142
2003
  setStatusOpen(false);
1143
2004
  }, []);
1144
- return /* @__PURE__ */ jsxs5("div", { "data-afterbefore": "true", children: [
1145
- /* @__PURE__ */ jsx5(
2005
+ return /* @__PURE__ */ jsxs6("div", { "data-afterbefore": "true", children: [
2006
+ /* @__PURE__ */ jsx6(
1146
2007
  Icon,
1147
2008
  {
1148
2009
  phase: state.phase,
@@ -1151,7 +2012,7 @@ function AfterBefore() {
1151
2012
  onPositionChange: handlePositionChange
1152
2013
  }
1153
2014
  ),
1154
- menuOpen && (state.phase === "idle" || state.phase === "captured-before") && /* @__PURE__ */ jsx5(
2015
+ menuOpen && (state.phase === "idle" || state.phase === "captured-before") && /* @__PURE__ */ jsx6(
1155
2016
  Menu,
1156
2017
  {
1157
2018
  onSelect: handleModeSelect,
@@ -1159,8 +2020,9 @@ function AfterBefore() {
1159
2020
  position: iconPos.current
1160
2021
  }
1161
2022
  ),
1162
- selectorActive && /* @__PURE__ */ jsx5(Selector, { onSelect: handleAreaSelect, onCancel: handleAreaCancel }),
1163
- statusOpen && state.phase === "ready" && /* @__PURE__ */ jsx5(
2023
+ selectorActive && /* @__PURE__ */ jsx6(Selector, { onSelect: handleAreaSelect, onCancel: handleAreaCancel }),
2024
+ inspectorActive && /* @__PURE__ */ jsx6(Inspector, { onSelect: handleComponentSelect, onCancel: handleComponentCancel }),
2025
+ statusOpen && state.phase === "ready" && /* @__PURE__ */ jsx6(
1164
2026
  Status,
1165
2027
  {
1166
2028
  onReset: handleReset,