afterbefore 0.2.10 → 0.2.12

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,28 +1,19 @@
1
1
  "use client";
2
2
 
3
3
  // src/overlay/index.tsx
4
- import { useState as useState8, useCallback as useCallback6, useRef as useRef5, useEffect as useEffect7 } from "react";
4
+ import { useState as useState6, useCallback as useCallback5, useRef as useRef5, useEffect as useEffect5 } from "react";
5
5
 
6
6
  // src/overlay/state.ts
7
7
  import { useState, useCallback } from "react";
8
8
  var initialState = {
9
9
  phase: "idle",
10
- before: null,
11
- after: null
10
+ lastCapture: null
12
11
  };
13
12
  function useOverlayState() {
14
13
  const [state, setState] = useState(initialState);
15
14
  const captureComplete = useCallback(
16
15
  (result) => {
17
- setState((prev) => {
18
- if (prev.phase === "idle") {
19
- return { ...prev, phase: "captured-before", before: result };
20
- }
21
- if (prev.phase === "captured-before") {
22
- return { ...prev, phase: "ready", after: result };
23
- }
24
- return prev;
25
- });
16
+ setState({ phase: "ready", lastCapture: result });
26
17
  },
27
18
  []
28
19
  );
@@ -34,6 +25,20 @@ function useOverlayState() {
34
25
 
35
26
  // src/overlay/capture.ts
36
27
  import { snapdom } from "@zumer/snapdom";
28
+ var DEFAULT_FRAME_SETTINGS = {
29
+ enabled: false,
30
+ size: { w: 1920, h: 1080 },
31
+ bgType: "color",
32
+ bgColor: "#000000",
33
+ bgImage: null,
34
+ padding: 40
35
+ };
36
+ var FRAME_SIZE_PRESETS = [
37
+ { label: "1920 x 1080", w: 1920, h: 1080 },
38
+ { label: "1080 x 1080", w: 1080, h: 1080 },
39
+ { label: "1200 x 630", w: 1200, h: 630 },
40
+ { label: "1080 x 1920", w: 1080, h: 1920 }
41
+ ];
37
42
  var DEV_UI_SELECTORS = [
38
43
  // Afterbefore overlay
39
44
  "[data-afterbefore]",
@@ -55,18 +60,15 @@ async function toPngDataUrl(el, opts) {
55
60
  return img.src;
56
61
  }
57
62
  async function capture(options) {
58
- const { mode, area, element } = options;
63
+ const { mode, element } = options;
59
64
  if (mode === "viewport") {
60
65
  return captureViewport();
61
66
  }
62
67
  if (mode === "fullpage") {
63
68
  return captureFullPage();
64
69
  }
65
- if (mode === "area" && area) {
66
- return captureArea(area);
67
- }
68
70
  if (mode === "component" && element) {
69
- return captureComponent(element, options.frameOnBlack);
71
+ return captureComponent(element, options.frameSettings);
70
72
  }
71
73
  throw new Error(`Invalid capture mode: ${mode}`);
72
74
  }
@@ -120,39 +122,18 @@ async function captureFullPage() {
120
122
  window.scrollTo(0, scrollY);
121
123
  }
122
124
  }
123
- async function captureArea(area) {
124
- const fullDataUrl = await captureViewport();
125
- const img = await loadImage(fullDataUrl);
126
- const dpr = window.devicePixelRatio || 1;
127
- const canvas = document.createElement("canvas");
128
- canvas.width = area.width * dpr;
129
- canvas.height = area.height * dpr;
130
- const ctx = canvas.getContext("2d");
131
- ctx.drawImage(
132
- img,
133
- area.x * dpr,
134
- area.y * dpr,
135
- area.width * dpr,
136
- area.height * dpr,
137
- 0,
138
- 0,
139
- area.width * dpr,
140
- area.height * dpr
141
- );
142
- return canvas.toDataURL("image/png");
143
- }
144
- async function captureComponent(element, frameOnBlack) {
125
+ async function captureComponent(element, frameSettings) {
145
126
  const dataUrl = await toPngDataUrl(element);
146
- if (!frameOnBlack) {
127
+ if (!frameSettings?.enabled) {
147
128
  return dataUrl;
148
129
  }
149
130
  const img = await loadImage(dataUrl);
150
131
  const dpr = window.devicePixelRatio || 1;
151
- const FRAME_W = 1920;
152
- const FRAME_H = 1080;
132
+ const FRAME_W = frameSettings.size.w;
133
+ const FRAME_H = frameSettings.size.h;
134
+ const padding = frameSettings.padding;
153
135
  const compW = img.width / dpr;
154
136
  const compH = img.height / dpr;
155
- const padding = 40;
156
137
  const maxW = FRAME_W - padding * 2;
157
138
  const maxH = FRAME_H - padding * 2;
158
139
  const scale = Math.min(1, maxW / compW, maxH / compH);
@@ -162,13 +143,31 @@ async function captureComponent(element, frameOnBlack) {
162
143
  canvas.width = FRAME_W * dpr;
163
144
  canvas.height = FRAME_H * dpr;
164
145
  const ctx = canvas.getContext("2d");
165
- ctx.fillStyle = "#000000";
166
- ctx.fillRect(0, 0, canvas.width, canvas.height);
146
+ if (frameSettings.bgType === "image" && frameSettings.bgImage) {
147
+ try {
148
+ const bgImg = await loadImage(frameSettings.bgImage);
149
+ drawCover(ctx, bgImg, canvas.width, canvas.height);
150
+ } catch {
151
+ ctx.fillStyle = frameSettings.bgColor;
152
+ ctx.fillRect(0, 0, canvas.width, canvas.height);
153
+ }
154
+ } else {
155
+ ctx.fillStyle = frameSettings.bgColor;
156
+ ctx.fillRect(0, 0, canvas.width, canvas.height);
157
+ }
167
158
  const dx = (canvas.width - drawW) / 2;
168
159
  const dy = (canvas.height - drawH) / 2;
169
160
  ctx.drawImage(img, dx, dy, drawW, drawH);
170
161
  return canvas.toDataURL("image/png");
171
162
  }
163
+ function drawCover(ctx, img, cw, ch) {
164
+ const scale = Math.max(cw / img.width, ch / img.height);
165
+ const sw = cw / scale;
166
+ const sh = ch / scale;
167
+ const sx = (img.width - sw) / 2;
168
+ const sy = (img.height - sh) / 2;
169
+ ctx.drawImage(img, sx, sy, sw, sh, 0, 0, cw, ch);
170
+ }
172
171
  function loadImage(src) {
173
172
  return new Promise((resolve, reject) => {
174
173
  const img = new Image();
@@ -182,23 +181,25 @@ function loadImage(src) {
182
181
  import { useRef, useCallback as useCallback2, useEffect, useState as useState2 } from "react";
183
182
  import { Camera, Check, LoaderCircle } from "lucide-react";
184
183
  import { jsx, jsxs } from "react/jsx-runtime";
185
- var ICON_SIZE = 40;
184
+ var ICON_SIZE = 32;
186
185
  var EDGE_MARGIN = 24;
187
186
  function Icon({ phase, onClick, loading, onPositionChange }) {
188
187
  const ref = useRef(null);
189
- const [pos, setPos] = useState2({ x: EDGE_MARGIN, y: -1 });
188
+ const [pos, setPos] = useState2({ x: -1, y: -1 });
190
189
  const dragState = useRef(null);
191
190
  useEffect(() => {
192
191
  setPos((prev) => {
193
- if (prev.y === -1) {
194
- const y = window.innerHeight - ICON_SIZE - EDGE_MARGIN;
195
- return { x: prev.x, y };
192
+ if (prev.x === -1 || prev.y === -1) {
193
+ return {
194
+ x: window.innerWidth - ICON_SIZE - EDGE_MARGIN,
195
+ y: window.innerHeight - ICON_SIZE - EDGE_MARGIN
196
+ };
196
197
  }
197
198
  return prev;
198
199
  });
199
200
  }, []);
200
201
  useEffect(() => {
201
- if (pos.y !== -1) {
202
+ if (pos.x !== -1 && pos.y !== -1) {
202
203
  onPositionChange?.({ x: pos.x, y: pos.y });
203
204
  }
204
205
  }, [pos, onPositionChange]);
@@ -248,7 +249,7 @@ function Icon({ phase, onClick, loading, onPositionChange }) {
248
249
  window.removeEventListener("mouseup", handleMouseUp);
249
250
  };
250
251
  }, [onClick]);
251
- if (pos.y === -1) return null;
252
+ if (pos.x === -1 || pos.y === -1) return null;
252
253
  return /* @__PURE__ */ jsxs(
253
254
  "div",
254
255
  {
@@ -284,10 +285,6 @@ function Icon({ phase, onClick, loading, onPositionChange }) {
284
285
  {
285
286
  dangerouslySetInnerHTML: {
286
287
  __html: `
287
- @keyframes ab-pulse {
288
- 0%, 100% { transform: scale(1); opacity: 1; }
289
- 50% { transform: scale(1.08); opacity: 0.85; }
290
- }
291
288
  @keyframes ab-spin {
292
289
  0% { transform: rotate(0deg); }
293
290
  100% { transform: rotate(360deg); }
@@ -298,113 +295,20 @@ function Icon({ phase, onClick, loading, onPositionChange }) {
298
295
  loading ? /* @__PURE__ */ jsx(
299
296
  LoaderCircle,
300
297
  {
301
- size: 20,
298
+ size: 16,
302
299
  strokeWidth: 2,
303
300
  style: { animation: "ab-spin 0.8s linear infinite", color: "white" }
304
301
  }
305
- ) : phase === "ready" ? /* @__PURE__ */ jsx(Check, { size: 20, strokeWidth: 2.6, color: "#4ade80" }) : /* @__PURE__ */ jsxs(
306
- "div",
307
- {
308
- style: {
309
- position: "relative",
310
- display: "flex",
311
- alignItems: "center",
312
- justifyContent: "center",
313
- animation: phase === "captured-before" ? "ab-pulse 2s ease-in-out infinite" : "none"
314
- },
315
- children: [
316
- /* @__PURE__ */ jsx(Camera, { size: 20, strokeWidth: 1.9, color: "white" }),
317
- phase === "captured-before" && /* @__PURE__ */ jsx(
318
- "div",
319
- {
320
- style: {
321
- position: "absolute",
322
- top: -6,
323
- right: -8,
324
- width: 14,
325
- height: 14,
326
- borderRadius: "50%",
327
- background: "#3b82f6",
328
- color: "white",
329
- fontSize: "9px",
330
- fontWeight: 700,
331
- display: "flex",
332
- alignItems: "center",
333
- justifyContent: "center",
334
- lineHeight: 1,
335
- fontFamily: "system-ui, sans-serif"
336
- },
337
- children: "1"
338
- }
339
- )
340
- ]
341
- }
342
- )
302
+ ) : phase === "ready" ? /* @__PURE__ */ jsx(Check, { size: 16, strokeWidth: 2.6, color: "#4ade80" }) : /* @__PURE__ */ jsx(Camera, { size: 16, strokeWidth: 1.9, color: "white" })
343
303
  ]
344
304
  }
345
305
  );
346
306
  }
347
307
 
348
308
  // src/overlay/ui/preview.tsx
349
- import { useEffect as useEffect2, useState as useState3 } from "react";
350
309
  import { jsx as jsx2 } from "react/jsx-runtime";
351
- var DEFAULT_ASPECT_RATIO = 16 / 9;
352
- function getAreaPreviewRect() {
353
- const safeWidth = Math.max(320, window.innerWidth - 120);
354
- const safeHeight = Math.max(180, window.innerHeight - 220);
355
- const width = Math.min(window.innerWidth * 0.72, safeHeight * DEFAULT_ASPECT_RATIO, safeWidth);
356
- const height = width / DEFAULT_ASPECT_RATIO;
357
- return {
358
- x: (window.innerWidth - width) / 2,
359
- y: Math.max(40, (window.innerHeight - height) / 2 - 20),
360
- width,
361
- height
362
- };
363
- }
364
310
  var CAMERA_CURSOR = `url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='32' height='32' viewBox='0 0 24 24' fill='none' stroke='white' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M14.5 4h-5L7 7H4a2 2 0 0 0-2 2v9a2 2 0 0 0 2 2h16a2 2 0 0 0 2-2V9a2 2 0 0 0-2-2h-3l-2.5-3z'/%3E%3Ccircle cx='12' cy='13' r='3'/%3E%3C/svg%3E") 16 16, pointer`;
365
311
  function CapturePreview({ mode, onClick }) {
366
- const [areaRect, setAreaRect] = useState3(null);
367
- useEffect2(() => {
368
- if (mode !== "area") {
369
- return;
370
- }
371
- const syncRect = () => {
372
- setAreaRect(getAreaPreviewRect());
373
- };
374
- syncRect();
375
- window.addEventListener("resize", syncRect);
376
- return () => window.removeEventListener("resize", syncRect);
377
- }, [mode]);
378
- if (mode === "area" && areaRect) {
379
- return /* @__PURE__ */ jsx2(
380
- "div",
381
- {
382
- "data-afterbefore": "true",
383
- style: {
384
- position: "fixed",
385
- inset: 0,
386
- zIndex: 2147483645,
387
- pointerEvents: "none"
388
- },
389
- children: /* @__PURE__ */ jsx2(
390
- "div",
391
- {
392
- style: {
393
- position: "absolute",
394
- left: areaRect.x,
395
- top: areaRect.y,
396
- width: areaRect.width,
397
- height: areaRect.height,
398
- background: "rgba(125, 211, 252, 0.16)",
399
- border: "1.5px solid rgba(125, 211, 252, 0.95)",
400
- boxShadow: "0 0 0 1px rgba(191, 219, 254, 0.4), 0 0 32px rgba(56, 189, 248, 0.18)",
401
- borderRadius: 14
402
- }
403
- }
404
- )
405
- }
406
- );
407
- }
408
312
  if (mode === "viewport" || mode === "fullpage") {
409
313
  return /* @__PURE__ */ jsx2(
410
314
  "div",
@@ -416,518 +320,47 @@ function CapturePreview({ mode, onClick }) {
416
320
  inset: 0,
417
321
  zIndex: 2147483645,
418
322
  cursor: CAMERA_CURSOR,
419
- background: "rgba(125, 211, 252, 0.15)",
420
- boxShadow: "inset 0 0 0 1.5px rgba(125, 211, 252, 0.9)"
421
- },
422
- children: /* @__PURE__ */ jsx2(
423
- "div",
424
- {
425
- style: {
426
- position: "absolute",
427
- top: 36,
428
- left: "50%",
429
- transform: "translateX(-50%)",
430
- padding: "8px 14px",
431
- borderRadius: 999,
432
- background: "rgba(125, 211, 252, 0.16)",
433
- border: "1px solid rgba(125, 211, 252, 0.42)",
434
- color: "rgba(224, 242, 254, 0.96)",
435
- fontSize: 12,
436
- fontFamily: "system-ui, -apple-system, sans-serif",
437
- boxShadow: "0 10px 30px rgba(14, 116, 144, 0.18)",
438
- pointerEvents: "none"
439
- },
440
- children: mode === "fullpage" ? "Click to capture full page" : "Click to capture viewport"
441
- }
442
- )
323
+ background: "rgba(59, 130, 246, 0.15)",
324
+ boxShadow: "inset 0 0 0 2px rgba(59, 130, 246, 0.7)"
325
+ }
443
326
  }
444
327
  );
445
328
  }
446
- return /* @__PURE__ */ jsx2(
447
- "div",
448
- {
449
- "data-afterbefore": "true",
450
- style: {
451
- position: "fixed",
452
- inset: 0,
453
- zIndex: 2147483645,
454
- pointerEvents: "none"
455
- },
456
- children: /* @__PURE__ */ jsx2(
457
- "div",
458
- {
459
- style: {
460
- position: "absolute",
461
- top: 36,
462
- left: "50%",
463
- transform: "translateX(-50%)",
464
- padding: "8px 14px",
465
- borderRadius: 999,
466
- background: "rgba(125, 211, 252, 0.16)",
467
- border: "1px solid rgba(125, 211, 252, 0.42)",
468
- color: "rgba(224, 242, 254, 0.96)",
469
- fontSize: 12,
470
- fontFamily: "system-ui, -apple-system, sans-serif",
471
- boxShadow: "0 10px 30px rgba(14, 116, 144, 0.18)"
472
- },
473
- children: "Click Capture, then hover an element to preview it"
474
- }
475
- )
476
- }
477
- );
329
+ return null;
478
330
  }
479
331
 
480
332
  // src/overlay/ui/toolbar.tsx
481
- import { useEffect as useEffect4, useState as useState5 } from "react";
333
+ import { useEffect as useEffect2, useRef as useRef2, useState as useState3 } from "react";
482
334
  import {
483
- Camera as Camera2,
484
335
  ChevronDown,
485
- Crop,
486
- FileText,
336
+ Maximize,
337
+ FolderOpen,
487
338
  Frame,
488
- Magnet,
339
+ ImageIcon,
489
340
  Monitor,
490
341
  MousePointer2,
491
- Save,
492
- Settings2,
342
+ Palette,
343
+ Settings,
344
+ Trash2,
345
+ Upload,
493
346
  X
494
347
  } from "lucide-react";
495
-
496
- // src/overlay/ui/selector.tsx
497
- import React3, { useRef as useRef2, useCallback as useCallback3, useEffect as useEffect3, useMemo, useState as useState4 } from "react";
498
348
  import { Fragment, jsx as jsx3, jsxs as jsxs2 } from "react/jsx-runtime";
499
- var DEFAULT_AREA_ASPECT = "16:9";
500
- var AREA_ASPECT_RATIOS = [
501
- { label: "Free", value: 0 },
502
- { label: "16:9", value: 16 / 9 },
503
- { label: "4:3", value: 4 / 3 },
504
- { label: "1:1", value: 1 },
505
- { label: "3:2", value: 3 / 2 },
506
- { label: "21:9", value: 21 / 9 }
507
- ];
508
- var HANDLE_R = 6;
509
- var HANDLE_HIT = 16;
510
- var MIN_SIZE = 20;
511
- var SNAP_THRESHOLD = 8;
512
- var CAMERA_CURSOR2 = `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`;
513
- function createInitialAreaRect() {
514
- const safeWidth = Math.max(320, window.innerWidth - 96);
515
- const safeHeight = Math.max(180, window.innerHeight - 220);
516
- const w = Math.min(window.innerWidth * 0.72, safeHeight * (16 / 9), safeWidth);
517
- const h = w / (16 / 9);
518
- return {
519
- x: (window.innerWidth - w) / 2,
520
- y: Math.max(40, (window.innerHeight - h) / 2 - 20),
521
- w,
522
- h
523
- };
524
- }
525
- function getAspectRatio(label) {
526
- return AREA_ASPECT_RATIOS.find((item) => item.label === label)?.value ?? 0;
527
- }
528
- function Selector({
529
- rect,
530
- aspect,
531
- magnetEnabled,
532
- onRectChange,
533
- onSelect,
534
- onCancel
535
- }) {
536
- const [snappedX, setSnappedX] = useState4(false);
537
- const [cursor, setCursor] = useState4("crosshair");
538
- const mode = useRef2("none");
539
- const start = useRef2({ x: 0, y: 0 });
540
- const snapRef = useRef2({ x: 0, y: 0, w: 0, h: 0 });
541
- const corner = useRef2("br");
542
- const ratio = useMemo(() => getAspectRatio(aspect), [aspect]);
543
- const applySnap = useCallback3(
544
- (nextRect) => {
545
- if (!magnetEnabled) {
546
- setSnappedX(false);
547
- return nextRect;
548
- }
549
- const centerX = nextRect.x + nextRect.w / 2;
550
- const viewportCenterX = window.innerWidth / 2;
551
- if (Math.abs(centerX - viewportCenterX) < SNAP_THRESHOLD) {
552
- setSnappedX(true);
553
- return { ...nextRect, x: viewportCenterX - nextRect.w / 2 };
554
- }
555
- setSnappedX(false);
556
- return nextRect;
557
- },
558
- [magnetEnabled]
559
- );
560
- useEffect3(() => {
561
- const onKey = (e) => {
562
- if (e.key === "Escape") {
563
- onCancel();
564
- }
565
- };
566
- document.addEventListener("keydown", onKey);
567
- return () => document.removeEventListener("keydown", onKey);
568
- }, [onCancel]);
569
- const hitCorner = useCallback3((mx, my, currentRect) => {
570
- const corners = [
571
- ["tl", currentRect.x, currentRect.y],
572
- ["tr", currentRect.x + currentRect.w, currentRect.y],
573
- ["bl", currentRect.x, currentRect.y + currentRect.h],
574
- ["br", currentRect.x + currentRect.w, currentRect.y + currentRect.h]
575
- ];
576
- for (const [hitCorner2, cx, cy] of corners) {
577
- if (Math.abs(mx - cx) <= HANDLE_HIT && Math.abs(my - cy) <= HANDLE_HIT) {
578
- return hitCorner2;
579
- }
580
- }
581
- return null;
582
- }, []);
583
- const hitInside = useCallback3(
584
- (mx, my, currentRect) => mx >= currentRect.x && mx <= currentRect.x + currentRect.w && my >= currentRect.y && my <= currentRect.y + currentRect.h,
585
- []
586
- );
587
- const onDown = useCallback3(
588
- (e) => {
589
- e.preventDefault();
590
- const mx = e.clientX;
591
- const my = e.clientY;
592
- if (rect) {
593
- const activeCorner = hitCorner(mx, my, rect);
594
- if (activeCorner) {
595
- mode.current = "resizing";
596
- corner.current = activeCorner;
597
- start.current = { x: mx, y: my };
598
- snapRef.current = { ...rect };
599
- return;
600
- }
601
- if (hitInside(mx, my, rect)) {
602
- mode.current = "moving";
603
- start.current = { x: mx, y: my };
604
- snapRef.current = { ...rect };
605
- return;
606
- }
607
- }
608
- mode.current = "drawing";
609
- start.current = { x: mx, y: my };
610
- onRectChange({ x: mx, y: my, w: 0, h: 0 });
611
- },
612
- [hitCorner, hitInside, onRectChange, rect]
613
- );
614
- const onMove = useCallback3(
615
- (e) => {
616
- const mx = e.clientX;
617
- const my = e.clientY;
618
- if (mode.current === "none") {
619
- if (rect) {
620
- const activeCorner = hitCorner(mx, my, rect);
621
- if (activeCorner) {
622
- setCursor(
623
- activeCorner === "tl" || activeCorner === "br" ? "nwse-resize" : "nesw-resize"
624
- );
625
- } else if (hitInside(mx, my, rect)) {
626
- setCursor(CAMERA_CURSOR2);
627
- } else {
628
- setCursor("crosshair");
629
- }
630
- } else {
631
- setCursor("crosshair");
632
- }
633
- return;
634
- }
635
- if (mode.current === "drawing") {
636
- const sx = start.current.x;
637
- const sy = start.current.y;
638
- let x = Math.min(sx, mx);
639
- let y = Math.min(sy, my);
640
- let w = Math.abs(mx - sx);
641
- let h = Math.abs(my - sy);
642
- if (ratio > 0) {
643
- h = w / ratio;
644
- if (my < sy) {
645
- y = sy - h;
646
- }
647
- }
648
- onRectChange(applySnap({ x, y, w, h }));
649
- return;
650
- }
651
- if (mode.current === "moving") {
652
- const dx = mx - start.current.x;
653
- const dy = my - start.current.y;
654
- onRectChange(
655
- applySnap({
656
- ...snapRef.current,
657
- x: snapRef.current.x + dx,
658
- y: snapRef.current.y + dy
659
- })
660
- );
661
- return;
662
- }
663
- if (mode.current === "resizing") {
664
- const original = snapRef.current;
665
- const nextRect = { ...original };
666
- if (corner.current === "br") {
667
- nextRect.w = Math.max(MIN_SIZE, mx - original.x);
668
- nextRect.h = ratio > 0 ? nextRect.w / ratio : Math.max(MIN_SIZE, my - original.y);
669
- } else if (corner.current === "bl") {
670
- nextRect.w = Math.max(MIN_SIZE, original.x + original.w - mx);
671
- nextRect.x = original.x + original.w - nextRect.w;
672
- nextRect.h = ratio > 0 ? nextRect.w / ratio : Math.max(MIN_SIZE, my - original.y);
673
- } else if (corner.current === "tr") {
674
- nextRect.w = Math.max(MIN_SIZE, mx - original.x);
675
- nextRect.h = ratio > 0 ? nextRect.w / ratio : Math.max(MIN_SIZE, original.y + original.h - my);
676
- nextRect.y = original.y + original.h - nextRect.h;
677
- } else {
678
- nextRect.w = Math.max(MIN_SIZE, original.x + original.w - mx);
679
- nextRect.h = ratio > 0 ? nextRect.w / ratio : Math.max(MIN_SIZE, original.y + original.h - my);
680
- nextRect.x = original.x + original.w - nextRect.w;
681
- nextRect.y = original.y + original.h - nextRect.h;
682
- }
683
- onRectChange(applySnap(nextRect));
684
- }
685
- },
686
- [applySnap, hitCorner, hitInside, onRectChange, ratio, rect]
687
- );
688
- const onUp = useCallback3(
689
- (e) => {
690
- const previousMode = mode.current;
691
- if (previousMode === "drawing" && rect) {
692
- if (rect.w < MIN_SIZE || rect.h < MIN_SIZE) {
693
- onRectChange(null);
694
- }
695
- }
696
- if (previousMode === "moving" && rect) {
697
- const dx = Math.abs(e.clientX - start.current.x);
698
- const dy = Math.abs(e.clientY - start.current.y);
699
- if (dx < 3 && dy < 3) {
700
- onSelect({
701
- x: Math.round(rect.x),
702
- y: Math.round(rect.y),
703
- width: Math.round(rect.w),
704
- height: Math.round(rect.h)
705
- });
706
- }
707
- }
708
- mode.current = "none";
709
- },
710
- [onRectChange, onSelect, rect]
711
- );
712
- const hasRect = rect && rect.w > 0 && rect.h > 0;
713
- return /* @__PURE__ */ jsxs2(
714
- "div",
715
- {
716
- "data-afterbefore": "true",
717
- onMouseDown: onDown,
718
- onMouseMove: onMove,
719
- onMouseUp: onUp,
720
- style: {
721
- position: "fixed",
722
- inset: 0,
723
- zIndex: 2147483646,
724
- cursor
725
- },
726
- children: [
727
- snappedX && /* @__PURE__ */ jsx3(
728
- "div",
729
- {
730
- style: {
731
- position: "absolute",
732
- left: "50%",
733
- top: 0,
734
- bottom: 0,
735
- width: 0,
736
- borderLeft: "1px solid rgba(56, 189, 248, 0.55)",
737
- pointerEvents: "none",
738
- zIndex: 2
739
- }
740
- }
741
- ),
742
- hasRect ? /* @__PURE__ */ jsxs2(Fragment, { children: [
743
- /* @__PURE__ */ jsx3(
744
- "div",
745
- {
746
- style: {
747
- position: "absolute",
748
- left: 0,
749
- top: 0,
750
- width: "100%",
751
- height: rect.y,
752
- background: "rgba(0, 0, 0, 0.5)",
753
- pointerEvents: "none"
754
- }
755
- }
756
- ),
757
- /* @__PURE__ */ jsx3(
758
- "div",
759
- {
760
- style: {
761
- position: "absolute",
762
- left: 0,
763
- top: rect.y,
764
- width: rect.x,
765
- height: rect.h,
766
- background: "rgba(0, 0, 0, 0.5)",
767
- pointerEvents: "none"
768
- }
769
- }
770
- ),
771
- /* @__PURE__ */ jsx3(
772
- "div",
773
- {
774
- style: {
775
- position: "absolute",
776
- left: rect.x + rect.w,
777
- top: rect.y,
778
- width: `calc(100% - ${rect.x + rect.w}px)`,
779
- height: rect.h,
780
- background: "rgba(0, 0, 0, 0.5)",
781
- pointerEvents: "none"
782
- }
783
- }
784
- ),
785
- /* @__PURE__ */ jsx3(
786
- "div",
787
- {
788
- style: {
789
- position: "absolute",
790
- left: 0,
791
- top: rect.y + rect.h,
792
- width: "100%",
793
- height: `calc(100% - ${rect.y + rect.h}px)`,
794
- background: "rgba(0, 0, 0, 0.5)",
795
- pointerEvents: "none"
796
- }
797
- }
798
- ),
799
- /* @__PURE__ */ jsx3(
800
- "div",
801
- {
802
- style: {
803
- position: "absolute",
804
- left: rect.x,
805
- top: rect.y,
806
- width: rect.w,
807
- height: rect.h,
808
- background: "rgba(125, 211, 252, 0.08)",
809
- pointerEvents: "none",
810
- borderRadius: 12
811
- }
812
- }
813
- ),
814
- /* @__PURE__ */ jsx3(
815
- "div",
816
- {
817
- style: {
818
- position: "absolute",
819
- left: rect.x,
820
- top: rect.y,
821
- width: rect.w,
822
- height: rect.h,
823
- border: "1.5px dashed rgba(255, 255, 255, 0.45)",
824
- borderRadius: 12,
825
- pointerEvents: "none",
826
- boxShadow: "0 0 0 1px rgba(125, 211, 252, 0.38)"
827
- }
828
- }
829
- ),
830
- [1, 2].map((line) => /* @__PURE__ */ jsxs2(React3.Fragment, { children: [
831
- /* @__PURE__ */ jsx3(
832
- "div",
833
- {
834
- style: {
835
- position: "absolute",
836
- left: rect.x + rect.w * line / 3,
837
- top: rect.y,
838
- width: 0,
839
- height: rect.h,
840
- borderLeft: "1px dashed rgba(255, 255, 255, 0.18)",
841
- pointerEvents: "none"
842
- }
843
- }
844
- ),
845
- /* @__PURE__ */ jsx3(
846
- "div",
847
- {
848
- style: {
849
- position: "absolute",
850
- left: rect.x,
851
- top: rect.y + rect.h * line / 3,
852
- width: rect.w,
853
- height: 0,
854
- borderTop: "1px dashed rgba(255, 255, 255, 0.18)",
855
- pointerEvents: "none"
856
- }
857
- }
858
- )
859
- ] }, line)),
860
- [
861
- [rect.x, rect.y],
862
- [rect.x + rect.w, rect.y],
863
- [rect.x, rect.y + rect.h],
864
- [rect.x + rect.w, rect.y + rect.h]
865
- ].map(([cx, cy], index) => /* @__PURE__ */ jsx3(
866
- "div",
867
- {
868
- style: {
869
- position: "absolute",
870
- left: cx - HANDLE_R,
871
- top: cy - HANDLE_R,
872
- width: HANDLE_R * 2,
873
- height: HANDLE_R * 2,
874
- borderRadius: "50%",
875
- border: "2px solid rgba(255, 255, 255, 0.8)",
876
- background: "rgba(0, 0, 0, 0.25)",
877
- pointerEvents: "none"
878
- }
879
- },
880
- index
881
- ))
882
- ] }) : /* @__PURE__ */ jsx3(
883
- "div",
884
- {
885
- style: {
886
- position: "absolute",
887
- inset: 0,
888
- background: "rgba(0, 0, 0, 0.5)",
889
- pointerEvents: "none"
890
- }
891
- }
892
- )
893
- ]
894
- }
895
- );
896
- }
897
-
898
- // src/overlay/ui/toolbar.tsx
899
- import { Fragment as Fragment2, jsx as jsx4, jsxs as jsxs3 } from "react/jsx-runtime";
900
349
  var MODES = [
901
- { mode: "area", label: "Capture Selected Area", icon: Crop },
350
+ { mode: "component", label: "Capture Component", icon: MousePointer2 },
902
351
  { mode: "viewport", label: "Capture Viewport", icon: Monitor },
903
- { mode: "fullpage", label: "Capture Full Page", icon: FileText },
904
- { mode: "component", label: "Capture Component", icon: MousePointer2 }
352
+ { mode: "fullpage", label: "Capture Full Page", icon: Maximize }
905
353
  ];
906
354
  function Toolbar({
907
355
  selectedMode,
908
356
  onModeChange,
909
357
  onCapture,
910
358
  onCancel,
911
- magnetEnabled,
912
- onMagnetChange,
913
- areaSelectionActive,
914
- areaRect,
915
- areaAspect,
916
- areaPresets,
917
- onAreaSizeChange,
918
- onAreaPositionChange,
919
- onAreaAspectChange,
920
- onAreaSavePreset,
921
- onAreaLoadPreset,
922
- frameOnBlackEnabled,
923
- onFrameOnBlackChange
359
+ frameSettings,
360
+ onFrameSettingsChange
924
361
  }) {
925
- const [settingsOpen, setSettingsOpen] = useState5(false);
926
- const [aspectOpen, setAspectOpen] = useState5(false);
927
- const [savedOpen, setSavedOpen] = useState5(false);
928
- const showAreaControls = selectedMode === "area" && areaSelectionActive && areaRect !== null;
929
- const activeAreaRect = showAreaControls ? areaRect : null;
930
- useEffect4(() => {
362
+ const [settingsOpen, setSettingsOpen] = useState3(false);
363
+ useEffect2(() => {
931
364
  const onKey = (e) => {
932
365
  if (e.target?.tagName === "INPUT") {
933
366
  if (e.key === "Escape") {
@@ -940,14 +373,6 @@ function Toolbar({
940
373
  setSettingsOpen(false);
941
374
  return;
942
375
  }
943
- if (aspectOpen) {
944
- setAspectOpen(false);
945
- return;
946
- }
947
- if (savedOpen) {
948
- setSavedOpen(false);
949
- return;
950
- }
951
376
  onCancel();
952
377
  } else if (e.key === "Enter") {
953
378
  onCapture(selectedMode);
@@ -955,8 +380,8 @@ function Toolbar({
955
380
  };
956
381
  document.addEventListener("keydown", onKey);
957
382
  return () => document.removeEventListener("keydown", onKey);
958
- }, [aspectOpen, onCancel, onCapture, savedOpen, selectedMode, settingsOpen]);
959
- return /* @__PURE__ */ jsxs3(
383
+ }, [onCancel, onCapture, selectedMode, settingsOpen]);
384
+ return /* @__PURE__ */ jsx3(
960
385
  "div",
961
386
  {
962
387
  "data-afterbefore": "true",
@@ -976,163 +401,64 @@ function Toolbar({
976
401
  backdropFilter: "blur(20px)",
977
402
  WebkitBackdropFilter: "blur(20px)",
978
403
  border: "1px solid rgba(255, 255, 255, 0.1)",
979
- borderRadius: 18,
980
- padding: "6px 10px",
404
+ borderRadius: 999,
405
+ padding: "5px 8px",
981
406
  boxShadow: "0 8px 32px rgba(0, 0, 0, 0.4)",
982
407
  fontFamily: "system-ui, -apple-system, sans-serif"
983
408
  },
984
- children: [
985
- /* @__PURE__ */ jsxs3("div", { style: { display: "flex", alignItems: "center", gap: 0 }, children: [
986
- /* @__PURE__ */ jsx4(CloseButton, { onClick: onCancel }),
987
- /* @__PURE__ */ jsx4("div", { style: { display: "flex", alignItems: "center", gap: 2, padding: "0 4px" }, children: MODES.map(({ mode, label, icon: Icon2 }) => /* @__PURE__ */ jsx4(
988
- ModeButton,
989
- {
990
- label,
991
- selected: selectedMode === mode,
992
- onClick: () => {
993
- setSettingsOpen(false);
994
- onModeChange(mode);
995
- },
996
- children: /* @__PURE__ */ jsx4(Icon2, { size: 18, strokeWidth: 1.7 })
409
+ children: /* @__PURE__ */ jsxs2("div", { style: { display: "flex", alignItems: "center", gap: 0 }, children: [
410
+ /* @__PURE__ */ jsx3(CloseButton, { onClick: onCancel }),
411
+ /* @__PURE__ */ jsx3("div", { style: { display: "flex", alignItems: "center", gap: 2, padding: "0 4px" }, children: MODES.map(({ mode, label, icon: Icon2 }) => /* @__PURE__ */ jsx3(
412
+ ModeButton,
413
+ {
414
+ label,
415
+ selected: selectedMode === mode,
416
+ onClick: () => {
417
+ setSettingsOpen(false);
418
+ onModeChange(mode);
997
419
  },
998
- mode
999
- )) }),
1000
- /* @__PURE__ */ jsx4(Separator, {}),
1001
- /* @__PURE__ */ jsx4(
1002
- SettingsButton,
1003
- {
1004
- open: settingsOpen,
1005
- onClick: () => {
1006
- setAspectOpen(false);
1007
- setSavedOpen(false);
1008
- setSettingsOpen((prev) => !prev);
1009
- },
1010
- selectedMode,
1011
- magnetEnabled,
1012
- onMagnetChange,
1013
- frameOnBlackEnabled,
1014
- onFrameOnBlackChange
1015
- }
1016
- ),
1017
- !showAreaControls && /* @__PURE__ */ jsx4(
1018
- CaptureButton,
1019
- {
1020
- label: "Capture",
1021
- onClick: () => onCapture(selectedMode)
1022
- }
1023
- )
1024
- ] }),
1025
- activeAreaRect && /* @__PURE__ */ jsxs3(Fragment2, { children: [
1026
- /* @__PURE__ */ jsx4(Separator, { vertical: false }),
1027
- /* @__PURE__ */ jsxs3(
1028
- "div",
1029
- {
1030
- style: {
1031
- display: "flex",
1032
- alignItems: "center",
1033
- gap: 8,
1034
- flexWrap: "wrap",
1035
- justifyContent: "center"
1036
- },
1037
- children: [
1038
- /* @__PURE__ */ jsxs3(ControlGroup, { label: "Size", children: [
1039
- /* @__PURE__ */ jsx4(NumInput, { value: Math.round(activeAreaRect.w), onChange: (value) => onAreaSizeChange("w", value) }),
1040
- /* @__PURE__ */ jsx4(StaticText, { children: "x" }),
1041
- /* @__PURE__ */ jsx4(NumInput, { value: Math.round(activeAreaRect.h), onChange: (value) => onAreaSizeChange("h", value) })
1042
- ] }),
1043
- /* @__PURE__ */ jsxs3(ControlGroup, { label: "Position", children: [
1044
- /* @__PURE__ */ jsx4(NumInput, { value: Math.round(activeAreaRect.x), onChange: (value) => onAreaPositionChange("x", value) }),
1045
- /* @__PURE__ */ jsx4(StaticText, { children: "x" }),
1046
- /* @__PURE__ */ jsx4(NumInput, { value: Math.round(activeAreaRect.y), onChange: (value) => onAreaPositionChange("y", value) })
1047
- ] }),
1048
- /* @__PURE__ */ jsxs3("div", { style: { position: "relative" }, children: [
1049
- /* @__PURE__ */ jsxs3(
1050
- DropButton,
1051
- {
1052
- active: areaAspect !== "Free",
1053
- onClick: () => {
1054
- setSavedOpen(false);
1055
- setAspectOpen((prev) => !prev);
1056
- },
1057
- children: [
1058
- areaAspect,
1059
- /* @__PURE__ */ jsx4(ChevronDown, { size: 14, strokeWidth: 1.8 })
1060
- ]
1061
- }
1062
- ),
1063
- aspectOpen && /* @__PURE__ */ jsx4(DropMenu, { children: AREA_ASPECT_RATIOS.map((item) => /* @__PURE__ */ jsx4(
1064
- DropItem,
1065
- {
1066
- active: item.label === areaAspect,
1067
- onClick: () => {
1068
- onAreaAspectChange(item.label);
1069
- setAspectOpen(false);
1070
- },
1071
- children: item.label
1072
- },
1073
- item.label
1074
- )) })
1075
- ] }),
1076
- /* @__PURE__ */ jsxs3("div", { style: { position: "relative" }, children: [
1077
- /* @__PURE__ */ jsxs3(
1078
- DropButton,
1079
- {
1080
- onClick: () => {
1081
- setAspectOpen(false);
1082
- setSavedOpen((prev) => !prev);
1083
- },
1084
- children: [
1085
- /* @__PURE__ */ jsx4(Save, { size: 14, strokeWidth: 1.8 }),
1086
- "Saved",
1087
- /* @__PURE__ */ jsx4(ChevronDown, { size: 14, strokeWidth: 1.8 })
1088
- ]
1089
- }
1090
- ),
1091
- savedOpen && /* @__PURE__ */ jsxs3(DropMenu, { children: [
1092
- /* @__PURE__ */ jsx4(DropItem, { accent: true, onClick: onAreaSavePreset, children: "Save current" }),
1093
- areaPresets.length > 0 && /* @__PURE__ */ jsx4(MenuDivider, {}),
1094
- areaPresets.map((preset) => /* @__PURE__ */ jsx4(
1095
- DropItem,
1096
- {
1097
- onClick: () => {
1098
- onAreaLoadPreset(preset);
1099
- setSavedOpen(false);
1100
- },
1101
- children: preset.label
1102
- },
1103
- preset.label
1104
- )),
1105
- areaPresets.length === 0 && /* @__PURE__ */ jsx4(
1106
- "div",
1107
- {
1108
- style: {
1109
- padding: "6px 12px",
1110
- color: "rgba(255,255,255,0.3)",
1111
- fontSize: 12
1112
- },
1113
- children: "No saved areas"
1114
- }
1115
- )
1116
- ] })
1117
- ] })
1118
- ]
1119
- }
1120
- )
1121
- ] })
1122
- ]
420
+ children: /* @__PURE__ */ jsx3(Icon2, { size: 16, strokeWidth: 1.7 })
421
+ },
422
+ mode
423
+ )) }),
424
+ /* @__PURE__ */ jsx3(Separator, {}),
425
+ /* @__PURE__ */ jsx3(
426
+ SettingsButton,
427
+ {
428
+ open: settingsOpen,
429
+ onClick: () => setSettingsOpen((prev) => !prev),
430
+ selectedMode,
431
+ frameSettings,
432
+ onFrameSettingsChange
433
+ }
434
+ )
435
+ ] })
1123
436
  }
1124
437
  );
1125
438
  }
1126
439
  function CloseButton({ onClick }) {
1127
- const [hovered, setHovered] = useState5(false);
1128
- return /* @__PURE__ */ jsx4(
440
+ const [hovered, setHovered] = useState3(false);
441
+ return /* @__PURE__ */ jsx3(
1129
442
  "button",
1130
443
  {
1131
444
  onClick,
1132
445
  onMouseEnter: () => setHovered(true),
1133
446
  onMouseLeave: () => setHovered(false),
1134
- style: circleButtonStyle(hovered, true),
1135
- children: /* @__PURE__ */ jsx4(X, { size: 18, strokeWidth: 1.9 })
447
+ style: {
448
+ width: 32,
449
+ height: 32,
450
+ borderRadius: 10,
451
+ border: "none",
452
+ background: hovered ? "rgba(255, 255, 255, 0.08)" : "transparent",
453
+ display: "flex",
454
+ alignItems: "center",
455
+ justifyContent: "center",
456
+ cursor: "pointer",
457
+ padding: 0,
458
+ color: hovered ? "rgba(255, 255, 255, 0.96)" : "rgba(255, 255, 255, 0.52)",
459
+ transition: "background 0.12s ease, color 0.12s ease"
460
+ },
461
+ children: /* @__PURE__ */ jsx3(X, { size: 16, strokeWidth: 1.7 })
1136
462
  }
1137
463
  );
1138
464
  }
@@ -1142,9 +468,9 @@ function ModeButton({
1142
468
  selected,
1143
469
  onClick
1144
470
  }) {
1145
- const [hovered, setHovered] = useState5(false);
1146
- return /* @__PURE__ */ jsxs3("div", { style: { position: "relative" }, children: [
1147
- hovered && /* @__PURE__ */ jsx4(
471
+ const [hovered, setHovered] = useState3(false);
472
+ return /* @__PURE__ */ jsxs2("div", { style: { position: "relative" }, children: [
473
+ hovered && /* @__PURE__ */ jsx3(
1148
474
  "div",
1149
475
  {
1150
476
  style: {
@@ -1167,16 +493,16 @@ function ModeButton({
1167
493
  children: label
1168
494
  }
1169
495
  ),
1170
- /* @__PURE__ */ jsx4(
496
+ /* @__PURE__ */ jsx3(
1171
497
  "button",
1172
498
  {
1173
499
  onClick,
1174
500
  onMouseEnter: () => setHovered(true),
1175
501
  onMouseLeave: () => setHovered(false),
1176
502
  style: {
1177
- width: 42,
1178
- height: 40,
1179
- borderRadius: 12,
503
+ width: 32,
504
+ height: 32,
505
+ borderRadius: 10,
1180
506
  border: "none",
1181
507
  background: selected ? "rgba(255, 255, 255, 0.15)" : hovered ? "rgba(255, 255, 255, 0.08)" : "transparent",
1182
508
  display: "flex",
@@ -1196,28 +522,61 @@ function SettingsButton({
1196
522
  open,
1197
523
  onClick,
1198
524
  selectedMode,
1199
- magnetEnabled,
1200
- onMagnetChange,
1201
- frameOnBlackEnabled,
1202
- onFrameOnBlackChange
525
+ frameSettings,
526
+ onFrameSettingsChange
1203
527
  }) {
1204
- const [hovered, setHovered] = useState5(false);
1205
- const settingsHeader = selectedMode === "component" ? "Component Settings" : selectedMode === "area" ? "Area Settings" : "Settings";
1206
- return /* @__PURE__ */ jsxs3("div", { style: { position: "relative", marginRight: 6 }, children: [
1207
- /* @__PURE__ */ jsx4(
528
+ const [hovered, setHovered] = useState3(false);
529
+ const [saveDir, setSaveDir] = useState3(null);
530
+ const [picking, setPicking] = useState3(false);
531
+ useEffect2(() => {
532
+ if (!open) return;
533
+ fetch("/__afterbefore/config").then((r) => r.json()).then((data) => setSaveDir(data.saveDir)).catch(() => {
534
+ });
535
+ }, [open]);
536
+ const handlePickFolder = async () => {
537
+ setPicking(true);
538
+ try {
539
+ const res = await fetch("/__afterbefore/pick-folder", { method: "POST" });
540
+ const data = await res.json();
541
+ if (data.folder) {
542
+ await fetch("/__afterbefore/config", {
543
+ method: "POST",
544
+ headers: { "Content-Type": "application/json" },
545
+ body: JSON.stringify({ saveDir: data.folder })
546
+ });
547
+ setSaveDir(data.folder);
548
+ }
549
+ } catch {
550
+ } finally {
551
+ setPicking(false);
552
+ }
553
+ };
554
+ const shortDir = saveDir ? saveDir.replace(/^\/Users\/[^/]+/, "~") : "~/Desktop";
555
+ return /* @__PURE__ */ jsxs2("div", { style: { position: "relative" }, children: [
556
+ /* @__PURE__ */ jsx3(
1208
557
  "button",
1209
558
  {
1210
559
  onClick,
1211
560
  onMouseEnter: () => setHovered(true),
1212
561
  onMouseLeave: () => setHovered(false),
1213
562
  style: {
1214
- ...circleButtonStyle(open || hovered, false),
1215
- color: "rgba(255, 255, 255, 0.58)"
563
+ width: 32,
564
+ height: 32,
565
+ borderRadius: "50%",
566
+ border: "none",
567
+ background: open || hovered ? "rgba(255, 255, 255, 0.12)" : "transparent",
568
+ display: "flex",
569
+ alignItems: "center",
570
+ justifyContent: "center",
571
+ cursor: "pointer",
572
+ padding: 0,
573
+ color: open || hovered ? "rgba(255, 255, 255, 0.96)" : "rgba(255, 255, 255, 0.52)",
574
+ transition: "background 0.12s ease, color 0.12s ease"
1216
575
  },
1217
- children: /* @__PURE__ */ jsx4(Settings2, { size: 18, strokeWidth: 1.7 })
576
+ children: /* @__PURE__ */ jsx3(Settings, { size: 16, strokeWidth: 1.7 })
1218
577
  }
1219
578
  ),
1220
- open && /* @__PURE__ */ jsxs3(
579
+ open && /* @__PURE__ */ jsxs2(
1221
580
  "div",
1222
581
  {
1223
582
  style: {
@@ -1225,7 +584,7 @@ function SettingsButton({
1225
584
  left: "50%",
1226
585
  bottom: "calc(100% + 12px)",
1227
586
  transform: "translateX(-50%)",
1228
- minWidth: 210,
587
+ minWidth: 260,
1229
588
  padding: "10px 12px",
1230
589
  borderRadius: 12,
1231
590
  background: "rgba(32, 32, 36, 0.96)",
@@ -1235,7 +594,7 @@ function SettingsButton({
1235
594
  WebkitBackdropFilter: "blur(20px)"
1236
595
  },
1237
596
  children: [
1238
- /* @__PURE__ */ jsx4(
597
+ /* @__PURE__ */ jsx3(
1239
598
  "div",
1240
599
  {
1241
600
  style: {
@@ -1245,25 +604,46 @@ function SettingsButton({
1245
604
  textTransform: "uppercase",
1246
605
  marginBottom: 10
1247
606
  },
1248
- children: settingsHeader
1249
- }
1250
- ),
1251
- selectedMode === "area" && /* @__PURE__ */ jsx4(
1252
- ToggleRow,
1253
- {
1254
- icon: /* @__PURE__ */ jsx4(Magnet, { size: 15, strokeWidth: 1.8 }),
1255
- label: "Magnet snap",
1256
- enabled: magnetEnabled,
1257
- onChange: () => onMagnetChange(!magnetEnabled)
607
+ children: "Settings"
1258
608
  }
1259
609
  ),
1260
- selectedMode === "component" && /* @__PURE__ */ jsx4(
1261
- ToggleRow,
610
+ selectedMode === "component" && /* @__PURE__ */ jsxs2(Fragment, { children: [
611
+ /* @__PURE__ */ jsx3(
612
+ ToggleRow,
613
+ {
614
+ icon: /* @__PURE__ */ jsx3(Frame, { size: 15, strokeWidth: 1.8 }),
615
+ label: "Frame",
616
+ enabled: frameSettings.enabled,
617
+ onChange: () => onFrameSettingsChange({ ...frameSettings, enabled: !frameSettings.enabled })
618
+ }
619
+ ),
620
+ frameSettings.enabled && /* @__PURE__ */ jsxs2("div", { style: { marginTop: 8, display: "flex", flexDirection: "column", gap: 10 }, children: [
621
+ /* @__PURE__ */ jsx3(
622
+ FrameSizeControl,
623
+ {
624
+ size: frameSettings.size,
625
+ onChange: (size) => onFrameSettingsChange({ ...frameSettings, size })
626
+ }
627
+ ),
628
+ /* @__PURE__ */ jsx3(
629
+ FrameBackgroundControl,
630
+ {
631
+ bgType: frameSettings.bgType,
632
+ bgColor: frameSettings.bgColor,
633
+ bgImage: frameSettings.bgImage,
634
+ frameSize: frameSettings.size,
635
+ onChange: (updates) => onFrameSettingsChange({ ...frameSettings, ...updates })
636
+ }
637
+ )
638
+ ] })
639
+ ] }),
640
+ selectedMode === "component" && /* @__PURE__ */ jsx3("div", { style: { height: 1, background: "rgba(255,255,255,0.08)", margin: "8px 0" } }),
641
+ /* @__PURE__ */ jsx3(
642
+ SaveLocationRow,
1262
643
  {
1263
- icon: /* @__PURE__ */ jsx4(Frame, { size: 15, strokeWidth: 1.8 }),
1264
- label: "1920 x 1080 frame",
1265
- enabled: frameOnBlackEnabled,
1266
- onChange: () => onFrameOnBlackChange(!frameOnBlackEnabled)
644
+ dir: shortDir,
645
+ picking,
646
+ onPick: handlePickFolder
1267
647
  }
1268
648
  )
1269
649
  ]
@@ -1271,13 +651,390 @@ function SettingsButton({
1271
651
  )
1272
652
  ] });
1273
653
  }
654
+ function FrameSizeControl({
655
+ size,
656
+ onChange
657
+ }) {
658
+ const [sizeOpen, setSizeOpen] = useState3(false);
659
+ const currentPreset = FRAME_SIZE_PRESETS.find((p) => p.w === size.w && p.h === size.h);
660
+ const isCustom = !currentPreset;
661
+ return /* @__PURE__ */ jsxs2("div", { children: [
662
+ /* @__PURE__ */ jsx3(SettingsLabel, { children: "Size" }),
663
+ /* @__PURE__ */ jsx3("div", { style: { display: "flex", alignItems: "center", gap: 6, marginTop: 4 }, children: /* @__PURE__ */ jsxs2("div", { style: { position: "relative", flex: 1 }, children: [
664
+ /* @__PURE__ */ jsxs2(
665
+ "button",
666
+ {
667
+ onClick: () => setSizeOpen((prev) => !prev),
668
+ style: {
669
+ display: "flex",
670
+ alignItems: "center",
671
+ justifyContent: "space-between",
672
+ gap: 6,
673
+ width: "100%",
674
+ height: 30,
675
+ padding: "0 8px",
676
+ borderRadius: 7,
677
+ border: "1px solid rgba(255,255,255,0.1)",
678
+ background: "rgba(255,255,255,0.07)",
679
+ color: "rgba(255,255,255,0.88)",
680
+ cursor: "pointer",
681
+ fontSize: 12,
682
+ fontFamily: "inherit"
683
+ },
684
+ children: [
685
+ currentPreset ? currentPreset.label : "Custom",
686
+ /* @__PURE__ */ jsx3(ChevronDown, { size: 12, strokeWidth: 2 })
687
+ ]
688
+ }
689
+ ),
690
+ sizeOpen && /* @__PURE__ */ jsxs2(
691
+ "div",
692
+ {
693
+ style: {
694
+ position: "absolute",
695
+ bottom: "calc(100% + 4px)",
696
+ left: 0,
697
+ right: 0,
698
+ background: "rgba(32, 32, 36, 0.96)",
699
+ border: "1px solid rgba(255, 255, 255, 0.1)",
700
+ borderRadius: 8,
701
+ padding: "4px 0",
702
+ boxShadow: "0 10px 30px rgba(0, 0, 0, 0.3)",
703
+ backdropFilter: "blur(20px)",
704
+ WebkitBackdropFilter: "blur(20px)",
705
+ zIndex: 1
706
+ },
707
+ children: [
708
+ FRAME_SIZE_PRESETS.map((preset) => /* @__PURE__ */ jsx3(
709
+ DropItem,
710
+ {
711
+ active: !isCustom && preset.w === size.w && preset.h === size.h,
712
+ onClick: () => {
713
+ onChange({ w: preset.w, h: preset.h });
714
+ setSizeOpen(false);
715
+ },
716
+ children: preset.label
717
+ },
718
+ preset.label
719
+ )),
720
+ /* @__PURE__ */ jsx3(DropItem, { active: isCustom, onClick: () => setSizeOpen(false), children: "Custom" })
721
+ ]
722
+ }
723
+ )
724
+ ] }) }),
725
+ isCustom && /* @__PURE__ */ jsxs2("div", { style: { display: "flex", alignItems: "center", gap: 4, marginTop: 6 }, children: [
726
+ /* @__PURE__ */ jsx3(
727
+ NumInput,
728
+ {
729
+ value: size.w,
730
+ onChange: (v) => {
731
+ const n = parseInt(v, 10);
732
+ if (!Number.isNaN(n) && n > 0) onChange({ ...size, w: n });
733
+ }
734
+ }
735
+ ),
736
+ /* @__PURE__ */ jsx3(StaticText, { children: "x" }),
737
+ /* @__PURE__ */ jsx3(
738
+ NumInput,
739
+ {
740
+ value: size.h,
741
+ onChange: (v) => {
742
+ const n = parseInt(v, 10);
743
+ if (!Number.isNaN(n) && n > 0) onChange({ ...size, h: n });
744
+ }
745
+ }
746
+ )
747
+ ] })
748
+ ] });
749
+ }
750
+ function FrameBackgroundControl({
751
+ bgType,
752
+ bgColor,
753
+ bgImage,
754
+ frameSize,
755
+ onChange
756
+ }) {
757
+ const fileInputRef = useRef2(null);
758
+ const handleFileSelect = (e) => {
759
+ const file = e.target.files?.[0];
760
+ if (!file) return;
761
+ const reader = new FileReader();
762
+ reader.onload = () => {
763
+ const dataUrl = reader.result;
764
+ if (dataUrl.length > 2 * 1024 * 1024) {
765
+ downscaleImage(dataUrl, frameSize.w, frameSize.h).then((scaled) => {
766
+ onChange({ bgType: "image", bgImage: scaled });
767
+ });
768
+ } else {
769
+ onChange({ bgType: "image", bgImage: dataUrl });
770
+ }
771
+ };
772
+ reader.readAsDataURL(file);
773
+ e.target.value = "";
774
+ };
775
+ return /* @__PURE__ */ jsxs2("div", { children: [
776
+ /* @__PURE__ */ jsx3(SettingsLabel, { children: "Background" }),
777
+ /* @__PURE__ */ jsxs2("div", { style: { display: "flex", gap: 2, marginTop: 4 }, children: [
778
+ /* @__PURE__ */ jsxs2(
779
+ SegmentButton,
780
+ {
781
+ active: bgType === "color",
782
+ onClick: () => onChange({ bgType: "color" }),
783
+ style: { borderRadius: "6px 0 0 6px" },
784
+ children: [
785
+ /* @__PURE__ */ jsx3(Palette, { size: 12, strokeWidth: 2 }),
786
+ "Color"
787
+ ]
788
+ }
789
+ ),
790
+ /* @__PURE__ */ jsxs2(
791
+ SegmentButton,
792
+ {
793
+ active: bgType === "image",
794
+ onClick: () => onChange({ bgType: "image" }),
795
+ style: { borderRadius: "0 6px 6px 0" },
796
+ children: [
797
+ /* @__PURE__ */ jsx3(ImageIcon, { size: 12, strokeWidth: 2 }),
798
+ "Image"
799
+ ]
800
+ }
801
+ )
802
+ ] }),
803
+ bgType === "color" && /* @__PURE__ */ jsxs2("div", { style: { display: "flex", alignItems: "center", gap: 8, marginTop: 6 }, children: [
804
+ /* @__PURE__ */ jsx3(ColorSwatch, { color: bgColor, onChange: (c) => onChange({ bgColor: c }) }),
805
+ /* @__PURE__ */ jsx3("span", { style: { fontSize: 12, color: "rgba(255,255,255,0.6)" }, children: bgColor })
806
+ ] }),
807
+ bgType === "image" && /* @__PURE__ */ jsxs2("div", { style: { marginTop: 6 }, children: [
808
+ /* @__PURE__ */ jsx3(
809
+ "input",
810
+ {
811
+ ref: fileInputRef,
812
+ type: "file",
813
+ accept: "image/*",
814
+ onChange: handleFileSelect,
815
+ style: { display: "none" }
816
+ }
817
+ ),
818
+ bgImage ? /* @__PURE__ */ jsxs2("div", { style: { display: "flex", alignItems: "center", gap: 8 }, children: [
819
+ /* @__PURE__ */ jsx3(
820
+ "img",
821
+ {
822
+ src: bgImage,
823
+ alt: "",
824
+ style: {
825
+ width: 48,
826
+ height: 28,
827
+ borderRadius: 4,
828
+ objectFit: "cover",
829
+ border: "1px solid rgba(255,255,255,0.12)"
830
+ }
831
+ }
832
+ ),
833
+ /* @__PURE__ */ jsxs2(SmallButton, { onClick: () => fileInputRef.current?.click(), children: [
834
+ /* @__PURE__ */ jsx3(Upload, { size: 11, strokeWidth: 2 }),
835
+ "Replace"
836
+ ] }),
837
+ /* @__PURE__ */ jsx3(SmallButton, { onClick: () => onChange({ bgImage: null }), children: /* @__PURE__ */ jsx3(Trash2, { size: 11, strokeWidth: 2 }) })
838
+ ] }) : /* @__PURE__ */ jsxs2(SmallButton, { onClick: () => fileInputRef.current?.click(), children: [
839
+ /* @__PURE__ */ jsx3(Upload, { size: 11, strokeWidth: 2 }),
840
+ "Upload image"
841
+ ] })
842
+ ] })
843
+ ] });
844
+ }
845
+ function SettingsLabel({ children }) {
846
+ return /* @__PURE__ */ jsx3(
847
+ "div",
848
+ {
849
+ style: {
850
+ fontSize: 11,
851
+ color: "rgba(255, 255, 255, 0.42)",
852
+ letterSpacing: "0.02em"
853
+ },
854
+ children
855
+ }
856
+ );
857
+ }
858
+ function SegmentButton({
859
+ children,
860
+ active,
861
+ onClick,
862
+ style
863
+ }) {
864
+ return /* @__PURE__ */ jsx3(
865
+ "button",
866
+ {
867
+ onClick,
868
+ style: {
869
+ display: "flex",
870
+ alignItems: "center",
871
+ gap: 4,
872
+ height: 26,
873
+ padding: "0 10px",
874
+ border: "1px solid rgba(255,255,255,0.1)",
875
+ background: active ? "rgba(255,255,255,0.14)" : "rgba(255,255,255,0.04)",
876
+ color: active ? "rgba(255,255,255,0.92)" : "rgba(255,255,255,0.5)",
877
+ cursor: "pointer",
878
+ fontSize: 11,
879
+ fontFamily: "inherit",
880
+ transition: "background 0.12s ease, color 0.12s ease",
881
+ ...style
882
+ },
883
+ children
884
+ }
885
+ );
886
+ }
887
+ function ColorSwatch({
888
+ color,
889
+ onChange
890
+ }) {
891
+ const inputRef = useRef2(null);
892
+ return /* @__PURE__ */ jsxs2("div", { style: { position: "relative" }, children: [
893
+ /* @__PURE__ */ jsx3(
894
+ "button",
895
+ {
896
+ onClick: () => inputRef.current?.click(),
897
+ style: {
898
+ width: 24,
899
+ height: 24,
900
+ borderRadius: 6,
901
+ border: "1px solid rgba(255,255,255,0.18)",
902
+ background: color,
903
+ cursor: "pointer",
904
+ padding: 0
905
+ }
906
+ }
907
+ ),
908
+ /* @__PURE__ */ jsx3(
909
+ "input",
910
+ {
911
+ ref: inputRef,
912
+ type: "color",
913
+ value: color,
914
+ onChange: (e) => onChange(e.target.value),
915
+ style: {
916
+ position: "absolute",
917
+ top: 0,
918
+ left: 0,
919
+ width: 0,
920
+ height: 0,
921
+ opacity: 0,
922
+ pointerEvents: "none"
923
+ }
924
+ }
925
+ )
926
+ ] });
927
+ }
928
+ function SmallButton({
929
+ children,
930
+ onClick
931
+ }) {
932
+ const [hovered, setHovered] = useState3(false);
933
+ return /* @__PURE__ */ jsx3(
934
+ "button",
935
+ {
936
+ onClick,
937
+ onMouseEnter: () => setHovered(true),
938
+ onMouseLeave: () => setHovered(false),
939
+ style: {
940
+ display: "flex",
941
+ alignItems: "center",
942
+ gap: 4,
943
+ padding: "3px 8px",
944
+ borderRadius: 6,
945
+ border: "1px solid rgba(255,255,255,0.12)",
946
+ background: hovered ? "rgba(255,255,255,0.12)" : "rgba(255,255,255,0.06)",
947
+ color: "rgba(255,255,255,0.78)",
948
+ fontSize: 11,
949
+ cursor: "pointer",
950
+ fontFamily: "inherit",
951
+ transition: "background 0.12s ease"
952
+ },
953
+ children
954
+ }
955
+ );
956
+ }
957
+ async function downscaleImage(dataUrl, maxW, maxH) {
958
+ return new Promise((resolve) => {
959
+ const img = new Image();
960
+ img.onload = () => {
961
+ const canvas = document.createElement("canvas");
962
+ const scale = Math.min(maxW / img.width, maxH / img.height, 1);
963
+ canvas.width = img.width * scale;
964
+ canvas.height = img.height * scale;
965
+ const ctx = canvas.getContext("2d");
966
+ ctx.drawImage(img, 0, 0, canvas.width, canvas.height);
967
+ resolve(canvas.toDataURL("image/jpeg", 0.85));
968
+ };
969
+ img.onerror = () => resolve(dataUrl);
970
+ img.src = dataUrl;
971
+ });
972
+ }
973
+ function SaveLocationRow({
974
+ dir,
975
+ picking,
976
+ onPick
977
+ }) {
978
+ const [btnHovered, setBtnHovered] = useState3(false);
979
+ return /* @__PURE__ */ jsx3("div", { style: { display: "flex", flexDirection: "column", gap: 6 }, children: /* @__PURE__ */ jsxs2(
980
+ "div",
981
+ {
982
+ style: {
983
+ display: "flex",
984
+ alignItems: "center",
985
+ gap: 8,
986
+ color: "rgba(255, 255, 255, 0.88)",
987
+ fontSize: 13
988
+ },
989
+ children: [
990
+ /* @__PURE__ */ jsx3(FolderOpen, { size: 15, strokeWidth: 1.8, style: { flexShrink: 0 } }),
991
+ /* @__PURE__ */ jsx3(
992
+ "span",
993
+ {
994
+ style: {
995
+ overflow: "hidden",
996
+ textOverflow: "ellipsis",
997
+ whiteSpace: "nowrap",
998
+ flex: 1,
999
+ minWidth: 0
1000
+ },
1001
+ title: dir,
1002
+ children: dir
1003
+ }
1004
+ ),
1005
+ /* @__PURE__ */ jsx3(
1006
+ "button",
1007
+ {
1008
+ onClick: onPick,
1009
+ disabled: picking,
1010
+ onMouseEnter: () => setBtnHovered(true),
1011
+ onMouseLeave: () => setBtnHovered(false),
1012
+ style: {
1013
+ padding: "3px 8px",
1014
+ borderRadius: 6,
1015
+ border: "1px solid rgba(255,255,255,0.12)",
1016
+ background: btnHovered ? "rgba(255,255,255,0.12)" : "rgba(255,255,255,0.06)",
1017
+ color: "rgba(255,255,255,0.78)",
1018
+ fontSize: 11,
1019
+ cursor: picking ? "wait" : "pointer",
1020
+ flexShrink: 0,
1021
+ fontFamily: "inherit",
1022
+ transition: "background 0.12s ease"
1023
+ },
1024
+ children: picking ? "..." : "Change"
1025
+ }
1026
+ )
1027
+ ]
1028
+ }
1029
+ ) });
1030
+ }
1274
1031
  function ToggleRow({
1275
1032
  icon,
1276
1033
  label,
1277
1034
  enabled,
1278
1035
  onChange
1279
1036
  }) {
1280
- return /* @__PURE__ */ jsxs3(
1037
+ return /* @__PURE__ */ jsxs2(
1281
1038
  "label",
1282
1039
  {
1283
1040
  style: {
@@ -1288,7 +1045,7 @@ function ToggleRow({
1288
1045
  cursor: "pointer"
1289
1046
  },
1290
1047
  children: [
1291
- /* @__PURE__ */ jsxs3(
1048
+ /* @__PURE__ */ jsxs2(
1292
1049
  "span",
1293
1050
  {
1294
1051
  style: {
@@ -1305,7 +1062,7 @@ function ToggleRow({
1305
1062
  ]
1306
1063
  }
1307
1064
  ),
1308
- /* @__PURE__ */ jsx4(
1065
+ /* @__PURE__ */ jsx3(
1309
1066
  "button",
1310
1067
  {
1311
1068
  type: "button",
@@ -1322,7 +1079,7 @@ function ToggleRow({
1322
1079
  flexShrink: 0,
1323
1080
  transition: "background 0.12s ease"
1324
1081
  },
1325
- children: /* @__PURE__ */ jsx4(
1082
+ children: /* @__PURE__ */ jsx3(
1326
1083
  "span",
1327
1084
  {
1328
1085
  style: {
@@ -1343,85 +1100,18 @@ function ToggleRow({
1343
1100
  }
1344
1101
  );
1345
1102
  }
1346
- function CaptureButton({
1347
- label,
1348
- onClick
1349
- }) {
1350
- const [hovered, setHovered] = useState5(false);
1351
- return /* @__PURE__ */ jsxs3(
1352
- "button",
1353
- {
1354
- onClick,
1355
- onMouseEnter: () => setHovered(true),
1356
- onMouseLeave: () => setHovered(false),
1357
- style: {
1358
- padding: "0 18px",
1359
- height: 40,
1360
- borderRadius: 12,
1361
- border: "none",
1362
- background: hovered ? "#2f7bf8" : "#3b82f6",
1363
- color: "white",
1364
- fontSize: 13,
1365
- fontWeight: 600,
1366
- fontFamily: "inherit",
1367
- cursor: "pointer",
1368
- whiteSpace: "nowrap",
1369
- transition: "background 0.12s ease",
1370
- flexShrink: 0,
1371
- display: "flex",
1372
- alignItems: "center",
1373
- gap: 8
1374
- },
1375
- children: [
1376
- /* @__PURE__ */ jsx4(Camera2, { size: 16, strokeWidth: 1.9 }),
1377
- label
1378
- ]
1379
- }
1380
- );
1381
- }
1382
- function ControlGroup({
1383
- label,
1384
- children
1385
- }) {
1386
- return /* @__PURE__ */ jsxs3(
1387
- "div",
1388
- {
1389
- style: {
1390
- display: "flex",
1391
- alignItems: "center",
1392
- gap: 6,
1393
- padding: "0 4px"
1394
- },
1395
- children: [
1396
- /* @__PURE__ */ jsx4(
1397
- "span",
1398
- {
1399
- style: {
1400
- fontSize: 11,
1401
- color: "rgba(255, 255, 255, 0.42)",
1402
- textTransform: "uppercase",
1403
- letterSpacing: "0.03em"
1404
- },
1405
- children: label
1406
- }
1407
- ),
1408
- children
1409
- ]
1410
- }
1411
- );
1412
- }
1413
1103
  function NumInput({
1414
1104
  value,
1415
1105
  onChange
1416
1106
  }) {
1417
- const [editing, setEditing] = useState5(false);
1418
- const [text, setText] = useState5(String(value));
1419
- useEffect4(() => {
1107
+ const [editing, setEditing] = useState3(false);
1108
+ const [text, setText] = useState3(String(value));
1109
+ useEffect2(() => {
1420
1110
  if (!editing) {
1421
1111
  setText(String(value));
1422
1112
  }
1423
1113
  }, [editing, value]);
1424
- return /* @__PURE__ */ jsx4(
1114
+ return /* @__PURE__ */ jsx3(
1425
1115
  "input",
1426
1116
  {
1427
1117
  type: "text",
@@ -1456,7 +1146,7 @@ function NumInput({
1456
1146
  );
1457
1147
  }
1458
1148
  function StaticText({ children }) {
1459
- return /* @__PURE__ */ jsx4(
1149
+ return /* @__PURE__ */ jsx3(
1460
1150
  "span",
1461
1151
  {
1462
1152
  style: {
@@ -1469,62 +1159,13 @@ function StaticText({ children }) {
1469
1159
  }
1470
1160
  );
1471
1161
  }
1472
- function DropButton({
1473
- children,
1474
- onClick,
1475
- active
1476
- }) {
1477
- return /* @__PURE__ */ jsx4(
1478
- "button",
1479
- {
1480
- onClick,
1481
- style: {
1482
- display: "flex",
1483
- alignItems: "center",
1484
- gap: 6,
1485
- height: 34,
1486
- padding: "0 10px",
1487
- borderRadius: 10,
1488
- border: "1px solid rgba(255,255,255,0.08)",
1489
- background: "rgba(255,255,255,0.04)",
1490
- color: active ? "rgba(125, 211, 252, 0.96)" : "rgba(255,255,255,0.78)",
1491
- cursor: "pointer",
1492
- fontSize: 12,
1493
- fontFamily: "inherit"
1494
- },
1495
- children
1496
- }
1497
- );
1498
- }
1499
- function DropMenu({ children }) {
1500
- return /* @__PURE__ */ jsx4(
1501
- "div",
1502
- {
1503
- style: {
1504
- position: "absolute",
1505
- bottom: "calc(100% + 8px)",
1506
- left: "50%",
1507
- transform: "translateX(-50%)",
1508
- minWidth: 120,
1509
- background: "rgba(32, 32, 36, 0.96)",
1510
- border: "1px solid rgba(255, 255, 255, 0.1)",
1511
- borderRadius: 10,
1512
- padding: "4px 0",
1513
- boxShadow: "0 10px 30px rgba(0, 0, 0, 0.3)",
1514
- backdropFilter: "blur(20px)",
1515
- WebkitBackdropFilter: "blur(20px)"
1516
- },
1517
- children
1518
- }
1519
- );
1520
- }
1521
1162
  function DropItem({
1522
1163
  children,
1523
1164
  onClick,
1524
1165
  active,
1525
1166
  accent
1526
1167
  }) {
1527
- return /* @__PURE__ */ jsx4(
1168
+ return /* @__PURE__ */ jsx3(
1528
1169
  "button",
1529
1170
  {
1530
1171
  onClick,
@@ -1544,56 +1185,29 @@ function DropItem({
1544
1185
  }
1545
1186
  );
1546
1187
  }
1547
- function MenuDivider() {
1548
- return /* @__PURE__ */ jsx4(
1549
- "div",
1550
- {
1551
- style: {
1552
- height: 1,
1553
- background: "rgba(255,255,255,0.08)",
1554
- margin: "4px 0"
1555
- }
1556
- }
1557
- );
1558
- }
1559
1188
  function Separator({ vertical = true }) {
1560
- return /* @__PURE__ */ jsx4(
1189
+ return /* @__PURE__ */ jsx3(
1561
1190
  "div",
1562
1191
  {
1563
1192
  style: {
1564
1193
  width: vertical ? 1 : 24,
1565
- height: vertical ? 24 : 1,
1194
+ height: vertical ? 18 : 1,
1566
1195
  background: "rgba(255,255,255,0.12)",
1567
- flexShrink: 0
1196
+ flexShrink: 0,
1197
+ margin: vertical ? "0 6px" : "6px 0"
1568
1198
  }
1569
1199
  }
1570
1200
  );
1571
1201
  }
1572
- function circleButtonStyle(active, round) {
1573
- return {
1574
- width: 40,
1575
- height: 40,
1576
- borderRadius: round ? "50%" : 12,
1577
- border: "none",
1578
- background: active ? "rgba(255, 255, 255, 0.12)" : "transparent",
1579
- display: "flex",
1580
- alignItems: "center",
1581
- justifyContent: "center",
1582
- cursor: "pointer",
1583
- padding: 0,
1584
- transition: "background 0.12s ease, color 0.12s ease",
1585
- marginRight: round ? 6 : 0
1586
- };
1587
- }
1588
1202
 
1589
1203
  // src/overlay/ui/inspector.tsx
1590
- import { useEffect as useEffect5, useRef as useRef3, useCallback as useCallback4, useState as useState6 } from "react";
1591
- import { Fragment as Fragment3, jsx as jsx5, jsxs as jsxs4 } from "react/jsx-runtime";
1204
+ import { useEffect as useEffect3, useRef as useRef3, useCallback as useCallback3, useState as useState4 } from "react";
1205
+ import { Fragment as Fragment2, jsx as jsx4, jsxs as jsxs3 } from "react/jsx-runtime";
1592
1206
  function Inspector({ onSelect, onCancel }) {
1593
- const [highlight, setHighlight] = useState6(null);
1207
+ const [highlight, setHighlight] = useState4(null);
1594
1208
  const hoveredEl = useRef3(null);
1595
1209
  const styleEl = useRef3(null);
1596
- useEffect5(() => {
1210
+ useEffect3(() => {
1597
1211
  const style = document.createElement("style");
1598
1212
  style.setAttribute("data-afterbefore", "true");
1599
1213
  style.textContent = "*, *::before, *::after { cursor: crosshair !important; }";
@@ -1603,7 +1217,7 @@ function Inspector({ onSelect, onCancel }) {
1603
1217
  style.remove();
1604
1218
  };
1605
1219
  }, []);
1606
- const isOverlayElement = useCallback4((el) => {
1220
+ const isOverlayElement = useCallback3((el) => {
1607
1221
  let node = el;
1608
1222
  while (node) {
1609
1223
  if (node instanceof HTMLElement && node.dataset.afterbefore) return true;
@@ -1611,7 +1225,7 @@ function Inspector({ onSelect, onCancel }) {
1611
1225
  }
1612
1226
  return false;
1613
1227
  }, []);
1614
- const handleMouseMove = useCallback4(
1228
+ const handleMouseMove = useCallback3(
1615
1229
  (e) => {
1616
1230
  const el = document.elementFromPoint(e.clientX, e.clientY);
1617
1231
  if (!el || !(el instanceof HTMLElement) || isOverlayElement(el)) {
@@ -1631,7 +1245,7 @@ function Inspector({ onSelect, onCancel }) {
1631
1245
  },
1632
1246
  [isOverlayElement]
1633
1247
  );
1634
- const handleClick = useCallback4(
1248
+ const handleClick = useCallback3(
1635
1249
  (e) => {
1636
1250
  e.preventDefault();
1637
1251
  e.stopPropagation();
@@ -1642,7 +1256,7 @@ function Inspector({ onSelect, onCancel }) {
1642
1256
  },
1643
1257
  [onSelect]
1644
1258
  );
1645
- const handleKeyDown = useCallback4(
1259
+ const handleKeyDown = useCallback3(
1646
1260
  (e) => {
1647
1261
  if (e.key === "Escape") {
1648
1262
  onCancel();
@@ -1650,7 +1264,7 @@ function Inspector({ onSelect, onCancel }) {
1650
1264
  },
1651
1265
  [onCancel]
1652
1266
  );
1653
- useEffect5(() => {
1267
+ useEffect3(() => {
1654
1268
  document.addEventListener("mousemove", handleMouseMove, true);
1655
1269
  document.addEventListener("click", handleClick, true);
1656
1270
  document.addEventListener("keydown", handleKeyDown);
@@ -1660,81 +1274,59 @@ function Inspector({ onSelect, onCancel }) {
1660
1274
  document.removeEventListener("keydown", handleKeyDown);
1661
1275
  };
1662
1276
  }, [handleMouseMove, handleClick, handleKeyDown]);
1663
- return /* @__PURE__ */ jsxs4("div", { "data-afterbefore": "true", style: { position: "fixed", inset: 0, zIndex: 2147483646, pointerEvents: "none" }, children: [
1664
- highlight && /* @__PURE__ */ jsxs4(Fragment3, { children: [
1665
- /* @__PURE__ */ jsx5(
1666
- "div",
1667
- {
1668
- style: {
1669
- position: "fixed",
1670
- left: highlight.x,
1671
- top: highlight.y,
1672
- width: highlight.width,
1673
- height: highlight.height,
1674
- background: "rgba(59, 130, 246, 0.15)",
1675
- border: "2px solid rgba(59, 130, 246, 0.7)",
1676
- borderRadius: 2,
1677
- pointerEvents: "none"
1678
- }
1679
- }
1680
- ),
1681
- /* @__PURE__ */ jsx5(
1682
- "div",
1683
- {
1684
- style: {
1685
- position: "fixed",
1686
- left: highlight.x,
1687
- top: Math.max(0, highlight.y - 24),
1688
- background: "rgba(59, 130, 246, 0.9)",
1689
- color: "#fff",
1690
- fontSize: 11,
1691
- fontFamily: "system-ui, -apple-system, monospace",
1692
- padding: "2px 6px",
1693
- borderRadius: 3,
1694
- pointerEvents: "none",
1695
- whiteSpace: "nowrap",
1696
- lineHeight: "18px"
1697
- },
1698
- children: highlight.tag
1277
+ return /* @__PURE__ */ jsx4("div", { "data-afterbefore": "true", style: { position: "fixed", inset: 0, zIndex: 2147483646, pointerEvents: "none" }, children: highlight && /* @__PURE__ */ jsxs3(Fragment2, { children: [
1278
+ /* @__PURE__ */ jsx4(
1279
+ "div",
1280
+ {
1281
+ style: {
1282
+ position: "fixed",
1283
+ left: highlight.x,
1284
+ top: highlight.y,
1285
+ width: highlight.width,
1286
+ height: highlight.height,
1287
+ background: "rgba(59, 130, 246, 0.15)",
1288
+ border: "2px solid rgba(59, 130, 246, 0.7)",
1289
+ borderRadius: 2,
1290
+ pointerEvents: "none"
1699
1291
  }
1700
- )
1701
- ] }),
1702
- !highlight && /* @__PURE__ */ jsx5(
1292
+ }
1293
+ ),
1294
+ /* @__PURE__ */ jsx4(
1703
1295
  "div",
1704
1296
  {
1705
1297
  style: {
1706
1298
  position: "fixed",
1707
- top: "50%",
1708
- left: "50%",
1709
- transform: "translate(-50%, -50%)",
1710
- color: "rgba(255, 255, 255, 0.7)",
1711
- fontSize: 14,
1712
- fontFamily: "system-ui, -apple-system, sans-serif",
1299
+ left: highlight.x,
1300
+ top: Math.max(0, highlight.y - 24),
1301
+ background: "rgba(59, 130, 246, 0.9)",
1302
+ color: "#fff",
1303
+ fontSize: 11,
1304
+ fontFamily: "system-ui, -apple-system, monospace",
1305
+ padding: "2px 6px",
1306
+ borderRadius: 3,
1713
1307
  pointerEvents: "none",
1714
- textShadow: "0 1px 4px rgba(0, 0, 0, 0.5)",
1715
- background: "rgba(0, 0, 0, 0.5)",
1716
- padding: "8px 16px",
1717
- borderRadius: 8
1308
+ whiteSpace: "nowrap",
1309
+ lineHeight: "18px"
1718
1310
  },
1719
- children: "Hover to inspect \xB7 Click to capture \xB7 Esc to cancel"
1311
+ children: highlight.tag
1720
1312
  }
1721
1313
  )
1722
- ] });
1314
+ ] }) });
1723
1315
  }
1724
1316
 
1725
1317
  // src/overlay/ui/status.tsx
1726
- import { useState as useState7, useRef as useRef4, useEffect as useEffect6, useCallback as useCallback5 } from "react";
1727
- import { jsx as jsx6, jsxs as jsxs5 } from "react/jsx-runtime";
1318
+ import { useState as useState5, useRef as useRef4, useEffect as useEffect4, useCallback as useCallback4 } from "react";
1319
+ import { jsx as jsx5, jsxs as jsxs4 } from "react/jsx-runtime";
1728
1320
  var PANEL_WIDTH = 220;
1729
1321
  function Status({ onReset, position, onClose }) {
1730
1322
  const panelRef = useRef4(null);
1731
- const [toast, setToast] = useState7(null);
1732
- const [pushing, setPushing] = useState7(false);
1733
- const showToast = useCallback5((message, type) => {
1323
+ const [toast, setToast] = useState5(null);
1324
+ const [pushing, setPushing] = useState5(false);
1325
+ const showToast = useCallback4((message, type) => {
1734
1326
  setToast({ message, type });
1735
1327
  setTimeout(() => setToast(null), 3e3);
1736
1328
  }, []);
1737
- useEffect6(() => {
1329
+ useEffect4(() => {
1738
1330
  const handler = (e) => {
1739
1331
  if (panelRef.current && !panelRef.current.contains(e.target)) {
1740
1332
  onClose();
@@ -1743,7 +1335,7 @@ function Status({ onReset, position, onClose }) {
1743
1335
  document.addEventListener("mousedown", handler);
1744
1336
  return () => document.removeEventListener("mousedown", handler);
1745
1337
  }, [onClose]);
1746
- useEffect6(() => {
1338
+ useEffect4(() => {
1747
1339
  const handler = (e) => {
1748
1340
  if (e.key === "Escape") onClose();
1749
1341
  };
@@ -1819,7 +1411,7 @@ function Status({ onReset, position, onClose }) {
1819
1411
  const onLeave = (e) => {
1820
1412
  e.currentTarget.style.background = "transparent";
1821
1413
  };
1822
- return /* @__PURE__ */ jsxs5(
1414
+ return /* @__PURE__ */ jsxs4(
1823
1415
  "div",
1824
1416
  {
1825
1417
  ref: panelRef,
@@ -1838,7 +1430,7 @@ function Status({ onReset, position, onClose }) {
1838
1430
  overflow: "hidden"
1839
1431
  },
1840
1432
  children: [
1841
- /* @__PURE__ */ jsx6(
1433
+ /* @__PURE__ */ jsx5(
1842
1434
  "div",
1843
1435
  {
1844
1436
  style: {
@@ -1848,11 +1440,11 @@ function Status({ onReset, position, onClose }) {
1848
1440
  color: "rgba(255,255,255,0.5)",
1849
1441
  letterSpacing: "0.02em"
1850
1442
  },
1851
- children: "Before & After captured"
1443
+ children: "Screenshot captured"
1852
1444
  }
1853
1445
  ),
1854
- /* @__PURE__ */ jsxs5("div", { style: { padding: "0 4px 4px" }, children: [
1855
- /* @__PURE__ */ jsxs5(
1446
+ /* @__PURE__ */ jsxs4("div", { style: { padding: "0 4px 4px" }, children: [
1447
+ /* @__PURE__ */ jsxs4(
1856
1448
  "button",
1857
1449
  {
1858
1450
  style: buttonStyle,
@@ -1860,12 +1452,12 @@ function Status({ onReset, position, onClose }) {
1860
1452
  onMouseEnter: onEnter,
1861
1453
  onMouseLeave: onLeave,
1862
1454
  children: [
1863
- /* @__PURE__ */ jsx6(FolderIcon, {}),
1455
+ /* @__PURE__ */ jsx5(FolderIcon, {}),
1864
1456
  "Open Folder"
1865
1457
  ]
1866
1458
  }
1867
1459
  ),
1868
- /* @__PURE__ */ jsxs5(
1460
+ /* @__PURE__ */ jsxs4(
1869
1461
  "button",
1870
1462
  {
1871
1463
  style: buttonStyle,
@@ -1873,12 +1465,12 @@ function Status({ onReset, position, onClose }) {
1873
1465
  onMouseEnter: onEnter,
1874
1466
  onMouseLeave: onLeave,
1875
1467
  children: [
1876
- /* @__PURE__ */ jsx6(CopyIcon, {}),
1468
+ /* @__PURE__ */ jsx5(CopyIcon, {}),
1877
1469
  "Copy Markdown"
1878
1470
  ]
1879
1471
  }
1880
1472
  ),
1881
- /* @__PURE__ */ jsxs5(
1473
+ /* @__PURE__ */ jsxs4(
1882
1474
  "button",
1883
1475
  {
1884
1476
  style: buttonStyle,
@@ -1887,12 +1479,12 @@ function Status({ onReset, position, onClose }) {
1887
1479
  onMouseEnter: onEnter,
1888
1480
  onMouseLeave: onLeave,
1889
1481
  children: [
1890
- /* @__PURE__ */ jsx6(PushIcon, {}),
1482
+ /* @__PURE__ */ jsx5(PushIcon, {}),
1891
1483
  pushing ? "Pushing..." : "Push to PR"
1892
1484
  ]
1893
1485
  }
1894
1486
  ),
1895
- /* @__PURE__ */ jsx6(
1487
+ /* @__PURE__ */ jsx5(
1896
1488
  "div",
1897
1489
  {
1898
1490
  style: {
@@ -1902,7 +1494,7 @@ function Status({ onReset, position, onClose }) {
1902
1494
  }
1903
1495
  }
1904
1496
  ),
1905
- /* @__PURE__ */ jsxs5(
1497
+ /* @__PURE__ */ jsxs4(
1906
1498
  "button",
1907
1499
  {
1908
1500
  style: { ...buttonStyle, color: "rgba(255,255,255,0.5)" },
@@ -1910,13 +1502,13 @@ function Status({ onReset, position, onClose }) {
1910
1502
  onMouseEnter: onEnter,
1911
1503
  onMouseLeave: onLeave,
1912
1504
  children: [
1913
- /* @__PURE__ */ jsx6(ResetIcon, {}),
1505
+ /* @__PURE__ */ jsx5(ResetIcon, {}),
1914
1506
  "Reset"
1915
1507
  ]
1916
1508
  }
1917
1509
  )
1918
1510
  ] }),
1919
- toast && /* @__PURE__ */ jsx6(
1511
+ toast && /* @__PURE__ */ jsx5(
1920
1512
  "div",
1921
1513
  {
1922
1514
  style: {
@@ -1942,14 +1534,14 @@ function Status({ onReset, position, onClose }) {
1942
1534
  );
1943
1535
  }
1944
1536
  function FolderIcon() {
1945
- return /* @__PURE__ */ jsx6(
1537
+ return /* @__PURE__ */ jsx5(
1946
1538
  "svg",
1947
1539
  {
1948
1540
  width: "14",
1949
1541
  height: "14",
1950
1542
  viewBox: "0 0 14 14",
1951
1543
  style: { color: "rgba(255,255,255,0.5)" },
1952
- children: /* @__PURE__ */ jsx6(
1544
+ children: /* @__PURE__ */ jsx5(
1953
1545
  "path",
1954
1546
  {
1955
1547
  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",
@@ -1962,7 +1554,7 @@ function FolderIcon() {
1962
1554
  );
1963
1555
  }
1964
1556
  function CopyIcon() {
1965
- return /* @__PURE__ */ jsxs5(
1557
+ return /* @__PURE__ */ jsxs4(
1966
1558
  "svg",
1967
1559
  {
1968
1560
  width: "14",
@@ -1970,7 +1562,7 @@ function CopyIcon() {
1970
1562
  viewBox: "0 0 14 14",
1971
1563
  style: { color: "rgba(255,255,255,0.5)" },
1972
1564
  children: [
1973
- /* @__PURE__ */ jsx6(
1565
+ /* @__PURE__ */ jsx5(
1974
1566
  "rect",
1975
1567
  {
1976
1568
  x: "4",
@@ -1983,7 +1575,7 @@ function CopyIcon() {
1983
1575
  strokeWidth: "1.3"
1984
1576
  }
1985
1577
  ),
1986
- /* @__PURE__ */ jsx6(
1578
+ /* @__PURE__ */ jsx5(
1987
1579
  "path",
1988
1580
  {
1989
1581
  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",
@@ -1997,14 +1589,14 @@ function CopyIcon() {
1997
1589
  );
1998
1590
  }
1999
1591
  function PushIcon() {
2000
- return /* @__PURE__ */ jsx6(
1592
+ return /* @__PURE__ */ jsx5(
2001
1593
  "svg",
2002
1594
  {
2003
1595
  width: "14",
2004
1596
  height: "14",
2005
1597
  viewBox: "0 0 14 14",
2006
1598
  style: { color: "rgba(255,255,255,0.5)" },
2007
- children: /* @__PURE__ */ jsx6(
1599
+ children: /* @__PURE__ */ jsx5(
2008
1600
  "path",
2009
1601
  {
2010
1602
  d: "M7 11V3m0 0L4 6m3-3l3 3",
@@ -2019,7 +1611,7 @@ function PushIcon() {
2019
1611
  );
2020
1612
  }
2021
1613
  function ResetIcon() {
2022
- return /* @__PURE__ */ jsxs5(
1614
+ return /* @__PURE__ */ jsxs4(
2023
1615
  "svg",
2024
1616
  {
2025
1617
  width: "14",
@@ -2027,7 +1619,7 @@ function ResetIcon() {
2027
1619
  viewBox: "0 0 14 14",
2028
1620
  style: { color: "rgba(255,255,255,0.4)" },
2029
1621
  children: [
2030
- /* @__PURE__ */ jsx6(
1622
+ /* @__PURE__ */ jsx5(
2031
1623
  "path",
2032
1624
  {
2033
1625
  d: "M2.5 7a4.5 4.5 0 118 2.5",
@@ -2037,7 +1629,7 @@ function ResetIcon() {
2037
1629
  strokeLinecap: "round"
2038
1630
  }
2039
1631
  ),
2040
- /* @__PURE__ */ jsx6(
1632
+ /* @__PURE__ */ jsx5(
2041
1633
  "path",
2042
1634
  {
2043
1635
  d: "M2.5 3v4h4",
@@ -2054,55 +1646,47 @@ function ResetIcon() {
2054
1646
  }
2055
1647
 
2056
1648
  // src/overlay/index.tsx
2057
- import { jsx as jsx7, jsxs as jsxs6 } from "react/jsx-runtime";
2058
- async function saveCapture(type, mode, dataUrl) {
1649
+ import { jsx as jsx6, jsxs as jsxs5 } from "react/jsx-runtime";
1650
+ async function saveCapture(mode, dataUrl) {
2059
1651
  try {
2060
1652
  const res = await fetch("/__afterbefore/save", {
2061
1653
  method: "POST",
2062
1654
  headers: { "Content-Type": "application/json" },
2063
- body: JSON.stringify({ type, mode, image: dataUrl })
1655
+ body: JSON.stringify({ mode, image: dataUrl })
2064
1656
  });
2065
1657
  if (!res.ok) throw new Error("Save failed");
2066
1658
  } catch {
2067
1659
  const link = document.createElement("a");
2068
- link.download = `${type}.png`;
1660
+ link.download = "screenshot.png";
2069
1661
  link.href = dataUrl;
2070
1662
  link.click();
2071
1663
  }
2072
1664
  }
2073
1665
  function AfterBefore() {
2074
1666
  const { state, captureComplete, reset } = useOverlayState();
2075
- const [statusOpen, setStatusOpen] = useState8(false);
2076
- const [toolbarActive, setToolbarActive] = useState8(false);
2077
- const [selectorActive, setSelectorActive] = useState8(false);
2078
- const [inspectorActive, setInspectorActive] = useState8(false);
2079
- const [loading, setLoading] = useState8(false);
2080
- const [selectedMode, setSelectedMode] = useState8("area");
2081
- const [magnetEnabled, setMagnetEnabled] = useState8(true);
2082
- const [areaRect, setAreaRect] = useState8(null);
2083
- const [areaAspect, setAreaAspect] = useState8(DEFAULT_AREA_ASPECT);
2084
- const [areaPresets, setAreaPresets] = useState8([]);
2085
- const [frameOnBlackEnabled, setFrameOnBlackEnabled] = useState8(false);
1667
+ const [statusOpen, setStatusOpen] = useState6(false);
1668
+ const [toolbarActive, setToolbarActive] = useState6(false);
1669
+ const [inspectorActive, setInspectorActive] = useState6(false);
1670
+ const [loading, setLoading] = useState6(false);
1671
+ const [selectedMode, setSelectedMode] = useState6("component");
1672
+ const [frameSettings, setFrameSettings] = useState6(DEFAULT_FRAME_SETTINGS);
2086
1673
  const iconPos = useRef5({ x: 24, y: 0 });
2087
- useEffect7(() => {
2088
- try {
2089
- setMagnetEnabled(localStorage.getItem("ab-magnet") !== "false");
2090
- } catch {
2091
- setMagnetEnabled(true);
2092
- }
2093
- try {
2094
- const savedPresets = localStorage.getItem("ab-area-presets");
2095
- setAreaPresets(savedPresets ? JSON.parse(savedPresets) : []);
2096
- } catch {
2097
- setAreaPresets([]);
2098
- }
1674
+ useEffect5(() => {
2099
1675
  try {
2100
- setFrameOnBlackEnabled(localStorage.getItem("ab-frame-black") === "true");
1676
+ const stored = localStorage.getItem("ab-frame-settings");
1677
+ if (stored) {
1678
+ setFrameSettings({ ...DEFAULT_FRAME_SETTINGS, ...JSON.parse(stored) });
1679
+ } else if (localStorage.getItem("ab-frame-black") === "true") {
1680
+ const migrated = { ...DEFAULT_FRAME_SETTINGS, enabled: true };
1681
+ setFrameSettings(migrated);
1682
+ localStorage.setItem("ab-frame-settings", JSON.stringify(migrated));
1683
+ localStorage.removeItem("ab-frame-black");
1684
+ }
2101
1685
  } catch {
2102
- setFrameOnBlackEnabled(false);
1686
+ setFrameSettings(DEFAULT_FRAME_SETTINGS);
2103
1687
  }
2104
1688
  }, []);
2105
- useEffect7(() => {
1689
+ useEffect5(() => {
2106
1690
  if (state.phase === "ready") {
2107
1691
  const timer = setTimeout(() => {
2108
1692
  reset();
@@ -2111,43 +1695,37 @@ function AfterBefore() {
2111
1695
  return () => clearTimeout(timer);
2112
1696
  }
2113
1697
  }, [state.phase, reset]);
2114
- const handlePositionChange = useCallback6(
1698
+ const handlePositionChange = useCallback5(
2115
1699
  (pos) => {
2116
1700
  iconPos.current = pos;
2117
1701
  },
2118
1702
  []
2119
1703
  );
2120
- const handleIconClick = useCallback6(() => {
1704
+ const handleIconClick = useCallback5(() => {
2121
1705
  if (loading) return;
2122
1706
  if (state.phase === "ready") {
2123
1707
  setStatusOpen((prev) => !prev);
2124
1708
  } else if (toolbarActive || inspectorActive) {
2125
1709
  setToolbarActive(false);
2126
- setSelectorActive(false);
2127
1710
  setInspectorActive(false);
2128
1711
  setStatusOpen(false);
2129
1712
  } else {
2130
1713
  setStatusOpen(false);
2131
1714
  if (selectedMode === "component") {
1715
+ setToolbarActive(true);
2132
1716
  setInspectorActive(true);
2133
1717
  } else {
2134
1718
  setToolbarActive(true);
2135
1719
  setInspectorActive(false);
2136
- if (selectedMode === "area") {
2137
- setSelectorActive(true);
2138
- setAreaRect(createInitialAreaRect());
2139
- setAreaAspect(DEFAULT_AREA_ASPECT);
2140
- }
2141
1720
  }
2142
1721
  }
2143
1722
  }, [state.phase, loading, toolbarActive, inspectorActive, selectedMode]);
2144
- const performCapture = useCallback6(
2145
- async (mode, area, element) => {
1723
+ const performCapture = useCallback5(
1724
+ async (mode, element) => {
2146
1725
  setLoading(true);
2147
1726
  try {
2148
- const dataUrl = await capture({ mode, area, element, frameOnBlack: frameOnBlackEnabled });
2149
- const type = state.phase === "idle" ? "before" : "after";
2150
- await saveCapture(type, mode, dataUrl);
1727
+ const dataUrl = await capture({ mode, element, frameSettings });
1728
+ await saveCapture(mode, dataUrl);
2151
1729
  captureComplete({
2152
1730
  dataUrl,
2153
1731
  mode,
@@ -2159,160 +1737,59 @@ function AfterBefore() {
2159
1737
  setLoading(false);
2160
1738
  }
2161
1739
  },
2162
- [state.phase, captureComplete, frameOnBlackEnabled]
1740
+ [captureComplete, frameSettings]
2163
1741
  );
2164
- const handleToolbarCapture = useCallback6(
1742
+ const handleToolbarCapture = useCallback5(
2165
1743
  (mode) => {
2166
- if (mode === "area") {
2167
- if (areaRect) {
2168
- setSelectorActive(false);
2169
- setToolbarActive(false);
2170
- performCapture("area", {
2171
- x: Math.round(areaRect.x),
2172
- y: Math.round(areaRect.y),
2173
- width: Math.round(areaRect.w),
2174
- height: Math.round(areaRect.h)
2175
- });
2176
- }
2177
- return;
2178
- } else if (mode === "viewport") {
1744
+ if (mode === "viewport") {
2179
1745
  setToolbarActive(false);
2180
1746
  performCapture("viewport");
2181
1747
  } else if (mode === "fullpage") {
2182
1748
  setToolbarActive(false);
2183
1749
  performCapture("fullpage");
2184
1750
  } else if (mode === "component") {
2185
- setToolbarActive(false);
2186
1751
  setInspectorActive(true);
2187
1752
  }
2188
1753
  },
2189
- [areaRect, performCapture]
1754
+ [performCapture]
2190
1755
  );
2191
- const handleToolbarCancel = useCallback6(() => {
1756
+ const handleToolbarCancel = useCallback5(() => {
2192
1757
  setToolbarActive(false);
2193
- setSelectorActive(false);
2194
1758
  }, []);
2195
- const handleComponentSelect = useCallback6(
1759
+ const handleComponentSelect = useCallback5(
2196
1760
  (element) => {
2197
1761
  setInspectorActive(false);
2198
- performCapture("component", void 0, element);
2199
- },
2200
- [performCapture]
2201
- );
2202
- const handleComponentCancel = useCallback6(() => {
2203
- setInspectorActive(false);
2204
- setToolbarActive(true);
2205
- }, []);
2206
- const handleAreaSelect = useCallback6(
2207
- (area) => {
2208
- setSelectorActive(false);
2209
1762
  setToolbarActive(false);
2210
- performCapture("area", area);
1763
+ performCapture("component", element);
2211
1764
  },
2212
1765
  [performCapture]
2213
1766
  );
2214
- const handleAreaCancel = useCallback6(() => {
2215
- setSelectorActive(false);
1767
+ const handleComponentCancel = useCallback5(() => {
1768
+ setInspectorActive(false);
2216
1769
  setToolbarActive(true);
2217
1770
  }, []);
2218
- const handleMagnetChange = useCallback6((enabled) => {
2219
- setMagnetEnabled(enabled);
2220
- try {
2221
- localStorage.setItem("ab-magnet", String(enabled));
2222
- } catch {
2223
- }
2224
- }, []);
2225
- const handleFrameOnBlackChange = useCallback6((enabled) => {
2226
- setFrameOnBlackEnabled(enabled);
1771
+ const handleFrameSettingsChange = useCallback5((next) => {
1772
+ setFrameSettings(next);
2227
1773
  try {
2228
- localStorage.setItem("ab-frame-black", String(enabled));
1774
+ localStorage.setItem("ab-frame-settings", JSON.stringify(next));
2229
1775
  } catch {
2230
1776
  }
2231
1777
  }, []);
2232
- const handleModeChange = useCallback6((mode) => {
1778
+ const handleModeChange = useCallback5((mode) => {
2233
1779
  setSelectedMode(mode);
2234
- if (mode === "area") {
2235
- setSelectorActive(true);
2236
- setAreaRect(createInitialAreaRect());
2237
- setAreaAspect(DEFAULT_AREA_ASPECT);
2238
- } else {
2239
- setSelectorActive(false);
2240
- }
2241
- }, []);
2242
- const handleAreaRectChange = useCallback6((nextRect) => {
2243
- setAreaRect(nextRect);
2244
- }, []);
2245
- const handleAreaSizeChange = useCallback6(
2246
- (field, value) => {
2247
- const nextValue = parseInt(value, 10);
2248
- if (Number.isNaN(nextValue) || nextValue < 20 || !areaRect) {
2249
- return;
2250
- }
2251
- 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];
2252
- if (field === "w") {
2253
- setAreaRect({
2254
- ...areaRect,
2255
- w: nextValue,
2256
- h: aspectRatio > 0 ? nextValue / aspectRatio : areaRect.h
2257
- });
2258
- } else {
2259
- setAreaRect({
2260
- ...areaRect,
2261
- h: nextValue,
2262
- w: aspectRatio > 0 ? nextValue * aspectRatio : areaRect.w
2263
- });
2264
- }
2265
- },
2266
- [areaAspect, areaRect]
2267
- );
2268
- const handleAreaPositionChange = useCallback6(
2269
- (field, value) => {
2270
- const nextValue = parseInt(value, 10);
2271
- if (Number.isNaN(nextValue) || !areaRect) {
2272
- return;
2273
- }
2274
- setAreaRect({ ...areaRect, [field]: nextValue });
2275
- },
2276
- [areaRect]
2277
- );
2278
- const handleAreaAspectChange = useCallback6(
2279
- (nextAspect) => {
2280
- setAreaAspect(nextAspect);
2281
- if (!areaRect || nextAspect === "Free") {
2282
- return;
2283
- }
2284
- const aspectRatio = { "16:9": 16 / 9, "4:3": 4 / 3, "1:1": 1, "3:2": 3 / 2, "21:9": 21 / 9 }[nextAspect];
2285
- setAreaRect({ ...areaRect, h: areaRect.w / aspectRatio });
2286
- },
2287
- [areaRect]
2288
- );
2289
- const handleAreaSavePreset = useCallback6(() => {
2290
- if (!areaRect) {
2291
- return;
2292
- }
2293
- const label = `${Math.round(areaRect.w)}x${Math.round(areaRect.h)}`;
2294
- const nextPresets = [
2295
- ...areaPresets.filter((preset) => preset.label !== label),
2296
- { label, rect: { ...areaRect } }
2297
- ];
2298
- setAreaPresets(nextPresets);
2299
- try {
2300
- localStorage.setItem("ab-area-presets", JSON.stringify(nextPresets));
2301
- } catch {
1780
+ if (mode === "component") {
1781
+ setInspectorActive(true);
2302
1782
  }
2303
- }, [areaPresets, areaRect]);
2304
- const handleAreaLoadPreset = useCallback6((preset) => {
2305
- setAreaRect({ ...preset.rect });
2306
1783
  }, []);
2307
- const handleReset = useCallback6(() => {
1784
+ const handleReset = useCallback5(() => {
2308
1785
  reset();
2309
1786
  setStatusOpen(false);
2310
1787
  }, [reset]);
2311
- const handleStatusClose = useCallback6(() => {
1788
+ const handleStatusClose = useCallback5(() => {
2312
1789
  setStatusOpen(false);
2313
1790
  }, []);
2314
- return /* @__PURE__ */ jsxs6("div", { "data-afterbefore": "true", children: [
2315
- /* @__PURE__ */ jsx7(
1791
+ return /* @__PURE__ */ jsxs5("div", { "data-afterbefore": "true", children: [
1792
+ /* @__PURE__ */ jsx6(
2316
1793
  Icon,
2317
1794
  {
2318
1795
  phase: state.phase,
@@ -2321,48 +1798,26 @@ function AfterBefore() {
2321
1798
  onPositionChange: handlePositionChange
2322
1799
  }
2323
1800
  ),
2324
- toolbarActive && !selectorActive && !inspectorActive && !loading && /* @__PURE__ */ jsx7(
1801
+ toolbarActive && !inspectorActive && !loading && /* @__PURE__ */ jsx6(
2325
1802
  CapturePreview,
2326
1803
  {
2327
1804
  mode: selectedMode,
2328
1805
  onClick: () => handleToolbarCapture(selectedMode)
2329
1806
  }
2330
1807
  ),
2331
- toolbarActive && !inspectorActive && /* @__PURE__ */ jsx7(
1808
+ toolbarActive && /* @__PURE__ */ jsx6(
2332
1809
  Toolbar,
2333
1810
  {
2334
1811
  selectedMode,
2335
1812
  onModeChange: handleModeChange,
2336
1813
  onCapture: handleToolbarCapture,
2337
1814
  onCancel: handleToolbarCancel,
2338
- magnetEnabled,
2339
- onMagnetChange: handleMagnetChange,
2340
- areaSelectionActive: selectorActive,
2341
- areaRect,
2342
- areaAspect,
2343
- areaPresets,
2344
- onAreaSizeChange: handleAreaSizeChange,
2345
- onAreaPositionChange: handleAreaPositionChange,
2346
- onAreaAspectChange: handleAreaAspectChange,
2347
- onAreaSavePreset: handleAreaSavePreset,
2348
- onAreaLoadPreset: handleAreaLoadPreset,
2349
- frameOnBlackEnabled,
2350
- onFrameOnBlackChange: handleFrameOnBlackChange
2351
- }
2352
- ),
2353
- selectorActive && /* @__PURE__ */ jsx7(
2354
- Selector,
2355
- {
2356
- rect: areaRect,
2357
- aspect: areaAspect,
2358
- onRectChange: handleAreaRectChange,
2359
- onSelect: handleAreaSelect,
2360
- onCancel: handleAreaCancel,
2361
- magnetEnabled
1815
+ frameSettings,
1816
+ onFrameSettingsChange: handleFrameSettingsChange
2362
1817
  }
2363
1818
  ),
2364
- inspectorActive && /* @__PURE__ */ jsx7(Inspector, { onSelect: handleComponentSelect, onCancel: handleComponentCancel }),
2365
- statusOpen && state.phase === "ready" && /* @__PURE__ */ jsx7(
1819
+ inspectorActive && /* @__PURE__ */ jsx6(Inspector, { onSelect: handleComponentSelect, onCancel: handleComponentCancel }),
1820
+ statusOpen && state.phase === "ready" && /* @__PURE__ */ jsx6(
2366
1821
  Status,
2367
1822
  {
2368
1823
  onReset: handleReset,