afterbefore 0.2.6 → 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 useState6, useCallback as useCallback6, useRef as useRef5, useEffect as useEffect5 } 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",
257
+ LoaderCircle,
272
258
  {
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",
294
- {
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,15 +303,155 @@ function Icon({ phase, onClick, loading, onPositionChange }) {
368
303
  );
369
304
  }
370
305
 
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);
324
+ useEffect2(() => {
325
+ if (mode !== "area") {
326
+ return;
327
+ }
328
+ const syncRect = () => {
329
+ setAreaRect(getAreaPreviewRect());
330
+ };
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(
347
+ "div",
348
+ {
349
+ style: {
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
+ }
360
+ }
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(
379
+ "div",
380
+ {
381
+ style: {
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)"
394
+ },
395
+ children: mode === "fullpage" ? "Full-page capture begins from the current viewport" : "Current viewport capture"
396
+ }
397
+ )
398
+ }
399
+ );
400
+ }
401
+ return /* @__PURE__ */ jsx2(
402
+ "div",
403
+ {
404
+ "data-afterbefore": "true",
405
+ style: {
406
+ position: "fixed",
407
+ inset: 0,
408
+ zIndex: 2147483645,
409
+ pointerEvents: "none"
410
+ },
411
+ children: /* @__PURE__ */ jsx2(
412
+ "div",
413
+ {
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"
429
+ }
430
+ )
431
+ }
432
+ );
433
+ }
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";
449
+
371
450
  // src/overlay/ui/selector.tsx
