afterbefore 0.2.7 → 0.2.8

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,94 @@ 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);
711
491
  const mode = useRef2("none");
712
492
  const start = useRef2({ x: 0, y: 0 });
713
493
  const snapRef = useRef2({ x: 0, y: 0, w: 0, h: 0 });
714
494
  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) {
495
+ const ratio = useMemo(() => getAspectRatio(aspect), [aspect]);
496
+ const applySnap = useCallback3(
497
+ (nextRect) => {
498
+ if (!magnetEnabled) {
720
499
  setSnappedX(false);
721
- return r;
500
+ return nextRect;
722
501
  }
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) {
502
+ const centerX = nextRect.x + nextRect.w / 2;
503
+ const viewportCenterX = window.innerWidth / 2;
504
+ if (Math.abs(centerX - viewportCenterX) < SNAP_THRESHOLD) {
727
505
  setSnappedX(true);
728
- return { ...r, x: viewCenterX - r.w / 2 };
506
+ return { ...nextRect, x: viewportCenterX - nextRect.w / 2 };
729
507
  }
730
508
  setSnappedX(false);
731
- return r;
509
+ return nextRect;
732
510
  },
733
- [magnet]
511
+ [magnetEnabled]
734
512
  );
735
513
  useEffect3(() => {
736
514
  const onKey = (e) => {
737
- if (e.target?.tagName === "INPUT") {
738
- if (e.key === "Escape") e.target.blur();
739
- return;
740
- }
741
515
  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
- });
516
+ onCancel();
755
517
  }
756
518
  };
757
519
  document.addEventListener("keydown", onKey);
758
520
  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;
521
+ }, [onCancel]);
522
+ const hitCorner = useCallback3((mx, my, currentRect) => {
523
+ const corners = [
524
+ ["tl", currentRect.x, currentRect.y],
525
+ ["tr", currentRect.x + currentRect.w, currentRect.y],
526
+ ["bl", currentRect.x, currentRect.y + currentRect.h],
527
+ ["br", currentRect.x + currentRect.w, currentRect.y + currentRect.h]
528
+ ];
529
+ for (const [hitCorner2, cx, cy] of corners) {
530
+ if (Math.abs(mx - cx) <= HANDLE_HIT && Math.abs(my - cy) <= HANDLE_HIT) {
531
+ return hitCorner2;
771
532
  }
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,
533
+ }
534
+ return null;
535
+ }, []);
536
+ const hitInside = useCallback3(
537
+ (mx, my, currentRect) => mx >= currentRect.x && mx <= currentRect.x + currentRect.w && my >= currentRect.y && my <= currentRect.y + currentRect.h,
778
538
  []
779
539
  );
