afterbefore 0.2.7 → 0.2.9

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 useState7, useCallback as useCallback7, useRef as useRef5, useEffect as useEffect6 } from "react";
4
+ import { useState as useState8, useCallback as useCallback6, useRef as useRef5, useEffect as useEffect7 } from "react";
5
5
 
6
6
  // src/overlay/state.ts
7
7
  import { useState, useCallback } from "react";
@@ -33,7 +33,7 @@ function useOverlayState() {
33
33
  }
34
34
 
35
35
  // src/overlay/capture.ts
36
- import { domToPng } from "modern-screenshot";
36
+ import { snapdom } from "@zumer/snapdom";
37
37
  var DEV_UI_SELECTORS = [
38
38
  // Afterbefore overlay
39
39
  "[data-afterbefore]",
@@ -44,53 +44,37 @@ var DEV_UI_SELECTORS = [
44
44
  "[data-nextjs-dialog-backdrop]",
45
45
  "[data-next-badge]",
46
46
  "[data-next-mark]"
47
- ].join(",");
48
- function filterDevUI(node) {
49
- if (node instanceof HTMLElement) {
50
- return !node.matches(DEV_UI_SELECTORS);
51
- }
52
- return true;
53
- }
54
- function hideDevUI() {
55
- const elements = document.querySelectorAll(DEV_UI_SELECTORS);
56
- const saved = /* @__PURE__ */ new Map();
57
- elements.forEach((el) => {
58
- saved.set(el, el.style.display);
59
- el.style.display = "none";
60
- });
61
- return () => {
62
- saved.forEach((v, el) => {
63
- el.style.display = v;
64
- });
65
- };
47
+ ];
48
+ var SNAPDOM_BASE = {
49
+ scale: window.devicePixelRatio || 1,
50
+ exclude: DEV_UI_SELECTORS,
51
+ excludeMode: "remove"
52
+ };
53
+ async function toPngDataUrl(el, opts) {
54
+ const result = await snapdom(el, { ...SNAPDOM_BASE, ...opts });
55
+ const img = await result.toPng();
56
+ return img.src;
66
57
  }
67
58
  async function capture(options) {
68
59
  const { mode, area, element } = options;
69
- const restore = hideDevUI();
70
- try {
71
- if (mode === "viewport") {
72
- return await captureViewport();
73
- }
74
- if (mode === "fullpage") {
75
- return await captureFullPage();
76
- }
77
- if (mode === "area" && area) {
78
- return await captureArea(area);
79
- }
80
- if (mode === "component" && element) {
81
- return await captureComponent(element);
82
- }
83
- throw new Error(`Invalid capture mode: ${mode}`);
84
- } finally {
85
- restore();
60
+ if (mode === "viewport") {
61
+ return captureViewport();
62
+ }
63
+ if (mode === "fullpage") {
64
+ return captureFullPage();
65
+ }
66
+ if (mode === "area" && area) {
67
+ return captureArea(area);
86
68
  }
69
+ if (mode === "component" && element) {
70
+ return captureComponent(element);
71
+ }
72
+ throw new Error(`Invalid capture mode: ${mode}`);
87
73
  }
88
74
  async function captureViewport() {
89
- return domToPng(document.documentElement, {
75
+ return toPngDataUrl(document.documentElement, {
90
76
  width: window.innerWidth,
91
- height: window.innerHeight,
92
- style: { overflow: "hidden" },
93
- filter: filterDevUI
77
+ height: window.innerHeight
94
78
  });
95
79
  }
96
80
  async function captureFullPage() {
@@ -104,17 +88,20 @@ async function captureFullPage() {
104
88
  html.scrollHeight,
105
89
  html.offsetHeight
106
90
  );
107
- const dataUrl = await domToPng(document.documentElement, {
108
- width: window.innerWidth,
109
- height: fullHeight,
110
- style: {
111
- overflow: "visible",
112
- height: `${fullHeight}px`
113
- },
114
- filter: filterDevUI
115
- });
116
- window.scrollTo(0, scrollY);
117
- return dataUrl;
91
+ const prevOverflow = html.style.overflow;
92
+ const prevHeight = html.style.height;
93
+ html.style.overflow = "visible";
94
+ html.style.height = `${fullHeight}px`;
95
+ try {
96
+ return await toPngDataUrl(document.documentElement, {
97
+ width: window.innerWidth,
98
+ height: fullHeight
99
+ });
100
+ } finally {
101
+ html.style.overflow = prevOverflow;
102
+ html.style.height = prevHeight;
103
+ window.scrollTo(0, scrollY);
104
+ }
118
105
  }
119
106
  async function captureArea(area) {
120
107
  const fullDataUrl = await captureViewport();
@@ -138,9 +125,7 @@ async function captureArea(area) {
138
125
  return canvas.toDataURL("image/png");
139
126
  }
140
127
  async function captureComponent(element) {
141
- return domToPng(element, {
142
- filter: filterDevUI
143
- });
128
+ return toPngDataUrl(element);
144
129
  }
145
130
  function loadImage(src) {
146
131
  return new Promise((resolve, reject) => {
@@ -153,6 +138,7 @@ function loadImage(src) {
153
138
 
154
139
  // src/overlay/ui/icon.tsx
155
140
  import { useRef, useCallback as useCallback2, useEffect, useState as useState2 } from "react";
141
+ import { Camera, Check, LoaderCircle } from "lucide-react";
156
142
  import { jsx, jsxs } from "react/jsx-runtime";
157
143
  var ICON_SIZE = 40;
158
144
  var EDGE_MARGIN = 24;
@@ -268,38 +254,13 @@ function Icon({ phase, onClick, loading, onPositionChange }) {
268
254
  }
269
255
  ),
270
256
  loading ? /* @__PURE__ */ jsx(
271
- "svg",
272
- {
273
- width: "20",
274
- height: "20",
275
- viewBox: "0 0 20 20",
276
- style: { animation: "ab-spin 0.8s linear infinite" },
277
- children: /* @__PURE__ */ jsx(
278
- "circle",
279
- {
280
- cx: "10",
281
- cy: "10",
282
- r: "8",
283
- fill: "none",
284
- stroke: "white",
285
- strokeWidth: "2",
286
- strokeDasharray: "40",
287
- strokeDashoffset: "10",
288
- strokeLinecap: "round"
289
- }
290
- )
291
- }
292
- ) : phase === "ready" ? /* @__PURE__ */ jsx("svg", { width: "20", height: "20", viewBox: "0 0 20 20", children: /* @__PURE__ */ jsx(
293
- "path",
257
+ LoaderCircle,
294
258
  {
295
- d: "M4 10l4 4 8-8",
296
- fill: "none",
297
- stroke: "#4ade80",
298
- strokeWidth: "2.5",
299
- strokeLinecap: "round",
300
- strokeLinejoin: "round"
259
+ size: 20,
260
+ strokeWidth: 2,
261
+ style: { animation: "ab-spin 0.8s linear infinite", color: "white" }
301
262
  }
302
- ) }) : /* @__PURE__ */ jsxs(
263
+ ) : phase === "ready" ? /* @__PURE__ */ jsx(Check, { size: 20, strokeWidth: 2.6, color: "#4ade80" }) : /* @__PURE__ */ jsxs(
303
264
  "div",
304
265
  {
305
266
  style: {
@@ -310,33 +271,7 @@ function Icon({ phase, onClick, loading, onPositionChange }) {
310
271
  animation: phase === "captured-before" ? "ab-pulse 2s ease-in-out infinite" : "none"
311
272
  },
312
273
  children: [
313
- /* @__PURE__ */ jsxs("svg", { width: "20", height: "20", viewBox: "0 0 20 20", children: [
314
- /* @__PURE__ */ jsx(
315
- "rect",
316
- {
317
- x: "2",
318
- y: "5",
319
- width: "16",
320
- height: "12",
321
- rx: "2",
322
- fill: "none",
323
- stroke: "white",
324
- strokeWidth: "1.5"
325
- }
326
- ),
327
- /* @__PURE__ */ jsx(
328
- "circle",
329
- {
330
- cx: "10",
331
- cy: "11",
332
- r: "3",
333
- fill: "none",
334
- stroke: "white",
335
- strokeWidth: "1.5"
336
- }
337
- ),
338
- /* @__PURE__ */ jsx("path", { d: "M7 5l1-2h4l1 2", fill: "none", stroke: "white", strokeWidth: "1.5" })
339
- ] }),
274
+ /* @__PURE__ */ jsx(Camera, { size: 20, strokeWidth: 1.9, color: "white" }),
340
275
  phase === "captured-before" && /* @__PURE__ */ jsx(
341
276
  "div",
342
277
  {
@@ -368,314 +303,155 @@ function Icon({ phase, onClick, loading, onPositionChange }) {
368
303
  );
369
304
  }
370
305
 
371
- // src/overlay/ui/toolbar.tsx
372
- import { useState as useState3, useEffect as useEffect2 } from "react";
373
- import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
374
- var MODES = [
375
- { mode: "area", label: "Capture Selected Area" },
376
- { mode: "viewport", label: "Capture Viewport" },
377
- { mode: "fullpage", label: "Capture Full Page" },
378
- { mode: "component", label: "Capture Component" }
379
- ];
380
- function Toolbar({ onCapture, onCancel }) {
381
- const [selectedMode, setSelectedMode] = useState3("area");
382
- const [hoveredMode, setHoveredMode] = useState3(null);
306
+ // src/overlay/ui/preview.tsx
307
+ import { useEffect as useEffect2, useState as useState3 } from "react";
308
+ import { jsx as jsx2 } from "react/jsx-runtime";
309
+ var DEFAULT_ASPECT_RATIO = 16 / 9;
310
+ function getAreaPreviewRect() {
311
+ const safeWidth = Math.max(320, window.innerWidth - 120);
312
+ const safeHeight = Math.max(180, window.innerHeight - 220);
313
+ const width = Math.min(window.innerWidth * 0.72, safeHeight * DEFAULT_ASPECT_RATIO, safeWidth);
314
+ const height = width / DEFAULT_ASPECT_RATIO;
315
+ return {
316
+ x: (window.innerWidth - width) / 2,
317
+ y: Math.max(40, (window.innerHeight - height) / 2 - 20),
318
+ width,
319
+ height
320
+ };
321
+ }
322
+ function CapturePreview({ mode }) {
323
+ const [areaRect, setAreaRect] = useState3(null);
383
324
  useEffect2(() => {
384
- const onKey = (e) => {
385
- if (e.key === "Escape") onCancel();
386
- else if (e.key === "Enter") onCapture(selectedMode);
325
+ if (mode !== "area") {
326
+ return;
327
+ }
328
+ const syncRect = () => {
329
+ setAreaRect(getAreaPreviewRect());
387
330
  };
388
- document.addEventListener("keydown", onKey);
389
- return () => document.removeEventListener("keydown", onKey);
390
- }, [onCancel, onCapture, selectedMode]);
391
- const tooltipLabel = hoveredMode ? MODES.find((m) => m.mode === hoveredMode)?.label : null;
392
- return /* @__PURE__ */ jsxs2(
393
- "div",
394
- {
395
- "data-afterbefore": "true",
396
- style: {
397
- position: "fixed",
398
- bottom: 48,
399
- left: "50%",
400
- transform: "translateX(-50%)",
401
- zIndex: 2147483647,
402
- display: "flex",
403
- flexDirection: "column",
404
- alignItems: "center",
405
- gap: 8
406
- },
407
- children: [
408
- tooltipLabel && /* @__PURE__ */ jsx2(
331
+ syncRect();
332
+ window.addEventListener("resize", syncRect);
333
+ return () => window.removeEventListener("resize", syncRect);
334
+ }, [mode]);
335
+ if (mode === "area" && areaRect) {
336
+ return /* @__PURE__ */ jsx2(
337
+ "div",
338
+ {
339
+ "data-afterbefore": "true",
340
+ style: {
341
+ position: "fixed",
342
+ inset: 0,
343
+ zIndex: 2147483645,
344
+ pointerEvents: "none"
345
+ },
346
+ children: /* @__PURE__ */ jsx2(
409
347
  "div",
410
348
  {
411
349
  style: {
412
- background: "rgba(32, 32, 36, 0.92)",
413
- backdropFilter: "blur(20px)",
414
- WebkitBackdropFilter: "blur(20px)",
415
- border: "1px solid rgba(255, 255, 255, 0.1)",
416
- borderRadius: 6,
417
- padding: "4px 10px",
418
- color: "rgba(255, 255, 255, 0.85)",
419
- fontSize: 12,
420
- fontFamily: "system-ui, -apple-system, sans-serif",
421
- whiteSpace: "nowrap",
422
- boxShadow: "0 4px 12px rgba(0, 0, 0, 0.3)"
423
- },
424
- children: tooltipLabel
350
+ position: "absolute",
351
+ left: areaRect.x,
352
+ top: areaRect.y,
353
+ width: areaRect.width,
354
+ height: areaRect.height,
355
+ background: "rgba(125, 211, 252, 0.16)",
356
+ border: "1.5px solid rgba(125, 211, 252, 0.95)",
357
+ boxShadow: "0 0 0 1px rgba(191, 219, 254, 0.4), 0 0 32px rgba(56, 189, 248, 0.18)",
358
+ borderRadius: 14
359
+ }
425
360
  }
426
- ),
427
- /* @__PURE__ */ jsxs2(
361
+ )
362
+ }
363
+ );
364
+ }
365
+ if (mode === "viewport" || mode === "fullpage") {
366
+ return /* @__PURE__ */ jsx2(
367
+ "div",
368
+ {
369
+ "data-afterbefore": "true",
370
+ style: {
371
+ position: "fixed",
372
+ inset: 0,
373
+ zIndex: 2147483645,
374
+ pointerEvents: "none",
375
+ background: "rgba(125, 211, 252, 0.15)",
376
+ boxShadow: "inset 0 0 0 1.5px rgba(125, 211, 252, 0.9)"
377
+ },
378
+ children: /* @__PURE__ */ jsx2(
428
379
  "div",
429
380
  {
430
381
  style: {
431
- display: "flex",
432
- alignItems: "center",
433
- gap: 0,
434
- background: "rgba(32, 32, 36, 0.92)",
435
- backdropFilter: "blur(20px)",
436
- WebkitBackdropFilter: "blur(20px)",
437
- border: "1px solid rgba(255, 255, 255, 0.1)",
438
- borderRadius: 14,
439
- padding: "6px 6px",
440
- boxShadow: "0 8px 32px rgba(0, 0, 0, 0.4)",
441
- fontFamily: "system-ui, -apple-system, sans-serif"
382
+ position: "absolute",
383
+ top: 36,
384
+ left: "50%",
385
+ transform: "translateX(-50%)",
386
+ padding: "8px 14px",
387
+ borderRadius: 999,
388
+ background: "rgba(125, 211, 252, 0.16)",
389
+ border: "1px solid rgba(125, 211, 252, 0.42)",
390
+ color: "rgba(224, 242, 254, 0.96)",
391
+ fontSize: 12,
392
+ fontFamily: "system-ui, -apple-system, sans-serif",
393
+ boxShadow: "0 10px 30px rgba(14, 116, 144, 0.18)"
442
394
  },
443
- children: [
444
- /* @__PURE__ */ jsx2(CloseButton, { onClick: onCancel }),
445
- /* @__PURE__ */ jsx2("div", { style: { display: "flex", alignItems: "center", gap: 2, padding: "0 4px" }, children: MODES.map(({ mode, label }) => /* @__PURE__ */ jsx2(
446
- ModeButton,
447
- {
448
- mode,
449
- label,
450
- selected: selectedMode === mode,
451
- onClick: () => setSelectedMode(mode),
452
- onHover: (hovered) => setHoveredMode(hovered ? mode : null)
453
- },
454
- mode
455
- )) }),
456
- /* @__PURE__ */ jsx2(
457
- "div",
458
- {
459
- style: {
460
- width: 1,
461
- height: 24,
462
- background: "rgba(255, 255, 255, 0.15)",
463
- margin: "0 6px",
464
- flexShrink: 0
465
- }
466
- }
467
- ),
468
- /* @__PURE__ */ jsx2(CaptureButton, { onClick: () => onCapture(selectedMode) })
469
- ]
395
+ children: mode === "fullpage" ? "Full-page capture begins from the current viewport" : "Current viewport capture"
470
396
  }
471
397
  )
472
- ]
473
- }
474
- );
475
- }
476
- function CloseButton({ onClick }) {
477
- const [hovered, setHovered] = useState3(false);
398
+ }
399
+ );
400
+ }
478
401
  return /* @__PURE__ */ jsx2(
479
- "button",
402
+ "div",
480
403
  {
481
- onClick,
482
- onMouseEnter: () => setHovered(true),
483
- onMouseLeave: () => setHovered(false),
404
+ "data-afterbefore": "true",
484
405
  style: {
485
- width: 28,
486
- height: 28,
487
- borderRadius: "50%",
488
- border: "1px solid rgba(255, 255, 255, 0.1)",
489
- background: hovered ? "rgba(255, 255, 255, 0.12)" : "rgba(255, 255, 255, 0.06)",
490
- display: "flex",
491
- alignItems: "center",
492
- justifyContent: "center",
493
- cursor: "pointer",
494
- flexShrink: 0,
495
- padding: 0,
496
- transition: "background 0.1s",
497
- marginRight: 4
406
+ position: "fixed",
407
+ inset: 0,
408
+ zIndex: 2147483645,
409
+ pointerEvents: "none"
498
410
  },
499
- children: /* @__PURE__ */ jsx2("svg", { width: "12", height: "12", viewBox: "0 0 12 12", fill: "none", children: /* @__PURE__ */ jsx2(
500
- "path",
411
+ children: /* @__PURE__ */ jsx2(
412
+ "div",
501
413
  {
502
- d: "M3 3l6 6M9 3l-6 6",
503
- stroke: "rgba(255, 255, 255, 0.6)",
504
- strokeWidth: "1.5",
505
- strokeLinecap: "round"
414
+ style: {
415
+ position: "absolute",
416
+ top: 36,
417
+ left: "50%",
418
+ transform: "translateX(-50%)",
419
+ padding: "8px 14px",
420
+ borderRadius: 999,
421
+ background: "rgba(125, 211, 252, 0.16)",
422
+ border: "1px solid rgba(125, 211, 252, 0.42)",
423
+ color: "rgba(224, 242, 254, 0.96)",
424
+ fontSize: 12,
425
+ fontFamily: "system-ui, -apple-system, sans-serif",
426
+ boxShadow: "0 10px 30px rgba(14, 116, 144, 0.18)"
427
+ },
428
+ children: "Click Capture, then hover an element to preview it"
506
429
  }
507
- ) })
508
- }
509
- );
510
- }
511
- function ModeButton({
512
- mode,
513
- label,
514
- selected,
515
- onClick,
516
- onHover
517
- }) {
518
- const [hovered, setHovered] = useState3(false);
519
- return /* @__PURE__ */ jsx2(
520
- "button",
521
- {
522
- onClick,
523
- onMouseEnter: () => {
524
- setHovered(true);
525
- onHover(true);
526
- },
527
- onMouseLeave: () => {
528
- setHovered(false);
529
- onHover(false);
530
- },
531
- title: label,
532
- style: {
533
- width: 40,
534
- height: 34,
535
- borderRadius: 8,
536
- border: "none",
537
- background: selected ? "rgba(255, 255, 255, 0.15)" : hovered ? "rgba(255, 255, 255, 0.08)" : "transparent",
538
- display: "flex",
539
- alignItems: "center",
540
- justifyContent: "center",
541
- cursor: "pointer",
542
- padding: 0,
543
- color: selected ? "rgba(255, 255, 255, 0.95)" : "rgba(255, 255, 255, 0.45)",
544
- transition: "background 0.1s, color 0.1s"
545
- },
546
- children: /* @__PURE__ */ jsx2(ModeIcon, { mode })
547
- }
548
- );
549
- }
550
- function CaptureButton({ onClick }) {
551
- const [hovered, setHovered] = useState3(false);
552
- return /* @__PURE__ */ jsx2(
553
- "button",
554
- {
555
- onClick,
556
- onMouseEnter: () => setHovered(true),
557
- onMouseLeave: () => setHovered(false),
558
- style: {
559
- padding: "6px 16px",
560
- borderRadius: 8,
561
- border: "none",
562
- background: hovered ? "#2563eb" : "#3b82f6",
563
- color: "white",
564
- fontSize: 13,
565
- fontWeight: 600,
566
- fontFamily: "inherit",
567
- cursor: "pointer",
568
- whiteSpace: "nowrap",
569
- transition: "background 0.1s",
570
- flexShrink: 0
571
- },
572
- children: "Capture"
430
+ )
573
431
  }
574
432
  );
575
433
  }
576
- function ModeIcon({ mode }) {
577
- switch (mode) {
578
- case "area":
579
- return /* @__PURE__ */ jsxs2("svg", { width: "20", height: "20", viewBox: "0 0 20 20", fill: "none", children: [
580
- /* @__PURE__ */ jsx2(
581
- "rect",
582
- {
583
- x: "3",
584
- y: "4",
585
- width: "14",
586
- height: "12",
587
- rx: "1.5",
588
- stroke: "currentColor",
589
- strokeWidth: "1.3",
590
- strokeDasharray: "3 2"
591
- }
592
- ),
593
- /* @__PURE__ */ jsx2("line", { x1: "10", y1: "7", x2: "10", y2: "13", stroke: "currentColor", strokeWidth: "1", strokeLinecap: "round" }),
594
- /* @__PURE__ */ jsx2("line", { x1: "7", y1: "10", x2: "13", y2: "10", stroke: "currentColor", strokeWidth: "1", strokeLinecap: "round" })
595
- ] });
596
- case "viewport":
597
- return /* @__PURE__ */ jsxs2("svg", { width: "20", height: "20", viewBox: "0 0 20 20", fill: "none", children: [
598
- /* @__PURE__ */ jsx2(
599
- "rect",
600
- {
601
- x: "2",
602
- y: "3",
603
- width: "16",
604
- height: "12",
605
- rx: "1.5",
606
- stroke: "currentColor",
607
- strokeWidth: "1.3"
608
- }
609
- ),
610
- /* @__PURE__ */ jsx2(
611
- "line",
612
- {
613
- x1: "2",
614
- y1: "17",
615
- x2: "18",
616
- y2: "17",
617
- stroke: "currentColor",
618
- strokeWidth: "1.3",
619
- strokeLinecap: "round"
620
- }
621
- )
622
- ] });
623
- case "fullpage":
624
- return /* @__PURE__ */ jsxs2("svg", { width: "20", height: "20", viewBox: "0 0 20 20", fill: "none", children: [
625
- /* @__PURE__ */ jsx2(
626
- "rect",
627
- {
628
- x: "4",
629
- y: "1",
630
- width: "12",
631
- height: "18",
632
- rx: "1.5",
633
- stroke: "currentColor",
634
- strokeWidth: "1.3"
635
- }
636
- ),
637
- /* @__PURE__ */ jsx2("line", { x1: "7", y1: "5", x2: "13", y2: "5", stroke: "currentColor", strokeWidth: "1", strokeLinecap: "round" }),
638
- /* @__PURE__ */ jsx2("line", { x1: "7", y1: "8", x2: "13", y2: "8", stroke: "currentColor", strokeWidth: "1", strokeLinecap: "round" }),
639
- /* @__PURE__ */ jsx2("line", { x1: "7", y1: "11", x2: "13", y2: "11", stroke: "currentColor", strokeWidth: "1", strokeLinecap: "round" })
640
- ] });
641
- case "component":
642
- return /* @__PURE__ */ jsxs2("svg", { width: "20", height: "20", viewBox: "0 0 20 20", fill: "none", children: [
643
- /* @__PURE__ */ jsx2(
644
- "path",
645
- {
646
- d: "M4 3l3 12 2.5-4.5L14 13 4 3z",
647
- stroke: "currentColor",
648
- strokeWidth: "1.2",
649
- strokeLinejoin: "round",
650
- strokeLinecap: "round"
651
- }
652
- ),
653
- /* @__PURE__ */ jsx2(
654
- "rect",
655
- {
656
- x: "10",
657
- y: "7",
658
- width: "7",
659
- height: "7",
660
- rx: "1",
661
- stroke: "currentColor",
662
- strokeWidth: "1.1",
663
- strokeDasharray: "2 1.5"
664
- }
665
- )
666
- ] });
667
- }
668
- }
434
+
435
+ // src/overlay/ui/toolbar.tsx
436
+ import { useEffect as useEffect4, useState as useState5 } from "react";
437
+ import {
438
+ Camera as Camera2,
439
+ ChevronDown,
440
+ Crop,
441
+ FileText,
442
+ Magnet,
443
+ Monitor,
444
+ MousePointer2,
445
+ Save,
446
+ Settings2,
447
+ X
448
+ } from "lucide-react";
669
449
 
670
450
  // src/overlay/ui/selector.tsx
671
- import React3, { useState as useState4, useRef as useRef2, useCallback as useCallback4, useEffect as useEffect3 } from "react";
672
- import { Fragment, jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
673
- var HANDLE_R = 6;
674
- var HANDLE_HIT = 16;
675
- var MIN_SIZE = 20;
676
- var PANEL_HEIGHT_EST = 140;
677
- var SNAP_THRESHOLD = 8;
678
- var ASPECT_RATIOS = [
451
+ import React3, { useRef as useRef2, useCallback as useCallback3, useEffect as useEffect3, useMemo, useState as useState4 } from "react";
452
+ import { Fragment, jsx as jsx3, jsxs as jsxs2 } from "react/jsx-runtime";
453
+ var DEFAULT_AREA_ASPECT = "16:9";
454
+ var AREA_ASPECT_RATIOS = [
679
455
  { label: "Free", value: 0 },
680
456
  { label: "16:9", value: 16 / 9 },
681
457
  { label: "4:3", value: 4 / 3 },
@@ -683,112 +459,95 @@ var ASPECT_RATIOS = [
683
459
  { label: "3:2", value: 3 / 2 },
684
460
  { label: "21:9", value: 21 / 9 }
685
461
  ];
686
- function Selector({ onSelect, onCancel }) {
687
- const [rect, setRect] = useState4(null);
688
- const [placed, setPlaced] = useState4(false);
689
- const [aspect, setAspect] = useState4("Free");
690
- const [aspectOpen, setAspectOpen] = useState4(false);
691
- const [savedOpen, setSavedOpen] = useState4(false);
692
- const [presets, setPresets] = useState4(
693
- () => {
694
- try {
695
- const s = localStorage.getItem("ab-area-presets");
696
- return s ? JSON.parse(s) : [];
697
- } catch {
698
- return [];
699
- }
700
- }
701
- );
702
- const [cursor, setCursor] = useState4("crosshair");
703
- const [magnet, setMagnet] = useState4(() => {
704
- try {
705
- return localStorage.getItem("ab-magnet") !== "false";
706
- } catch {
707
- return true;
708
- }
709
- });
462
+ var HANDLE_R = 6;
463
+ var HANDLE_HIT = 16;
464
+ var MIN_SIZE = 20;
465
+ var SNAP_THRESHOLD = 8;
466
+ var CAMERA_CURSOR = `url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='28' height='28' viewBox='0 0 24 24' fill='none' stroke='%23e0f2fe' stroke-width='1.8' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M4 7h3l1.5-2h7L17 7h3a2 2 0 0 1 2 2v8a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V9a2 2 0 0 1 2-2Z'/%3E%3Ccircle cx='12' cy='13' r='4'/%3E%3C/svg%3E") 12 12, crosshair`;
467
+ function createInitialAreaRect() {
468
+ const safeWidth = Math.max(320, window.innerWidth - 96);
469
+ const safeHeight = Math.max(180, window.innerHeight - 220);
470
+ const w = Math.min(window.innerWidth * 0.72, safeHeight * (16 / 9), safeWidth);
471
+ const h = w / (16 / 9);
472
+ return {
473
+ x: (window.innerWidth - w) / 2,
474
+ y: Math.max(40, (window.innerHeight - h) / 2 - 20),
475
+ w,
476
+ h
477
+ };
478
+ }
479
+ function getAspectRatio(label) {
480
+ return AREA_ASPECT_RATIOS.find((item) => item.label === label)?.value ?? 0;
481
+ }
482
+ function Selector({
483
+ rect,
484
+ aspect,
485
+ magnetEnabled,
486
+ onRectChange,
487
+ onSelect,
488
+ onCancel
489
+ }) {
710
490
  const [snappedX, setSnappedX] = useState4(false);
491
+ const [cursor, setCursor] = useState4("crosshair");
711
492
  const mode = useRef2("none");
712
493
  const start = useRef2({ x: 0, y: 0 });
713
494
  const snapRef = useRef2({ x: 0, y: 0, w: 0, h: 0 });
714
495
  const corner = useRef2("br");
715
- const panelRef = useRef2(null);
716
- const ratio = ASPECT_RATIOS.find((a) => a.label === aspect)?.value ?? 0;
717
- const applySnap = useCallback4(
718
- (r) => {
719
- if (!magnet) {
496
+ const ratio = useMemo(() => getAspectRatio(aspect), [aspect]);
497
+ const applySnap = useCallback3(
498
+ (nextRect) => {
499
+ if (!magnetEnabled) {
720
500
  setSnappedX(false);
721
- return r;
501
+ return nextRect;
722
502
  }
723
- const vw = window.innerWidth;
724
- const centerX = r.x + r.w / 2;
725
- const viewCenterX = vw / 2;
726
- if (Math.abs(centerX - viewCenterX) < SNAP_THRESHOLD) {
503
+ const centerX = nextRect.x + nextRect.w / 2;
504
+ const viewportCenterX = window.innerWidth / 2;
505
+ if (Math.abs(centerX - viewportCenterX) < SNAP_THRESHOLD) {
727
506
  setSnappedX(true);
728
- return { ...r, x: viewCenterX - r.w / 2 };
507
+ return { ...nextRect, x: viewportCenterX - nextRect.w / 2 };
729
508
  }
730
509
  setSnappedX(false);
731
- return r;
510
+ return nextRect;
732
511
  },
733
- [magnet]
512
+ [magnetEnabled]
734
513
  );
735
514
  useEffect3(() => {
736
515
  const onKey = (e) => {
737
- if (e.target?.tagName === "INPUT") {
738
- if (e.key === "Escape") e.target.blur();
739
- return;
740
- }
741
516
  if (e.key === "Escape") {
742
- if (placed) {
743
- setPlaced(false);
744
- setRect(null);
745
- } else {
746
- onCancel();
747
- }
748
- } else if (e.key === "Enter" && placed && rect) {
749
- onSelect({
750
- x: Math.round(rect.x),
751
- y: Math.round(rect.y),
752
- width: Math.round(rect.w),
753
- height: Math.round(rect.h)
754
- });
517
+ onCancel();
755
518
  }
756
519
  };
757
520
  document.addEventListener("keydown", onKey);
758
521
  return () => document.removeEventListener("keydown", onKey);
759
- }, [placed, rect, onSelect, onCancel]);
760
- const hitCorner = useCallback4(
761
- (mx, my, r) => {
762
- const cs = [
763
- ["tl", r.x, r.y],
764
- ["tr", r.x + r.w, r.y],
765
- ["bl", r.x, r.y + r.h],
766
- ["br", r.x + r.w, r.y + r.h]
767
- ];
768
- for (const [c, cx, cy] of cs) {
769
- if (Math.abs(mx - cx) <= HANDLE_HIT && Math.abs(my - cy) <= HANDLE_HIT)
770
- return c;
522
+ }, [onCancel]);
523
+ const hitCorner = useCallback3((mx, my, currentRect) => {
524
+ const corners = [
525
+ ["tl", currentRect.x, currentRect.y],
526
+ ["tr", currentRect.x + currentRect.w, currentRect.y],
527
+ ["bl", currentRect.x, currentRect.y + currentRect.h],
528
+ ["br", currentRect.x + currentRect.w, currentRect.y + currentRect.h]
529
+ ];
530
+ for (const [hitCorner2, cx, cy] of corners) {
531
+ if (Math.abs(mx - cx) <= HANDLE_HIT && Math.abs(my - cy) <= HANDLE_HIT) {
532
+ return hitCorner2;
771
533
  }
772
- return null;
773
- },
774
- []
775
- );
776
- const hitInside = useCallback4(
777
- (mx, my, r) => mx >= r.x && mx <= r.x + r.w && my >= r.y && my <= r.y + r.h,
534
+ }
535
+ return null;
536
+ }, []);
537
+ const hitInside = useCallback3(
538
+ (mx, my, currentRect) => mx >= currentRect.x && mx <= currentRect.x + currentRect.w && my >= currentRect.y && my <= currentRect.y + currentRect.h,
778
539
  []
779
540
  );
780
- const onDown = useCallback4(
541
+ const onDown = useCallback3(
781
542
  (e) => {
782
- if (panelRef.current?.contains(e.target)) return;
783
543
  e.preventDefault();
784
- setAspectOpen(false);
785
- setSavedOpen(false);
786
- const mx = e.clientX, my = e.clientY;
787
- if (placed && rect) {
788
- const c = hitCorner(mx, my, rect);
789
- if (c) {
544
+ const mx = e.clientX;
545
+ const my = e.clientY;
546
+ if (rect) {
547
+ const activeCorner = hitCorner(mx, my, rect);
548
+ if (activeCorner) {
790
549
  mode.current = "resizing";
791
- corner.current = c;
550
+ corner.current = activeCorner;
792
551
  start.current = { x: mx, y: my };
793
552
  snapRef.current = { ...rect };
794
553
  return;
@@ -799,78 +558,96 @@ function Selector({ onSelect, onCancel }) {
799
558
  snapRef.current = { ...rect };
800
559
  return;
801
560
  }
802
- setPlaced(false);
803
561
  }
804
562
  mode.current = "drawing";
805
563
  start.current = { x: mx, y: my };
806
- setRect({ x: mx, y: my, w: 0, h: 0 });
564
+ onRectChange({ x: mx, y: my, w: 0, h: 0 });
807
565
  },
808
- [placed, rect, hitCorner, hitInside]
566
+ [hitCorner, hitInside, onRectChange, rect]
809
567
  );
810
- const onMove = useCallback4(
568
+ const onMove = useCallback3(
811
569
  (e) => {
812
- const mx = e.clientX, my = e.clientY;
570
+ const mx = e.clientX;
571
+ const my = e.clientY;
572
+ if (mode.current === "none") {
573
+ if (rect) {
574
+ const activeCorner = hitCorner(mx, my, rect);
575
+ if (activeCorner) {
576
+ setCursor(
577
+ activeCorner === "tl" || activeCorner === "br" ? "nwse-resize" : "nesw-resize"
578
+ );
579
+ } else if (hitInside(mx, my, rect)) {
580
+ setCursor(CAMERA_CURSOR);
581
+ } else {
582
+ setCursor("crosshair");
583
+ }
584
+ } else {
585
+ setCursor("crosshair");
586
+ }
587
+ return;
588
+ }
813
589
  if (mode.current === "drawing") {
814
- const sx = start.current.x, sy = start.current.y;
815
- let x = Math.min(sx, mx), y = Math.min(sy, my);
816
- let w = Math.abs(mx - sx), h = Math.abs(my - sy);
590
+ const sx = start.current.x;
591
+ const sy = start.current.y;
592
+ let x = Math.min(sx, mx);
593
+ let y = Math.min(sy, my);
594
+ let w = Math.abs(mx - sx);
595
+ let h = Math.abs(my - sy);
817
596
  if (ratio > 0) {
818
597
  h = w / ratio;
819
- if (my < sy) y = sy - h;
598
+ if (my < sy) {
599
+ y = sy - h;
600
+ }
820
601
  }
821
- setRect(applySnap({ x, y, w, h }));
822
- } else if (mode.current === "moving") {
823
- const dx = mx - start.current.x, dy = my - start.current.y;
824
- setRect(
602
+ onRectChange(applySnap({ x, y, w, h }));
603
+ return;
604
+ }
605
+ if (mode.current === "moving") {
606
+ const dx = mx - start.current.x;
607
+ const dy = my - start.current.y;
608
+ onRectChange(
825
609
  applySnap({
826
610
  ...snapRef.current,
827
611
  x: snapRef.current.x + dx,
828
612
  y: snapRef.current.y + dy
829
613
  })
830
614
  );
831
- } else if (mode.current === "resizing") {
832
- const o = snapRef.current;
833
- const c = corner.current;
834
- const nr = { ...o };
835
- if (c === "br") {
836
- nr.w = Math.max(MIN_SIZE, mx - o.x);
837
- nr.h = ratio > 0 ? nr.w / ratio : Math.max(MIN_SIZE, my - o.y);
838
- } else if (c === "bl") {
839
- nr.w = Math.max(MIN_SIZE, o.x + o.w - mx);
840
- nr.x = o.x + o.w - nr.w;
841
- nr.h = ratio > 0 ? nr.w / ratio : Math.max(MIN_SIZE, my - o.y);
842
- } else if (c === "tr") {
843
- nr.w = Math.max(MIN_SIZE, mx - o.x);
844
- nr.h = ratio > 0 ? nr.w / ratio : Math.max(MIN_SIZE, o.y + o.h - my);
845
- nr.y = o.y + o.h - nr.h;
615
+ return;
616
+ }
617
+ if (mode.current === "resizing") {
618
+ const original = snapRef.current;
619
+ const nextRect = { ...original };
620
+ if (corner.current === "br") {
621
+ nextRect.w = Math.max(MIN_SIZE, mx - original.x);
622
+ nextRect.h = ratio > 0 ? nextRect.w / ratio : Math.max(MIN_SIZE, my - original.y);
623
+ } else if (corner.current === "bl") {
624
+ nextRect.w = Math.max(MIN_SIZE, original.x + original.w - mx);
625
+ nextRect.x = original.x + original.w - nextRect.w;
626
+ nextRect.h = ratio > 0 ? nextRect.w / ratio : Math.max(MIN_SIZE, my - original.y);
627
+ } else if (corner.current === "tr") {
628
+ nextRect.w = Math.max(MIN_SIZE, mx - original.x);
629
+ nextRect.h = ratio > 0 ? nextRect.w / ratio : Math.max(MIN_SIZE, original.y + original.h - my);
630
+ nextRect.y = original.y + original.h - nextRect.h;
846
631
  } else {
847
- nr.w = Math.max(MIN_SIZE, o.x + o.w - mx);
848
- nr.h = ratio > 0 ? nr.w / ratio : Math.max(MIN_SIZE, o.y + o.h - my);
849
- nr.x = o.x + o.w - nr.w;
850
- nr.y = o.y + o.h - nr.h;
632
+ nextRect.w = Math.max(MIN_SIZE, original.x + original.w - mx);
633
+ nextRect.h = ratio > 0 ? nextRect.w / ratio : Math.max(MIN_SIZE, original.y + original.h - my);
634
+ nextRect.x = original.x + original.w - nextRect.w;
635
+ nextRect.y = original.y + original.h - nextRect.h;
851
636
  }
852
- setRect(applySnap(nr));
853
- } else if (placed && rect) {
854
- const c = hitCorner(mx, my, rect);
855
- if (c === "tl" || c === "br") setCursor("nwse-resize");
856
- else if (c === "tr" || c === "bl") setCursor("nesw-resize");
857
- else if (hitInside(mx, my, rect)) setCursor("move");
858
- else setCursor("crosshair");
637
+ onRectChange(applySnap(nextRect));
859
638
  }
860
639
  },
861
- [ratio, placed, rect, hitCorner, hitInside, applySnap]
640
+ [applySnap, hitCorner, hitInside, onRectChange, ratio, rect]
862
641
  );
863
- const onUp = useCallback4(
642
+ const onUp = useCallback3(
864
643
  (e) => {
865
- const prevMode = mode.current;
866
- if (prevMode === "drawing" && rect) {
867
- if (rect.w >= MIN_SIZE && rect.h >= MIN_SIZE) {
868
- setPlaced(true);
869
- } else {
870
- setRect(null);
644
+ const previousMode = mode.current;
645
+ if (previousMode === "drawing" && rect) {
646
+ if (rect.w < MIN_SIZE || rect.h < MIN_SIZE) {
647
+ onRectChange(null);
871
648
  }
872
649
  }
873
- if (prevMode === "moving" && placed && rect) {
650
+ if (previousMode === "moving" && rect) {
874
651
  const dx = Math.abs(e.clientX - start.current.x);
875
652
  const dy = Math.abs(e.clientY - start.current.y);
876
653
  if (dx < 3 && dy < 3) {
@@ -884,51 +661,10 @@ function Selector({ onSelect, onCancel }) {
884
661
  }
885
662
  mode.current = "none";
886
663
  },
887
- [rect, placed, onSelect]
664
+ [onRectChange, onSelect, rect]
888
665
  );
889
- const setSize = useCallback4(
890
- (field, v) => {
891
- const n = parseInt(v, 10);
892
- if (isNaN(n) || n < MIN_SIZE || !rect) return;
893
- if (field === "w") {
894
- setRect({ ...rect, w: n, h: ratio > 0 ? n / ratio : rect.h });
895
- } else {
896
- setRect({ ...rect, h: n, w: ratio > 0 ? n * ratio : rect.w });
897
- }
898
- },
899
- [rect, ratio]
900
- );
901
- const setPos = useCallback4(
902
- (field, v) => {
903
- const n = parseInt(v, 10);
904
- if (isNaN(n) || !rect) return;
905
- setRect({ ...rect, [field]: n });
906
- },
907
- [rect]
908
- );
909
- const savePreset = useCallback4(() => {
910
- if (!rect) return;
911
- const label = `${Math.round(rect.w)}\xD7${Math.round(rect.h)}`;
912
- const next = [
913
- ...presets.filter((p) => p.label !== label),
914
- { label, rect: { ...rect } }
915
- ];
916
- setPresets(next);
917
- try {
918
- localStorage.setItem("ab-area-presets", JSON.stringify(next));
919
- } catch {
920
- }
921
- setSavedOpen(false);
922
- }, [rect, presets]);
923
- const loadPreset = useCallback4((p) => {
924
- setRect({ ...p.rect });
925
- setPlaced(true);
926
- setSavedOpen(false);
927
- }, []);
928
- const activeCursor = mode.current === "moving" ? "move" : mode.current === "resizing" ? "nwse-resize" : mode.current === "drawing" ? "crosshair" : cursor;
929
- const panelAbove = rect && rect.y + rect.h + PANEL_HEIGHT_EST > window.innerHeight;
930
666
  const hasRect = rect && rect.w > 0 && rect.h > 0;
931
- return /* @__PURE__ */ jsxs3(
667
+ return /* @__PURE__ */ jsxs2(
932
668
  "div",
933
669
  {
934
670
  "data-afterbefore": "true",
@@ -938,8 +674,8 @@ function Selector({ onSelect, onCancel }) {
938
674
  style: {
939
675
  position: "fixed",
940
676
  inset: 0,
941
- zIndex: 2147483647,
942
- cursor: activeCursor
677
+ zIndex: 2147483646,
678
+ cursor
943
679
  },
944
680
  children: [
945
681
  snappedX && /* @__PURE__ */ jsx3(
@@ -951,34 +687,84 @@ function Selector({ onSelect, onCancel }) {
951
687
  top: 0,
952
688
  bottom: 0,
953
689
  width: 0,
954
- borderLeft: "1px solid rgba(147, 130, 220, 0.5)",
690
+ borderLeft: "1px solid rgba(56, 189, 248, 0.55)",
955
691
  pointerEvents: "none",
956
692
  zIndex: 2
957
693
  }
958
694
  }
959
695
  ),
960
- /* @__PURE__ */ jsx3(
961
- "div",
962
- {
963
- style: {
964
- position: "absolute",
965
- inset: 0,
966
- background: "rgba(0, 0, 0, 0.5)",
967
- pointerEvents: "none",
968
- ...hasRect ? {
969
- clipPath: `polygon(
970
- 0% 0%, 0% 100%, 100% 100%, 100% 0%, 0% 0%,
971
- ${rect.x}px ${rect.y}px,
972
- ${rect.x}px ${rect.y + rect.h}px,
973
- ${rect.x + rect.w}px ${rect.y + rect.h}px,
974
- ${rect.x + rect.w}px ${rect.y}px,
975
- ${rect.x}px ${rect.y}px
976
- )`
977
- } : {}
696
+ hasRect ? /* @__PURE__ */ jsxs2(Fragment, { children: [
697
+ /* @__PURE__ */ jsx3(
698
+ "div",
699
+ {
700
+ style: {
701
+ position: "absolute",
702
+ left: 0,
703
+ top: 0,
704
+ width: "100%",
705
+ height: rect.y,
706
+ background: "rgba(0, 0, 0, 0.5)",
707
+ pointerEvents: "none"
708
+ }
978
709
  }
979
- }
980
- ),
981
- hasRect && /* @__PURE__ */ jsxs3(Fragment, { children: [
710
+ ),
711
+ /* @__PURE__ */ jsx3(
712
+ "div",
713
+ {
714
+ style: {
715
+ position: "absolute",
716
+ left: 0,
717
+ top: rect.y,
718
+ width: rect.x,
719
+ height: rect.h,
720
+ background: "rgba(0, 0, 0, 0.5)",
721
+ pointerEvents: "none"
722
+ }
723
+ }
724
+ ),
725
+ /* @__PURE__ */ jsx3(
726
+ "div",
727
+ {
728
+ style: {
729
+ position: "absolute",
730
+ left: rect.x + rect.w,
731
+ top: rect.y,
732
+ width: `calc(100% - ${rect.x + rect.w}px)`,
733
+ height: rect.h,
734
+ background: "rgba(0, 0, 0, 0.5)",
735
+ pointerEvents: "none"
736
+ }
737
+ }
738
+ ),
739
+ /* @__PURE__ */ jsx3(
740
+ "div",
741
+ {
742
+ style: {
743
+ position: "absolute",
744
+ left: 0,
745
+ top: rect.y + rect.h,
746
+ width: "100%",
747
+ height: `calc(100% - ${rect.y + rect.h}px)`,
748
+ background: "rgba(0, 0, 0, 0.5)",
749
+ pointerEvents: "none"
750
+ }
751
+ }
752
+ ),
753
+ /* @__PURE__ */ jsx3(
754
+ "div",
755
+ {
756
+ style: {
757
+ position: "absolute",
758
+ left: rect.x,
759
+ top: rect.y,
760
+ width: rect.w,
761
+ height: rect.h,
762
+ background: "rgba(125, 211, 252, 0.08)",
763
+ pointerEvents: "none",
764
+ borderRadius: 12
765
+ }
766
+ }
767
+ ),
982
768
  /* @__PURE__ */ jsx3(
983
769
  "div",
984
770
  {
@@ -989,17 +775,19 @@ function Selector({ onSelect, onCancel }) {
989
775
  width: rect.w,
990
776
  height: rect.h,
991
777
  border: "1.5px dashed rgba(255, 255, 255, 0.45)",
992
- pointerEvents: "none"
778
+ borderRadius: 12,
779
+ pointerEvents: "none",
780
+ boxShadow: "0 0 0 1px rgba(125, 211, 252, 0.38)"
993
781
  }
994
782
  }
995
783
  ),
996
- placed && [1, 2].map((i) => /* @__PURE__ */ jsxs3(React3.Fragment, { children: [
784
+ [1, 2].map((line) => /* @__PURE__ */ jsxs2(React3.Fragment, { children: [
997
785
  /* @__PURE__ */ jsx3(
998
786
  "div",
999
787
  {
1000
788
  style: {
1001
789
  position: "absolute",
1002
- left: rect.x + rect.w * i / 3,
790
+ left: rect.x + rect.w * line / 3,
1003
791
  top: rect.y,
1004
792
  width: 0,
1005
793
  height: rect.h,
@@ -1014,7 +802,7 @@ function Selector({ onSelect, onCancel }) {
1014
802
  style: {
1015
803
  position: "absolute",
1016
804
  left: rect.x,
1017
- top: rect.y + rect.h * i / 3,
805
+ top: rect.y + rect.h * line / 3,
1018
806
  width: rect.w,
1019
807
  height: 0,
1020
808
  borderTop: "1px dashed rgba(255, 255, 255, 0.18)",
@@ -1022,327 +810,521 @@ function Selector({ onSelect, onCancel }) {
1022
810
  }
1023
811
  }
1024
812
  )
1025
- ] }, i)),
1026
- placed && [
813
+ ] }, line)),
814
+ [
1027
815
  [rect.x, rect.y],
1028
816
  [rect.x + rect.w, rect.y],
1029
817
  [rect.x, rect.y + rect.h],
1030
818
  [rect.x + rect.w, rect.y + rect.h]
1031
- ].map(([cx, cy], i) => /* @__PURE__ */ jsx3(
819
+ ].map(([cx, cy], index) => /* @__PURE__ */ jsx3(
820
+ "div",
821
+ {
822
+ style: {
823
+ position: "absolute",
824
+ left: cx - HANDLE_R,
825
+ top: cy - HANDLE_R,
826
+ width: HANDLE_R * 2,
827
+ height: HANDLE_R * 2,
828
+ borderRadius: "50%",
829
+ border: "2px solid rgba(255, 255, 255, 0.8)",
830
+ background: "rgba(0, 0, 0, 0.25)",
831
+ pointerEvents: "none"
832
+ }
833
+ },
834
+ index
835
+ ))
836
+ ] }) : /* @__PURE__ */ jsx3(
837
+ "div",
838
+ {
839
+ style: {
840
+ position: "absolute",
841
+ inset: 0,
842
+ background: "rgba(0, 0, 0, 0.5)",
843
+ pointerEvents: "none"
844
+ }
845
+ }
846
+ )
847
+ ]
848
+ }
849
+ );
850
+ }
851
+
852
+ // src/overlay/ui/toolbar.tsx
853
+ import { Fragment as Fragment2, jsx as jsx4, jsxs as jsxs3 } from "react/jsx-runtime";
854
+ var MODES = [
855
+ { mode: "area", label: "Capture Selected Area", icon: Crop },
856
+ { mode: "viewport", label: "Capture Viewport", icon: Monitor },
857
+ { mode: "fullpage", label: "Capture Full Page", icon: FileText },
858
+ { mode: "component", label: "Capture Component", icon: MousePointer2 }
859
+ ];
860
+ function Toolbar({
861
+ selectedMode,
862
+ onModeChange,
863
+ onCapture,
864
+ onCancel,
865
+ magnetEnabled,
866
+ onMagnetChange,
867
+ areaSelectionActive,
868
+ areaRect,
869
+ areaAspect,
870
+ areaPresets,
871
+ onAreaSizeChange,
872
+ onAreaPositionChange,
873
+ onAreaAspectChange,
874
+ onAreaSavePreset,
875
+ onAreaLoadPreset
876
+ }) {
877
+ const [settingsOpen, setSettingsOpen] = useState5(false);
878
+ const [aspectOpen, setAspectOpen] = useState5(false);
879
+ const [savedOpen, setSavedOpen] = useState5(false);
880
+ const showAreaControls = selectedMode === "area" && areaSelectionActive && areaRect !== null;
881
+ const activeAreaRect = showAreaControls ? areaRect : null;
882
+ useEffect4(() => {
883
+ const onKey = (e) => {
884
+ if (e.target?.tagName === "INPUT") {
885
+ if (e.key === "Escape") {
886
+ e.target.blur();
887
+ }
888
+ return;
889
+ }
890
+ if (e.key === "Escape") {
891
+ if (settingsOpen) {
892
+ setSettingsOpen(false);
893
+ return;
894
+ }
895
+ if (aspectOpen) {
896
+ setAspectOpen(false);
897
+ return;
898
+ }
899
+ if (savedOpen) {
900
+ setSavedOpen(false);
901
+ return;
902
+ }
903
+ onCancel();
904
+ } else if (e.key === "Enter") {
905
+ onCapture(selectedMode);
906
+ }
907
+ };
908
+ document.addEventListener("keydown", onKey);
909
+ return () => document.removeEventListener("keydown", onKey);
910
+ }, [aspectOpen, onCancel, onCapture, savedOpen, selectedMode, settingsOpen]);
911
+ return /* @__PURE__ */ jsxs3(
912
+ "div",
913
+ {
914
+ "data-afterbefore": "true",
915
+ style: {
916
+ position: "fixed",
917
+ bottom: 48,
918
+ left: "50%",
919
+ transform: "translateX(-50%)",
920
+ zIndex: 2147483647,
921
+ display: "flex",
922
+ alignItems: "center",
923
+ gap: 10,
924
+ flexWrap: "wrap",
925
+ justifyContent: "center",
926
+ maxWidth: "min(calc(100vw - 32px), 1120px)",
927
+ background: "rgba(32, 32, 36, 0.92)",
928
+ backdropFilter: "blur(20px)",
929
+ WebkitBackdropFilter: "blur(20px)",
930
+ border: "1px solid rgba(255, 255, 255, 0.1)",
931
+ borderRadius: 18,
932
+ padding: "6px 10px",
933
+ boxShadow: "0 8px 32px rgba(0, 0, 0, 0.4)",
934
+ fontFamily: "system-ui, -apple-system, sans-serif"
935
+ },
936
+ children: [
937
+ /* @__PURE__ */ jsxs3("div", { style: { display: "flex", alignItems: "center", gap: 0 }, children: [
938
+ /* @__PURE__ */ jsx4(CloseButton, { onClick: onCancel }),
939
+ /* @__PURE__ */ jsx4("div", { style: { display: "flex", alignItems: "center", gap: 2, padding: "0 4px" }, children: MODES.map(({ mode, label, icon: Icon2 }) => /* @__PURE__ */ jsx4(
940
+ ModeButton,
941
+ {
942
+ label,
943
+ selected: selectedMode === mode,
944
+ onClick: () => {
945
+ setSettingsOpen(false);
946
+ onModeChange(mode);
947
+ },
948
+ children: /* @__PURE__ */ jsx4(Icon2, { size: 18, strokeWidth: 1.7 })
949
+ },
950
+ mode
951
+ )) }),
952
+ /* @__PURE__ */ jsx4(Separator, {}),
953
+ /* @__PURE__ */ jsx4(
954
+ SettingsButton,
955
+ {
956
+ open: settingsOpen,
957
+ onClick: () => {
958
+ setAspectOpen(false);
959
+ setSavedOpen(false);
960
+ setSettingsOpen((prev) => !prev);
961
+ },
962
+ magnetEnabled,
963
+ onMagnetChange
964
+ }
965
+ ),
966
+ !showAreaControls && /* @__PURE__ */ jsx4(
967
+ CaptureButton,
968
+ {
969
+ label: "Capture",
970
+ onClick: () => onCapture(selectedMode)
971
+ }
972
+ )
973
+ ] }),
974
+ activeAreaRect && /* @__PURE__ */ jsxs3(Fragment2, { children: [
975
+ /* @__PURE__ */ jsx4(Separator, { vertical: false }),
976
+ /* @__PURE__ */ jsxs3(
977
+ "div",
978
+ {
979
+ style: {
980
+ display: "flex",
981
+ alignItems: "center",
982
+ gap: 8,
983
+ flexWrap: "wrap",
984
+ justifyContent: "center"
985
+ },
986
+ children: [
987
+ /* @__PURE__ */ jsxs3(ControlGroup, { label: "Size", children: [
988
+ /* @__PURE__ */ jsx4(NumInput, { value: Math.round(activeAreaRect.w), onChange: (value) => onAreaSizeChange("w", value) }),
989
+ /* @__PURE__ */ jsx4(StaticText, { children: "x" }),
990
+ /* @__PURE__ */ jsx4(NumInput, { value: Math.round(activeAreaRect.h), onChange: (value) => onAreaSizeChange("h", value) })
991
+ ] }),
992
+ /* @__PURE__ */ jsxs3(ControlGroup, { label: "Position", children: [
993
+ /* @__PURE__ */ jsx4(NumInput, { value: Math.round(activeAreaRect.x), onChange: (value) => onAreaPositionChange("x", value) }),
994
+ /* @__PURE__ */ jsx4(StaticText, { children: "x" }),
995
+ /* @__PURE__ */ jsx4(NumInput, { value: Math.round(activeAreaRect.y), onChange: (value) => onAreaPositionChange("y", value) })
996
+ ] }),
997
+ /* @__PURE__ */ jsxs3("div", { style: { position: "relative" }, children: [
998
+ /* @__PURE__ */ jsxs3(
999
+ DropButton,
1000
+ {
1001
+ active: areaAspect !== "Free",
1002
+ onClick: () => {
1003
+ setSavedOpen(false);
1004
+ setAspectOpen((prev) => !prev);
1005
+ },
1006
+ children: [
1007
+ areaAspect,
1008
+ /* @__PURE__ */ jsx4(ChevronDown, { size: 14, strokeWidth: 1.8 })
1009
+ ]
1010
+ }
1011
+ ),
1012
+ aspectOpen && /* @__PURE__ */ jsx4(DropMenu, { children: AREA_ASPECT_RATIOS.map((item) => /* @__PURE__ */ jsx4(
1013
+ DropItem,
1014
+ {
1015
+ active: item.label === areaAspect,
1016
+ onClick: () => {
1017
+ onAreaAspectChange(item.label);
1018
+ setAspectOpen(false);
1019
+ },
1020
+ children: item.label
1021
+ },
1022
+ item.label
1023
+ )) })
1024
+ ] }),
1025
+ /* @__PURE__ */ jsxs3("div", { style: { position: "relative" }, children: [
1026
+ /* @__PURE__ */ jsxs3(
1027
+ DropButton,
1028
+ {
1029
+ onClick: () => {
1030
+ setAspectOpen(false);
1031
+ setSavedOpen((prev) => !prev);
1032
+ },
1033
+ children: [
1034
+ /* @__PURE__ */ jsx4(Save, { size: 14, strokeWidth: 1.8 }),
1035
+ "Saved",
1036
+ /* @__PURE__ */ jsx4(ChevronDown, { size: 14, strokeWidth: 1.8 })
1037
+ ]
1038
+ }
1039
+ ),
1040
+ savedOpen && /* @__PURE__ */ jsxs3(DropMenu, { children: [
1041
+ /* @__PURE__ */ jsx4(DropItem, { accent: true, onClick: onAreaSavePreset, children: "Save current" }),
1042
+ areaPresets.length > 0 && /* @__PURE__ */ jsx4(MenuDivider, {}),
1043
+ areaPresets.map((preset) => /* @__PURE__ */ jsx4(
1044
+ DropItem,
1045
+ {
1046
+ onClick: () => {
1047
+ onAreaLoadPreset(preset);
1048
+ setSavedOpen(false);
1049
+ },
1050
+ children: preset.label
1051
+ },
1052
+ preset.label
1053
+ )),
1054
+ areaPresets.length === 0 && /* @__PURE__ */ jsx4(
1055
+ "div",
1056
+ {
1057
+ style: {
1058
+ padding: "6px 12px",
1059
+ color: "rgba(255,255,255,0.3)",
1060
+ fontSize: 12
1061
+ },
1062
+ children: "No saved areas"
1063
+ }
1064
+ )
1065
+ ] })
1066
+ ] })
1067
+ ]
1068
+ }
1069
+ )
1070
+ ] })
1071
+ ]
1072
+ }
1073
+ );
1074
+ }
1075
+ function CloseButton({ onClick }) {
1076
+ const [hovered, setHovered] = useState5(false);
1077
+ return /* @__PURE__ */ jsx4(
1078
+ "button",
1079
+ {
1080
+ onClick,
1081
+ onMouseEnter: () => setHovered(true),
1082
+ onMouseLeave: () => setHovered(false),
1083
+ style: circleButtonStyle(hovered, true),
1084
+ children: /* @__PURE__ */ jsx4(X, { size: 18, strokeWidth: 1.9 })
1085
+ }
1086
+ );
1087
+ }
1088
+ function ModeButton({
1089
+ children,
1090
+ label,
1091
+ selected,
1092
+ onClick
1093
+ }) {
1094
+ const [hovered, setHovered] = useState5(false);
1095
+ return /* @__PURE__ */ jsxs3("div", { style: { position: "relative" }, children: [
1096
+ hovered && /* @__PURE__ */ jsx4(
1097
+ "div",
1098
+ {
1099
+ style: {
1100
+ position: "absolute",
1101
+ left: "50%",
1102
+ bottom: "calc(100% + 10px)",
1103
+ transform: "translateX(-50%)",
1104
+ background: "rgba(32, 32, 36, 0.96)",
1105
+ backdropFilter: "blur(20px)",
1106
+ WebkitBackdropFilter: "blur(20px)",
1107
+ border: "1px solid rgba(255, 255, 255, 0.1)",
1108
+ borderRadius: 8,
1109
+ padding: "5px 10px",
1110
+ color: "rgba(255, 255, 255, 0.88)",
1111
+ fontSize: 12,
1112
+ whiteSpace: "nowrap",
1113
+ boxShadow: "0 8px 28px rgba(0, 0, 0, 0.28)",
1114
+ pointerEvents: "none"
1115
+ },
1116
+ children: label
1117
+ }
1118
+ ),
1119
+ /* @__PURE__ */ jsx4(
1120
+ "button",
1121
+ {
1122
+ onClick,
1123
+ onMouseEnter: () => setHovered(true),
1124
+ onMouseLeave: () => setHovered(false),
1125
+ style: {
1126
+ width: 42,
1127
+ height: 40,
1128
+ borderRadius: 12,
1129
+ border: "none",
1130
+ background: selected ? "rgba(255, 255, 255, 0.15)" : hovered ? "rgba(255, 255, 255, 0.08)" : "transparent",
1131
+ display: "flex",
1132
+ alignItems: "center",
1133
+ justifyContent: "center",
1134
+ cursor: "pointer",
1135
+ padding: 0,
1136
+ color: selected ? "rgba(255, 255, 255, 0.96)" : "rgba(255, 255, 255, 0.52)",
1137
+ transition: "background 0.12s ease, color 0.12s ease"
1138
+ },
1139
+ children
1140
+ }
1141
+ )
1142
+ ] });
1143
+ }
1144
+ function SettingsButton({
1145
+ open,
1146
+ onClick,
1147
+ magnetEnabled,
1148
+ onMagnetChange
1149
+ }) {
1150
+ const [hovered, setHovered] = useState5(false);
1151
+ return /* @__PURE__ */ jsxs3("div", { style: { position: "relative", marginRight: 6 }, children: [
1152
+ /* @__PURE__ */ jsx4(
1153
+ "button",
1154
+ {
1155
+ onClick,
1156
+ onMouseEnter: () => setHovered(true),
1157
+ onMouseLeave: () => setHovered(false),
1158
+ style: {
1159
+ ...circleButtonStyle(open || hovered, false),
1160
+ color: "rgba(255, 255, 255, 0.58)"
1161
+ },
1162
+ children: /* @__PURE__ */ jsx4(Settings2, { size: 18, strokeWidth: 1.7 })
1163
+ }
1164
+ ),
1165
+ open && /* @__PURE__ */ jsxs3(
1166
+ "div",
1167
+ {
1168
+ style: {
1169
+ position: "absolute",
1170
+ left: "50%",
1171
+ bottom: "calc(100% + 12px)",
1172
+ transform: "translateX(-50%)",
1173
+ minWidth: 184,
1174
+ padding: "10px 12px",
1175
+ borderRadius: 12,
1176
+ background: "rgba(32, 32, 36, 0.96)",
1177
+ border: "1px solid rgba(255, 255, 255, 0.1)",
1178
+ boxShadow: "0 14px 36px rgba(0, 0, 0, 0.32)",
1179
+ backdropFilter: "blur(20px)",
1180
+ WebkitBackdropFilter: "blur(20px)"
1181
+ },
1182
+ children: [
1183
+ /* @__PURE__ */ jsx4(
1032
1184
  "div",
1033
1185
  {
1034
1186
  style: {
1035
- position: "absolute",
1036
- left: cx - HANDLE_R,
1037
- top: cy - HANDLE_R,
1038
- width: HANDLE_R * 2,
1039
- height: HANDLE_R * 2,
1040
- borderRadius: "50%",
1041
- border: "2px solid rgba(255, 255, 255, 0.8)",
1042
- background: "rgba(0, 0, 0, 0.25)",
1043
- pointerEvents: "none"
1044
- }
1045
- },
1046
- i
1047
- )),
1048
- placed && /* @__PURE__ */ jsxs3(
1049
- "div",
1187
+ fontSize: 11,
1188
+ color: "rgba(255, 255, 255, 0.46)",
1189
+ letterSpacing: "0.03em",
1190
+ textTransform: "uppercase",
1191
+ marginBottom: 10
1192
+ },
1193
+ children: "Area Settings"
1194
+ }
1195
+ ),
1196
+ /* @__PURE__ */ jsxs3(
1197
+ "label",
1050
1198
  {
1051
- ref: panelRef,
1052
- onMouseDown: (e) => e.stopPropagation(),
1053
1199
  style: {
1054
- position: "absolute",
1055
- left: rect.x + rect.w / 2,
1056
- ...panelAbove ? { bottom: window.innerHeight - rect.y + 16 } : { top: rect.y + rect.h + 16 },
1057
- transform: "translateX(-50%)",
1058
- background: "rgba(32, 32, 36, 0.92)",
1059
- backdropFilter: "blur(20px)",
1060
- WebkitBackdropFilter: "blur(20px)",
1061
- border: "1px solid rgba(255, 255, 255, 0.1)",
1062
- borderRadius: 10,
1063
- padding: "10px 14px",
1064
1200
  display: "flex",
1065
- flexDirection: "column",
1066
- gap: 6,
1067
- minWidth: 0,
1068
- fontFamily: "system-ui, -apple-system, sans-serif",
1069
- color: "rgba(255, 255, 255, 0.9)",
1070
- boxShadow: "0 8px 32px rgba(0, 0, 0, 0.4)",
1071
- zIndex: 1
1201
+ alignItems: "center",
1202
+ justifyContent: "space-between",
1203
+ gap: 12,
1204
+ cursor: "pointer"
1072
1205
  },
1073
1206
  children: [
1074
- /* @__PURE__ */ jsxs3(Row, { label: "Size", children: [
1075
- /* @__PURE__ */ jsx3(
1076
- NumInput,
1077
- {
1078
- value: Math.round(rect.w),
1079
- onChange: (v) => setSize("w", v)
1080
- }
1081
- ),
1082
- /* @__PURE__ */ jsx3(Sep, { children: "\xD7" }),
1083
- /* @__PURE__ */ jsx3(
1084
- NumInput,
1085
- {
1086
- value: Math.round(rect.h),
1087
- onChange: (v) => setSize("h", v)
1088
- }
1089
- ),
1090
- /* @__PURE__ */ jsx3(Unit, { children: "px" })
1091
- ] }),
1092
- /* @__PURE__ */ jsxs3(Row, { label: "Position", children: [
1093
- /* @__PURE__ */ jsx3(
1094
- NumInput,
1095
- {
1096
- value: Math.round(rect.x),
1097
- onChange: (v) => setPos("x", v)
1098
- }
1099
- ),
1100
- /* @__PURE__ */ jsx3(Sep, {}),
1101
- /* @__PURE__ */ jsx3(
1102
- NumInput,
1103
- {
1104
- value: Math.round(rect.y),
1105
- onChange: (v) => setPos("y", v)
1106
- }
1107
- ),
1108
- /* @__PURE__ */ jsx3(Unit, { children: "px" })
1109
- ] }),
1110
- /* @__PURE__ */ jsx3(
1111
- "div",
1112
- {
1113
- style: {
1114
- height: 1,
1115
- background: "rgba(255, 255, 255, 0.08)",
1116
- margin: "1px 0"
1117
- }
1118
- }
1119
- ),
1120
1207
  /* @__PURE__ */ jsxs3(
1121
- "div",
1208
+ "span",
1122
1209
  {
1123
- style: { display: "flex", alignItems: "center", gap: 12 },
1210
+ style: {
1211
+ display: "flex",
1212
+ alignItems: "center",
1213
+ gap: 8,
1214
+ color: "rgba(255, 255, 255, 0.88)",
1215
+ fontSize: 13
1216
+ },
1124
1217
  children: [
1125
- /* @__PURE__ */ jsxs3("div", { style: { position: "relative" }, children: [
1126
- /* @__PURE__ */ jsxs3(
1127
- DropBtn,
1128
- {
1129
- active: aspect !== "Free",
1130
- onClick: () => {
1131
- setAspectOpen(!aspectOpen);
1132
- setSavedOpen(false);
1133
- },
1134
- children: [
1135
- /* @__PURE__ */ jsx3(AspectIcon, {}),
1136
- aspect === "Free" ? "Free" : aspect,
1137
- /* @__PURE__ */ jsx3(Chevron, {})
1138
- ]
1139
- }
1140
- ),
1141
- aspectOpen && /* @__PURE__ */ jsx3(DropMenu, { children: ASPECT_RATIOS.map((ar) => /* @__PURE__ */ jsx3(
1142
- DropItem,
1143
- {
1144
- active: ar.label === aspect,
1145
- onClick: () => {
1146
- setAspect(ar.label);
1147
- setAspectOpen(false);
1148
- if (ar.value > 0 && rect) {
1149
- setRect({ ...rect, h: rect.w / ar.value });
1150
- }
1151
- },
1152
- children: ar.label
1153
- },
1154
- ar.label
1155
- )) })
1156
- ] }),
1157
- /* @__PURE__ */ jsxs3("div", { style: { position: "relative" }, children: [
1158
- /* @__PURE__ */ jsxs3(
1159
- DropBtn,
1160
- {
1161
- onClick: () => {
1162
- setSavedOpen(!savedOpen);
1163
- setAspectOpen(false);
1164
- },
1165
- children: [
1166
- /* @__PURE__ */ jsx3(SavedIcon, {}),
1167
- "Saved",
1168
- /* @__PURE__ */ jsx3(Chevron, {})
1169
- ]
1170
- }
1171
- ),
1172
- savedOpen && /* @__PURE__ */ jsxs3(DropMenu, { children: [
1173
- /* @__PURE__ */ jsx3(
1174
- DropItem,
1175
- {
1176
- accent: true,
1177
- onClick: savePreset,
1178
- children: "Save current"
1179
- }
1180
- ),
1181
- presets.length > 0 && /* @__PURE__ */ jsx3(
1182
- "div",
1183
- {
1184
- style: {
1185
- height: 1,
1186
- background: "rgba(255,255,255,0.08)",
1187
- margin: "4px 0"
1188
- }
1189
- }
1190
- ),
1191
- presets.map((p) => /* @__PURE__ */ jsx3(
1192
- DropItem,
1193
- {
1194
- onClick: () => loadPreset(p),
1195
- children: p.label
1196
- },
1197
- p.label
1198
- )),
1199
- presets.length === 0 && /* @__PURE__ */ jsx3(
1200
- "div",
1201
- {
1202
- style: {
1203
- padding: "6px 12px",
1204
- color: "rgba(255,255,255,0.3)",
1205
- fontSize: 12
1206
- },
1207
- children: "No saved areas"
1208
- }
1209
- )
1210
- ] })
1211
- ] })
1218
+ /* @__PURE__ */ jsx4(Magnet, { size: 15, strokeWidth: 1.8 }),
1219
+ "Magnet snap"
1212
1220
  ]
1213
1221
  }
1214
1222
  ),
1215
- /* @__PURE__ */ jsx3(
1216
- "div",
1223
+ /* @__PURE__ */ jsx4(
1224
+ "button",
1217
1225
  {
1226
+ type: "button",
1227
+ onClick: () => onMagnetChange(!magnetEnabled),
1218
1228
  style: {
1219
- display: "flex",
1220
- alignItems: "center",
1221
- gap: 6
1229
+ width: 38,
1230
+ height: 22,
1231
+ borderRadius: 999,
1232
+ border: "none",
1233
+ background: magnetEnabled ? "#38bdf8" : "rgba(255, 255, 255, 0.18)",
1234
+ position: "relative",
1235
+ cursor: "pointer",
1236
+ padding: 0,
1237
+ transition: "background 0.12s ease"
1222
1238
  },
1223
- children: /* @__PURE__ */ jsx3(
1224
- MagnetToggle,
1239
+ children: /* @__PURE__ */ jsx4(
1240
+ "span",
1225
1241
  {
1226
- enabled: magnet,
1227
- onToggle: () => {
1228
- const next = !magnet;
1229
- setMagnet(next);
1230
- try {
1231
- localStorage.setItem("ab-magnet", String(next));
1232
- } catch {
1233
- }
1242
+ style: {
1243
+ position: "absolute",
1244
+ top: 2,
1245
+ left: magnetEnabled ? 18 : 2,
1246
+ width: 18,
1247
+ height: 18,
1248
+ borderRadius: "50%",
1249
+ background: "#fff",
1250
+ transition: "left 0.12s ease"
1234
1251
  }
1235
1252
  }
1236
1253
  )
1237
1254
  }
1238
- ),
1239
- /* @__PURE__ */ jsx3(
1240
- "div",
1241
- {
1242
- style: {
1243
- fontSize: 10,
1244
- color: "rgba(255, 255, 255, 0.25)",
1245
- textAlign: "center"
1246
- },
1247
- children: "Click area or Enter to capture \xB7 Esc to cancel"
1248
- }
1249
1255
  )
1250
1256
  ]
1251
1257
  }
1252
- ),
1253
- !placed && /* @__PURE__ */ jsxs3(
1254
- "div",
1255
- {
1256
- style: {
1257
- position: "absolute",
1258
- left: rect.x + rect.w / 2,
1259
- top: rect.y + rect.h + 8,
1260
- transform: "translateX(-50%)",
1261
- background: "rgba(24, 24, 27, 0.9)",
1262
- color: "rgba(255, 255, 255, 0.9)",
1263
- fontSize: 11,
1264
- fontFamily: "system-ui, -apple-system, monospace",
1265
- padding: "2px 8px",
1266
- borderRadius: 4,
1267
- whiteSpace: "nowrap",
1268
- pointerEvents: "none"
1269
- },
1270
- children: [
1271
- Math.round(rect.w),
1272
- " \xD7 ",
1273
- Math.round(rect.h)
1274
- ]
1275
- }
1276
1258
  )
1277
- ] }),
1278
- !rect && /* @__PURE__ */ jsx3(
1279
- "div",
1280
- {
1281
- style: {
1282
- position: "absolute",
1283
- top: "50%",
1284
- left: "50%",
1285
- transform: "translate(-50%, -50%)",
1286
- color: "rgba(255, 255, 255, 0.7)",
1287
- fontSize: 14,
1288
- fontFamily: "system-ui, -apple-system, sans-serif",
1289
- pointerEvents: "none",
1290
- textShadow: "0 1px 4px rgba(0, 0, 0, 0.5)"
1291
- },
1292
- children: "Drag to select an area \xB7 Esc to cancel"
1293
- }
1294
- )
1295
- ]
1296
- }
1297
- );
1298
- }
1299
- function Row({
1300
- label,
1301
- children
1302
- }) {
1303
- return /* @__PURE__ */ jsxs3("div", { style: { display: "flex", alignItems: "center", gap: 6 }, children: [
1304
- /* @__PURE__ */ jsx3(
1305
- "span",
1306
- {
1307
- style: {
1308
- width: 36,
1309
- fontSize: 11,
1310
- color: "rgba(255, 255, 255, 0.4)",
1311
- fontWeight: 400,
1312
- flexShrink: 0
1313
- },
1314
- children: label
1259
+ ]
1315
1260
  }
1316
- ),
1317
- children
1261
+ )
1318
1262
  ] });
1319
1263
  }
1320
- function Sep({ children }) {
1321
- return /* @__PURE__ */ jsx3(
1322
- "span",
1264
+ function CaptureButton({
1265
+ label,
1266
+ onClick
1267
+ }) {
1268
+ const [hovered, setHovered] = useState5(false);
1269
+ return /* @__PURE__ */ jsxs3(
1270
+ "button",
1323
1271
  {
1272
+ onClick,
1273
+ onMouseEnter: () => setHovered(true),
1274
+ onMouseLeave: () => setHovered(false),
1324
1275
  style: {
1325
- fontSize: 11,
1326
- color: "rgba(255, 255, 255, 0.35)",
1327
- width: 12,
1328
- textAlign: "center",
1276
+ padding: "0 18px",
1277
+ height: 40,
1278
+ borderRadius: 12,
1279
+ border: "none",
1280
+ background: hovered ? "#2f7bf8" : "#3b82f6",
1281
+ color: "white",
1282
+ fontSize: 13,
1283
+ fontWeight: 600,
1284
+ fontFamily: "inherit",
1285
+ cursor: "pointer",
1286
+ whiteSpace: "nowrap",
1287
+ transition: "background 0.12s ease",
1329
1288
  flexShrink: 0,
1330
- visibility: children ? "visible" : "hidden"
1289
+ display: "flex",
1290
+ alignItems: "center",
1291
+ gap: 8
1331
1292
  },
1332
- children: children || "\xD7"
1293
+ children: [
1294
+ /* @__PURE__ */ jsx4(Camera2, { size: 16, strokeWidth: 1.9 }),
1295
+ label
1296
+ ]
1333
1297
  }
1334
1298
  );
1335
1299
  }
1336
- function Unit({ children }) {
1337
- return /* @__PURE__ */ jsx3(
1338
- "span",
1300
+ function ControlGroup({
1301
+ label,
1302
+ children
1303
+ }) {
1304
+ return /* @__PURE__ */ jsxs3(
1305
+ "div",
1339
1306
  {
1340
1307
  style: {
1341
- fontSize: 10,
1342
- color: "rgba(255, 255, 255, 0.3)",
1343
- flexShrink: 0
1308
+ display: "flex",
1309
+ alignItems: "center",
1310
+ gap: 6,
1311
+ padding: "0 4px"
1344
1312
  },
1345
- children
1313
+ children: [
1314
+ /* @__PURE__ */ jsx4(
1315
+ "span",
1316
+ {
1317
+ style: {
1318
+ fontSize: 11,
1319
+ color: "rgba(255, 255, 255, 0.42)",
1320
+ textTransform: "uppercase",
1321
+ letterSpacing: "0.03em"
1322
+ },
1323
+ children: label
1324
+ }
1325
+ ),
1326
+ children
1327
+ ]
1346
1328
  }
1347
1329
  );
1348
1330
  }
@@ -1350,12 +1332,14 @@ function NumInput({
1350
1332
  value,
1351
1333
  onChange
1352
1334
  }) {
1353
- const [editing, setEditing] = useState4(false);
1354
- const [text, setText] = useState4(String(value));
1355
- useEffect3(() => {
1356
- if (!editing) setText(String(value));
1357
- }, [value, editing]);
1358
- return /* @__PURE__ */ jsx3(
1335
+ const [editing, setEditing] = useState5(false);
1336
+ const [text, setText] = useState5(String(value));
1337
+ useEffect4(() => {
1338
+ if (!editing) {
1339
+ setText(String(value));
1340
+ }
1341
+ }, [editing, value]);
1342
+ return /* @__PURE__ */ jsx4(
1359
1343
  "input",
1360
1344
  {
1361
1345
  type: "text",
@@ -1370,14 +1354,16 @@ function NumInput({
1370
1354
  },
1371
1355
  onChange: (e) => setText(e.target.value),
1372
1356
  onKeyDown: (e) => {
1373
- if (e.key === "Enter") e.target.blur();
1357
+ if (e.key === "Enter") {
1358
+ e.target.blur();
1359
+ }
1374
1360
  },
1375
1361
  style: {
1376
- width: 52,
1377
- padding: "3px 6px",
1362
+ width: 54,
1363
+ padding: "4px 6px",
1378
1364
  background: "rgba(255, 255, 255, 0.07)",
1379
1365
  border: "1px solid rgba(255, 255, 255, 0.1)",
1380
- borderRadius: 4,
1366
+ borderRadius: 7,
1381
1367
  color: "rgba(255, 255, 255, 0.9)",
1382
1368
  fontSize: 12,
1383
1369
  fontFamily: "system-ui, -apple-system, sans-serif",
@@ -1387,46 +1373,62 @@ function NumInput({
1387
1373
  }
1388
1374
  );
1389
1375
  }
1390
- function DropBtn({
1376
+ function StaticText({ children }) {
1377
+ return /* @__PURE__ */ jsx4(
1378
+ "span",
1379
+ {
1380
+ style: {
1381
+ fontSize: 11,
1382
+ color: "rgba(255,255,255,0.35)",
1383
+ minWidth: 8,
1384
+ textAlign: "center"
1385
+ },
1386
+ children
1387
+ }
1388
+ );
1389
+ }
1390
+ function DropButton({
1391
1391
  children,
1392
1392
  onClick,
1393
1393
  active
1394
1394
  }) {
1395
- return /* @__PURE__ */ jsx3(
1395
+ return /* @__PURE__ */ jsx4(
1396
1396
  "button",
1397
1397
  {
1398
1398
  onClick,
1399
1399
  style: {
1400
1400
  display: "flex",
1401
1401
  alignItems: "center",
1402
- gap: 4,
1403
- background: "none",
1404
- border: "none",
1405
- color: active ? "rgba(147, 130, 220, 0.9)" : "rgba(255, 255, 255, 0.5)",
1402
+ gap: 6,
1403
+ height: 34,
1404
+ padding: "0 10px",
1405
+ borderRadius: 10,
1406
+ border: "1px solid rgba(255,255,255,0.08)",
1407
+ background: "rgba(255,255,255,0.04)",
1408
+ color: active ? "rgba(125, 211, 252, 0.96)" : "rgba(255,255,255,0.78)",
1406
1409
  cursor: "pointer",
1407
- fontSize: 11,
1408
- fontFamily: "inherit",
1409
- padding: "2px 0"
1410
+ fontSize: 12,
1411
+ fontFamily: "inherit"
1410
1412
  },
1411
1413
  children
1412
1414
  }
1413
1415
  );
1414
1416
  }
1415
1417
  function DropMenu({ children }) {
1416
- return /* @__PURE__ */ jsx3(
1418
+ return /* @__PURE__ */ jsx4(
1417
1419
  "div",
1418
1420
  {
1419
1421
  style: {
1420
1422
  position: "absolute",
1421
- bottom: "100%",
1422
- left: 0,
1423
- marginBottom: 4,
1424
- background: "rgba(32, 32, 36, 0.95)",
1423
+ bottom: "calc(100% + 8px)",
1424
+ left: "50%",
1425
+ transform: "translateX(-50%)",
1426
+ minWidth: 120,
1427
+ background: "rgba(32, 32, 36, 0.96)",
1425
1428
  border: "1px solid rgba(255, 255, 255, 0.1)",
1426
- borderRadius: 8,
1429
+ borderRadius: 10,
1427
1430
  padding: "4px 0",
1428
- minWidth: 110,
1429
- boxShadow: "0 4px 16px rgba(0, 0, 0, 0.3)",
1431
+ boxShadow: "0 10px 30px rgba(0, 0, 0, 0.3)",
1430
1432
  backdropFilter: "blur(20px)",
1431
1433
  WebkitBackdropFilter: "blur(20px)"
1432
1434
  },
@@ -1440,17 +1442,17 @@ function DropItem({
1440
1442
  active,
1441
1443
  accent
1442
1444
  }) {
1443
- return /* @__PURE__ */ jsx3(
1445
+ return /* @__PURE__ */ jsx4(
1444
1446
  "button",
1445
1447
  {
1446
1448
  onClick,
1447
1449
  style: {
1448
1450
  display: "block",
1449
1451
  width: "100%",
1450
- padding: "6px 12px",
1451
- background: active ? "rgba(255, 255, 255, 0.08)" : "none",
1452
+ padding: "7px 12px",
1453
+ background: active ? "rgba(255, 255, 255, 0.08)" : "transparent",
1452
1454
  border: "none",
1453
- color: accent ? "rgba(147, 130, 220, 0.9)" : "rgba(255, 255, 255, 0.8)",
1455
+ color: accent ? "rgba(125, 211, 252, 0.96)" : "rgba(255, 255, 255, 0.86)",
1454
1456
  textAlign: "left",
1455
1457
  cursor: "pointer",
1456
1458
  fontSize: 13,
@@ -1460,123 +1462,56 @@ function DropItem({
1460
1462
  }
1461
1463
  );
1462
1464
  }
1463
- function Chevron() {
1464
- return /* @__PURE__ */ jsx3("svg", { width: "10", height: "10", viewBox: "0 0 10 10", fill: "none", children: /* @__PURE__ */ jsx3(
1465
- "path",
1465
+ function MenuDivider() {
1466
+ return /* @__PURE__ */ jsx4(
1467
+ "div",
1466
1468
  {
1467
- d: "M3 4l2 2.5L7 4",
1468
- stroke: "currentColor",
1469
- strokeWidth: "1.2"
1470
- }
1471
- ) });
1472
- }
1473
- function AspectIcon() {
1474
- return /* @__PURE__ */ jsxs3("svg", { width: "16", height: "16", viewBox: "0 0 16 16", fill: "none", children: [
1475
- /* @__PURE__ */ jsx3(
1476
- "rect",
1477
- {
1478
- x: "2",
1479
- y: "4",
1480
- width: "12",
1481
- height: "8",
1482
- rx: "1.5",
1483
- stroke: "currentColor",
1484
- strokeWidth: "1.2",
1485
- strokeDasharray: "2 1.5"
1486
- }
1487
- ),
1488
- /* @__PURE__ */ jsx3(
1489
- "line",
1490
- {
1491
- x1: "6.33",
1492
- y1: "4",
1493
- x2: "6.33",
1494
- y2: "12",
1495
- stroke: "currentColor",
1496
- strokeWidth: "0.8",
1497
- strokeDasharray: "1.5 1"
1498
- }
1499
- ),
1500
- /* @__PURE__ */ jsx3(
1501
- "line",
1502
- {
1503
- x1: "9.67",
1504
- y1: "4",
1505
- x2: "9.67",
1506
- y2: "12",
1507
- stroke: "currentColor",
1508
- strokeWidth: "0.8",
1509
- strokeDasharray: "1.5 1"
1469
+ style: {
1470
+ height: 1,
1471
+ background: "rgba(255,255,255,0.08)",
1472
+ margin: "4px 0"
1510
1473
  }
1511
- )
1512
- ] });
1513
- }
1514
- function SavedIcon() {
1515
- return /* @__PURE__ */ jsx3("svg", { width: "16", height: "16", viewBox: "0 0 16 16", fill: "none", children: /* @__PURE__ */ jsx3(
1516
- "rect",
1517
- {
1518
- x: "2",
1519
- y: "3",
1520
- width: "12",
1521
- height: "10",
1522
- rx: "1.5",
1523
- stroke: "currentColor",
1524
- strokeWidth: "1.2",
1525
- strokeDasharray: "2 1.5"
1526
1474
  }
1527
- ) });
1475
+ );
1528
1476
  }
1529
- function MagnetToggle({
1530
- enabled,
1531
- onToggle
1532
- }) {
1533
- return /* @__PURE__ */ jsxs3(
1534
- "button",
1477
+ function Separator({ vertical = true }) {
1478
+ return /* @__PURE__ */ jsx4(
1479
+ "div",
1535
1480
  {
1536
- onClick: onToggle,
1537
1481
  style: {
1538
- display: "flex",
1539
- alignItems: "center",
1540
- gap: 5,
1541
- background: "none",
1542
- border: "none",
1543
- color: enabled ? "rgba(147, 130, 220, 0.9)" : "rgba(255, 255, 255, 0.35)",
1544
- cursor: "pointer",
1545
- fontSize: 11,
1546
- fontFamily: "inherit",
1547
- padding: "2px 0"
1548
- },
1549
- children: [
1550
- /* @__PURE__ */ jsx3(MagnetIcon, {}),
1551
- "Magnet"
1552
- ]
1482
+ width: vertical ? 1 : 24,
1483
+ height: vertical ? 24 : 1,
1484
+ background: "rgba(255,255,255,0.12)",
1485
+ flexShrink: 0
1486
+ }
1553
1487
  }
1554
1488
  );
1555
1489
  }
1556
- function MagnetIcon() {
1557
- return /* @__PURE__ */ jsxs3("svg", { width: "14", height: "14", viewBox: "0 0 16 16", fill: "none", children: [
1558
- /* @__PURE__ */ jsx3(
1559
- "path",
1560
- {
1561
- d: "M4 2v4a4 4 0 008 0V2",
1562
- stroke: "currentColor",
1563
- strokeWidth: "1.3",
1564
- strokeLinecap: "round"
1565
- }
1566
- ),
1567
- /* @__PURE__ */ jsx3("line", { x1: "4", y1: "2", x2: "4", y2: "5", stroke: "currentColor", strokeWidth: "2.5", strokeLinecap: "round" }),
1568
- /* @__PURE__ */ jsx3("line", { x1: "12", y1: "2", x2: "12", y2: "5", stroke: "currentColor", strokeWidth: "2.5", strokeLinecap: "round" })
1569
- ] });
1490
+ function circleButtonStyle(active, round) {
1491
+ return {
1492
+ width: 40,
1493
+ height: 40,
1494
+ borderRadius: round ? "50%" : 12,
1495
+ border: "none",
1496
+ background: active ? "rgba(255, 255, 255, 0.12)" : "transparent",
1497
+ display: "flex",
1498
+ alignItems: "center",
1499
+ justifyContent: "center",
1500
+ cursor: "pointer",
1501
+ padding: 0,
1502
+ transition: "background 0.12s ease, color 0.12s ease",
1503
+ marginRight: round ? 6 : 0
1504
+ };
1570
1505
  }
1571
1506
 
1572
1507
  // src/overlay/ui/inspector.tsx
1573
- import { useEffect as useEffect4, useRef as useRef3, useCallback as useCallback5, useState as useState5 } from "react";
1574
- import { Fragment as Fragment2, jsx as jsx4, jsxs as jsxs4 } from "react/jsx-runtime";
1508
+ import { useEffect as useEffect5, useRef as useRef3, useCallback as useCallback4, useState as useState6 } from "react";
1509
+ import { Fragment as Fragment3, jsx as jsx5, jsxs as jsxs4 } from "react/jsx-runtime";
1575
1510
  function Inspector({ onSelect, onCancel }) {
1576
- const [highlight, setHighlight] = useState5(null);
1511
+ const [highlight, setHighlight] = useState6(null);
1577
1512
  const hoveredEl = useRef3(null);
1578
1513
  const styleEl = useRef3(null);
1579
- useEffect4(() => {
1514
+ useEffect5(() => {
1580
1515
  const style = document.createElement("style");
1581
1516
  style.setAttribute("data-afterbefore", "true");
1582
1517
  style.textContent = "*, *::before, *::after { cursor: crosshair !important; }";
@@ -1586,7 +1521,7 @@ function Inspector({ onSelect, onCancel }) {
1586
1521
  style.remove();
1587
1522
  };
1588
1523
  }, []);
1589
- const isOverlayElement = useCallback5((el) => {
1524
+ const isOverlayElement = useCallback4((el) => {
1590
1525
  let node = el;
1591
1526
  while (node) {
1592
1527
  if (node instanceof HTMLElement && node.dataset.afterbefore) return true;
@@ -1594,7 +1529,7 @@ function Inspector({ onSelect, onCancel }) {
1594
1529
  }
1595
1530
  return false;
1596
1531
  }, []);
1597
- const handleMouseMove = useCallback5(
1532
+ const handleMouseMove = useCallback4(
1598
1533
  (e) => {
1599
1534
  const el = document.elementFromPoint(e.clientX, e.clientY);
1600
1535
  if (!el || !(el instanceof HTMLElement) || isOverlayElement(el)) {
@@ -1614,7 +1549,7 @@ function Inspector({ onSelect, onCancel }) {
1614
1549
  },
1615
1550
  [isOverlayElement]
1616
1551
  );
1617
- const handleClick = useCallback5(
1552
+ const handleClick = useCallback4(
1618
1553
  (e) => {
1619
1554
  e.preventDefault();
1620
1555
  e.stopPropagation();
@@ -1625,7 +1560,7 @@ function Inspector({ onSelect, onCancel }) {
1625
1560
  },
1626
1561
  [onSelect]
1627
1562
  );
1628
- const handleKeyDown = useCallback5(
1563
+ const handleKeyDown = useCallback4(
1629
1564
  (e) => {
1630
1565
  if (e.key === "Escape") {
1631
1566
  onCancel();
@@ -1633,7 +1568,7 @@ function Inspector({ onSelect, onCancel }) {
1633
1568
  },
1634
1569
  [onCancel]
1635
1570
  );
1636
- useEffect4(() => {
1571
+ useEffect5(() => {
1637
1572
  document.addEventListener("mousemove", handleMouseMove, true);
1638
1573
  document.addEventListener("click", handleClick, true);
1639
1574
  document.addEventListener("keydown", handleKeyDown);
@@ -1644,8 +1579,8 @@ function Inspector({ onSelect, onCancel }) {
1644
1579
  };
1645
1580
  }, [handleMouseMove, handleClick, handleKeyDown]);
1646
1581
  return /* @__PURE__ */ jsxs4("div", { "data-afterbefore": "true", style: { position: "fixed", inset: 0, zIndex: 2147483646, pointerEvents: "none" }, children: [
1647
- highlight && /* @__PURE__ */ jsxs4(Fragment2, { children: [
1648
- /* @__PURE__ */ jsx4(
1582
+ highlight && /* @__PURE__ */ jsxs4(Fragment3, { children: [
1583
+ /* @__PURE__ */ jsx5(
1649
1584
  "div",
1650
1585
  {
1651
1586
  style: {
@@ -1661,7 +1596,7 @@ function Inspector({ onSelect, onCancel }) {
1661
1596
  }
1662
1597
  }
1663
1598
  ),
1664
- /* @__PURE__ */ jsx4(
1599
+ /* @__PURE__ */ jsx5(
1665
1600
  "div",
1666
1601
  {
1667
1602
  style: {
@@ -1682,7 +1617,7 @@ function Inspector({ onSelect, onCancel }) {
1682
1617
  }
1683
1618
  )
1684
1619
  ] }),
1685
- !highlight && /* @__PURE__ */ jsx4(
1620
+ !highlight && /* @__PURE__ */ jsx5(
1686
1621
  "div",
1687
1622
  {
1688
1623
  style: {
@@ -1706,18 +1641,18 @@ function Inspector({ onSelect, onCancel }) {
1706
1641
  }
1707
1642
 
1708
1643
  // src/overlay/ui/status.tsx
1709
- import { useState as useState6, useRef as useRef4, useEffect as useEffect5, useCallback as useCallback6 } from "react";
1710
- import { jsx as jsx5, jsxs as jsxs5 } from "react/jsx-runtime";
1644
+ import { useState as useState7, useRef as useRef4, useEffect as useEffect6, useCallback as useCallback5 } from "react";
1645
+ import { jsx as jsx6, jsxs as jsxs5 } from "react/jsx-runtime";
1711
1646
  var PANEL_WIDTH = 220;
1712
1647
  function Status({ onReset, position, onClose }) {
1713
1648
  const panelRef = useRef4(null);
1714
- const [toast, setToast] = useState6(null);
1715
- const [pushing, setPushing] = useState6(false);
1716
- const showToast = useCallback6((message, type) => {
1649
+ const [toast, setToast] = useState7(null);
1650
+ const [pushing, setPushing] = useState7(false);
1651
+ const showToast = useCallback5((message, type) => {
1717
1652
  setToast({ message, type });
1718
1653
  setTimeout(() => setToast(null), 3e3);
1719
1654
  }, []);
1720
- useEffect5(() => {
1655
+ useEffect6(() => {
1721
1656
  const handler = (e) => {
1722
1657
  if (panelRef.current && !panelRef.current.contains(e.target)) {
1723
1658
  onClose();
@@ -1726,7 +1661,7 @@ function Status({ onReset, position, onClose }) {
1726
1661
  document.addEventListener("mousedown", handler);
1727
1662
  return () => document.removeEventListener("mousedown", handler);
1728
1663
  }, [onClose]);
1729
- useEffect5(() => {
1664
+ useEffect6(() => {
1730
1665
  const handler = (e) => {
1731
1666
  if (e.key === "Escape") onClose();
1732
1667
  };
@@ -1821,7 +1756,7 @@ function Status({ onReset, position, onClose }) {
1821
1756
  overflow: "hidden"
1822
1757
  },
1823
1758
  children: [
1824
- /* @__PURE__ */ jsx5(
1759
+ /* @__PURE__ */ jsx6(
1825
1760
  "div",
1826
1761
  {
1827
1762
  style: {
@@ -1843,7 +1778,7 @@ function Status({ onReset, position, onClose }) {
1843
1778
  onMouseEnter: onEnter,
1844
1779
  onMouseLeave: onLeave,
1845
1780
  children: [
1846
- /* @__PURE__ */ jsx5(FolderIcon, {}),
1781
+ /* @__PURE__ */ jsx6(FolderIcon, {}),
1847
1782
  "Open Folder"
1848
1783
  ]
1849
1784
  }
@@ -1856,7 +1791,7 @@ function Status({ onReset, position, onClose }) {
1856
1791
  onMouseEnter: onEnter,
1857
1792
  onMouseLeave: onLeave,
1858
1793
  children: [
1859
- /* @__PURE__ */ jsx5(CopyIcon, {}),
1794
+ /* @__PURE__ */ jsx6(CopyIcon, {}),
1860
1795
  "Copy Markdown"
1861
1796
  ]
1862
1797
  }
@@ -1870,12 +1805,12 @@ function Status({ onReset, position, onClose }) {
1870
1805
  onMouseEnter: onEnter,
1871
1806
  onMouseLeave: onLeave,
1872
1807
  children: [
1873
- /* @__PURE__ */ jsx5(PushIcon, {}),
1808
+ /* @__PURE__ */ jsx6(PushIcon, {}),
1874
1809
  pushing ? "Pushing..." : "Push to PR"
1875
1810
  ]
1876
1811
  }
1877
1812
  ),
1878
- /* @__PURE__ */ jsx5(
1813
+ /* @__PURE__ */ jsx6(
1879
1814
  "div",
1880
1815
  {
1881
1816
  style: {
@@ -1893,13 +1828,13 @@ function Status({ onReset, position, onClose }) {
1893
1828
  onMouseEnter: onEnter,
1894
1829
  onMouseLeave: onLeave,
1895
1830
  children: [
1896
- /* @__PURE__ */ jsx5(ResetIcon, {}),
1831
+ /* @__PURE__ */ jsx6(ResetIcon, {}),
1897
1832
  "Reset"
1898
1833
  ]
1899
1834
  }
1900
1835
  )
1901
1836
  ] }),
1902
- toast && /* @__PURE__ */ jsx5(
1837
+ toast && /* @__PURE__ */ jsx6(
1903
1838
  "div",
1904
1839
  {
1905
1840
  style: {
@@ -1925,14 +1860,14 @@ function Status({ onReset, position, onClose }) {
1925
1860
  );
1926
1861
  }
1927
1862
  function FolderIcon() {
1928
- return /* @__PURE__ */ jsx5(
1863
+ return /* @__PURE__ */ jsx6(
1929
1864
  "svg",
1930
1865
  {
1931
1866
  width: "14",
1932
1867
  height: "14",
1933
1868
  viewBox: "0 0 14 14",
1934
1869
  style: { color: "rgba(255,255,255,0.5)" },
1935
- children: /* @__PURE__ */ jsx5(
1870
+ children: /* @__PURE__ */ jsx6(
1936
1871
  "path",
1937
1872
  {
1938
1873
  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",
@@ -1953,7 +1888,7 @@ function CopyIcon() {
1953
1888
  viewBox: "0 0 14 14",
1954
1889
  style: { color: "rgba(255,255,255,0.5)" },
1955
1890
  children: [
1956
- /* @__PURE__ */ jsx5(
1891
+ /* @__PURE__ */ jsx6(
1957
1892
  "rect",
1958
1893
  {
1959
1894
  x: "4",
@@ -1966,7 +1901,7 @@ function CopyIcon() {
1966
1901
  strokeWidth: "1.3"
1967
1902
  }
1968
1903
  ),
1969
- /* @__PURE__ */ jsx5(
1904
+ /* @__PURE__ */ jsx6(
1970
1905
  "path",
1971
1906
  {
1972
1907
  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",
@@ -1980,14 +1915,14 @@ function CopyIcon() {
1980
1915
  );
1981
1916
  }
1982
1917
  function PushIcon() {
1983
- return /* @__PURE__ */ jsx5(
1918
+ return /* @__PURE__ */ jsx6(
1984
1919
  "svg",
1985
1920
  {
1986
1921
  width: "14",
1987
1922
  height: "14",
1988
1923
  viewBox: "0 0 14 14",
1989
1924
  style: { color: "rgba(255,255,255,0.5)" },
1990
- children: /* @__PURE__ */ jsx5(
1925
+ children: /* @__PURE__ */ jsx6(
1991
1926
  "path",
1992
1927
  {
1993
1928
  d: "M7 11V3m0 0L4 6m3-3l3 3",
@@ -2010,7 +1945,7 @@ function ResetIcon() {
2010
1945
  viewBox: "0 0 14 14",
2011
1946
  style: { color: "rgba(255,255,255,0.4)" },
2012
1947
  children: [
2013
- /* @__PURE__ */ jsx5(
1948
+ /* @__PURE__ */ jsx6(
2014
1949
  "path",
2015
1950
  {
2016
1951
  d: "M2.5 7a4.5 4.5 0 118 2.5",
@@ -2020,7 +1955,7 @@ function ResetIcon() {
2020
1955
  strokeLinecap: "round"
2021
1956
  }
2022
1957
  ),
2023
- /* @__PURE__ */ jsx5(
1958
+ /* @__PURE__ */ jsx6(
2024
1959
  "path",
2025
1960
  {
2026
1961
  d: "M2.5 3v4h4",
@@ -2037,7 +1972,7 @@ function ResetIcon() {
2037
1972
  }
2038
1973
 
2039
1974
  // src/overlay/index.tsx
2040
- import { jsx as jsx6, jsxs as jsxs6 } from "react/jsx-runtime";
1975
+ import { jsx as jsx7, jsxs as jsxs6 } from "react/jsx-runtime";
2041
1976
  async function saveCapture(type, mode, dataUrl) {
2042
1977
  try {
2043
1978
  const res = await fetch("/__afterbefore/save", {
@@ -2055,13 +1990,31 @@ async function saveCapture(type, mode, dataUrl) {
2055
1990
  }
2056
1991
  function AfterBefore() {
2057
1992
  const { state, captureComplete, reset } = useOverlayState();
2058
- const [statusOpen, setStatusOpen] = useState7(false);
2059
- const [toolbarActive, setToolbarActive] = useState7(false);
2060
- const [selectorActive, setSelectorActive] = useState7(false);
2061
- const [inspectorActive, setInspectorActive] = useState7(false);
2062
- const [loading, setLoading] = useState7(false);
1993
+ const [statusOpen, setStatusOpen] = useState8(false);
1994
+ const [toolbarActive, setToolbarActive] = useState8(false);
1995
+ const [selectorActive, setSelectorActive] = useState8(false);
1996
+ const [inspectorActive, setInspectorActive] = useState8(false);
1997
+ const [loading, setLoading] = useState8(false);
1998
+ const [selectedMode, setSelectedMode] = useState8("area");
1999
+ const [magnetEnabled, setMagnetEnabled] = useState8(true);
2000
+ const [areaRect, setAreaRect] = useState8(null);
2001
+ const [areaAspect, setAreaAspect] = useState8(DEFAULT_AREA_ASPECT);
2002
+ const [areaPresets, setAreaPresets] = useState8([]);
2063
2003
  const iconPos = useRef5({ x: 24, y: 0 });
2064
- useEffect6(() => {
2004
+ useEffect7(() => {
2005
+ try {
2006
+ setMagnetEnabled(localStorage.getItem("ab-magnet") !== "false");
2007
+ } catch {
2008
+ setMagnetEnabled(true);
2009
+ }
2010
+ try {
2011
+ const savedPresets = localStorage.getItem("ab-area-presets");
2012
+ setAreaPresets(savedPresets ? JSON.parse(savedPresets) : []);
2013
+ } catch {
2014
+ setAreaPresets([]);
2015
+ }
2016
+ }, []);
2017
+ useEffect7(() => {
2065
2018
  if (state.phase === "ready") {
2066
2019
  const timer = setTimeout(() => {
2067
2020
  reset();
@@ -2070,24 +2023,33 @@ function AfterBefore() {
2070
2023
  return () => clearTimeout(timer);
2071
2024
  }
2072
2025
  }, [state.phase, reset]);
2073
- const handlePositionChange = useCallback7(
2026
+ const handlePositionChange = useCallback6(
2074
2027
  (pos) => {
2075
2028
  iconPos.current = pos;
2076
2029
  },
2077
2030
  []
2078
2031
  );
2079
- const handleIconClick = useCallback7(() => {
2032
+ const handleIconClick = useCallback6(() => {
2080
2033
  if (loading) return;
2081
2034
  if (state.phase === "ready") {
2082
2035
  setStatusOpen((prev) => !prev);
2083
- } else {
2084
- setToolbarActive((prev) => !prev);
2036
+ } else if (toolbarActive) {
2037
+ setToolbarActive(false);
2085
2038
  setSelectorActive(false);
2086
2039
  setInspectorActive(false);
2087
2040
  setStatusOpen(false);
2041
+ } else {
2042
+ setToolbarActive(true);
2043
+ setInspectorActive(false);
2044
+ setStatusOpen(false);
2045
+ if (selectedMode === "area") {
2046
+ setSelectorActive(true);
2047
+ setAreaRect(createInitialAreaRect());
2048
+ setAreaAspect(DEFAULT_AREA_ASPECT);
2049
+ }
2088
2050
  }
2089
- }, [state.phase, loading]);
2090
- const performCapture = useCallback7(
2051
+ }, [state.phase, loading, toolbarActive, selectedMode]);
2052
+ const performCapture = useCallback6(
2091
2053
  async (mode, area, element) => {
2092
2054
  setLoading(true);
2093
2055
  try {
@@ -2107,53 +2069,151 @@ function AfterBefore() {
2107
2069
  },
2108
2070
  [state.phase, captureComplete]
2109
2071
  );
2110
- const handleToolbarCapture = useCallback7(
2072
+ const handleToolbarCapture = useCallback6(
2111
2073
  (mode) => {
2112
- setToolbarActive(false);
2113
2074
  if (mode === "area") {
2114
- setSelectorActive(true);
2075
+ if (areaRect) {
2076
+ setSelectorActive(false);
2077
+ setToolbarActive(false);
2078
+ performCapture("area", {
2079
+ x: Math.round(areaRect.x),
2080
+ y: Math.round(areaRect.y),
2081
+ width: Math.round(areaRect.w),
2082
+ height: Math.round(areaRect.h)
2083
+ });
2084
+ }
2085
+ return;
2115
2086
  } else if (mode === "viewport") {
2087
+ setToolbarActive(false);
2116
2088
  performCapture("viewport");
2117
2089
  } else if (mode === "fullpage") {
2090
+ setToolbarActive(false);
2118
2091
  performCapture("fullpage");
2119
2092
  } else if (mode === "component") {
2093
+ setToolbarActive(false);
2120
2094
  setInspectorActive(true);
2121
2095
  }
2122
2096
  },
2123
- [performCapture]
2097
+ [areaRect, performCapture]
2124
2098
  );
2125
- const handleToolbarCancel = useCallback7(() => {
2099
+ const handleToolbarCancel = useCallback6(() => {
2126
2100
  setToolbarActive(false);
2101
+ setSelectorActive(false);
2127
2102
  }, []);
2128
- const handleComponentSelect = useCallback7(
2103
+ const handleComponentSelect = useCallback6(
2129
2104
  (element) => {
2130
2105
  setInspectorActive(false);
2131
2106
  performCapture("component", void 0, element);
2132
2107
  },
2133
2108
  [performCapture]
2134
2109
  );
2135
- const handleComponentCancel = useCallback7(() => {
2110
+ const handleComponentCancel = useCallback6(() => {
2136
2111
  setInspectorActive(false);
2112
+ setToolbarActive(true);
2137
2113
  }, []);
2138
- const handleAreaSelect = useCallback7(
2114
+ const handleAreaSelect = useCallback6(
2139
2115
  (area) => {
2140
2116
  setSelectorActive(false);
2117
+ setToolbarActive(false);
2141
2118
  performCapture("area", area);
2142
2119
  },
2143
2120
  [performCapture]
2144
2121
  );
2145
- const handleAreaCancel = useCallback7(() => {
2122
+ const handleAreaCancel = useCallback6(() => {
2146
2123
  setSelectorActive(false);
2124
+ setToolbarActive(true);
2125
+ }, []);
2126
+ const handleMagnetChange = useCallback6((enabled) => {
2127
+ setMagnetEnabled(enabled);
2128
+ try {
2129
+ localStorage.setItem("ab-magnet", String(enabled));
2130
+ } catch {
2131
+ }
2132
+ }, []);
2133
+ const handleModeChange = useCallback6((mode) => {
2134
+ setSelectedMode(mode);
2135
+ if (mode === "area") {
2136
+ setSelectorActive(true);
2137
+ setAreaRect(createInitialAreaRect());
2138
+ setAreaAspect(DEFAULT_AREA_ASPECT);
2139
+ } else {
2140
+ setSelectorActive(false);
2141
+ }
2142
+ }, []);
2143
+ const handleAreaRectChange = useCallback6((nextRect) => {
2144
+ setAreaRect(nextRect);
2145
+ }, []);
2146
+ const handleAreaSizeChange = useCallback6(
2147
+ (field, value) => {
2148
+ const nextValue = parseInt(value, 10);
2149
+ if (Number.isNaN(nextValue) || nextValue < 20 || !areaRect) {
2150
+ return;
2151
+ }
2152
+ const aspectRatio = areaAspect === "Free" ? 0 : { "16:9": 16 / 9, "4:3": 4 / 3, "1:1": 1, "3:2": 3 / 2, "21:9": 21 / 9 }[areaAspect];
2153
+ if (field === "w") {
2154
+ setAreaRect({
2155
+ ...areaRect,
2156
+ w: nextValue,
2157
+ h: aspectRatio > 0 ? nextValue / aspectRatio : areaRect.h
2158
+ });
2159
+ } else {
2160
+ setAreaRect({
2161
+ ...areaRect,
2162
+ h: nextValue,
2163
+ w: aspectRatio > 0 ? nextValue * aspectRatio : areaRect.w
2164
+ });
2165
+ }
2166
+ },
2167
+ [areaAspect, areaRect]
2168
+ );
2169
+ const handleAreaPositionChange = useCallback6(
2170
+ (field, value) => {
2171
+ const nextValue = parseInt(value, 10);
2172
+ if (Number.isNaN(nextValue) || !areaRect) {
2173
+ return;
2174
+ }
2175
+ setAreaRect({ ...areaRect, [field]: nextValue });
2176
+ },
2177
+ [areaRect]
2178
+ );
2179
+ const handleAreaAspectChange = useCallback6(
2180
+ (nextAspect) => {
2181
+ setAreaAspect(nextAspect);
2182
+ if (!areaRect || nextAspect === "Free") {
2183
+ return;
2184
+ }
2185
+ const aspectRatio = { "16:9": 16 / 9, "4:3": 4 / 3, "1:1": 1, "3:2": 3 / 2, "21:9": 21 / 9 }[nextAspect];
2186
+ setAreaRect({ ...areaRect, h: areaRect.w / aspectRatio });
2187
+ },
2188
+ [areaRect]
2189
+ );
2190
+ const handleAreaSavePreset = useCallback6(() => {
2191
+ if (!areaRect) {
2192
+ return;
2193
+ }
2194
+ const label = `${Math.round(areaRect.w)}x${Math.round(areaRect.h)}`;
2195
+ const nextPresets = [
2196
+ ...areaPresets.filter((preset) => preset.label !== label),
2197
+ { label, rect: { ...areaRect } }
2198
+ ];
2199
+ setAreaPresets(nextPresets);
2200
+ try {
2201
+ localStorage.setItem("ab-area-presets", JSON.stringify(nextPresets));
2202
+ } catch {
2203
+ }
2204
+ }, [areaPresets, areaRect]);
2205
+ const handleAreaLoadPreset = useCallback6((preset) => {
2206
+ setAreaRect({ ...preset.rect });
2147
2207
  }, []);
2148
- const handleReset = useCallback7(() => {
2208
+ const handleReset = useCallback6(() => {
2149
2209
  reset();
2150
2210
  setStatusOpen(false);
2151
2211
  }, [reset]);
2152
- const handleStatusClose = useCallback7(() => {
2212
+ const handleStatusClose = useCallback6(() => {
2153
2213
  setStatusOpen(false);
2154
2214
  }, []);
2155
2215
  return /* @__PURE__ */ jsxs6("div", { "data-afterbefore": "true", children: [
2156
- /* @__PURE__ */ jsx6(
2216
+ /* @__PURE__ */ jsx7(
2157
2217
  Icon,
2158
2218
  {
2159
2219
  phase: state.phase,
@@ -2162,16 +2222,40 @@ function AfterBefore() {
2162
2222
  onPositionChange: handlePositionChange
2163
2223
  }
2164
2224
  ),
2165
- toolbarActive && !selectorActive && !inspectorActive && /* @__PURE__ */ jsx6(Toolbar, { onCapture: handleToolbarCapture, onCancel: handleToolbarCancel }),
2166
- selectorActive && /* @__PURE__ */ jsx6(
2225
+ toolbarActive && !selectorActive && !inspectorActive && !loading && /* @__PURE__ */ jsx7(CapturePreview, { mode: selectedMode }),
2226
+ toolbarActive && !inspectorActive && /* @__PURE__ */ jsx7(
2227
+ Toolbar,
2228
+ {
2229
+ selectedMode,
2230
+ onModeChange: handleModeChange,
2231
+ onCapture: handleToolbarCapture,
2232
+ onCancel: handleToolbarCancel,
2233
+ magnetEnabled,
2234
+ onMagnetChange: handleMagnetChange,
2235
+ areaSelectionActive: selectorActive,
2236
+ areaRect,
2237
+ areaAspect,
2238
+ areaPresets,
2239
+ onAreaSizeChange: handleAreaSizeChange,
2240
+ onAreaPositionChange: handleAreaPositionChange,
2241
+ onAreaAspectChange: handleAreaAspectChange,
2242
+ onAreaSavePreset: handleAreaSavePreset,
2243
+ onAreaLoadPreset: handleAreaLoadPreset
2244
+ }
2245
+ ),
2246
+ selectorActive && /* @__PURE__ */ jsx7(
2167
2247
  Selector,
2168
2248
  {
2249
+ rect: areaRect,
2250
+ aspect: areaAspect,
2251
+ onRectChange: handleAreaRectChange,
2169
2252
  onSelect: handleAreaSelect,
2170
- onCancel: handleAreaCancel
2253
+ onCancel: handleAreaCancel,
2254
+ magnetEnabled
2171
2255
  }
2172
2256
  ),
2173
- inspectorActive && /* @__PURE__ */ jsx6(Inspector, { onSelect: handleComponentSelect, onCancel: handleComponentCancel }),
2174
- statusOpen && state.phase === "ready" && /* @__PURE__ */ jsx6(
2257
+ inspectorActive && /* @__PURE__ */ jsx7(Inspector, { onSelect: handleComponentSelect, onCancel: handleComponentCancel }),
2258
+ statusOpen && state.phase === "ready" && /* @__PURE__ */ jsx7(
2175
2259
  Status,
2176
2260
  {
2177
2261
  onReset: handleReset,