372
- import React2, { useState as useState3, useRef as useRef2, useCallback as useCallback3, useEffect as useEffect2 } from "react";
373
- import { Fragment, jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
374
- var HANDLE_R = 6;
375
- var HANDLE_HIT = 16;
376
- var MIN_SIZE = 20;
377
- var PANEL_HEIGHT_EST = 140;
378
- var SNAP_THRESHOLD = 8;
379
- 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 = [
380
455
  { label: "Free", value: 0 },
381
456
  { label: "16:9", value: 16 / 9 },
382
457
  { label: "4:3", value: 4 / 3 },
@@ -384,112 +459,94 @@ var ASPECT_RATIOS = [
384
459
  { label: "3:2", value: 3 / 2 },
385
460
  { label: "21:9", value: 21 / 9 }
386
461
  ];
387
- function Selector({ onSelect, onCancel, onViewport, onFullPage, onComponent }) {
388
- const [rect, setRect] = useState3(null);
389
- const [placed, setPlaced] = useState3(false);
390
- const [aspect, setAspect] = useState3("Free");
391
- const [aspectOpen, setAspectOpen] = useState3(false);
392
- const [savedOpen, setSavedOpen] = useState3(false);
393
- const [presets, setPresets] = useState3(
394
- () => {
395
- try {
396
- const s = localStorage.getItem("ab-area-presets");
397
- return s ? JSON.parse(s) : [];
398
- } catch {
399
- return [];
400
- }
401
- }
402
- );
403
- const [cursor, setCursor] = useState3("crosshair");
404
- const [magnet, setMagnet] = useState3(() => {
405
- try {
406
- return localStorage.getItem("ab-magnet") !== "false";
407
- } catch {
408
- return true;
409
- }
410
- });
411
- const [snappedX, setSnappedX] = useState3(false);
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
+ }) {
490
+ const [snappedX, setSnappedX] = useState4(false);
412
491
  const mode = useRef2("none");
413
492
  const start = useRef2({ x: 0, y: 0 });
414
493
  const snapRef = useRef2({ x: 0, y: 0, w: 0, h: 0 });
415
494
  const corner = useRef2("br");
416
- const panelRef = useRef2(null);
417
- const ratio = ASPECT_RATIOS.find((a) => a.label === aspect)?.value ?? 0;
495
+ const ratio = useMemo(() => getAspectRatio(aspect), [aspect]);
418
496
  const applySnap = useCallback3(
419
- (r) => {
420
- if (!magnet) {
497
+ (nextRect) => {
498
+ if (!magnetEnabled) {
421
499
  setSnappedX(false);
422
- return r;
500
+ return nextRect;
423
501
  }
424
- const vw = window.innerWidth;
425
- const centerX = r.x + r.w / 2;
426
- const viewCenterX = vw / 2;
427
- if (Math.abs(centerX - viewCenterX) < SNAP_THRESHOLD) {
502
+ const centerX = nextRect.x + nextRect.w / 2;
503
+ const viewportCenterX = window.innerWidth / 2;
504
+ if (Math.abs(centerX - viewportCenterX) < SNAP_THRESHOLD) {
428
505
  setSnappedX(true);
429
- return { ...r, x: viewCenterX - r.w / 2 };
506
+ return { ...nextRect, x: viewportCenterX - nextRect.w / 2 };
430
507
  }
431
508
  setSnappedX(false);
432
- return r;
509
+ return nextRect;
433
510
  },
434
- [magnet]
511
+ [magnetEnabled]
435
512
  );
436
- useEffect2(() => {
513
+ useEffect3(() => {
437
514
  const onKey = (e) => {
438
- if (e.target?.tagName === "INPUT") {
439
- if (e.key === "Escape") e.target.blur();
440
- return;
441
- }
442
515
  if (e.key === "Escape") {
443
- if (placed) {
444
- setPlaced(false);
445
- setRect(null);
446
- } else {
447
- onCancel();
448
- }
449
- } else if (e.key === "Enter" && placed && rect) {
450
- onSelect({
451
- x: Math.round(rect.x),
452
- y: Math.round(rect.y),
453
- width: Math.round(rect.w),
454
- height: Math.round(rect.h)
455
- });
516
+ onCancel();
456
517
  }
457
518
  };
458
519
  document.addEventListener("keydown", onKey);
459
520
  return () => document.removeEventListener("keydown", onKey);
460
- }, [placed, rect, onSelect, onCancel]);
461
- const hitCorner = useCallback3(
462
- (mx, my, r) => {
463
- const cs = [
464
- ["tl", r.x, r.y],
465
- ["tr", r.x + r.w, r.y],
466
- ["bl", r.x, r.y + r.h],
467
- ["br", r.x + r.w, r.y + r.h]
468
- ];
469
- for (const [c, cx, cy] of cs) {
470
- if (Math.abs(mx - cx) <= HANDLE_HIT && Math.abs(my - cy) <= HANDLE_HIT)
471
- 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;
472
532
  }
473
- return null;
474
- },
475
- []
476
- );
533
+ }
534
+ return null;
535
+ }, []);
477
536
  const hitInside = useCallback3(
478
- (mx, my, r) => mx >= r.x && mx <= r.x + r.w && my >= r.y && my <= r.y + r.h,
537
+ (mx, my, currentRect) => mx >= currentRect.x && mx <= currentRect.x + currentRect.w && my >= currentRect.y && my <= currentRect.y + currentRect.h,
479
538
  []
480
539
  );
481
540
  const onDown = useCallback3(
482
541
  (e) => {
483
- if (panelRef.current?.contains(e.target)) return;
484
542
  e.preventDefault();
485
- setAspectOpen(false);
486
- setSavedOpen(false);
487
- const mx = e.clientX, my = e.clientY;
488
- if (placed && rect) {
489
- const c = hitCorner(mx, my, rect);
490
- 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) {
491
548
  mode.current = "resizing";
492
- corner.current = c;
549
+ corner.current = activeCorner;
493
550
  start.current = { x: mx, y: my };
494
551
  snapRef.current = { ...rect };
495
552
  return;
@@ -500,78 +557,79 @@ function Selector({ onSelect, onCancel, onViewport, onFullPage, onComponent }) {
500
557
  snapRef.current = { ...rect };
501
558
  return;
502
559
  }
503
- setPlaced(false);
504
560
  }
505
561
  mode.current = "drawing";
506
562
  start.current = { x: mx, y: my };
507
- setRect({ x: mx, y: my, w: 0, h: 0 });
563
+ onRectChange({ x: mx, y: my, w: 0, h: 0 });
508
564
  },
509
- [placed, rect, hitCorner, hitInside]
565
+ [hitCorner, hitInside, onRectChange, rect]
510
566
  );
511
567
  const onMove = useCallback3(
512
568
  (e) => {
513
- const mx = e.clientX, my = e.clientY;
569
+ const mx = e.clientX;
570
+ const my = e.clientY;
514
571
  if (mode.current === "drawing") {
515
- const sx = start.current.x, sy = start.current.y;
516
- let x = Math.min(sx, mx), y = Math.min(sy, my);
517
- 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);
518
578
  if (ratio > 0) {
519
579
  h = w / ratio;
520
- if (my < sy) y = sy - h;
580
+ if (my < sy) {
581
+ y = sy - h;
582
+ }
521
583
  }
522
- setRect(applySnap({ x, y, w, h }));
523
- } else if (mode.current === "moving") {
524
- const dx = mx - start.current.x, dy = my - start.current.y;
525
- 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(
526
591
  applySnap({
527
592
  ...snapRef.current,
528
593
  x: snapRef.current.x + dx,
529
594
  y: snapRef.current.y + dy
530
595
  })
531
596
  );
532
- } else if (mode.current === "resizing") {
533
- const o = snapRef.current;
534
- const c = corner.current;
535
- const nr = { ...o };
536
- if (c === "br") {
537
- nr.w = Math.max(MIN_SIZE, mx - o.x);
538
- nr.h = ratio > 0 ? nr.w / ratio : Math.max(MIN_SIZE, my - o.y);
539
- } else if (c === "bl") {
540
- nr.w = Math.max(MIN_SIZE, o.x + o.w - mx);
541
- nr.x = o.x + o.w - nr.w;
542
- nr.h = ratio > 0 ? nr.w / ratio : Math.max(MIN_SIZE, my - o.y);
543
- } else if (c === "tr") {
544
- nr.w = Math.max(MIN_SIZE, mx - o.x);
545
- nr.h = ratio > 0 ? nr.w / ratio : Math.max(MIN_SIZE, o.y + o.h - my);
546
- 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;
547
613
  } else {
548
- nr.w = Math.max(MIN_SIZE, o.x + o.w - mx);
549
- nr.h = ratio > 0 ? nr.w / ratio : Math.max(MIN_SIZE, o.y + o.h - my);
550
- nr.x = o.x + o.w - nr.w;
551
- 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;
552
618
  }
553
- setRect(applySnap(nr));
554
- } else if (placed && rect) {
555
- const c = hitCorner(mx, my, rect);
556
- if (c === "tl" || c === "br") setCursor("nwse-resize");
557
- else if (c === "tr" || c === "bl") setCursor("nesw-resize");
558
- else if (hitInside(mx, my, rect)) setCursor("move");
559
- else setCursor("crosshair");
619
+ onRectChange(applySnap(nextRect));
560
620
  }
561
621
  },
562
- [ratio, placed, rect, hitCorner, hitInside, applySnap]
622
+ [applySnap, onRectChange, ratio]
563
623
  );
564
624
  const onUp = useCallback3(
565
625
  (e) => {
566
- const prevMode = mode.current;
567
- if (prevMode === "drawing" && rect) {
568
- if (rect.w >= MIN_SIZE && rect.h >= MIN_SIZE) {
569
- setPlaced(true);
570
- } else {
571
- 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);
572
630
  }
573
631
  }
574
- if (prevMode === "moving" && placed && rect) {
632
+ if (previousMode === "moving" && rect) {
575
633
  const dx = Math.abs(e.clientX - start.current.x);
576
634
  const dy = Math.abs(e.clientY - start.current.y);
577
635
  if (dx < 3 && dy < 3) {
@@ -585,49 +643,8 @@ function Selector({ onSelect, onCancel, onViewport, onFullPage, onComponent }) {
585
643
  }
586
644
  mode.current = "none";
587
645
  },
588
- [rect, placed, onSelect]
589
- );
590
- const setSize = useCallback3(
591
- (field, v) => {
592
- const n = parseInt(v, 10);
593
- if (isNaN(n) || n < MIN_SIZE || !rect) return;
594
- if (field === "w") {
595
- setRect({ ...rect, w: n, h: ratio > 0 ? n / ratio : rect.h });
596
- } else {
597
- setRect({ ...rect, h: n, w: ratio > 0 ? n * ratio : rect.w });
598
- }
599
- },
600
- [rect, ratio]
601
- );
602
- const setPos = useCallback3(
603
- (field, v) => {
604
- const n = parseInt(v, 10);
605
- if (isNaN(n) || !rect) return;
606
- setRect({ ...rect, [field]: n });
607
- },
608
- [rect]
646
+ [onRectChange, onSelect, rect]
609
647
  );
610
- const savePreset = useCallback3(() => {
611
- if (!rect) return;
612
- const label = `${Math.round(rect.w)}\xD7${Math.round(rect.h)}`;
613
- const next = [
614
- ...presets.filter((p) => p.label !== label),
615
- { label, rect: { ...rect } }
616
- ];
617
- setPresets(next);
618
- try {
619
- localStorage.setItem("ab-area-presets", JSON.stringify(next));
620
- } catch {
621
- }
622
- setSavedOpen(false);
623
- }, [rect, presets]);
624
- const loadPreset = useCallback3((p) => {
625
- setRect({ ...p.rect });
626
- setPlaced(true);
627
- setSavedOpen(false);
628
- }, []);
629
- const activeCursor = mode.current === "moving" ? "move" : mode.current === "resizing" ? "nwse-resize" : mode.current === "drawing" ? "crosshair" : cursor;
630
- const panelAbove = rect && rect.y + rect.h + PANEL_HEIGHT_EST > window.innerHeight;
631
648
  const hasRect = rect && rect.w > 0 && rect.h > 0;
632
649
  return /* @__PURE__ */ jsxs2(
633
650
  "div",
@@ -639,11 +656,11 @@ function Selector({ onSelect, onCancel, onViewport, onFullPage, onComponent }) {
639
656
  style: {
640
657
  position: "fixed",
641
658
  inset: 0,
642
- zIndex: 2147483647,
643
- cursor: activeCursor
659
+ zIndex: 2147483646,
660
+ cursor: CAMERA_CURSOR
644
661
  },
645
662
  children: [
646
- snappedX && /* @__PURE__ */ jsx2(
663
+ snappedX && /* @__PURE__ */ jsx3(
647
664
  "div",
648
665
  {
649
666
  style: {
@@ -652,35 +669,85 @@ function Selector({ onSelect, onCancel, onViewport, onFullPage, onComponent }) {
652
669
  top: 0,
653
670
  bottom: 0,
654
671
  width: 0,
655
- borderLeft: "1px solid rgba(147, 130, 220, 0.5)",
672
+ borderLeft: "1px solid rgba(56, 189, 248, 0.55)",
656
673
  pointerEvents: "none",
657
674
  zIndex: 2
658
675
  }
659
676
  }
660
677
  ),
661
- /* @__PURE__ */ jsx2(
662
- "div",
663
- {
664
- style: {
665
- position: "absolute",
666
- inset: 0,
667
- background: "rgba(0, 0, 0, 0.5)",
668
- pointerEvents: "none",
669
- ...hasRect ? {
670
- clipPath: `polygon(
671
- 0% 0%, 0% 100%, 100% 100%, 100% 0%, 0% 0%,
672
- ${rect.x}px ${rect.y}px,
673
- ${rect.x}px ${rect.y + rect.h}px,
674
- ${rect.x + rect.w}px ${rect.y + rect.h}px,
675
- ${rect.x + rect.w}px ${rect.y}px,
676
- ${rect.x}px ${rect.y}px
677
- )`
678
- } : {}
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
+ }
679
691
  }
680
- }
681
- ),
682
- hasRect && /* @__PURE__ */ jsxs2(Fragment, { children: [
683
- /* @__PURE__ */ jsx2(
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
+ ),
750
+ /* @__PURE__ */ jsx3(
684
751
  "div",
685
752
  {
686
753
  style: {
@@ -690,17 +757,19 @@ function Selector({ onSelect, onCancel, onViewport, onFullPage, onComponent }) {
690
757
  width: rect.w,
691
758
  height: rect.h,
692
759
  border: "1.5px dashed rgba(255, 255, 255, 0.45)",
693
- pointerEvents: "none"
760
+ borderRadius: 12,
761
+ pointerEvents: "none",
762
+ boxShadow: "0 0 0 1px rgba(125, 211, 252, 0.38)"
694
763
  }
695
764
  }
696
765
  ),
697
- placed && [1, 2].map((i) => /* @__PURE__ */ jsxs2(React2.Fragment, { children: [
698
- /* @__PURE__ */ jsx2(
766
+ [1, 2].map((line) => /* @__PURE__ */ jsxs2(React3.Fragment, { children: [
767
+ /* @__PURE__ */ jsx3(
699
768
  "div",
700
769
  {
701
770
  style: {
702
771
  position: "absolute",
703
- left: rect.x + rect.w * i / 3,
772
+ left: rect.x + rect.w * line / 3,
704
773
  top: rect.y,
705
774
  width: 0,
706
775
  height: rect.h,
@@ -709,13 +778,13 @@ function Selector({ onSelect, onCancel, onViewport, onFullPage, onComponent }) {
709
778
  }
710
779
  }
711
780
  ),
712
- /* @__PURE__ */ jsx2(
781
+ /* @__PURE__ */ jsx3(
713
782
  "div",
714
783
  {
715
784
  style: {
716
785
  position: "absolute",
717
786
  left: rect.x,
718
- top: rect.y + rect.h * i / 3,
787
+ top: rect.y + rect.h * line / 3,
719
788
  width: rect.w,
720
789
  height: 0,
721
790
  borderTop: "1px dashed rgba(255, 255, 255, 0.18)",
@@ -723,13 +792,13 @@ function Selector({ onSelect, onCancel, onViewport, onFullPage, onComponent }) {
723
792
  }
724
793
  }
725
794
  )
726
- ] }, i)),
727
- placed && [
795
+ ] }, line)),
796
+ [
728
797
  [rect.x, rect.y],
729
798
  [rect.x + rect.w, rect.y],
730
799
  [rect.x, rect.y + rect.h],
731
800
  [rect.x + rect.w, rect.y + rect.h]
732
- ].map(([cx, cy], i) => /* @__PURE__ */ jsx2(
801
+ ].map(([cx, cy], index) => /* @__PURE__ */ jsx3(
733
802
  "div",
734
803
  {
735
804
  style: {
@@ -744,382 +813,500 @@ function Selector({ onSelect, onCancel, onViewport, onFullPage, onComponent }) {
744
813
  pointerEvents: "none"
745
814
  }
746
815
  },
747
- i
748
- )),
749
- placed && /* @__PURE__ */ jsxs2(
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(
750
959
  "div",
751
960
  {
752
- ref: panelRef,
753
- onMouseDown: (e) => e.stopPropagation(),
754
961
  style: {
755
- position: "absolute",
756
- left: rect.x + rect.w / 2,
757
- ...panelAbove ? { bottom: window.innerHeight - rect.y + 16 } : { top: rect.y + rect.h + 16 },
758
- transform: "translateX(-50%)",
759
- background: "rgba(32, 32, 36, 0.92)",
760
- backdropFilter: "blur(20px)",
761
- WebkitBackdropFilter: "blur(20px)",
762
- border: "1px solid rgba(255, 255, 255, 0.1)",
763
- borderRadius: 10,
764
- padding: "10px 14px",
765
962
  display: "flex",
766
- flexDirection: "column",
767
- gap: 6,
768
- minWidth: 0,
769
- fontFamily: "system-ui, -apple-system, sans-serif",
770
- color: "rgba(255, 255, 255, 0.9)",
771
- boxShadow: "0 8px 32px rgba(0, 0, 0, 0.4)",
772
- zIndex: 1
963
+ alignItems: "center",
964
+ gap: 8,
965
+ flexWrap: "wrap",
966
+ justifyContent: "center"
773
967
  },
774
968
  children: [
775
- /* @__PURE__ */ jsxs2(Row, { label: "Size", children: [
776
- /* @__PURE__ */ jsx2(
777
- NumInput,
778
- {
779
- value: Math.round(rect.w),
780
- onChange: (v) => setSize("w", v)
781
- }
782
- ),
783
- /* @__PURE__ */ jsx2(Sep, { children: "\xD7" }),
784
- /* @__PURE__ */ jsx2(
785
- NumInput,
786
- {
787
- value: Math.round(rect.h),
788
- onChange: (v) => setSize("h", v)
789
- }
790
- ),
791
- /* @__PURE__ */ jsx2(Unit, { children: "px" })
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) })
792
978
  ] }),
793
- /* @__PURE__ */ jsxs2(Row, { label: "Position", children: [
794
- /* @__PURE__ */ jsx2(
795
- NumInput,
979
+ /* @__PURE__ */ jsxs3("div", { style: { position: "relative" }, children: [
980
+ /* @__PURE__ */ jsxs3(
981
+ DropButton,
796
982
  {
797
- value: Math.round(rect.x),
798
- onChange: (v) => setPos("x", v)
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
+ ]
799
992
  }
800
993
  ),
801
- /* @__PURE__ */ jsx2(Sep, {}),
802
- /* @__PURE__ */ jsx2(
803
- NumInput,
994
+ aspectOpen && /* @__PURE__ */ jsx4(DropMenu, { children: AREA_ASPECT_RATIOS.map((item) => /* @__PURE__ */ jsx4(
995
+ DropItem,
804
996
  {
805
- value: Math.round(rect.y),
806
- onChange: (v) => setPos("y", v)
807
- }
808
- ),
809
- /* @__PURE__ */ jsx2(Unit, { children: "px" })
997
+ active: item.label === areaAspect,
998
+ onClick: () => {
999
+ onAreaAspectChange(item.label);
1000
+ setAspectOpen(false);
1001
+ },
1002
+ children: item.label
1003
+ },
1004
+ item.label
1005
+ )) })
810
1006
  ] }),
811
- /* @__PURE__ */ jsx2(
812
- "div",
813
- {
814
- style: {
815
- height: 1,
816
- background: "rgba(255, 255, 255, 0.08)",
817
- margin: "1px 0"
818
- }
819
- }
820
- ),
821
- /* @__PURE__ */ jsxs2(
822
- "div",
823
- {
824
- style: { display: "flex", alignItems: "center", gap: 12 },
825
- children: [
826
- /* @__PURE__ */ jsxs2("div", { style: { position: "relative" }, children: [
827
- /* @__PURE__ */ jsxs2(
828
- DropBtn,
829
- {
830
- active: aspect !== "Free",
831
- onClick: () => {
832
- setAspectOpen(!aspectOpen);
833
- setSavedOpen(false);
834
- },
835
- children: [
836
- /* @__PURE__ */ jsx2(AspectIcon, {}),
837
- aspect === "Free" ? "Free" : aspect,
838
- /* @__PURE__ */ jsx2(Chevron, {})
839
- ]
840
- }
841
- ),
842
- aspectOpen && /* @__PURE__ */ jsx2(DropMenu, { children: ASPECT_RATIOS.map((ar) => /* @__PURE__ */ jsx2(
843
- DropItem,
844
- {
845
- active: ar.label === aspect,
846
- onClick: () => {
847
- setAspect(ar.label);
848
- setAspectOpen(false);
849
- if (ar.value > 0 && rect) {
850
- setRect({ ...rect, h: rect.w / ar.value });
851
- }
852
- },
853
- children: ar.label
854
- },
855
- ar.label
856
- )) })
857
- ] }),
858
- /* @__PURE__ */ jsxs2("div", { style: { position: "relative" }, children: [
859
- /* @__PURE__ */ jsxs2(
860
- DropBtn,
861
- {
862
- onClick: () => {
863
- setSavedOpen(!savedOpen);
864
- setAspectOpen(false);
865
- },
866
- children: [
867
- /* @__PURE__ */ jsx2(SavedIcon, {}),
868
- "Saved",
869
- /* @__PURE__ */ jsx2(Chevron, {})
870
- ]
871
- }
872
- ),
873
- savedOpen && /* @__PURE__ */ jsxs2(DropMenu, { children: [
874
- /* @__PURE__ */ jsx2(
875
- DropItem,
876
- {
877
- accent: true,
878
- onClick: savePreset,
879
- children: "Save current"
880
- }
881
- ),
882
- presets.length > 0 && /* @__PURE__ */ jsx2(
883
- "div",
884
- {
885
- style: {
886
- height: 1,
887
- background: "rgba(255,255,255,0.08)",
888
- margin: "4px 0"
889
- }
890
- }
891
- ),
892
- presets.map((p) => /* @__PURE__ */ jsx2(
893
- DropItem,
894
- {
895
- onClick: () => loadPreset(p),
896
- children: p.label
897
- },
898
- p.label
899
- )),
900
- presets.length === 0 && /* @__PURE__ */ jsx2(
901
- "div",
902
- {
903
- style: {
904
- padding: "6px 12px",
905
- color: "rgba(255,255,255,0.3)",
906
- fontSize: 12
907
- },
908
- children: "No saved areas"
909
- }
910
- )
911
- ] })
912
- ] })
913
- ]
914
- }
915
- ),
916
- (onViewport || onFullPage || onComponent) && /* @__PURE__ */ jsxs2(Fragment, { children: [
917
- /* @__PURE__ */ jsx2(
918
- "div",
919
- {
920
- style: {
921
- height: 1,
922
- background: "rgba(255, 255, 255, 0.08)",
923
- margin: "1px 0"
924
- }
925
- }
926
- ),
927
- /* @__PURE__ */ jsxs2(
928
- "div",
1007
+ /* @__PURE__ */ jsxs3("div", { style: { position: "relative" }, children: [
1008
+ /* @__PURE__ */ jsxs3(
1009
+ DropButton,
929
1010
  {
930
- style: { display: "flex", alignItems: "center", gap: 4 },
1011
+ onClick: () => {
1012
+ setAspectOpen(false);
1013
+ setSavedOpen((prev) => !prev);
1014
+ },
931
1015
  children: [
932
- onViewport && /* @__PURE__ */ jsxs2(ModeBtn, { onClick: onViewport, children: [
933
- /* @__PURE__ */ jsx2(ViewportIcon, {}),
934
- "Viewport"
935
- ] }),
936
- onFullPage && /* @__PURE__ */ jsxs2(ModeBtn, { onClick: onFullPage, children: [
937
- /* @__PURE__ */ jsx2(FullPageIcon, {}),
938
- "Full Page"
939
- ] }),
940
- onComponent && /* @__PURE__ */ jsxs2(ModeBtn, { onClick: onComponent, children: [
941
- /* @__PURE__ */ jsx2(ComponentIcon, {}),
942
- "Component"
943
- ] })
1016
+ /* @__PURE__ */ jsx4(Save, { size: 14, strokeWidth: 1.8 }),
1017
+ "Saved",
1018
+ /* @__PURE__ */ jsx4(ChevronDown, { size: 14, strokeWidth: 1.8 })
944
1019
  ]
945
1020
  }
946
- )
947
- ] }),
948
- /* @__PURE__ */ jsx2(
949
- "div",
950
- {
951
- style: {
952
- display: "flex",
953
- alignItems: "center",
954
- gap: 6
955
- },
956
- children: /* @__PURE__ */ jsx2(
957
- MagnetToggle,
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,
958
1027
  {
959
- enabled: magnet,
960
- onToggle: () => {
961
- const next = !magnet;
962
- setMagnet(next);
963
- try {
964
- localStorage.setItem("ab-magnet", String(next));
965
- } catch {
966
- }
967
- }
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"
968
1045
  }
969
1046
  )
970
- }
971
- ),
972
- /* @__PURE__ */ jsx2(
973
- "div",
974
- {
975
- style: {
976
- fontSize: 10,
977
- color: "rgba(255, 255, 255, 0.25)",
978
- textAlign: "center"
979
- },
980
- children: "Click area or Enter to capture \xB7 Esc to cancel"
981
- }
982
- )
983
- ]
984
- }
985
- ),
986
- !placed && /* @__PURE__ */ jsxs2(
987
- "div",
988
- {
989
- style: {
990
- position: "absolute",
991
- left: rect.x + rect.w / 2,
992
- top: rect.y + rect.h + 8,
993
- transform: "translateX(-50%)",
994
- background: "rgba(24, 24, 27, 0.9)",
995
- color: "rgba(255, 255, 255, 0.9)",
996
- fontSize: 11,
997
- fontFamily: "system-ui, -apple-system, monospace",
998
- padding: "2px 8px",
999
- borderRadius: 4,
1000
- whiteSpace: "nowrap",
1001
- pointerEvents: "none"
1002
- },
1003
- children: [
1004
- Math.round(rect.w),
1005
- " \xD7 ",
1006
- Math.round(rect.h)
1047
+ ] })
1048
+ ] })
1007
1049
  ]
1008
1050
  }
1009
1051
  )
1010
- ] }),
1011
- !rect && /* @__PURE__ */ jsxs2(
1012
- "div",
1013
- {
1014
- style: {
1015
- position: "absolute",
1016
- top: "50%",
1017
- left: "50%",
1018
- transform: "translate(-50%, -50%)",
1019
- display: "flex",
1020
- flexDirection: "column",
1021
- alignItems: "center",
1022
- gap: 16,
1023
- fontFamily: "system-ui, -apple-system, sans-serif"
1024
- },
1025
- children: [
1026
- /* @__PURE__ */ jsx2(
1027
- "div",
1028
- {
1029
- style: {
1030
- color: "rgba(255, 255, 255, 0.7)",
1031
- fontSize: 14,
1032
- pointerEvents: "none",
1033
- textShadow: "0 1px 4px rgba(0, 0, 0, 0.5)"
1034
- },
1035
- children: "Drag to select an area \xB7 Esc to cancel"
1036
- }
1037
- ),
1038
- (onViewport || onFullPage || onComponent) && /* @__PURE__ */ jsxs2(
1039
- "div",
1040
- {
1041
- onMouseDown: (e) => e.stopPropagation(),
1042
- style: {
1043
- display: "flex",
1044
- alignItems: "center",
1045
- gap: 4,
1046
- background: "rgba(32, 32, 36, 0.92)",
1047
- backdropFilter: "blur(20px)",
1048
- WebkitBackdropFilter: "blur(20px)",
1049
- border: "1px solid rgba(255, 255, 255, 0.1)",
1050
- borderRadius: 8,
1051
- padding: "6px 8px"
1052
- },
1053
- children: [
1054
- onViewport && /* @__PURE__ */ jsxs2(ModeBtn, { onClick: onViewport, children: [
1055
- /* @__PURE__ */ jsx2(ViewportIcon, {}),
1056
- "Viewport"
1057
- ] }),
1058
- onFullPage && /* @__PURE__ */ jsxs2(ModeBtn, { onClick: onFullPage, children: [
1059
- /* @__PURE__ */ jsx2(FullPageIcon, {}),
1060
- "Full Page"
1061
- ] }),
1062
- onComponent && /* @__PURE__ */ jsxs2(ModeBtn, { onClick: onComponent, children: [
1063
- /* @__PURE__ */ jsx2(ComponentIcon, {}),
1064
- "Component"
1065
- ] })
1066
- ]
1067
- }
1068
- )
1069
- ]
1070
- }
1071
- )
1052
+ ] })
1072
1053
  ]
1073
1054
  }
1074
1055
  );
1075
1056
  }
1076
- function Row({
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,
1077
1072
  label,
1078
- children
1073
+ selected,
1074
+ onClick
1079
1075
  }) {
1080
- return /* @__PURE__ */ jsxs2("div", { style: { display: "flex", alignItems: "center", gap: 6 }, children: [
1081
- /* @__PURE__ */ jsx2(
1082
- "span",
1076
+ const [hovered, setHovered] = useState5(false);
1077
+ return /* @__PURE__ */ jsxs3("div", { style: { position: "relative" }, children: [
1078
+ hovered && /* @__PURE__ */ jsx4(
1079
+ "div",
1083
1080
  {
1084
1081
  style: {
1085
- width: 36,
1086
- fontSize: 11,
1087
- color: "rgba(255, 255, 255, 0.4)",
1088
- fontWeight: 400,
1089
- flexShrink: 0
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"
1090
1097
  },
1091
1098
  children: label
1092
1099
  }
1093
- ),
1094
- children
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(
1166
+ "div",
1167
+ {
1168
+ style: {
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",
1180
+ {
1181
+ style: {
1182
+ display: "flex",
1183
+ alignItems: "center",
1184
+ justifyContent: "space-between",
1185
+ gap: 12,
1186
+ cursor: "pointer"
1187
+ },
1188
+ children: [
1189
+ /* @__PURE__ */ jsxs3(
1190
+ "span",
1191
+ {
1192
+ style: {
1193
+ display: "flex",
1194
+ alignItems: "center",
1195
+ gap: 8,
1196
+ color: "rgba(255, 255, 255, 0.88)",
1197
+ fontSize: 13
1198
+ },
1199
+ children: [
1200
+ /* @__PURE__ */ jsx4(Magnet, { size: 15, strokeWidth: 1.8 }),
1201
+ "Magnet snap"
1202
+ ]
1203
+ }
1204
+ ),
1205
+ /* @__PURE__ */ jsx4(
1206
+ "button",
1207
+ {
1208
+ type: "button",
1209
+ onClick: () => onMagnetChange(!magnetEnabled),
1210
+ style: {
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"
1220
+ },
1221
+ children: /* @__PURE__ */ jsx4(
1222
+ "span",
1223
+ {
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"
1233
+ }
1234
+ }
1235
+ )
1236
+ }
1237
+ )
1238
+ ]
1239
+ }
1240
+ )
1241
+ ]
1242
+ }
1243
+ )
1095
1244
  ] });
1096
1245
  }
1097
- function Sep({ children }) {
1098
- return /* @__PURE__ */ jsx2(
1099
- "span",
1246
+ function CaptureButton({
1247
+ label,
1248
+ onClick
1249
+ }) {
1250
+ const [hovered, setHovered] = useState5(false);
1251
+ return /* @__PURE__ */ jsxs3(
1252
+ "button",
1100
1253
  {
1254
+ onClick,
1255
+ onMouseEnter: () => setHovered(true),
1256
+ onMouseLeave: () => setHovered(false),
1101
1257
  style: {
1102
- fontSize: 11,
1103
- color: "rgba(255, 255, 255, 0.35)",
1104
- width: 12,
1105
- 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",
1106
1270
  flexShrink: 0,
1107
- visibility: children ? "visible" : "hidden"
1271
+ display: "flex",
1272
+ alignItems: "center",
1273
+ gap: 8
1108
1274
  },
1109
- children: children || "\xD7"
1275
+ children: [
1276
+ /* @__PURE__ */ jsx4(Camera2, { size: 16, strokeWidth: 1.9 }),
1277
+ label
1278
+ ]
1110
1279
  }
1111
1280
  );
1112
1281
  }
1113
- function Unit({ children }) {
1114
- return /* @__PURE__ */ jsx2(
1115
- "span",
1282
+ function ControlGroup({
1283
+ label,
1284
+ children
1285
+ }) {
1286
+ return /* @__PURE__ */ jsxs3(
1287
+ "div",
1116
1288
  {
1117
1289
  style: {
1118
- fontSize: 10,
1119
- color: "rgba(255, 255, 255, 0.3)",
1120
- flexShrink: 0
1290
+ display: "flex",
1291
+ alignItems: "center",
1292
+ gap: 6,
1293
+ padding: "0 4px"
1121
1294
  },
1122
- 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
+ ]
1123
1310
  }
1124
1311
  );
1125
1312
  }
@@ -1127,12 +1314,14 @@ function NumInput({
1127
1314
  value,
1128
1315
  onChange
1129
1316
  }) {
1130
- const [editing, setEditing] = useState3(false);
1131
- const [text, setText] = useState3(String(value));
1132
- useEffect2(() => {
1133
- if (!editing) setText(String(value));
1134
- }, [value, editing]);
1135
- return /* @__PURE__ */ jsx2(
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(
1136
1325
  "input",
1137
1326
  {
1138
1327
  type: "text",
@@ -1147,14 +1336,16 @@ function NumInput({
1147
1336
  },
1148
1337
  onChange: (e) => setText(e.target.value),
1149
1338
  onKeyDown: (e) => {
1150
- if (e.key === "Enter") e.target.blur();
1339
+ if (e.key === "Enter") {
1340
+ e.target.blur();
1341
+ }
1151
1342
  },
1152
1343
  style: {
1153
- width: 52,
1154
- padding: "3px 6px",
1344
+ width: 54,
1345
+ padding: "4px 6px",
1155
1346
  background: "rgba(255, 255, 255, 0.07)",
1156
1347
  border: "1px solid rgba(255, 255, 255, 0.1)",
1157
- borderRadius: 4,
1348
+ borderRadius: 7,
1158
1349
  color: "rgba(255, 255, 255, 0.9)",
1159
1350
  fontSize: 12,
1160
1351
  fontFamily: "system-ui, -apple-system, sans-serif",
@@ -1164,46 +1355,62 @@ function NumInput({
1164
1355
  }
1165
1356
  );
1166
1357
  }
1167
- 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({
1168
1373
  children,
1169
1374
  onClick,
1170
1375
  active
1171
1376
  }) {
1172
- return /* @__PURE__ */ jsx2(
1377
+ return /* @__PURE__ */ jsx4(
1173
1378
  "button",
1174
1379
  {
1175
1380
  onClick,
1176
1381
  style: {
1177
1382
  display: "flex",
1178
1383
  alignItems: "center",
1179
- gap: 4,
1180
- background: "none",
1181
- border: "none",
1182
- 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)",
1183
1391
  cursor: "pointer",
1184
- fontSize: 11,
1185
- fontFamily: "inherit",
1186
- padding: "2px 0"
1392
+ fontSize: 12,
1393
+ fontFamily: "inherit"
1187
1394
  },
1188
1395
  children
1189
1396
  }
1190
1397
  );
1191
1398
  }
1192
1399
  function DropMenu({ children }) {
1193
- return /* @__PURE__ */ jsx2(
1400
+ return /* @__PURE__ */ jsx4(
1194
1401
  "div",
1195
1402
  {
1196
1403
  style: {
1197
1404
  position: "absolute",
1198
- bottom: "100%",
1199
- left: 0,
1200
- marginBottom: 4,
1201
- 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)",
1202
1410
  border: "1px solid rgba(255, 255, 255, 0.1)",
1203
- borderRadius: 8,
1411
+ borderRadius: 10,
1204
1412
  padding: "4px 0",
1205
- minWidth: 110,
1206
- boxShadow: "0 4px 16px rgba(0, 0, 0, 0.3)",
1413
+ boxShadow: "0 10px 30px rgba(0, 0, 0, 0.3)",
1207
1414
  backdropFilter: "blur(20px)",
1208
1415
  WebkitBackdropFilter: "blur(20px)"
1209
1416
  },
@@ -1217,17 +1424,17 @@ function DropItem({
1217
1424
  active,
1218
1425
  accent
1219
1426
  }) {
1220
- return /* @__PURE__ */ jsx2(
1427
+ return /* @__PURE__ */ jsx4(
1221
1428
  "button",
1222
1429
  {
1223
1430
  onClick,
1224
1431
  style: {
1225
1432
  display: "block",
1226
1433
  width: "100%",
1227
- padding: "6px 12px",
1228
- background: active ? "rgba(255, 255, 255, 0.08)" : "none",
1434
+ padding: "7px 12px",
1435
+ background: active ? "rgba(255, 255, 255, 0.08)" : "transparent",
1229
1436
  border: "none",
1230
- 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)",
1231
1438
  textAlign: "left",
1232
1439
  cursor: "pointer",
1233
1440
  fontSize: 13,
@@ -1237,266 +1444,56 @@ function DropItem({
1237
1444
  }
1238
1445
  );
1239
1446
  }
1240
- function Chevron() {
1241
- return /* @__PURE__ */ jsx2("svg", { width: "10", height: "10", viewBox: "0 0 10 10", fill: "none", children: /* @__PURE__ */ jsx2(
1242
- "path",
1243
- {
1244
- d: "M3 4l2 2.5L7 4",
1245
- stroke: "currentColor",
1246
- strokeWidth: "1.2"
1247
- }
1248
- ) });
1249
- }
1250
- function AspectIcon() {
1251
- return /* @__PURE__ */ jsxs2("svg", { width: "16", height: "16", viewBox: "0 0 16 16", fill: "none", children: [
1252
- /* @__PURE__ */ jsx2(
1253
- "rect",
1254
- {
1255
- x: "2",
1256
- y: "4",
1257
- width: "12",
1258
- height: "8",
1259
- rx: "1.5",
1260
- stroke: "currentColor",
1261
- strokeWidth: "1.2",
1262
- strokeDasharray: "2 1.5"
1263
- }
1264
- ),
1265
- /* @__PURE__ */ jsx2(
1266
- "line",
1267
- {
1268
- x1: "6.33",
1269
- y1: "4",
1270
- x2: "6.33",
1271
- y2: "12",
1272
- stroke: "currentColor",
1273
- strokeWidth: "0.8",
1274
- strokeDasharray: "1.5 1"
1275
- }
1276
- ),
1277
- /* @__PURE__ */ jsx2(
1278
- "line",
1279
- {
1280
- x1: "9.67",
1281
- y1: "4",
1282
- x2: "9.67",
1283
- y2: "12",
1284
- stroke: "currentColor",
1285
- strokeWidth: "0.8",
1286
- strokeDasharray: "1.5 1"
1287
- }
1288
- )
1289
- ] });
1290
- }
1291
- function SavedIcon() {
1292
- return /* @__PURE__ */ jsx2("svg", { width: "16", height: "16", viewBox: "0 0 16 16", fill: "none", children: /* @__PURE__ */ jsx2(
1293
- "rect",
1294
- {
1295
- x: "2",
1296
- y: "3",
1297
- width: "12",
1298
- height: "10",
1299
- rx: "1.5",
1300
- stroke: "currentColor",
1301
- strokeWidth: "1.2",
1302
- strokeDasharray: "2 1.5"
1303
- }
1304
- ) });
1305
- }
1306
- function ModeBtn({
1307
- children,
1308
- onClick
1309
- }) {
1310
- return /* @__PURE__ */ jsx2(
1311
- "button",
1447
+ function MenuDivider() {
1448
+ return /* @__PURE__ */ jsx4(
1449
+ "div",
1312
1450
  {
1313
- onClick,
1314
1451
  style: {
1315
- display: "flex",
1316
- alignItems: "center",
1317
- gap: 5,
1318
- background: "rgba(255, 255, 255, 0.06)",
1319
- border: "1px solid rgba(255, 255, 255, 0.08)",
1320
- borderRadius: 6,
1321
- color: "rgba(255, 255, 255, 0.55)",
1322
- cursor: "pointer",
1323
- fontSize: 11,
1324
- fontFamily: "inherit",
1325
- padding: "4px 8px",
1326
- transition: "background 0.1s, color 0.1s"
1327
- },
1328
- onMouseEnter: (e) => {
1329
- const btn = e.currentTarget;
1330
- btn.style.background = "rgba(255, 255, 255, 0.12)";
1331
- btn.style.color = "rgba(255, 255, 255, 0.85)";
1332
- },
1333
- onMouseLeave: (e) => {
1334
- const btn = e.currentTarget;
1335
- btn.style.background = "rgba(255, 255, 255, 0.06)";
1336
- btn.style.color = "rgba(255, 255, 255, 0.55)";
1337
- },
1338
- children
1452
+ height: 1,
1453
+ background: "rgba(255,255,255,0.08)",
1454
+ margin: "4px 0"
1455
+ }
1339
1456
  }
1340
1457
  );
1341
1458
  }
1342
- function MagnetToggle({
1343
- enabled,
1344
- onToggle
1345
- }) {
1346
- return /* @__PURE__ */ jsxs2(
1347
- "button",
1459
+ function Separator({ vertical = true }) {
1460
+ return /* @__PURE__ */ jsx4(
1461
+ "div",
1348
1462
  {
1349
- onClick: onToggle,
1350
1463
  style: {
1351
- display: "flex",
1352
- alignItems: "center",
1353
- gap: 5,
1354
- background: "none",
1355
- border: "none",
1356
- color: enabled ? "rgba(147, 130, 220, 0.9)" : "rgba(255, 255, 255, 0.35)",
1357
- cursor: "pointer",
1358
- fontSize: 11,
1359
- fontFamily: "inherit",
1360
- padding: "2px 0"
1361
- },
1362
- children: [
1363
- /* @__PURE__ */ jsx2(MagnetIcon, {}),
1364
- "Magnet"
1365
- ]
1464
+ width: vertical ? 1 : 24,
1465
+ height: vertical ? 24 : 1,
1466
+ background: "rgba(255,255,255,0.12)",
1467
+ flexShrink: 0
1468
+ }
1366
1469
  }
1367
1470
  );
1368
1471
  }
1369
- function MagnetIcon() {
1370
- return /* @__PURE__ */ jsxs2("svg", { width: "14", height: "14", viewBox: "0 0 16 16", fill: "none", children: [
1371
- /* @__PURE__ */ jsx2(
1372
- "path",
1373
- {
1374
- d: "M4 2v4a4 4 0 008 0V2",
1375
- stroke: "currentColor",
1376
- strokeWidth: "1.3",
1377
- strokeLinecap: "round"
1378
- }
1379
- ),
1380
- /* @__PURE__ */ jsx2("line", { x1: "4", y1: "2", x2: "4", y2: "5", stroke: "currentColor", strokeWidth: "2.5", strokeLinecap: "round" }),
1381
- /* @__PURE__ */ jsx2("line", { x1: "12", y1: "2", x2: "12", y2: "5", stroke: "currentColor", strokeWidth: "2.5", strokeLinecap: "round" })
1382
- ] });
1383
- }
1384
- function ViewportIcon() {
1385
- return /* @__PURE__ */ jsxs2("svg", { width: "14", height: "14", viewBox: "0 0 16 16", fill: "none", children: [
1386
- /* @__PURE__ */ jsx2(
1387
- "rect",
1388
- {
1389
- x: "1",
1390
- y: "2",
1391
- width: "14",
1392
- height: "11",
1393
- rx: "1.5",
1394
- stroke: "currentColor",
1395
- strokeWidth: "1.3"
1396
- }
1397
- ),
1398
- /* @__PURE__ */ jsx2(
1399
- "line",
1400
- {
1401
- x1: "1",
1402
- y1: "14",
1403
- x2: "15",
1404
- y2: "14",
1405
- stroke: "currentColor",
1406
- strokeWidth: "1.3",
1407
- strokeLinecap: "round"
1408
- }
1409
- )
1410
- ] });
1411
- }
1412
- function FullPageIcon() {
1413
- return /* @__PURE__ */ jsxs2("svg", { width: "14", height: "14", viewBox: "0 0 16 16", fill: "none", children: [
1414
- /* @__PURE__ */ jsx2(
1415
- "rect",
1416
- {
1417
- x: "3",
1418
- y: "1",
1419
- width: "10",
1420
- height: "14",
1421
- rx: "1.5",
1422
- stroke: "currentColor",
1423
- strokeWidth: "1.3"
1424
- }
1425
- ),
1426
- /* @__PURE__ */ jsx2(
1427
- "line",
1428
- {
1429
- x1: "5.5",
1430
- y1: "4",
1431
- x2: "10.5",
1432
- y2: "4",
1433
- stroke: "currentColor",
1434
- strokeWidth: "1",
1435
- strokeLinecap: "round"
1436
- }
1437
- ),
1438
- /* @__PURE__ */ jsx2(
1439
- "line",
1440
- {
1441
- x1: "5.5",
1442
- y1: "6.5",
1443
- x2: "10.5",
1444
- y2: "6.5",
1445
- stroke: "currentColor",
1446
- strokeWidth: "1",
1447
- strokeLinecap: "round"
1448
- }
1449
- ),
1450
- /* @__PURE__ */ jsx2(
1451
- "line",
1452
- {
1453
- x1: "5.5",
1454
- y1: "9",
1455
- x2: "10.5",
1456
- y2: "9",
1457
- stroke: "currentColor",
1458
- strokeWidth: "1",
1459
- strokeLinecap: "round"
1460
- }
1461
- )
1462
- ] });
1463
- }
1464
- function ComponentIcon() {
1465
- return /* @__PURE__ */ jsxs2("svg", { width: "14", height: "14", viewBox: "0 0 16 16", fill: "none", children: [
1466
- /* @__PURE__ */ jsx2(
1467
- "path",
1468
- {
1469
- d: "M3 2l2.5 10 2-3.5L11 11 3 2z",
1470
- stroke: "currentColor",
1471
- strokeWidth: "1.2",
1472
- strokeLinejoin: "round",
1473
- strokeLinecap: "round"
1474
- }
1475
- ),
1476
- /* @__PURE__ */ jsx2(
1477
- "rect",
1478
- {
1479
- x: "8",
1480
- y: "5",
1481
- width: "6.5",
1482
- height: "6.5",
1483
- rx: "1",
1484
- stroke: "currentColor",
1485
- strokeWidth: "1.1",
1486
- strokeDasharray: "2 1.5"
1487
- }
1488
- )
1489
- ] });
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
+ };
1490
1487
  }
1491
1488
 
1492
1489
  // src/overlay/ui/inspector.tsx
1493
- import { useEffect as useEffect3, useRef as useRef3, useCallback as useCallback4, useState as useState4 } from "react";
1494
- import { Fragment as Fragment2, jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
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";
1495
1492
  function Inspector({ onSelect, onCancel }) {
1496
- const [highlight, setHighlight] = useState4(null);
1493
+ const [highlight, setHighlight] = useState6(null);
1497
1494
  const hoveredEl = useRef3(null);
1498
1495
  const styleEl = useRef3(null);
1499
- useEffect3(() => {
1496
+ useEffect5(() => {
1500
1497
  const style = document.createElement("style");
1501
1498
  style.setAttribute("data-afterbefore", "true");
1502
1499
  style.textContent = "*, *::before, *::after { cursor: crosshair !important; }";
@@ -1553,7 +1550,7 @@ function Inspector({ onSelect, onCancel }) {
1553
1550
  },
1554
1551
  [onCancel]
1555
1552
  );
1556
- useEffect3(() => {
1553
+ useEffect5(() => {
1557
1554
  document.addEventListener("mousemove", handleMouseMove, true);
1558
1555
  document.addEventListener("click", handleClick, true);
1559
1556
  document.addEventListener("keydown", handleKeyDown);
@@ -1563,9 +1560,9 @@ function Inspector({ onSelect, onCancel }) {
1563
1560
  document.removeEventListener("keydown", handleKeyDown);
1564
1561
  };
1565
1562
  }, [handleMouseMove, handleClick, handleKeyDown]);
1566
- return /* @__PURE__ */ jsxs3("div", { "data-afterbefore": "true", style: { position: "fixed", inset: 0, zIndex: 2147483646, pointerEvents: "none" }, children: [
1567
- highlight && /* @__PURE__ */ jsxs3(Fragment2, { children: [
1568
- /* @__PURE__ */ jsx3(
1563
+ return /* @__PURE__ */ jsxs4("div", { "data-afterbefore": "true", style: { position: "fixed", inset: 0, zIndex: 2147483646, pointerEvents: "none" }, children: [
1564
+ highlight && /* @__PURE__ */ jsxs4(Fragment3, { children: [
1565
+ /* @__PURE__ */ jsx5(
1569
1566
  "div",
1570
1567
  {
1571
1568
  style: {
@@ -1581,7 +1578,7 @@ function Inspector({ onSelect, onCancel }) {
1581
1578
  }
1582
1579
  }
1583
1580
  ),
1584
- /* @__PURE__ */ jsx3(
1581
+ /* @__PURE__ */ jsx5(
1585
1582
  "div",
1586
1583
  {
1587
1584
  style: {
@@ -1602,7 +1599,7 @@ function Inspector({ onSelect, onCancel }) {
1602
1599
  }
1603
1600
  )
1604
1601
  ] }),
1605
- !highlight && /* @__PURE__ */ jsx3(
1602
+ !highlight && /* @__PURE__ */ jsx5(
1606
1603
  "div",
1607
1604
  {
1608
1605
  style: {
@@ -1626,18 +1623,18 @@ function Inspector({ onSelect, onCancel }) {
1626
1623
  }
1627
1624
 
1628
1625
  // src/overlay/ui/status.tsx
1629
- import { useState as useState5, useRef as useRef4, useEffect as useEffect4, useCallback as useCallback5 } from "react";
1630
- import { jsx as jsx4, jsxs as jsxs4 } from "react/jsx-runtime";
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";
1631
1628
  var PANEL_WIDTH = 220;
1632
1629
  function Status({ onReset, position, onClose }) {
1633
1630
  const panelRef = useRef4(null);
1634
- const [toast, setToast] = useState5(null);
1635
- const [pushing, setPushing] = useState5(false);
1631
+ const [toast, setToast] = useState7(null);
1632
+ const [pushing, setPushing] = useState7(false);
1636
1633
  const showToast = useCallback5((message, type) => {
1637
1634
  setToast({ message, type });
1638
1635
  setTimeout(() => setToast(null), 3e3);
1639
1636
  }, []);
1640
- useEffect4(() => {
1637
+ useEffect6(() => {
1641
1638
  const handler = (e) => {
1642
1639
  if (panelRef.current && !panelRef.current.contains(e.target)) {
1643
1640
  onClose();
@@ -1646,7 +1643,7 @@ function Status({ onReset, position, onClose }) {
1646
1643
  document.addEventListener("mousedown", handler);
1647
1644
  return () => document.removeEventListener("mousedown", handler);
1648
1645
  }, [onClose]);
1649
- useEffect4(() => {
1646
+ useEffect6(() => {
1650
1647
  const handler = (e) => {
1651
1648
  if (e.key === "Escape") onClose();
1652
1649
  };
@@ -1722,7 +1719,7 @@ function Status({ onReset, position, onClose }) {
1722
1719
  const onLeave = (e) => {
1723
1720
  e.currentTarget.style.background = "transparent";
1724
1721
  };
1725
- return /* @__PURE__ */ jsxs4(
1722
+ return /* @__PURE__ */ jsxs5(
1726
1723
  "div",
1727
1724
  {
1728
1725
  ref: panelRef,
@@ -1741,7 +1738,7 @@ function Status({ onReset, position, onClose }) {
1741
1738
  overflow: "hidden"
1742
1739
  },
1743
1740
  children: [
1744
- /* @__PURE__ */ jsx4(
1741
+ /* @__PURE__ */ jsx6(
1745
1742
  "div",
1746
1743
  {
1747
1744
  style: {
@@ -1754,8 +1751,8 @@ function Status({ onReset, position, onClose }) {
1754
1751
  children: "Before & After captured"
1755
1752
  }
1756
1753
  ),
1757
- /* @__PURE__ */ jsxs4("div", { style: { padding: "0 4px 4px" }, children: [
1758
- /* @__PURE__ */ jsxs4(
1754
+ /* @__PURE__ */ jsxs5("div", { style: { padding: "0 4px 4px" }, children: [
1755
+ /* @__PURE__ */ jsxs5(
1759
1756
  "button",
1760
1757
  {
1761
1758
  style: buttonStyle,
@@ -1763,12 +1760,12 @@ function Status({ onReset, position, onClose }) {
1763
1760
  onMouseEnter: onEnter,
1764
1761
  onMouseLeave: onLeave,
1765
1762
  children: [
1766
- /* @__PURE__ */ jsx4(FolderIcon, {}),
1763
+ /* @__PURE__ */ jsx6(FolderIcon, {}),
1767
1764
  "Open Folder"
1768
1765
  ]
1769
1766
  }
1770
1767
  ),
1771
- /* @__PURE__ */ jsxs4(
1768
+ /* @__PURE__ */ jsxs5(
1772
1769
  "button",
1773
1770
  {
1774
1771
  style: buttonStyle,
@@ -1776,12 +1773,12 @@ function Status({ onReset, position, onClose }) {
1776
1773
  onMouseEnter: onEnter,
1777
1774
  onMouseLeave: onLeave,
1778
1775
  children: [
1779
- /* @__PURE__ */ jsx4(CopyIcon, {}),
1776
+ /* @__PURE__ */ jsx6(CopyIcon, {}),
1780
1777
  "Copy Markdown"
1781
1778
  ]
1782
1779
  }
1783
1780
  ),
1784
- /* @__PURE__ */ jsxs4(
1781
+ /* @__PURE__ */ jsxs5(
1785
1782
  "button",
1786
1783
  {
1787
1784
  style: buttonStyle,
@@ -1790,12 +1787,12 @@ function Status({ onReset, position, onClose }) {
1790
1787
  onMouseEnter: onEnter,
1791
1788
  onMouseLeave: onLeave,
1792
1789
  children: [
1793
- /* @__PURE__ */ jsx4(PushIcon, {}),
1790
+ /* @__PURE__ */ jsx6(PushIcon, {}),
1794
1791
  pushing ? "Pushing..." : "Push to PR"
1795
1792
  ]
1796
1793
  }
1797
1794
  ),
1798
- /* @__PURE__ */ jsx4(
1795
+ /* @__PURE__ */ jsx6(
1799
1796
  "div",
1800
1797
  {
1801
1798
  style: {
@@ -1805,7 +1802,7 @@ function Status({ onReset, position, onClose }) {
1805
1802
  }
1806
1803
  }
1807
1804
  ),
1808
- /* @__PURE__ */ jsxs4(
1805
+ /* @__PURE__ */ jsxs5(
1809
1806
  "button",
1810
1807
  {
1811
1808
  style: { ...buttonStyle, color: "rgba(255,255,255,0.5)" },
@@ -1813,13 +1810,13 @@ function Status({ onReset, position, onClose }) {
1813
1810
  onMouseEnter: onEnter,
1814
1811
  onMouseLeave: onLeave,
1815
1812
  children: [
1816
- /* @__PURE__ */ jsx4(ResetIcon, {}),
1813
+ /* @__PURE__ */ jsx6(ResetIcon, {}),
1817
1814
  "Reset"
1818
1815
  ]
1819
1816
  }
1820
1817
  )
1821
1818
  ] }),
1822
- toast && /* @__PURE__ */ jsx4(
1819
+ toast && /* @__PURE__ */ jsx6(
1823
1820
  "div",
1824
1821
  {
1825
1822
  style: {
@@ -1845,14 +1842,14 @@ function Status({ onReset, position, onClose }) {
1845
1842
  );
1846
1843
  }
1847
1844
  function FolderIcon() {
1848
- return /* @__PURE__ */ jsx4(
1845
+ return /* @__PURE__ */ jsx6(
1849
1846
  "svg",
1850
1847
  {
1851
1848
  width: "14",
1852
1849
  height: "14",
1853
1850
  viewBox: "0 0 14 14",
1854
1851
  style: { color: "rgba(255,255,255,0.5)" },
1855
- children: /* @__PURE__ */ jsx4(
1852
+ children: /* @__PURE__ */ jsx6(
1856
1853
  "path",
1857
1854
  {
1858
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",
@@ -1865,7 +1862,7 @@ function FolderIcon() {
1865
1862
  );
1866
1863
  }
1867
1864
  function CopyIcon() {
1868
- return /* @__PURE__ */ jsxs4(
1865
+ return /* @__PURE__ */ jsxs5(
1869
1866
  "svg",
1870
1867
  {
1871
1868
  width: "14",
@@ -1873,7 +1870,7 @@ function CopyIcon() {
1873
1870
  viewBox: "0 0 14 14",
1874
1871
  style: { color: "rgba(255,255,255,0.5)" },
1875
1872
  children: [
1876
- /* @__PURE__ */ jsx4(
1873
+ /* @__PURE__ */ jsx6(
1877
1874
  "rect",
1878
1875
  {
1879
1876
  x: "4",
@@ -1886,7 +1883,7 @@ function CopyIcon() {
1886
1883
  strokeWidth: "1.3"
1887
1884
  }
1888
1885
  ),
1889
- /* @__PURE__ */ jsx4(
1886
+ /* @__PURE__ */ jsx6(
1890
1887
  "path",
1891
1888
  {
1892
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",
@@ -1900,14 +1897,14 @@ function CopyIcon() {
1900
1897
  );
1901
1898
  }
1902
1899
  function PushIcon() {
1903
- return /* @__PURE__ */ jsx4(
1900
+ return /* @__PURE__ */ jsx6(
1904
1901
  "svg",
1905
1902
  {
1906
1903
  width: "14",
1907
1904
  height: "14",
1908
1905
  viewBox: "0 0 14 14",
1909
1906
  style: { color: "rgba(255,255,255,0.5)" },
1910
- children: /* @__PURE__ */ jsx4(
1907
+ children: /* @__PURE__ */ jsx6(
1911
1908
  "path",
1912
1909
  {
1913
1910
  d: "M7 11V3m0 0L4 6m3-3l3 3",
@@ -1922,7 +1919,7 @@ function PushIcon() {
1922
1919
  );
1923
1920
  }
1924
1921
  function ResetIcon() {
1925
- return /* @__PURE__ */ jsxs4(
1922
+ return /* @__PURE__ */ jsxs5(
1926
1923
  "svg",
1927
1924
  {
1928
1925
  width: "14",
@@ -1930,7 +1927,7 @@ function ResetIcon() {
1930
1927
  viewBox: "0 0 14 14",
1931
1928
  style: { color: "rgba(255,255,255,0.4)" },
1932
1929
  children: [
1933
- /* @__PURE__ */ jsx4(
1930
+ /* @__PURE__ */ jsx6(
1934
1931
  "path",
1935
1932
  {
1936
1933
  d: "M2.5 7a4.5 4.5 0 118 2.5",
@@ -1940,7 +1937,7 @@ function ResetIcon() {
1940
1937
  strokeLinecap: "round"
1941
1938
  }
1942
1939
  ),
1943
- /* @__PURE__ */ jsx4(
1940
+ /* @__PURE__ */ jsx6(
1944
1941
  "path",
1945
1942
  {
1946
1943
  d: "M2.5 3v4h4",
@@ -1957,7 +1954,7 @@ function ResetIcon() {
1957
1954
  }
1958
1955
 
1959
1956
  // src/overlay/index.tsx
1960
- import { jsx as jsx5, jsxs as jsxs5 } from "react/jsx-runtime";
1957
+ import { jsx as jsx7, jsxs as jsxs6 } from "react/jsx-runtime";
1961
1958
  async function saveCapture(type, mode, dataUrl) {
1962
1959
  try {
1963
1960
  const res = await fetch("/__afterbefore/save", {
@@ -1975,12 +1972,31 @@ async function saveCapture(type, mode, dataUrl) {
1975
1972
  }
1976
1973
  function AfterBefore() {
1977
1974
  const { state, captureComplete, reset } = useOverlayState();
1978
- const [statusOpen, setStatusOpen] = useState6(false);
1979
- const [selectorActive, setSelectorActive] = useState6(false);
1980
- const [inspectorActive, setInspectorActive] = useState6(false);
1981
- const [loading, setLoading] = useState6(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([]);
1982
1985
  const iconPos = useRef5({ x: 24, y: 0 });
1983
- useEffect5(() => {
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(() => {
1984
2000
  if (state.phase === "ready") {
1985
2001
  const timer = setTimeout(() => {
1986
2002
  reset();
@@ -2000,7 +2016,9 @@ function AfterBefore() {
2000
2016
  if (state.phase === "ready") {
2001
2017
  setStatusOpen((prev) => !prev);
2002
2018
  } else {
2003
- setSelectorActive((prev) => !prev);
2019
+ setToolbarActive((prev) => !prev);
2020
+ setSelectorActive(false);
2021
+ setInspectorActive(false);
2004
2022
  setStatusOpen(false);
2005
2023
  }
2006
2024
  }, [state.phase, loading]);
@@ -2024,17 +2042,41 @@ function AfterBefore() {
2024
2042
  },
2025
2043
  [state.phase, captureComplete]
2026
2044
  );
2027
- const handleViewportCapture = useCallback6(() => {
2028
- setSelectorActive(false);
2029
- performCapture("viewport");
2030
- }, [performCapture]);
2031
- const handleFullPageCapture = useCallback6(() => {
2032
- setSelectorActive(false);
2033
- performCapture("fullpage");
2034
- }, [performCapture]);
2035
- const handleComponentMode = useCallback6(() => {
2045
+ const handleToolbarCapture = useCallback6(
2046
+ (mode) => {
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());
2062
+ setSelectorActive(true);
2063
+ setToolbarActive(true);
2064
+ } else if (mode === "viewport") {
2065
+ setToolbarActive(false);
2066
+ performCapture("viewport");
2067
+ } else if (mode === "fullpage") {
2068
+ setToolbarActive(false);
2069
+ performCapture("fullpage");
2070
+ } else if (mode === "component") {
2071
+ setToolbarActive(false);
2072
+ setInspectorActive(true);
2073
+ }
2074
+ },
2075
+ [areaRect, performCapture, selectorActive]
2076
+ );
2077
+ const handleToolbarCancel = useCallback6(() => {
2078
+ setToolbarActive(false);
2036
2079
  setSelectorActive(false);
2037
- setInspectorActive(true);
2038
2080
  }, []);
2039
2081
  const handleComponentSelect = useCallback6(
2040
2082
  (element) => {
@@ -2045,16 +2087,97 @@ function AfterBefore() {
2045
2087
  );
2046
2088
  const handleComponentCancel = useCallback6(() => {
2047
2089
  setInspectorActive(false);
2090
+ setToolbarActive(true);
2048
2091
  }, []);
2049
2092
  const handleAreaSelect = useCallback6(
2050
2093
  (area) => {
2051
2094
  setSelectorActive(false);
2095
+ setToolbarActive(false);
2052
2096
  performCapture("area", area);
2053
2097
  },
2054
2098
  [performCapture]
2055
2099
  );
2056
2100
  const handleAreaCancel = useCallback6(() => {
2057
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 });
2058
2181
  }, []);
2059
2182
  const handleReset = useCallback6(() => {
2060
2183
  reset();
@@ -2063,8 +2186,8 @@ function AfterBefore() {
2063
2186
  const handleStatusClose = useCallback6(() => {
2064
2187
  setStatusOpen(false);
2065
2188
  }, []);
2066
- return /* @__PURE__ */ jsxs5("div", { "data-afterbefore": "true", children: [
2067
- /* @__PURE__ */ jsx5(
2189
+ return /* @__PURE__ */ jsxs6("div", { "data-afterbefore": "true", children: [
2190
+ /* @__PURE__ */ jsx7(
2068
2191
  Icon,
2069
2192
  {
2070
2193
  phase: state.phase,
@@ -2073,18 +2196,40 @@ function AfterBefore() {
2073
2196
  onPositionChange: handlePositionChange
2074
2197
  }
2075
2198
  ),
2076
- selectorActive && /* @__PURE__ */ jsx5(
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(
2077
2221
  Selector,
2078
2222
  {
2223
+ rect: areaRect,
2224
+ aspect: areaAspect,
2225
+ onRectChange: handleAreaRectChange,
2079
2226
  onSelect: handleAreaSelect,
2080
2227
  onCancel: handleAreaCancel,
2081
- onViewport: handleViewportCapture,
2082
- onFullPage: handleFullPageCapture,
2083
- onComponent: handleComponentMode
2228
+ magnetEnabled
2084
2229
  }
2085
2230
  ),
2086
- inspectorActive && /* @__PURE__ */ jsx5(Inspector, { onSelect: handleComponentSelect, onCancel: handleComponentCancel }),
2087
- statusOpen && state.phase === "ready" && /* @__PURE__ */ jsx5(
2231
+ inspectorActive && /* @__PURE__ */ jsx7(Inspector, { onSelect: handleComponentSelect, onCancel: handleComponentCancel }),
2232
+ statusOpen && state.phase === "ready" && /* @__PURE__ */ jsx7(
2088
2233
  Status,
2089
2234
  {
2090
2235
  onReset: handleReset,