780
- const onDown = useCallback4(
540
+ const onDown = useCallback3(
781
541
  (e) => {
782
- if (panelRef.current?.contains(e.target)) return;
783
542
  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) {
543
+ const mx = e.clientX;
544
+ const my = e.clientY;
545
+ if (rect) {
546
+ const activeCorner = hitCorner(mx, my, rect);
547
+ if (activeCorner) {
790
548
  mode.current = "resizing";
791
- corner.current = c;
549
+ corner.current = activeCorner;
792
550
  start.current = { x: mx, y: my };
793
551
  snapRef.current = { ...rect };
794
552
  return;
@@ -799,78 +557,79 @@ function Selector({ onSelect, onCancel }) {
799
557
  snapRef.current = { ...rect };
800
558
  return;
801
559
  }
802
- setPlaced(false);
803
560
  }
804
561
  mode.current = "drawing";
805
562
  start.current = { x: mx, y: my };
806
- setRect({ x: mx, y: my, w: 0, h: 0 });
563
+ onRectChange({ x: mx, y: my, w: 0, h: 0 });
807
564
  },
808
- [placed, rect, hitCorner, hitInside]
565
+ [hitCorner, hitInside, onRectChange, rect]
809
566
  );
810
- const onMove = useCallback4(
567
+ const onMove = useCallback3(
811
568
  (e) => {
812
- const mx = e.clientX, my = e.clientY;
569
+ const mx = e.clientX;
570
+ const my = e.clientY;
813
571
  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);
572
+ const sx = start.current.x;
573
+ const sy = start.current.y;
574
+ let x = Math.min(sx, mx);
575
+ let y = Math.min(sy, my);
576
+ let w = Math.abs(mx - sx);
577
+ let h = Math.abs(my - sy);
817
578
  if (ratio > 0) {
818
579
  h = w / ratio;
819
- if (my < sy) y = sy - h;
580
+ if (my < sy) {
581
+ y = sy - h;
582
+ }
820
583
  }
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(
584
+ onRectChange(applySnap({ x, y, w, h }));
585
+ return;
586
+ }
587
+ if (mode.current === "moving") {
588
+ const dx = mx - start.current.x;
589
+ const dy = my - start.current.y;
590
+ onRectChange(
825
591
  applySnap({
826
592
  ...snapRef.current,
827
593
  x: snapRef.current.x + dx,
828
594
  y: snapRef.current.y + dy
829
595
  })
830
596
  );
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;
597
+ return;
598
+ }
599
+ if (mode.current === "resizing") {
600
+ const original = snapRef.current;
601
+ const nextRect = { ...original };
602
+ if (corner.current === "br") {
603
+ nextRect.w = Math.max(MIN_SIZE, mx - original.x);
604
+ nextRect.h = ratio > 0 ? nextRect.w / ratio : Math.max(MIN_SIZE, my - original.y);
605
+ } else if (corner.current === "bl") {
606
+ nextRect.w = Math.max(MIN_SIZE, original.x + original.w - mx);
607
+ nextRect.x = original.x + original.w - nextRect.w;
608
+ nextRect.h = ratio > 0 ? nextRect.w / ratio : Math.max(MIN_SIZE, my - original.y);
609
+ } else if (corner.current === "tr") {
610
+ nextRect.w = Math.max(MIN_SIZE, mx - original.x);
611
+ nextRect.h = ratio > 0 ? nextRect.w / ratio : Math.max(MIN_SIZE, original.y + original.h - my);
612
+ nextRect.y = original.y + original.h - nextRect.h;
846
613
  } 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;
614
+ nextRect.w = Math.max(MIN_SIZE, original.x + original.w - mx);
615
+ nextRect.h = ratio > 0 ? nextRect.w / ratio : Math.max(MIN_SIZE, original.y + original.h - my);
616
+ nextRect.x = original.x + original.w - nextRect.w;
617
+ nextRect.y = original.y + original.h - nextRect.h;
851
618
  }
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");
619
+ onRectChange(applySnap(nextRect));
859
620
  }
860
621
  },
861
- [ratio, placed, rect, hitCorner, hitInside, applySnap]
622
+ [applySnap, onRectChange, ratio]
862
623
  );
863
- const onUp = useCallback4(
624
+ const onUp = useCallback3(
864
625
  (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);
626
+ const previousMode = mode.current;
627
+ if (previousMode === "drawing" && rect) {
628
+ if (rect.w < MIN_SIZE || rect.h < MIN_SIZE) {
629
+ onRectChange(null);
871
630
  }
872
631
  }
873
- if (prevMode === "moving" && placed && rect) {
632
+ if (previousMode === "moving" && rect) {
874
633
  const dx = Math.abs(e.clientX - start.current.x);
875
634
  const dy = Math.abs(e.clientY - start.current.y);
876
635
  if (dx < 3 && dy < 3) {
@@ -884,51 +643,10 @@ function Selector({ onSelect, onCancel }) {
884
643
  }
885
644
  mode.current = "none";
886
645
  },
887
- [rect, placed, onSelect]
888
- );
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]
646
+ [onRectChange, onSelect, rect]
900
647
  );
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
648
  const hasRect = rect && rect.w > 0 && rect.h > 0;
931
- return /* @__PURE__ */ jsxs3(
649
+ return /* @__PURE__ */ jsxs2(
932
650
  "div",
933
651
  {
934
652
  "data-afterbefore": "true",
@@ -938,8 +656,8 @@ function Selector({ onSelect, onCancel }) {
938
656
  style: {
939
657
  position: "fixed",
940
658
  inset: 0,
941
- zIndex: 2147483647,
942
- cursor: activeCursor
659
+ zIndex: 2147483646,
660
+ cursor: CAMERA_CURSOR
943
661
  },
944
662
  children: [
945
663
  snappedX && /* @__PURE__ */ jsx3(
@@ -951,34 +669,84 @@ function Selector({ onSelect, onCancel }) {
951
669
  top: 0,
952
670
  bottom: 0,
953
671
  width: 0,
954
- borderLeft: "1px solid rgba(147, 130, 220, 0.5)",
672
+ borderLeft: "1px solid rgba(56, 189, 248, 0.55)",
955
673
  pointerEvents: "none",
956
674
  zIndex: 2
957
675
  }
958
676
  }
959
677
  ),
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
- } : {}
678
+ hasRect ? /* @__PURE__ */ jsxs2(Fragment, { children: [
679
+ /* @__PURE__ */ jsx3(
680
+ "div",
681
+ {
682
+ style: {
683
+ position: "absolute",
684
+ left: 0,
685
+ top: 0,
686
+ width: "100%",
687
+ height: rect.y,
688
+ background: "rgba(0, 0, 0, 0.5)",
689
+ pointerEvents: "none"
690
+ }
978
691
  }
979
- }
980
- ),
981
- hasRect && /* @__PURE__ */ jsxs3(Fragment, { children: [
692
+ ),
693
+ /* @__PURE__ */ jsx3(
694
+ "div",
695
+ {
696
+ style: {
697
+ position: "absolute",
698
+ left: 0,
699
+ top: rect.y,
700
+ width: rect.x,
701
+ height: rect.h,
702
+ background: "rgba(0, 0, 0, 0.5)",
703
+ pointerEvents: "none"
704
+ }
705
+ }
706
+ ),
707
+ /* @__PURE__ */ jsx3(
708
+ "div",
709
+ {
710
+ style: {
711
+ position: "absolute",
712
+ left: rect.x + rect.w,
713
+ top: rect.y,
714
+ width: `calc(100% - ${rect.x + rect.w}px)`,
715
+ height: rect.h,
716
+ background: "rgba(0, 0, 0, 0.5)",
717
+ pointerEvents: "none"
718
+ }
719
+ }
720
+ ),
721
+ /* @__PURE__ */ jsx3(
722
+ "div",
723
+ {
724
+ style: {
725
+ position: "absolute",
726
+ left: 0,
727
+ top: rect.y + rect.h,
728
+ width: "100%",
729
+ height: `calc(100% - ${rect.y + rect.h}px)`,
730
+ background: "rgba(0, 0, 0, 0.5)",
731
+ pointerEvents: "none"
732
+ }
733
+ }
734
+ ),
735
+ /* @__PURE__ */ jsx3(
736
+ "div",
737
+ {
738
+ style: {
739
+ position: "absolute",
740
+ left: rect.x,
741
+ top: rect.y,
742
+ width: rect.w,
743
+ height: rect.h,
744
+ background: "rgba(125, 211, 252, 0.08)",
745
+ pointerEvents: "none",
746
+ borderRadius: 12
747
+ }
748
+ }
749
+ ),
982
750
  /* @__PURE__ */ jsx3(
983
751
  "div",
984
752
  {
@@ -989,17 +757,19 @@ function Selector({ onSelect, onCancel }) {
989
757
  width: rect.w,
990
758
  height: rect.h,
991
759
  border: "1.5px dashed rgba(255, 255, 255, 0.45)",
992
- pointerEvents: "none"
760
+ borderRadius: 12,
761
+ pointerEvents: "none",
762
+ boxShadow: "0 0 0 1px rgba(125, 211, 252, 0.38)"
993
763
  }
994
764
  }
995
765
  ),
996
- placed && [1, 2].map((i) => /* @__PURE__ */ jsxs3(React3.Fragment, { children: [
766
+ [1, 2].map((line) => /* @__PURE__ */ jsxs2(React3.Fragment, { children: [
997
767
  /* @__PURE__ */ jsx3(
998
768
  "div",
999
769
  {
1000
770
  style: {
1001
771
  position: "absolute",
1002
- left: rect.x + rect.w * i / 3,
772
+ left: rect.x + rect.w * line / 3,
1003
773
  top: rect.y,
1004
774
  width: 0,
1005
775
  height: rect.h,
@@ -1014,7 +784,7 @@ function Selector({ onSelect, onCancel }) {
1014
784
  style: {
1015
785
  position: "absolute",
1016
786
  left: rect.x,
1017
- top: rect.y + rect.h * i / 3,
787
+ top: rect.y + rect.h * line / 3,
1018
788
  width: rect.w,
1019
789
  height: 0,
1020
790
  borderTop: "1px dashed rgba(255, 255, 255, 0.18)",
@@ -1022,327 +792,521 @@ function Selector({ onSelect, onCancel }) {
1022
792
  }
1023
793
  }
1024
794
  )
1025
- ] }, i)),
1026
- placed && [
795
+ ] }, line)),
796
+ [
1027
797
  [rect.x, rect.y],
1028
798
  [rect.x + rect.w, rect.y],
1029
799
  [rect.x, rect.y + rect.h],
1030
800
  [rect.x + rect.w, rect.y + rect.h]
1031
- ].map(([cx, cy], i) => /* @__PURE__ */ jsx3(
801
+ ].map(([cx, cy], index) => /* @__PURE__ */ jsx3(
802
+ "div",
803
+ {
804
+ style: {
805
+ position: "absolute",
806
+ left: cx - HANDLE_R,
807
+ top: cy - HANDLE_R,
808
+ width: HANDLE_R * 2,
809
+ height: HANDLE_R * 2,
810
+ borderRadius: "50%",
811
+ border: "2px solid rgba(255, 255, 255, 0.8)",
812
+ background: "rgba(0, 0, 0, 0.25)",
813
+ pointerEvents: "none"
814
+ }
815
+ },
816
+ index
817
+ ))
818
+ ] }) : /* @__PURE__ */ jsx3(
819
+ "div",
820
+ {
821
+ style: {
822
+ position: "absolute",
823
+ inset: 0,
824
+ background: "rgba(0, 0, 0, 0.5)",
825
+ pointerEvents: "none"
826
+ }
827
+ }
828
+ )
829
+ ]
830
+ }
831
+ );
832
+ }
833
+
834
+ // src/overlay/ui/toolbar.tsx
835
+ import { Fragment as Fragment2, jsx as jsx4, jsxs as jsxs3 } from "react/jsx-runtime";
836
+ var MODES = [
837
+ { mode: "area", label: "Capture Selected Area", icon: Crop },
838
+ { mode: "viewport", label: "Capture Viewport", icon: Monitor },
839
+ { mode: "fullpage", label: "Capture Full Page", icon: FileText },
840
+ { mode: "component", label: "Capture Component", icon: MousePointer2 }
841
+ ];
842
+ function Toolbar({
843
+ selectedMode,
844
+ onModeChange,
845
+ onCapture,
846
+ onCancel,
847
+ magnetEnabled,
848
+ onMagnetChange,
849
+ areaSelectionActive,
850
+ areaRect,
851
+ areaAspect,
852
+ areaPresets,
853
+ onAreaSizeChange,
854
+ onAreaPositionChange,
855
+ onAreaAspectChange,
856
+ onAreaSavePreset,
857
+ onAreaLoadPreset
858
+ }) {
859
+ const [settingsOpen, setSettingsOpen] = useState5(false);
860
+ const [aspectOpen, setAspectOpen] = useState5(false);
861
+ const [savedOpen, setSavedOpen] = useState5(false);
862
+ const showAreaControls = selectedMode === "area" && areaSelectionActive && areaRect !== null;
863
+ const activeAreaRect = showAreaControls ? areaRect : null;
864
+ useEffect4(() => {
865
+ const onKey = (e) => {
866
+ if (e.target?.tagName === "INPUT") {
867
+ if (e.key === "Escape") {
868
+ e.target.blur();
869
+ }
870
+ return;
871
+ }
872
+ if (e.key === "Escape") {
873
+ if (settingsOpen) {
874
+ setSettingsOpen(false);
875
+ return;
876
+ }
877
+ if (aspectOpen) {
878
+ setAspectOpen(false);
879
+ return;
880
+ }
881
+ if (savedOpen) {
882
+ setSavedOpen(false);
883
+ return;
884
+ }
885
+ onCancel();
886
+ } else if (e.key === "Enter") {
887
+ onCapture(selectedMode);
888
+ }
889
+ };
890
+ document.addEventListener("keydown", onKey);
891
+ return () => document.removeEventListener("keydown", onKey);
892
+ }, [aspectOpen, onCancel, onCapture, savedOpen, selectedMode, settingsOpen]);
893
+ return /* @__PURE__ */ jsxs3(
894
+ "div",
895
+ {
896
+ "data-afterbefore": "true",
897
+ style: {
898
+ position: "fixed",
899
+ bottom: 48,
900
+ left: "50%",
901
+ transform: "translateX(-50%)",
902
+ zIndex: 2147483647,
903
+ display: "flex",
904
+ alignItems: "center",
905
+ gap: 10,
906
+ flexWrap: "wrap",
907
+ justifyContent: "center",
908
+ maxWidth: "min(calc(100vw - 32px), 1120px)",
909
+ background: "rgba(32, 32, 36, 0.92)",
910
+ backdropFilter: "blur(20px)",
911
+ WebkitBackdropFilter: "blur(20px)",
912
+ border: "1px solid rgba(255, 255, 255, 0.1)",
913
+ borderRadius: 18,
914
+ padding: "6px 10px",
915
+ boxShadow: "0 8px 32px rgba(0, 0, 0, 0.4)",
916
+ fontFamily: "system-ui, -apple-system, sans-serif"
917
+ },
918
+ children: [
919
+ /* @__PURE__ */ jsxs3("div", { style: { display: "flex", alignItems: "center", gap: 0 }, children: [
920
+ /* @__PURE__ */ jsx4(CloseButton, { onClick: onCancel }),
921
+ /* @__PURE__ */ jsx4("div", { style: { display: "flex", alignItems: "center", gap: 2, padding: "0 4px" }, children: MODES.map(({ mode, label, icon: Icon2 }) => /* @__PURE__ */ jsx4(
922
+ ModeButton,
923
+ {
924
+ label,
925
+ selected: selectedMode === mode,
926
+ onClick: () => {
927
+ setSettingsOpen(false);
928
+ onModeChange(mode);
929
+ },
930
+ children: /* @__PURE__ */ jsx4(Icon2, { size: 18, strokeWidth: 1.7 })
931
+ },
932
+ mode
933
+ )) }),
934
+ /* @__PURE__ */ jsx4(Separator, {}),
935
+ /* @__PURE__ */ jsx4(
936
+ SettingsButton,
937
+ {
938
+ open: settingsOpen,
939
+ onClick: () => {
940
+ setAspectOpen(false);
941
+ setSavedOpen(false);
942
+ setSettingsOpen((prev) => !prev);
943
+ },
944
+ magnetEnabled,
945
+ onMagnetChange
946
+ }
947
+ ),
948
+ /* @__PURE__ */ jsx4(
949
+ CaptureButton,
950
+ {
951
+ label: showAreaControls ? "Capture Area" : "Capture",
952
+ onClick: () => onCapture(selectedMode)
953
+ }
954
+ )
955
+ ] }),
956
+ activeAreaRect && /* @__PURE__ */ jsxs3(Fragment2, { children: [
957
+ /* @__PURE__ */ jsx4(Separator, { vertical: false }),
958
+ /* @__PURE__ */ jsxs3(
959
+ "div",
960
+ {
961
+ style: {
962
+ display: "flex",
963
+ alignItems: "center",
964
+ gap: 8,
965
+ flexWrap: "wrap",
966
+ justifyContent: "center"
967
+ },
968
+ children: [
969
+ /* @__PURE__ */ jsxs3(ControlGroup, { label: "Size", children: [
970
+ /* @__PURE__ */ jsx4(NumInput, { value: Math.round(activeAreaRect.w), onChange: (value) => onAreaSizeChange("w", value) }),
971
+ /* @__PURE__ */ jsx4(StaticText, { children: "x" }),
972
+ /* @__PURE__ */ jsx4(NumInput, { value: Math.round(activeAreaRect.h), onChange: (value) => onAreaSizeChange("h", value) })
973
+ ] }),
974
+ /* @__PURE__ */ jsxs3(ControlGroup, { label: "Position", children: [
975
+ /* @__PURE__ */ jsx4(NumInput, { value: Math.round(activeAreaRect.x), onChange: (value) => onAreaPositionChange("x", value) }),
976
+ /* @__PURE__ */ jsx4(StaticText, { children: "x" }),
977
+ /* @__PURE__ */ jsx4(NumInput, { value: Math.round(activeAreaRect.y), onChange: (value) => onAreaPositionChange("y", value) })
978
+ ] }),
979
+ /* @__PURE__ */ jsxs3("div", { style: { position: "relative" }, children: [
980
+ /* @__PURE__ */ jsxs3(
981
+ DropButton,
982
+ {
983
+ active: areaAspect !== "Free",
984
+ onClick: () => {
985
+ setSavedOpen(false);
986
+ setAspectOpen((prev) => !prev);
987
+ },
988
+ children: [
989
+ areaAspect,
990
+ /* @__PURE__ */ jsx4(ChevronDown, { size: 14, strokeWidth: 1.8 })
991
+ ]
992
+ }
993
+ ),
994
+ aspectOpen && /* @__PURE__ */ jsx4(DropMenu, { children: AREA_ASPECT_RATIOS.map((item) => /* @__PURE__ */ jsx4(
995
+ DropItem,
996
+ {
997
+ active: item.label === areaAspect,
998
+ onClick: () => {
999
+ onAreaAspectChange(item.label);
1000
+ setAspectOpen(false);
1001
+ },
1002
+ children: item.label
1003
+ },
1004
+ item.label
1005
+ )) })
1006
+ ] }),
1007
+ /* @__PURE__ */ jsxs3("div", { style: { position: "relative" }, children: [
1008
+ /* @__PURE__ */ jsxs3(
1009
+ DropButton,
1010
+ {
1011
+ onClick: () => {
1012
+ setAspectOpen(false);
1013
+ setSavedOpen((prev) => !prev);
1014
+ },
1015
+ children: [
1016
+ /* @__PURE__ */ jsx4(Save, { size: 14, strokeWidth: 1.8 }),
1017
+ "Saved",
1018
+ /* @__PURE__ */ jsx4(ChevronDown, { size: 14, strokeWidth: 1.8 })
1019
+ ]
1020
+ }
1021
+ ),
1022
+ savedOpen && /* @__PURE__ */ jsxs3(DropMenu, { children: [
1023
+ /* @__PURE__ */ jsx4(DropItem, { accent: true, onClick: onAreaSavePreset, children: "Save current" }),
1024
+ areaPresets.length > 0 && /* @__PURE__ */ jsx4(MenuDivider, {}),
1025
+ areaPresets.map((preset) => /* @__PURE__ */ jsx4(
1026
+ DropItem,
1027
+ {
1028
+ onClick: () => {
1029
+ onAreaLoadPreset(preset);
1030
+ setSavedOpen(false);
1031
+ },
1032
+ children: preset.label
1033
+ },
1034
+ preset.label
1035
+ )),
1036
+ areaPresets.length === 0 && /* @__PURE__ */ jsx4(
1037
+ "div",
1038
+ {
1039
+ style: {
1040
+ padding: "6px 12px",
1041
+ color: "rgba(255,255,255,0.3)",
1042
+ fontSize: 12
1043
+ },
1044
+ children: "No saved areas"
1045
+ }
1046
+ )
1047
+ ] })
1048
+ ] })
1049
+ ]
1050
+ }
1051
+ )
1052
+ ] })
1053
+ ]
1054
+ }
1055
+ );
1056
+ }
1057
+ function CloseButton({ onClick }) {
1058
+ const [hovered, setHovered] = useState5(false);
1059
+ return /* @__PURE__ */ jsx4(
1060
+ "button",
1061
+ {
1062
+ onClick,
1063
+ onMouseEnter: () => setHovered(true),
1064
+ onMouseLeave: () => setHovered(false),
1065
+ style: circleButtonStyle(hovered, true),
1066
+ children: /* @__PURE__ */ jsx4(X, { size: 18, strokeWidth: 1.9 })
1067
+ }
1068
+ );
1069
+ }
1070
+ function ModeButton({
1071
+ children,
1072
+ label,
1073
+ selected,
1074
+ onClick
1075
+ }) {
1076
+ const [hovered, setHovered] = useState5(false);
1077
+ return /* @__PURE__ */ jsxs3("div", { style: { position: "relative" }, children: [
1078
+ hovered && /* @__PURE__ */ jsx4(
1079
+ "div",
1080
+ {
1081
+ style: {
1082
+ position: "absolute",
1083
+ left: "50%",
1084
+ bottom: "calc(100% + 10px)",
1085
+ transform: "translateX(-50%)",
1086
+ background: "rgba(32, 32, 36, 0.96)",
1087
+ backdropFilter: "blur(20px)",
1088
+ WebkitBackdropFilter: "blur(20px)",
1089
+ border: "1px solid rgba(255, 255, 255, 0.1)",
1090
+ borderRadius: 8,
1091
+ padding: "5px 10px",
1092
+ color: "rgba(255, 255, 255, 0.88)",
1093
+ fontSize: 12,
1094
+ whiteSpace: "nowrap",
1095
+ boxShadow: "0 8px 28px rgba(0, 0, 0, 0.28)",
1096
+ pointerEvents: "none"
1097
+ },
1098
+ children: label
1099
+ }
1100
+ ),
1101
+ /* @__PURE__ */ jsx4(
1102
+ "button",
1103
+ {
1104
+ onClick,
1105
+ onMouseEnter: () => setHovered(true),
1106
+ onMouseLeave: () => setHovered(false),
1107
+ style: {
1108
+ width: 42,
1109
+ height: 40,
1110
+ borderRadius: 12,
1111
+ border: "none",
1112
+ background: selected ? "rgba(255, 255, 255, 0.15)" : hovered ? "rgba(255, 255, 255, 0.08)" : "transparent",
1113
+ display: "flex",
1114
+ alignItems: "center",
1115
+ justifyContent: "center",
1116
+ cursor: "pointer",
1117
+ padding: 0,
1118
+ color: selected ? "rgba(255, 255, 255, 0.96)" : "rgba(255, 255, 255, 0.52)",
1119
+ transition: "background 0.12s ease, color 0.12s ease"
1120
+ },
1121
+ children
1122
+ }
1123
+ )
1124
+ ] });
1125
+ }
1126
+ function SettingsButton({
1127
+ open,
1128
+ onClick,
1129
+ magnetEnabled,
1130
+ onMagnetChange
1131
+ }) {
1132
+ const [hovered, setHovered] = useState5(false);
1133
+ return /* @__PURE__ */ jsxs3("div", { style: { position: "relative", marginRight: 6 }, children: [
1134
+ /* @__PURE__ */ jsx4(
1135
+ "button",
1136
+ {
1137
+ onClick,
1138
+ onMouseEnter: () => setHovered(true),
1139
+ onMouseLeave: () => setHovered(false),
1140
+ style: {
1141
+ ...circleButtonStyle(open || hovered, false),
1142
+ color: "rgba(255, 255, 255, 0.58)"
1143
+ },
1144
+ children: /* @__PURE__ */ jsx4(Settings2, { size: 18, strokeWidth: 1.7 })
1145
+ }
1146
+ ),
1147
+ open && /* @__PURE__ */ jsxs3(
1148
+ "div",
1149
+ {
1150
+ style: {
1151
+ position: "absolute",
1152
+ left: "50%",
1153
+ bottom: "calc(100% + 12px)",
1154
+ transform: "translateX(-50%)",
1155
+ minWidth: 184,
1156
+ padding: "10px 12px",
1157
+ borderRadius: 12,
1158
+ background: "rgba(32, 32, 36, 0.96)",
1159
+ border: "1px solid rgba(255, 255, 255, 0.1)",
1160
+ boxShadow: "0 14px 36px rgba(0, 0, 0, 0.32)",
1161
+ backdropFilter: "blur(20px)",
1162
+ WebkitBackdropFilter: "blur(20px)"
1163
+ },
1164
+ children: [
1165
+ /* @__PURE__ */ jsx4(
1032
1166
  "div",
1033
1167
  {
1034
1168
  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",
1169
+ fontSize: 11,
1170
+ color: "rgba(255, 255, 255, 0.46)",
1171
+ letterSpacing: "0.03em",
1172
+ textTransform: "uppercase",
1173
+ marginBottom: 10
1174
+ },
1175
+ children: "Area Settings"
1176
+ }
1177
+ ),
1178
+ /* @__PURE__ */ jsxs3(
1179
+ "label",
1050
1180
  {
1051
- ref: panelRef,
1052
- onMouseDown: (e) => e.stopPropagation(),
1053
1181
  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
1182
  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
1183
+ alignItems: "center",
1184
+ justifyContent: "space-between",
1185
+ gap: 12,
1186
+ cursor: "pointer"
1072
1187
  },
1073
1188
  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
1189
  /* @__PURE__ */ jsxs3(
1121
- "div",
1190
+ "span",
1122
1191
  {
1123
- style: { display: "flex", alignItems: "center", gap: 12 },
1192
+ style: {
1193
+ display: "flex",
1194
+ alignItems: "center",
1195
+ gap: 8,
1196
+ color: "rgba(255, 255, 255, 0.88)",
1197
+ fontSize: 13
1198
+ },
1124
1199
  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
- ] })
1200
+ /* @__PURE__ */ jsx4(Magnet, { size: 15, strokeWidth: 1.8 }),
1201
+ "Magnet snap"
1212
1202
  ]
1213
1203
  }
1214
1204
  ),
1215
- /* @__PURE__ */ jsx3(
1216
- "div",
1205
+ /* @__PURE__ */ jsx4(
1206
+ "button",
1217
1207
  {
1208
+ type: "button",
1209
+ onClick: () => onMagnetChange(!magnetEnabled),
1218
1210
  style: {
1219
- display: "flex",
1220
- alignItems: "center",
1221
- gap: 6
1211
+ width: 38,
1212
+ height: 22,
1213
+ borderRadius: 999,
1214
+ border: "none",
1215
+ background: magnetEnabled ? "#38bdf8" : "rgba(255, 255, 255, 0.18)",
1216
+ position: "relative",
1217
+ cursor: "pointer",
1218
+ padding: 0,
1219
+ transition: "background 0.12s ease"
1222
1220
  },
1223
- children: /* @__PURE__ */ jsx3(
1224
- MagnetToggle,
1221
+ children: /* @__PURE__ */ jsx4(
1222
+ "span",
1225
1223
  {
1226
- enabled: magnet,
1227
- onToggle: () => {
1228
- const next = !magnet;
1229
- setMagnet(next);
1230
- try {
1231
- localStorage.setItem("ab-magnet", String(next));
1232
- } catch {
1233
- }
1224
+ style: {
1225
+ position: "absolute",
1226
+ top: 2,
1227
+ left: magnetEnabled ? 18 : 2,
1228
+ width: 18,
1229
+ height: 18,
1230
+ borderRadius: "50%",
1231
+ background: "#fff",
1232
+ transition: "left 0.12s ease"
1234
1233
  }
1235
1234
  }
1236
1235
  )
1237
1236
  }
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
1237
  )
1250
1238
  ]
1251
1239
  }
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
1240
  )
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
1241
+ ]
1315
1242
  }
1316
- ),
1317
- children
1243
+ )
1318
1244
  ] });
1319
1245
  }
1320
- function Sep({ children }) {
1321
- return /* @__PURE__ */ jsx3(
1322
- "span",
1246
+ function CaptureButton({
1247
+ label,
1248
+ onClick
1249
+ }) {
1250
+ const [hovered, setHovered] = useState5(false);
1251
+ return /* @__PURE__ */ jsxs3(
1252
+ "button",
1323
1253
  {
1254
+ onClick,
1255
+ onMouseEnter: () => setHovered(true),
1256
+ onMouseLeave: () => setHovered(false),
1324
1257
  style: {
1325
- fontSize: 11,
1326
- color: "rgba(255, 255, 255, 0.35)",
1327
- width: 12,
1328
- textAlign: "center",
1258
+ padding: "0 18px",
1259
+ height: 40,
1260
+ borderRadius: 12,
1261
+ border: "none",
1262
+ background: hovered ? "#2f7bf8" : "#3b82f6",
1263
+ color: "white",
1264
+ fontSize: 13,
1265
+ fontWeight: 600,
1266
+ fontFamily: "inherit",
1267
+ cursor: "pointer",
1268
+ whiteSpace: "nowrap",
1269
+ transition: "background 0.12s ease",
1329
1270
  flexShrink: 0,
1330
- visibility: children ? "visible" : "hidden"
1271
+ display: "flex",
1272
+ alignItems: "center",
1273
+ gap: 8
1331
1274
  },
1332
- children: children || "\xD7"
1275
+ children: [
1276
+ /* @__PURE__ */ jsx4(Camera2, { size: 16, strokeWidth: 1.9 }),
1277
+ label
1278
+ ]
1333
1279
  }
1334
1280
  );
1335
1281
  }
1336
- function Unit({ children }) {
1337
- return /* @__PURE__ */ jsx3(
1338
- "span",
1282
+ function ControlGroup({
1283
+ label,
1284
+ children
1285
+ }) {
1286
+ return /* @__PURE__ */ jsxs3(
1287
+ "div",
1339
1288
  {
1340
1289
  style: {
1341
- fontSize: 10,
1342
- color: "rgba(255, 255, 255, 0.3)",
1343
- flexShrink: 0
1290
+ display: "flex",
1291
+ alignItems: "center",
1292
+ gap: 6,
1293
+ padding: "0 4px"
1344
1294
  },
1345
- children
1295
+ children: [
1296
+ /* @__PURE__ */ jsx4(
1297
+ "span",
1298
+ {
1299
+ style: {
1300
+ fontSize: 11,
1301
+ color: "rgba(255, 255, 255, 0.42)",
1302
+ textTransform: "uppercase",
1303
+ letterSpacing: "0.03em"
1304
+ },
1305
+ children: label
1306
+ }
1307
+ ),
1308
+ children
1309
+ ]
1346
1310
  }
1347
1311
  );
1348
1312
  }
@@ -1350,12 +1314,14 @@ function NumInput({
1350
1314
  value,
1351
1315
  onChange
1352
1316
  }) {
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(
1317
+ const [editing, setEditing] = useState5(false);
1318
+ const [text, setText] = useState5(String(value));
1319
+ useEffect4(() => {
1320
+ if (!editing) {
1321
+ setText(String(value));
1322
+ }
1323
+ }, [editing, value]);
1324
+ return /* @__PURE__ */ jsx4(
1359
1325
  "input",
1360
1326
  {
1361
1327
  type: "text",
@@ -1370,14 +1336,16 @@ function NumInput({
1370
1336
  },
1371
1337
  onChange: (e) => setText(e.target.value),
1372
1338
  onKeyDown: (e) => {
1373
- if (e.key === "Enter") e.target.blur();
1339
+ if (e.key === "Enter") {
1340
+ e.target.blur();
1341
+ }
1374
1342
  },
1375
1343
  style: {
1376
- width: 52,
1377
- padding: "3px 6px",
1344
+ width: 54,
1345
+ padding: "4px 6px",
1378
1346
  background: "rgba(255, 255, 255, 0.07)",
1379
1347
  border: "1px solid rgba(255, 255, 255, 0.1)",
1380
- borderRadius: 4,
1348
+ borderRadius: 7,
1381
1349
  color: "rgba(255, 255, 255, 0.9)",
1382
1350
  fontSize: 12,
1383
1351
  fontFamily: "system-ui, -apple-system, sans-serif",
@@ -1387,46 +1355,62 @@ function NumInput({
1387
1355
  }
1388
1356
  );
1389
1357
  }
1390
- function DropBtn({
1358
+ function StaticText({ children }) {
1359
+ return /* @__PURE__ */ jsx4(
1360
+ "span",
1361
+ {
1362
+ style: {
1363
+ fontSize: 11,
1364
+ color: "rgba(255,255,255,0.35)",
1365
+ minWidth: 8,
1366
+ textAlign: "center"
1367
+ },
1368
+ children
1369
+ }
1370
+ );
1371
+ }
1372
+ function DropButton({
1391
1373
  children,
1392
1374
  onClick,
1393
1375
  active
1394
1376
  }) {
1395
- return /* @__PURE__ */ jsx3(
1377
+ return /* @__PURE__ */ jsx4(
1396
1378
  "button",
1397
1379
  {
1398
1380
  onClick,
1399
1381
  style: {
1400
1382
  display: "flex",
1401
1383
  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)",
1384
+ gap: 6,
1385
+ height: 34,
1386
+ padding: "0 10px",
1387
+ borderRadius: 10,
1388
+ border: "1px solid rgba(255,255,255,0.08)",
1389
+ background: "rgba(255,255,255,0.04)",
1390
+ color: active ? "rgba(125, 211, 252, 0.96)" : "rgba(255,255,255,0.78)",
1406
1391
  cursor: "pointer",
1407
- fontSize: 11,
1408
- fontFamily: "inherit",
1409
- padding: "2px 0"
1392
+ fontSize: 12,
1393
+ fontFamily: "inherit"
1410
1394
  },
1411
1395
  children
1412
1396
  }
1413
1397
  );
1414
1398
  }
1415
1399
  function DropMenu({ children }) {
1416
- return /* @__PURE__ */ jsx3(
1400
+ return /* @__PURE__ */ jsx4(
1417
1401
  "div",
1418
1402
  {
1419
1403
  style: {
1420
1404
  position: "absolute",
1421
- bottom: "100%",
1422
- left: 0,
1423
- marginBottom: 4,
1424
- background: "rgba(32, 32, 36, 0.95)",
1405
+ bottom: "calc(100% + 8px)",
1406
+ left: "50%",
1407
+ transform: "translateX(-50%)",
1408
+ minWidth: 120,
1409
+ background: "rgba(32, 32, 36, 0.96)",
1425
1410
  border: "1px solid rgba(255, 255, 255, 0.1)",
1426
- borderRadius: 8,
1411
+ borderRadius: 10,
1427
1412
  padding: "4px 0",
1428
- minWidth: 110,
1429
- boxShadow: "0 4px 16px rgba(0, 0, 0, 0.3)",
1413
+ boxShadow: "0 10px 30px rgba(0, 0, 0, 0.3)",
1430
1414
  backdropFilter: "blur(20px)",
1431
1415
  WebkitBackdropFilter: "blur(20px)"
1432
1416
  },
@@ -1440,17 +1424,17 @@ function DropItem({
1440
1424
  active,
1441
1425
  accent
1442
1426
  }) {
1443
- return /* @__PURE__ */ jsx3(
1427
+ return /* @__PURE__ */ jsx4(
1444
1428
  "button",
1445
1429
  {
1446
1430
  onClick,
1447
1431
  style: {
1448
1432
  display: "block",
1449
1433
  width: "100%",
1450
- padding: "6px 12px",
1451
- background: active ? "rgba(255, 255, 255, 0.08)" : "none",
1434
+ padding: "7px 12px",
1435
+ background: active ? "rgba(255, 255, 255, 0.08)" : "transparent",
1452
1436
  border: "none",
1453
- color: accent ? "rgba(147, 130, 220, 0.9)" : "rgba(255, 255, 255, 0.8)",
1437
+ color: accent ? "rgba(125, 211, 252, 0.96)" : "rgba(255, 255, 255, 0.86)",
1454
1438
  textAlign: "left",
1455
1439
  cursor: "pointer",
1456
1440
  fontSize: 13,
@@ -1460,123 +1444,56 @@ function DropItem({
1460
1444
  }
1461
1445
  );
1462
1446
  }
1463
- function Chevron() {
1464
- return /* @__PURE__ */ jsx3("svg", { width: "10", height: "10", viewBox: "0 0 10 10", fill: "none", children: /* @__PURE__ */ jsx3(
1465
- "path",
1447
+ function MenuDivider() {
1448
+ return /* @__PURE__ */ jsx4(
1449
+ "div",
1466
1450
  {
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"
1451
+ style: {
1452
+ height: 1,
1453
+ background: "rgba(255,255,255,0.08)",
1454
+ margin: "4px 0"
1510
1455
  }
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
1456
  }
1527
- ) });
1457
+ );
1528
1458
  }
1529
- function MagnetToggle({
1530
- enabled,
1531
- onToggle
1532
- }) {
1533
- return /* @__PURE__ */ jsxs3(
1534
- "button",
1459
+ function Separator({ vertical = true }) {
1460
+ return /* @__PURE__ */ jsx4(
1461
+ "div",
1535
1462
  {
1536
- onClick: onToggle,
1537
1463
  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
- ]
1464
+ width: vertical ? 1 : 24,
1465
+ height: vertical ? 24 : 1,
1466
+ background: "rgba(255,255,255,0.12)",
1467
+ flexShrink: 0
1468
+ }
1553
1469
  }
1554
1470
  );
1555
1471
  }
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
- ] });
1472
+ function circleButtonStyle(active, round) {
1473
+ return {
1474
+ width: 40,
1475
+ height: 40,
1476
+ borderRadius: round ? "50%" : 12,
1477
+ border: "none",
1478
+ background: active ? "rgba(255, 255, 255, 0.12)" : "transparent",
1479
+ display: "flex",
1480
+ alignItems: "center",
1481
+ justifyContent: "center",
1482
+ cursor: "pointer",
1483
+ padding: 0,
1484
+ transition: "background 0.12s ease, color 0.12s ease",
1485
+ marginRight: round ? 6 : 0
1486
+ };
1570
1487
  }
1571
1488
 
1572
1489
  // 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";
1490
+ import { useEffect as useEffect5, useRef as useRef3, useCallback as useCallback4, useState as useState6 } from "react";
1491
+ import { Fragment as Fragment3, jsx as jsx5, jsxs as jsxs4 } from "react/jsx-runtime";
1575
1492
  function Inspector({ onSelect, onCancel }) {
1576
- const [highlight, setHighlight] = useState5(null);
1493
+ const [highlight, setHighlight] = useState6(null);
1577
1494
  const hoveredEl = useRef3(null);
1578
1495
  const styleEl = useRef3(null);
1579
- useEffect4(() => {
1496
+ useEffect5(() => {
1580
1497
  const style = document.createElement("style");
1581
1498
  style.setAttribute("data-afterbefore", "true");
1582
1499
  style.textContent = "*, *::before, *::after { cursor: crosshair !important; }";
@@ -1586,7 +1503,7 @@ function Inspector({ onSelect, onCancel }) {
1586
1503
  style.remove();
1587
1504
  };
1588
1505
  }, []);
1589
- const isOverlayElement = useCallback5((el) => {
1506
+ const isOverlayElement = useCallback4((el) => {
1590
1507
  let node = el;
1591
1508
  while (node) {
1592
1509
  if (node instanceof HTMLElement && node.dataset.afterbefore) return true;
@@ -1594,7 +1511,7 @@ function Inspector({ onSelect, onCancel }) {
1594
1511
  }
1595
1512
  return false;
1596
1513
  }, []);
1597
- const handleMouseMove = useCallback5(
1514
+ const handleMouseMove = useCallback4(
1598
1515
  (e) => {
1599
1516
  const el = document.elementFromPoint(e.clientX, e.clientY);
1600
1517
  if (!el || !(el instanceof HTMLElement) || isOverlayElement(el)) {
@@ -1614,7 +1531,7 @@ function Inspector({ onSelect, onCancel }) {
1614
1531
  },
1615
1532
  [isOverlayElement]
1616
1533
  );
1617
- const handleClick = useCallback5(
1534
+ const handleClick = useCallback4(
1618
1535
  (e) => {
1619
1536
  e.preventDefault();
1620
1537
  e.stopPropagation();
@@ -1625,7 +1542,7 @@ function Inspector({ onSelect, onCancel }) {
1625
1542
  },
1626
1543
  [onSelect]
1627
1544
  );
1628
- const handleKeyDown = useCallback5(
1545
+ const handleKeyDown = useCallback4(
1629
1546
  (e) => {
1630
1547
  if (e.key === "Escape") {
1631
1548
  onCancel();
@@ -1633,7 +1550,7 @@ function Inspector({ onSelect, onCancel }) {
1633
1550
  },
1634
1551
  [onCancel]
1635
1552
  );
1636
- useEffect4(() => {
1553
+ useEffect5(() => {
1637
1554
  document.addEventListener("mousemove", handleMouseMove, true);
1638
1555
  document.addEventListener("click", handleClick, true);
1639
1556
  document.addEventListener("keydown", handleKeyDown);
@@ -1644,8 +1561,8 @@ function Inspector({ onSelect, onCancel }) {
1644
1561
  };
1645
1562
  }, [handleMouseMove, handleClick, handleKeyDown]);
1646
1563
  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(
1564
+ highlight && /* @__PURE__ */ jsxs4(Fragment3, { children: [
1565
+ /* @__PURE__ */ jsx5(
1649
1566
  "div",
1650
1567
  {
1651
1568
  style: {
@@ -1661,7 +1578,7 @@ function Inspector({ onSelect, onCancel }) {
1661
1578
  }
1662
1579
  }
1663
1580
  ),
1664
- /* @__PURE__ */ jsx4(
1581
+ /* @__PURE__ */ jsx5(
1665
1582
  "div",
1666
1583
  {
1667
1584
  style: {
@@ -1682,7 +1599,7 @@ function Inspector({ onSelect, onCancel }) {
1682
1599
  }
1683
1600
  )
1684
1601
  ] }),
1685
- !highlight && /* @__PURE__ */ jsx4(
1602
+ !highlight && /* @__PURE__ */ jsx5(
1686
1603
  "div",
1687
1604
  {
1688
1605
  style: {
@@ -1706,18 +1623,18 @@ function Inspector({ onSelect, onCancel }) {
1706
1623
  }
1707
1624
 
1708
1625
  // 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";
1626
+ import { useState as useState7, useRef as useRef4, useEffect as useEffect6, useCallback as useCallback5 } from "react";
1627
+ import { jsx as jsx6, jsxs as jsxs5 } from "react/jsx-runtime";
1711
1628
  var PANEL_WIDTH = 220;
1712
1629
  function Status({ onReset, position, onClose }) {
1713
1630
  const panelRef = useRef4(null);
1714
- const [toast, setToast] = useState6(null);
1715
- const [pushing, setPushing] = useState6(false);
1716
- const showToast = useCallback6((message, type) => {
1631
+ const [toast, setToast] = useState7(null);
1632
+ const [pushing, setPushing] = useState7(false);
1633
+ const showToast = useCallback5((message, type) => {
1717
1634
  setToast({ message, type });
1718
1635
  setTimeout(() => setToast(null), 3e3);
1719
1636
  }, []);
1720
- useEffect5(() => {
1637
+ useEffect6(() => {
1721
1638
  const handler = (e) => {
1722
1639
  if (panelRef.current && !panelRef.current.contains(e.target)) {
1723
1640
  onClose();
@@ -1726,7 +1643,7 @@ function Status({ onReset, position, onClose }) {
1726
1643
  document.addEventListener("mousedown", handler);
1727
1644
  return () => document.removeEventListener("mousedown", handler);
1728
1645
  }, [onClose]);
1729
- useEffect5(() => {
1646
+ useEffect6(() => {
1730
1647
  const handler = (e) => {
1731
1648
  if (e.key === "Escape") onClose();
1732
1649
  };
@@ -1821,7 +1738,7 @@ function Status({ onReset, position, onClose }) {
1821
1738
  overflow: "hidden"
1822
1739
  },
1823
1740
  children: [
1824
- /* @__PURE__ */ jsx5(
1741
+ /* @__PURE__ */ jsx6(
1825
1742
  "div",
1826
1743
  {
1827
1744
  style: {
@@ -1843,7 +1760,7 @@ function Status({ onReset, position, onClose }) {
1843
1760
  onMouseEnter: onEnter,
1844
1761
  onMouseLeave: onLeave,
1845
1762
  children: [
1846
- /* @__PURE__ */ jsx5(FolderIcon, {}),
1763
+ /* @__PURE__ */ jsx6(FolderIcon, {}),
1847
1764
  "Open Folder"
1848
1765
  ]
1849
1766
  }
@@ -1856,7 +1773,7 @@ function Status({ onReset, position, onClose }) {
1856
1773
  onMouseEnter: onEnter,
1857
1774
  onMouseLeave: onLeave,
1858
1775
  children: [
1859
- /* @__PURE__ */ jsx5(CopyIcon, {}),
1776
+ /* @__PURE__ */ jsx6(CopyIcon, {}),
1860
1777
  "Copy Markdown"
1861
1778
  ]
1862
1779
  }
@@ -1870,12 +1787,12 @@ function Status({ onReset, position, onClose }) {
1870
1787
  onMouseEnter: onEnter,
1871
1788
  onMouseLeave: onLeave,
1872
1789
  children: [
1873
- /* @__PURE__ */ jsx5(PushIcon, {}),
1790
+ /* @__PURE__ */ jsx6(PushIcon, {}),
1874
1791
  pushing ? "Pushing..." : "Push to PR"
1875
1792
  ]
1876
1793
  }
1877
1794
  ),
1878
- /* @__PURE__ */ jsx5(
1795
+ /* @__PURE__ */ jsx6(
1879
1796
  "div",
1880
1797
  {
1881
1798
  style: {
@@ -1893,13 +1810,13 @@ function Status({ onReset, position, onClose }) {
1893
1810
  onMouseEnter: onEnter,
1894
1811
  onMouseLeave: onLeave,
1895
1812
  children: [
1896
- /* @__PURE__ */ jsx5(ResetIcon, {}),
1813
+ /* @__PURE__ */ jsx6(ResetIcon, {}),
1897
1814
  "Reset"
1898
1815
  ]
1899
1816
  }
1900
1817
  )
1901
1818
  ] }),
1902
- toast && /* @__PURE__ */ jsx5(
1819
+ toast && /* @__PURE__ */ jsx6(
1903
1820
  "div",
1904
1821
  {
1905
1822
  style: {
@@ -1925,14 +1842,14 @@ function Status({ onReset, position, onClose }) {
1925
1842
  );
1926
1843
  }
1927
1844
  function FolderIcon() {
1928
- return /* @__PURE__ */ jsx5(
1845
+ return /* @__PURE__ */ jsx6(
1929
1846
  "svg",
1930
1847
  {
1931
1848
  width: "14",
1932
1849
  height: "14",
1933
1850
  viewBox: "0 0 14 14",
1934
1851
  style: { color: "rgba(255,255,255,0.5)" },
1935
- children: /* @__PURE__ */ jsx5(
1852
+ children: /* @__PURE__ */ jsx6(
1936
1853
  "path",
1937
1854
  {
1938
1855
  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 +1870,7 @@ function CopyIcon() {
1953
1870
  viewBox: "0 0 14 14",
1954
1871
  style: { color: "rgba(255,255,255,0.5)" },
1955
1872
  children: [
1956
- /* @__PURE__ */ jsx5(
1873
+ /* @__PURE__ */ jsx6(
1957
1874
  "rect",
1958
1875
  {
1959
1876
  x: "4",
@@ -1966,7 +1883,7 @@ function CopyIcon() {
1966
1883
  strokeWidth: "1.3"
1967
1884
  }
1968
1885
  ),
1969
- /* @__PURE__ */ jsx5(
1886
+ /* @__PURE__ */ jsx6(
1970
1887
  "path",
1971
1888
  {
1972
1889
  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 +1897,14 @@ function CopyIcon() {
1980
1897
  );
1981
1898
  }
1982
1899
  function PushIcon() {
1983
- return /* @__PURE__ */ jsx5(
1900
+ return /* @__PURE__ */ jsx6(
1984
1901
  "svg",
1985
1902
  {
1986
1903
  width: "14",
1987
1904
  height: "14",
1988
1905
  viewBox: "0 0 14 14",
1989
1906
  style: { color: "rgba(255,255,255,0.5)" },
1990
- children: /* @__PURE__ */ jsx5(
1907
+ children: /* @__PURE__ */ jsx6(
1991
1908
  "path",
1992
1909
  {
1993
1910
  d: "M7 11V3m0 0L4 6m3-3l3 3",
@@ -2010,7 +1927,7 @@ function ResetIcon() {
2010
1927
  viewBox: "0 0 14 14",
2011
1928
  style: { color: "rgba(255,255,255,0.4)" },
2012
1929
  children: [
2013
- /* @__PURE__ */ jsx5(
1930
+ /* @__PURE__ */ jsx6(
2014
1931
  "path",
2015
1932
  {
2016
1933
  d: "M2.5 7a4.5 4.5 0 118 2.5",
@@ -2020,7 +1937,7 @@ function ResetIcon() {
2020
1937
  strokeLinecap: "round"
2021
1938
  }
2022
1939
  ),
2023
- /* @__PURE__ */ jsx5(
1940
+ /* @__PURE__ */ jsx6(
2024
1941
  "path",
2025
1942
  {
2026
1943
  d: "M2.5 3v4h4",
@@ -2037,7 +1954,7 @@ function ResetIcon() {
2037
1954
  }
2038
1955
 
2039
1956
  // src/overlay/index.tsx
2040
- import { jsx as jsx6, jsxs as jsxs6 } from "react/jsx-runtime";
1957
+ import { jsx as jsx7, jsxs as jsxs6 } from "react/jsx-runtime";
2041
1958
  async function saveCapture(type, mode, dataUrl) {
2042
1959
  try {
2043
1960
  const res = await fetch("/__afterbefore/save", {
@@ -2055,13 +1972,31 @@ async function saveCapture(type, mode, dataUrl) {
2055
1972
  }
2056
1973
  function AfterBefore() {
2057
1974
  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);
1975
+ const [statusOpen, setStatusOpen] = useState8(false);
1976
+ const [toolbarActive, setToolbarActive] = useState8(false);
1977
+ const [selectorActive, setSelectorActive] = useState8(false);
1978
+ const [inspectorActive, setInspectorActive] = useState8(false);
1979
+ const [loading, setLoading] = useState8(false);
1980
+ const [selectedMode, setSelectedMode] = useState8("area");
1981
+ const [magnetEnabled, setMagnetEnabled] = useState8(true);
1982
+ const [areaRect, setAreaRect] = useState8(null);
1983
+ const [areaAspect, setAreaAspect] = useState8(DEFAULT_AREA_ASPECT);
1984
+ const [areaPresets, setAreaPresets] = useState8([]);
2063
1985
  const iconPos = useRef5({ x: 24, y: 0 });
2064
- useEffect6(() => {
1986
+ useEffect7(() => {
1987
+ try {
1988
+ setMagnetEnabled(localStorage.getItem("ab-magnet") !== "false");
1989
+ } catch {
1990
+ setMagnetEnabled(true);
1991
+ }
1992
+ try {
1993
+ const savedPresets = localStorage.getItem("ab-area-presets");
1994
+ setAreaPresets(savedPresets ? JSON.parse(savedPresets) : []);
1995
+ } catch {
1996
+ setAreaPresets([]);
1997
+ }
1998
+ }, []);
1999
+ useEffect7(() => {
2065
2000
  if (state.phase === "ready") {
2066
2001
  const timer = setTimeout(() => {
2067
2002
  reset();
@@ -2070,13 +2005,13 @@ function AfterBefore() {
2070
2005
  return () => clearTimeout(timer);
2071
2006
  }
2072
2007
  }, [state.phase, reset]);
2073
- const handlePositionChange = useCallback7(
2008
+ const handlePositionChange = useCallback6(
2074
2009
  (pos) => {
2075
2010
  iconPos.current = pos;
2076
2011
  },
2077
2012
  []
2078
2013
  );
2079
- const handleIconClick = useCallback7(() => {
2014
+ const handleIconClick = useCallback6(() => {
2080
2015
  if (loading) return;
2081
2016
  if (state.phase === "ready") {
2082
2017
  setStatusOpen((prev) => !prev);
@@ -2087,7 +2022,7 @@ function AfterBefore() {
2087
2022
  setStatusOpen(false);
2088
2023
  }
2089
2024
  }, [state.phase, loading]);
2090
- const performCapture = useCallback7(
2025
+ const performCapture = useCallback6(
2091
2026
  async (mode, area, element) => {
2092
2027
  setLoading(true);
2093
2028
  try {
@@ -2107,53 +2042,152 @@ function AfterBefore() {
2107
2042
  },
2108
2043
  [state.phase, captureComplete]
2109
2044
  );
2110
- const handleToolbarCapture = useCallback7(
2045
+ const handleToolbarCapture = useCallback6(
2111
2046
  (mode) => {
2112
- setToolbarActive(false);
2113
2047
  if (mode === "area") {
2048
+ if (selectorActive && areaRect) {
2049
+ setSelectorActive(false);
2050
+ setToolbarActive(false);
2051
+ performCapture("area", {
2052
+ x: Math.round(areaRect.x),
2053
+ y: Math.round(areaRect.y),
2054
+ width: Math.round(areaRect.w),
2055
+ height: Math.round(areaRect.h)
2056
+ });
2057
+ return;
2058
+ }
2059
+ setSelectedMode("area");
2060
+ setAreaAspect(DEFAULT_AREA_ASPECT);
2061
+ setAreaRect(createInitialAreaRect());
2114
2062
  setSelectorActive(true);
2063
+ setToolbarActive(true);
2115
2064
  } else if (mode === "viewport") {
2065
+ setToolbarActive(false);
2116
2066
  performCapture("viewport");
2117
2067
  } else if (mode === "fullpage") {
2068
+ setToolbarActive(false);
2118
2069
  performCapture("fullpage");
2119
2070
  } else if (mode === "component") {
2071
+ setToolbarActive(false);
2120
2072
  setInspectorActive(true);
2121
2073
  }
2122
2074
  },
2123
- [performCapture]
2075
+ [areaRect, performCapture, selectorActive]
2124
2076
  );
2125
- const handleToolbarCancel = useCallback7(() => {
2077
+ const handleToolbarCancel = useCallback6(() => {
2126
2078
  setToolbarActive(false);
2079
+ setSelectorActive(false);
2127
2080
  }, []);
2128
- const handleComponentSelect = useCallback7(
2081
+ const handleComponentSelect = useCallback6(
2129
2082
  (element) => {
2130
2083
  setInspectorActive(false);
2131
2084
  performCapture("component", void 0, element);
2132
2085
  },
2133
2086
  [performCapture]
2134
2087
  );
2135
- const handleComponentCancel = useCallback7(() => {
2088
+ const handleComponentCancel = useCallback6(() => {
2136
2089
  setInspectorActive(false);
2090
+ setToolbarActive(true);
2137
2091
  }, []);
2138
- const handleAreaSelect = useCallback7(
2092
+ const handleAreaSelect = useCallback6(
2139
2093
  (area) => {
2140
2094
  setSelectorActive(false);
2095
+ setToolbarActive(false);
2141
2096
  performCapture("area", area);
2142
2097
  },
2143
2098
  [performCapture]
2144
2099
  );
2145
- const handleAreaCancel = useCallback7(() => {
2100
+ const handleAreaCancel = useCallback6(() => {
2146
2101
  setSelectorActive(false);
2102
+ setToolbarActive(true);
2103
+ }, []);
2104
+ const handleMagnetChange = useCallback6((enabled) => {
2105
+ setMagnetEnabled(enabled);
2106
+ try {
2107
+ localStorage.setItem("ab-magnet", String(enabled));
2108
+ } catch {
2109
+ }
2110
+ }, []);
2111
+ const handleModeChange = useCallback6((mode) => {
2112
+ setSelectedMode(mode);
2113
+ if (mode !== "area") {
2114
+ setSelectorActive(false);
2115
+ }
2116
+ }, []);
2117
+ const handleAreaRectChange = useCallback6((nextRect) => {
2118
+ setAreaRect(nextRect);
2119
+ }, []);
2120
+ const handleAreaSizeChange = useCallback6(
2121
+ (field, value) => {
2122
+ const nextValue = parseInt(value, 10);
2123
+ if (Number.isNaN(nextValue) || nextValue < 20 || !areaRect) {
2124
+ return;
2125
+ }
2126
+ 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];
2127
+ if (field === "w") {
2128
+ setAreaRect({
2129
+ ...areaRect,
2130
+ w: nextValue,
2131
+ h: aspectRatio > 0 ? nextValue / aspectRatio : areaRect.h
2132
+ });
2133
+ } else {
2134
+ setAreaRect({
2135
+ ...areaRect,
2136
+ h: nextValue,
2137
+ w: aspectRatio > 0 ? nextValue * aspectRatio : areaRect.w
2138
+ });
2139
+ }
2140
+ },
2141
+ [areaAspect, areaRect]
2142
+ );
2143
+ const handleAreaPositionChange = useCallback6(
2144
+ (field, value) => {
2145
+ const nextValue = parseInt(value, 10);
2146
+ if (Number.isNaN(nextValue) || !areaRect) {
2147
+ return;
2148
+ }
2149
+ setAreaRect({ ...areaRect, [field]: nextValue });
2150
+ },
2151
+ [areaRect]
2152
+ );
2153
+ const handleAreaAspectChange = useCallback6(
2154
+ (nextAspect) => {
2155
+ setAreaAspect(nextAspect);
2156
+ if (!areaRect || nextAspect === "Free") {
2157
+ return;
2158
+ }
2159
+ const aspectRatio = { "16:9": 16 / 9, "4:3": 4 / 3, "1:1": 1, "3:2": 3 / 2, "21:9": 21 / 9 }[nextAspect];
2160
+ setAreaRect({ ...areaRect, h: areaRect.w / aspectRatio });
2161
+ },
2162
+ [areaRect]
2163
+ );
2164
+ const handleAreaSavePreset = useCallback6(() => {
2165
+ if (!areaRect) {
2166
+ return;
2167
+ }
2168
+ const label = `${Math.round(areaRect.w)}x${Math.round(areaRect.h)}`;
2169
+ const nextPresets = [
2170
+ ...areaPresets.filter((preset) => preset.label !== label),
2171
+ { label, rect: { ...areaRect } }
2172
+ ];
2173
+ setAreaPresets(nextPresets);
2174
+ try {
2175
+ localStorage.setItem("ab-area-presets", JSON.stringify(nextPresets));
2176
+ } catch {
2177
+ }
2178
+ }, [areaPresets, areaRect]);
2179
+ const handleAreaLoadPreset = useCallback6((preset) => {
2180
+ setAreaRect({ ...preset.rect });
2147
2181
  }, []);
2148
- const handleReset = useCallback7(() => {
2182
+ const handleReset = useCallback6(() => {
2149
2183
  reset();
2150
2184
  setStatusOpen(false);
2151
2185
  }, [reset]);
2152
- const handleStatusClose = useCallback7(() => {
2186
+ const handleStatusClose = useCallback6(() => {
2153
2187
  setStatusOpen(false);
2154
2188
  }, []);
2155
2189
  return /* @__PURE__ */ jsxs6("div", { "data-afterbefore": "true", children: [
2156
- /* @__PURE__ */ jsx6(
2190
+ /* @__PURE__ */ jsx7(
2157
2191
  Icon,
2158
2192
  {
2159
2193
  phase: state.phase,
@@ -2162,16 +2196,40 @@ function AfterBefore() {
2162
2196
  onPositionChange: handlePositionChange
2163
2197
  }
2164
2198
  ),
2165
- toolbarActive && !selectorActive && !inspectorActive && /* @__PURE__ */ jsx6(Toolbar, { onCapture: handleToolbarCapture, onCancel: handleToolbarCancel }),
2166
- selectorActive && /* @__PURE__ */ jsx6(
2199
+ toolbarActive && !selectorActive && !inspectorActive && !loading && /* @__PURE__ */ jsx7(CapturePreview, { mode: selectedMode }),
2200
+ toolbarActive && !inspectorActive && /* @__PURE__ */ jsx7(
2201
+ Toolbar,
2202
+ {
2203
+ selectedMode,
2204
+ onModeChange: handleModeChange,
2205
+ onCapture: handleToolbarCapture,
2206
+ onCancel: handleToolbarCancel,
2207
+ magnetEnabled,
2208
+ onMagnetChange: handleMagnetChange,
2209
+ areaSelectionActive: selectorActive,
2210
+ areaRect,
2211
+ areaAspect,
2212
+ areaPresets,
2213
+ onAreaSizeChange: handleAreaSizeChange,
2214
+ onAreaPositionChange: handleAreaPositionChange,
2215
+ onAreaAspectChange: handleAreaAspectChange,
2216
+ onAreaSavePreset: handleAreaSavePreset,
2217
+ onAreaLoadPreset: handleAreaLoadPreset
2218
+ }
2219
+ ),
2220
+ selectorActive && /* @__PURE__ */ jsx7(
2167
2221
  Selector,
2168
2222
  {
2223
+ rect: areaRect,
2224
+ aspect: areaAspect,
2225
+ onRectChange: handleAreaRectChange,
2169
2226
  onSelect: handleAreaSelect,
2170
- onCancel: handleAreaCancel
2227
+ onCancel: handleAreaCancel,
2228
+ magnetEnabled
2171
2229
  }
2172
2230
  ),
2173
- inspectorActive && /* @__PURE__ */ jsx6(Inspector, { onSelect: handleComponentSelect, onCancel: handleComponentCancel }),
2174
- statusOpen && state.phase === "ready" && /* @__PURE__ */ jsx6(
2231
+ inspectorActive && /* @__PURE__ */ jsx7(Inspector, { onSelect: handleComponentSelect, onCancel: handleComponentCancel }),
2232
+ statusOpen && state.phase === "ready" && /* @__PURE__ */ jsx7(
2175
2233
  Status,
2176
2234
  {
2177
2235
  onReset: handleReset,