afterbefore 0.2.11 → 0.2.13

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
  {
@@ -262,21 +263,24 @@ function Icon({ phase, onClick, loading, onPositionChange }) {
262
263
  width: ICON_SIZE,
263
264
  height: ICON_SIZE,
264
265
  borderRadius: "50%",
265
- background: "rgba(30, 30, 30, 0.85)",
266
+ background: "rgba(32, 32, 36, 0.92)",
267
+ backdropFilter: "blur(20px)",
268
+ WebkitBackdropFilter: "blur(20px)",
269
+ border: "1px solid rgba(255, 255, 255, 0.1)",
266
270
  display: "flex",
267
271
  alignItems: "center",
268
272
  justifyContent: "center",
269
273
  cursor: "grab",
270
274
  zIndex: 2147483647,
271
- boxShadow: "0 2px 8px rgba(0,0,0,0.3)",
275
+ boxShadow: "0 8px 32px rgba(0, 0, 0, 0.4)",
272
276
  transition: "background 0.15s",
273
277
  userSelect: "none"
274
278
  },
275
279
  onMouseEnter: (e) => {
276
- e.currentTarget.style.background = "rgba(30, 30, 30, 0.95)";
280
+ e.currentTarget.style.background = "rgba(32, 32, 36, 0.98)";
277
281
  },
278
282
  onMouseLeave: (e) => {
279
- e.currentTarget.style.background = "rgba(30, 30, 30, 0.85)";
283
+ e.currentTarget.style.background = "rgba(32, 32, 36, 0.92)";
280
284
  },
281
285
  children: [
282
286
  /* @__PURE__ */ jsx(
@@ -284,10 +288,6 @@ function Icon({ phase, onClick, loading, onPositionChange }) {
284
288
  {
285
289
  dangerouslySetInnerHTML: {
286
290
  __html: `
287
- @keyframes ab-pulse {
288
- 0%, 100% { transform: scale(1); opacity: 1; }
289
- 50% { transform: scale(1.08); opacity: 0.85; }
290
- }
291
291
  @keyframes ab-spin {
292
292
  0% { transform: rotate(0deg); }
293
293
  100% { transform: rotate(360deg); }
@@ -298,113 +298,20 @@ function Icon({ phase, onClick, loading, onPositionChange }) {
298
298
  loading ? /* @__PURE__ */ jsx(
299
299
  LoaderCircle,
300
300
  {
301
- size: 20,
301
+ size: 16,
302
302
  strokeWidth: 2,
303
303
  style: { animation: "ab-spin 0.8s linear infinite", color: "white" }
304
304
  }
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
- )
305
+ ) : phase === "ready" ? /* @__PURE__ */ jsx(Check, { size: 16, strokeWidth: 2.6, color: "#4ade80" }) : /* @__PURE__ */ jsx(Camera, { size: 16, strokeWidth: 1.9, color: "white" })
343
306
  ]
344
307
  }
345
308
  );
346
309
  }
347
310
 
348
311
  // src/overlay/ui/preview.tsx
349
- import { useEffect as useEffect2, useState as useState3 } from "react";
350
312
  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
313
  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
314
  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
315
  if (mode === "viewport" || mode === "fullpage") {
409
316
  return /* @__PURE__ */ jsx2(
410
317
  "div",
@@ -416,518 +323,47 @@ function CapturePreview({ mode, onClick }) {
416
323
  inset: 0,
417
324
  zIndex: 2147483645,
418
325
  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
- )
326
+ background: "rgba(59, 130, 246, 0.15)",
327
+ boxShadow: "inset 0 0 0 2px rgba(59, 130, 246, 0.7)"
328
+ }
443
329
  }
444
330
  );
445
331
  }
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
- );
332
+ return null;
478
333
  }
479
334
 
480
335
  // src/overlay/ui/toolbar.tsx
481
- import { useEffect as useEffect4, useState as useState5 } from "react";
336
+ import { useEffect as useEffect2, useRef as useRef2, useState as useState3 } from "react";
482
337
  import {
483
338
  ChevronDown,
484
- Crop,
485
- FileText,
339
+ Maximize,
486
340
  FolderOpen,
487
341
  Frame,
488
- Magnet,
342
+ ImageIcon,
489
343
  Monitor,
490
344
  MousePointer2,
491
- Save,
492
- Settings2,
345
+ Palette,
346
+ Settings,
347
+ Trash2,
348
+ Upload,
493
349
  X
494
350
  } 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
351
  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
352
  var MODES = [
901
353
  { mode: "component", label: "Capture Component", icon: MousePointer2 },
902
- { mode: "area", label: "Capture Selected Area", icon: Crop },
903
354
  { mode: "viewport", label: "Capture Viewport", icon: Monitor },
904
- { mode: "fullpage", label: "Capture Full Page", icon: FileText }
355
+ { mode: "fullpage", label: "Capture Full Page", icon: Maximize }
905
356
  ];
906
357
  function Toolbar({
907
358
  selectedMode,
908
359
  onModeChange,
909
360
  onCapture,
910
361
  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
362
+ frameSettings,
363
+ onFrameSettingsChange
924
364
  }) {
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(() => {
365
+ const [settingsOpen, setSettingsOpen] = useState3(false);
366
+ useEffect2(() => {
931
367
  const onKey = (e) => {
932
368
  if (e.target?.tagName === "INPUT") {
933
369
  if (e.key === "Escape") {
@@ -940,14 +376,6 @@ function Toolbar({
940
376
  setSettingsOpen(false);
941
377
  return;
942
378
  }
943
- if (aspectOpen) {
944
- setAspectOpen(false);
945
- return;
946
- }
947
- if (savedOpen) {
948
- setSavedOpen(false);
949
- return;
950
- }
951
379
  onCancel();
952
380
  } else if (e.key === "Enter") {
953
381
  onCapture(selectedMode);
@@ -955,8 +383,8 @@ function Toolbar({
955
383
  };
956
384
  document.addEventListener("keydown", onKey);
957
385
  return () => document.removeEventListener("keydown", onKey);
958
- }, [aspectOpen, onCancel, onCapture, savedOpen, selectedMode, settingsOpen]);
959
- return /* @__PURE__ */ jsxs3(
386
+ }, [onCancel, onCapture, selectedMode, settingsOpen]);
387
+ return /* @__PURE__ */ jsx3(
960
388
  "div",
961
389
  {
962
390
  "data-afterbefore": "true",
@@ -976,156 +404,64 @@ function Toolbar({
976
404
  backdropFilter: "blur(20px)",
977
405
  WebkitBackdropFilter: "blur(20px)",
978
406
  border: "1px solid rgba(255, 255, 255, 0.1)",
979
- borderRadius: 18,
980
- padding: "6px 10px",
407
+ borderRadius: 999,
408
+ padding: 6,
981
409
  boxShadow: "0 8px 32px rgba(0, 0, 0, 0.4)",
982
410
  fontFamily: "system-ui, -apple-system, sans-serif"
983
411
  },
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 })
412
+ children: /* @__PURE__ */ jsxs2("div", { style: { display: "flex", alignItems: "center", gap: 0 }, children: [
413
+ /* @__PURE__ */ jsx3(CloseButton, { onClick: onCancel }),
414
+ /* @__PURE__ */ jsx3("div", { style: { display: "flex", alignItems: "center", gap: 2, padding: "0 4px" }, children: MODES.map(({ mode, label, icon: Icon2 }) => /* @__PURE__ */ jsx3(
415
+ ModeButton,
416
+ {
417
+ label,
418
+ selected: selectedMode === mode,
419
+ onClick: () => {
420
+ setSettingsOpen(false);
421
+ onModeChange(mode);
997
422
  },
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
- ] }),
1018
- activeAreaRect && /* @__PURE__ */ jsxs3(Fragment2, { children: [
1019
- /* @__PURE__ */ jsx4(Separator, { vertical: false }),
1020
- /* @__PURE__ */ jsxs3(
1021
- "div",
1022
- {
1023
- style: {
1024
- display: "flex",
1025
- alignItems: "center",
1026
- gap: 8,
1027
- flexWrap: "wrap",
1028
- justifyContent: "center"
1029
- },
1030
- children: [
1031
- /* @__PURE__ */ jsxs3(ControlGroup, { label: "Size", children: [
1032
- /* @__PURE__ */ jsx4(NumInput, { value: Math.round(activeAreaRect.w), onChange: (value) => onAreaSizeChange("w", value) }),
1033
- /* @__PURE__ */ jsx4(StaticText, { children: "x" }),
1034
- /* @__PURE__ */ jsx4(NumInput, { value: Math.round(activeAreaRect.h), onChange: (value) => onAreaSizeChange("h", value) })
1035
- ] }),
1036
- /* @__PURE__ */ jsxs3(ControlGroup, { label: "Position", children: [
1037
- /* @__PURE__ */ jsx4(NumInput, { value: Math.round(activeAreaRect.x), onChange: (value) => onAreaPositionChange("x", value) }),
1038
- /* @__PURE__ */ jsx4(StaticText, { children: "x" }),
1039
- /* @__PURE__ */ jsx4(NumInput, { value: Math.round(activeAreaRect.y), onChange: (value) => onAreaPositionChange("y", value) })
1040
- ] }),
1041
- /* @__PURE__ */ jsxs3("div", { style: { position: "relative" }, children: [
1042
- /* @__PURE__ */ jsxs3(
1043
- DropButton,
1044
- {
1045
- active: areaAspect !== "Free",
1046
- onClick: () => {
1047
- setSavedOpen(false);
1048
- setAspectOpen((prev) => !prev);
1049
- },
1050
- children: [
1051
- areaAspect,
1052
- /* @__PURE__ */ jsx4(ChevronDown, { size: 14, strokeWidth: 1.8 })
1053
- ]
1054
- }
1055
- ),
1056
- aspectOpen && /* @__PURE__ */ jsx4(DropMenu, { children: AREA_ASPECT_RATIOS.map((item) => /* @__PURE__ */ jsx4(
1057
- DropItem,
1058
- {
1059
- active: item.label === areaAspect,
1060
- onClick: () => {
1061
- onAreaAspectChange(item.label);
1062
- setAspectOpen(false);
1063
- },
1064
- children: item.label
1065
- },
1066
- item.label
1067
- )) })
1068
- ] }),
1069
- /* @__PURE__ */ jsxs3("div", { style: { position: "relative" }, children: [
1070
- /* @__PURE__ */ jsxs3(
1071
- DropButton,
1072
- {
1073
- onClick: () => {
1074
- setAspectOpen(false);
1075
- setSavedOpen((prev) => !prev);
1076
- },
1077
- children: [
1078
- /* @__PURE__ */ jsx4(Save, { size: 14, strokeWidth: 1.8 }),
1079
- "Saved",
1080
- /* @__PURE__ */ jsx4(ChevronDown, { size: 14, strokeWidth: 1.8 })
1081
- ]
1082
- }
1083
- ),
1084
- savedOpen && /* @__PURE__ */ jsxs3(DropMenu, { children: [
1085
- /* @__PURE__ */ jsx4(DropItem, { accent: true, onClick: onAreaSavePreset, children: "Save current" }),
1086
- areaPresets.length > 0 && /* @__PURE__ */ jsx4(MenuDivider, {}),
1087
- areaPresets.map((preset) => /* @__PURE__ */ jsx4(
1088
- DropItem,
1089
- {
1090
- onClick: () => {
1091
- onAreaLoadPreset(preset);
1092
- setSavedOpen(false);
1093
- },
1094
- children: preset.label
1095
- },
1096
- preset.label
1097
- )),
1098
- areaPresets.length === 0 && /* @__PURE__ */ jsx4(
1099
- "div",
1100
- {
1101
- style: {
1102
- padding: "6px 12px",
1103
- color: "rgba(255,255,255,0.3)",
1104
- fontSize: 12
1105
- },
1106
- children: "No saved areas"
1107
- }
1108
- )
1109
- ] })
1110
- ] })
1111
- ]
1112
- }
1113
- )
1114
- ] })
1115
- ]
423
+ children: /* @__PURE__ */ jsx3(Icon2, { size: 16, strokeWidth: 1.7 })
424
+ },
425
+ mode
426
+ )) }),
427
+ /* @__PURE__ */ jsx3(Separator, {}),
428
+ /* @__PURE__ */ jsx3(
429
+ SettingsButton,
430
+ {
431
+ open: settingsOpen,
432
+ onClick: () => setSettingsOpen((prev) => !prev),
433
+ selectedMode,
434
+ frameSettings,
435
+ onFrameSettingsChange
436
+ }
437
+ )
438
+ ] })
1116
439
  }
1117
440
  );
1118
441
  }
1119
442
  function CloseButton({ onClick }) {
1120
- const [hovered, setHovered] = useState5(false);
1121
- return /* @__PURE__ */ jsx4(
443
+ const [hovered, setHovered] = useState3(false);
444
+ return /* @__PURE__ */ jsx3(
1122
445
  "button",
1123
446
  {
1124
447
  onClick,
1125
448
  onMouseEnter: () => setHovered(true),
1126
449
  onMouseLeave: () => setHovered(false),
1127
- style: circleButtonStyle(hovered, true),
1128
- children: /* @__PURE__ */ jsx4(X, { size: 18, strokeWidth: 1.9 })
450
+ style: {
451
+ width: 32,
452
+ height: 32,
453
+ borderRadius: 10,
454
+ border: "none",
455
+ background: hovered ? "rgba(255, 255, 255, 0.12)" : "transparent",
456
+ display: "flex",
457
+ alignItems: "center",
458
+ justifyContent: "center",
459
+ cursor: "pointer",
460
+ padding: 0,
461
+ color: hovered ? "rgba(255, 255, 255, 0.96)" : "rgba(255, 255, 255, 0.52)",
462
+ transition: "background 0.12s ease, color 0.12s ease"
463
+ },
464
+ children: /* @__PURE__ */ jsx3(X, { size: 16, strokeWidth: 1.7 })
1129
465
  }
1130
466
  );
1131
467
  }
@@ -1135,9 +471,9 @@ function ModeButton({
1135
471
  selected,
1136
472
  onClick
1137
473
  }) {
1138
- const [hovered, setHovered] = useState5(false);
1139
- return /* @__PURE__ */ jsxs3("div", { style: { position: "relative" }, children: [
1140
- hovered && /* @__PURE__ */ jsx4(
474
+ const [hovered, setHovered] = useState3(false);
475
+ return /* @__PURE__ */ jsxs2("div", { style: { position: "relative" }, children: [
476
+ hovered && /* @__PURE__ */ jsx3(
1141
477
  "div",
1142
478
  {
1143
479
  style: {
@@ -1160,24 +496,24 @@ function ModeButton({
1160
496
  children: label
1161
497
  }
1162
498
  ),
1163
- /* @__PURE__ */ jsx4(
499
+ /* @__PURE__ */ jsx3(
1164
500
  "button",
1165
501
  {
1166
502
  onClick,
1167
503
  onMouseEnter: () => setHovered(true),
1168
504
  onMouseLeave: () => setHovered(false),
1169
505
  style: {
1170
- width: 42,
1171
- height: 40,
1172
- borderRadius: 12,
506
+ width: 32,
507
+ height: 32,
508
+ borderRadius: 10,
1173
509
  border: "none",
1174
- background: selected ? "rgba(255, 255, 255, 0.15)" : hovered ? "rgba(255, 255, 255, 0.08)" : "transparent",
510
+ background: selected || hovered ? "rgba(255, 255, 255, 0.12)" : "transparent",
1175
511
  display: "flex",
1176
512
  alignItems: "center",
1177
513
  justifyContent: "center",
1178
514
  cursor: "pointer",
1179
515
  padding: 0,
1180
- color: selected ? "rgba(255, 255, 255, 0.96)" : "rgba(255, 255, 255, 0.52)",
516
+ color: selected || hovered ? "rgba(255, 255, 255, 0.96)" : "rgba(255, 255, 255, 0.52)",
1181
517
  transition: "background 0.12s ease, color 0.12s ease"
1182
518
  },
1183
519
  children
@@ -1185,126 +521,465 @@ function ModeButton({
1185
521
  )
1186
522
  ] });
1187
523
  }
1188
- function SettingsButton({
1189
- open,
1190
- onClick,
1191
- selectedMode,
1192
- magnetEnabled,
1193
- onMagnetChange,
1194
- frameOnBlackEnabled,
1195
- onFrameOnBlackChange
524
+ function SettingsButton({
525
+ open,
526
+ onClick,
527
+ selectedMode,
528
+ frameSettings,
529
+ onFrameSettingsChange
530
+ }) {
531
+ const [hovered, setHovered] = useState3(false);
532
+ const [saveDir, setSaveDir] = useState3(null);
533
+ const [picking, setPicking] = useState3(false);
534
+ useEffect2(() => {
535
+ if (!open) return;
536
+ fetch("/__afterbefore/config").then((r) => r.json()).then((data) => setSaveDir(data.saveDir)).catch(() => {
537
+ });
538
+ }, [open]);
539
+ const handlePickFolder = async () => {
540
+ setPicking(true);
541
+ try {
542
+ const res = await fetch("/__afterbefore/pick-folder", { method: "POST" });
543
+ const data = await res.json();
544
+ if (data.folder) {
545
+ await fetch("/__afterbefore/config", {
546
+ method: "POST",
547
+ headers: { "Content-Type": "application/json" },
548
+ body: JSON.stringify({ saveDir: data.folder })
549
+ });
550
+ setSaveDir(data.folder);
551
+ }
552
+ } catch {
553
+ } finally {
554
+ setPicking(false);
555
+ }
556
+ };
557
+ const shortDir = saveDir ? saveDir.replace(/^\/Users\/[^/]+/, "~") : "~/Desktop";
558
+ return /* @__PURE__ */ jsxs2("div", { style: { position: "relative" }, children: [
559
+ /* @__PURE__ */ jsx3(
560
+ "button",
561
+ {
562
+ onClick,
563
+ onMouseEnter: () => setHovered(true),
564
+ onMouseLeave: () => setHovered(false),
565
+ style: {
566
+ width: 32,
567
+ height: 32,
568
+ borderRadius: "50%",
569
+ border: "none",
570
+ background: open || hovered ? "rgba(255, 255, 255, 0.12)" : "transparent",
571
+ display: "flex",
572
+ alignItems: "center",
573
+ justifyContent: "center",
574
+ cursor: "pointer",
575
+ padding: 0,
576
+ color: open || hovered ? "rgba(255, 255, 255, 0.96)" : "rgba(255, 255, 255, 0.52)",
577
+ transition: "background 0.12s ease, color 0.12s ease"
578
+ },
579
+ children: /* @__PURE__ */ jsx3(Settings, { size: 16, strokeWidth: 1.7 })
580
+ }
581
+ ),
582
+ open && /* @__PURE__ */ jsxs2(
583
+ "div",
584
+ {
585
+ style: {
586
+ position: "absolute",
587
+ left: "50%",
588
+ bottom: "calc(100% + 12px)",
589
+ transform: "translateX(-50%)",
590
+ minWidth: 260,
591
+ padding: "10px 12px",
592
+ borderRadius: 12,
593
+ background: "rgba(32, 32, 36, 0.96)",
594
+ border: "1px solid rgba(255, 255, 255, 0.1)",
595
+ boxShadow: "0 14px 36px rgba(0, 0, 0, 0.32)",
596
+ backdropFilter: "blur(20px)",
597
+ WebkitBackdropFilter: "blur(20px)"
598
+ },
599
+ children: [
600
+ /* @__PURE__ */ jsx3(
601
+ "div",
602
+ {
603
+ style: {
604
+ fontSize: 11,
605
+ color: "rgba(255, 255, 255, 0.46)",
606
+ letterSpacing: "0.03em",
607
+ textTransform: "uppercase",
608
+ marginBottom: 10
609
+ },
610
+ children: "Settings"
611
+ }
612
+ ),
613
+ selectedMode === "component" && /* @__PURE__ */ jsxs2(Fragment, { children: [
614
+ /* @__PURE__ */ jsx3(
615
+ ToggleRow,
616
+ {
617
+ icon: /* @__PURE__ */ jsx3(Frame, { size: 15, strokeWidth: 1.8 }),
618
+ label: "Frame",
619
+ enabled: frameSettings.enabled,
620
+ onChange: () => onFrameSettingsChange({ ...frameSettings, enabled: !frameSettings.enabled })
621
+ }
622
+ ),
623
+ frameSettings.enabled && /* @__PURE__ */ jsxs2("div", { style: { marginTop: 8, display: "flex", flexDirection: "column", gap: 10 }, children: [
624
+ /* @__PURE__ */ jsx3(
625
+ FrameSizeControl,
626
+ {
627
+ size: frameSettings.size,
628
+ onChange: (size) => onFrameSettingsChange({ ...frameSettings, size })
629
+ }
630
+ ),
631
+ /* @__PURE__ */ jsx3(
632
+ FrameBackgroundControl,
633
+ {
634
+ bgType: frameSettings.bgType,
635
+ bgColor: frameSettings.bgColor,
636
+ bgImage: frameSettings.bgImage,
637
+ frameSize: frameSettings.size,
638
+ onChange: (updates) => onFrameSettingsChange({ ...frameSettings, ...updates })
639
+ }
640
+ )
641
+ ] })
642
+ ] }),
643
+ selectedMode === "component" && /* @__PURE__ */ jsx3("div", { style: { height: 1, background: "rgba(255,255,255,0.08)", margin: "8px 0" } }),
644
+ /* @__PURE__ */ jsx3(
645
+ SaveLocationRow,
646
+ {
647
+ dir: shortDir,
648
+ picking,
649
+ onPick: handlePickFolder
650
+ }
651
+ )
652
+ ]
653
+ }
654
+ )
655
+ ] });
656
+ }
657
+ function FrameSizeControl({
658
+ size,
659
+ onChange
660
+ }) {
661
+ const [sizeOpen, setSizeOpen] = useState3(false);
662
+ const currentPreset = FRAME_SIZE_PRESETS.find((p) => p.w === size.w && p.h === size.h);
663
+ const isCustom = !currentPreset;
664
+ return /* @__PURE__ */ jsxs2("div", { children: [
665
+ /* @__PURE__ */ jsx3(SettingsLabel, { children: "Size" }),
666
+ /* @__PURE__ */ jsx3("div", { style: { display: "flex", alignItems: "center", gap: 6, marginTop: 4 }, children: /* @__PURE__ */ jsxs2("div", { style: { position: "relative", flex: 1 }, children: [
667
+ /* @__PURE__ */ jsxs2(
668
+ "button",
669
+ {
670
+ onClick: () => setSizeOpen((prev) => !prev),
671
+ style: {
672
+ display: "flex",
673
+ alignItems: "center",
674
+ justifyContent: "space-between",
675
+ gap: 6,
676
+ width: "100%",
677
+ height: 30,
678
+ padding: "0 8px",
679
+ borderRadius: 7,
680
+ border: "1px solid rgba(255,255,255,0.1)",
681
+ background: "rgba(255,255,255,0.07)",
682
+ color: "rgba(255,255,255,0.88)",
683
+ cursor: "pointer",
684
+ fontSize: 12,
685
+ fontFamily: "inherit"
686
+ },
687
+ children: [
688
+ currentPreset ? currentPreset.label : "Custom",
689
+ /* @__PURE__ */ jsx3(ChevronDown, { size: 12, strokeWidth: 2 })
690
+ ]
691
+ }
692
+ ),
693
+ sizeOpen && /* @__PURE__ */ jsxs2(
694
+ "div",
695
+ {
696
+ style: {
697
+ position: "absolute",
698
+ bottom: "calc(100% + 4px)",
699
+ left: 0,
700
+ right: 0,
701
+ background: "rgba(32, 32, 36, 0.96)",
702
+ border: "1px solid rgba(255, 255, 255, 0.1)",
703
+ borderRadius: 8,
704
+ padding: "4px 0",
705
+ boxShadow: "0 10px 30px rgba(0, 0, 0, 0.3)",
706
+ backdropFilter: "blur(20px)",
707
+ WebkitBackdropFilter: "blur(20px)",
708
+ zIndex: 1
709
+ },
710
+ children: [
711
+ FRAME_SIZE_PRESETS.map((preset) => /* @__PURE__ */ jsx3(
712
+ DropItem,
713
+ {
714
+ active: !isCustom && preset.w === size.w && preset.h === size.h,
715
+ onClick: () => {
716
+ onChange({ w: preset.w, h: preset.h });
717
+ setSizeOpen(false);
718
+ },
719
+ children: preset.label
720
+ },
721
+ preset.label
722
+ )),
723
+ /* @__PURE__ */ jsx3(DropItem, { active: isCustom, onClick: () => setSizeOpen(false), children: "Custom" })
724
+ ]
725
+ }
726
+ )
727
+ ] }) }),
728
+ isCustom && /* @__PURE__ */ jsxs2("div", { style: { display: "flex", alignItems: "center", gap: 4, marginTop: 6 }, children: [
729
+ /* @__PURE__ */ jsx3(
730
+ NumInput,
731
+ {
732
+ value: size.w,
733
+ onChange: (v) => {
734
+ const n = parseInt(v, 10);
735
+ if (!Number.isNaN(n) && n > 0) onChange({ ...size, w: n });
736
+ }
737
+ }
738
+ ),
739
+ /* @__PURE__ */ jsx3(StaticText, { children: "x" }),
740
+ /* @__PURE__ */ jsx3(
741
+ NumInput,
742
+ {
743
+ value: size.h,
744
+ onChange: (v) => {
745
+ const n = parseInt(v, 10);
746
+ if (!Number.isNaN(n) && n > 0) onChange({ ...size, h: n });
747
+ }
748
+ }
749
+ )
750
+ ] })
751
+ ] });
752
+ }
753
+ function FrameBackgroundControl({
754
+ bgType,
755
+ bgColor,
756
+ bgImage,
757
+ frameSize,
758
+ onChange
759
+ }) {
760
+ const fileInputRef = useRef2(null);
761
+ const handleFileSelect = (e) => {
762
+ const file = e.target.files?.[0];
763
+ if (!file) return;
764
+ const reader = new FileReader();
765
+ reader.onload = () => {
766
+ const dataUrl = reader.result;
767
+ if (dataUrl.length > 2 * 1024 * 1024) {
768
+ downscaleImage(dataUrl, frameSize.w, frameSize.h).then((scaled) => {
769
+ onChange({ bgType: "image", bgImage: scaled });
770
+ });
771
+ } else {
772
+ onChange({ bgType: "image", bgImage: dataUrl });
773
+ }
774
+ };
775
+ reader.readAsDataURL(file);
776
+ e.target.value = "";
777
+ };
778
+ return /* @__PURE__ */ jsxs2("div", { children: [
779
+ /* @__PURE__ */ jsx3(SettingsLabel, { children: "Background" }),
780
+ /* @__PURE__ */ jsxs2("div", { style: { display: "flex", gap: 2, marginTop: 4 }, children: [
781
+ /* @__PURE__ */ jsxs2(
782
+ SegmentButton,
783
+ {
784
+ active: bgType === "color",
785
+ onClick: () => onChange({ bgType: "color" }),
786
+ style: { borderRadius: "6px 0 0 6px" },
787
+ children: [
788
+ /* @__PURE__ */ jsx3(Palette, { size: 12, strokeWidth: 2 }),
789
+ "Color"
790
+ ]
791
+ }
792
+ ),
793
+ /* @__PURE__ */ jsxs2(
794
+ SegmentButton,
795
+ {
796
+ active: bgType === "image",
797
+ onClick: () => onChange({ bgType: "image" }),
798
+ style: { borderRadius: "0 6px 6px 0" },
799
+ children: [
800
+ /* @__PURE__ */ jsx3(ImageIcon, { size: 12, strokeWidth: 2 }),
801
+ "Image"
802
+ ]
803
+ }
804
+ )
805
+ ] }),
806
+ bgType === "color" && /* @__PURE__ */ jsxs2("div", { style: { display: "flex", alignItems: "center", gap: 8, marginTop: 6 }, children: [
807
+ /* @__PURE__ */ jsx3(ColorSwatch, { color: bgColor, onChange: (c) => onChange({ bgColor: c }) }),
808
+ /* @__PURE__ */ jsx3("span", { style: { fontSize: 12, color: "rgba(255,255,255,0.6)" }, children: bgColor })
809
+ ] }),
810
+ bgType === "image" && /* @__PURE__ */ jsxs2("div", { style: { marginTop: 6 }, children: [
811
+ /* @__PURE__ */ jsx3(
812
+ "input",
813
+ {
814
+ ref: fileInputRef,
815
+ type: "file",
816
+ accept: "image/*",
817
+ onChange: handleFileSelect,
818
+ style: { display: "none" }
819
+ }
820
+ ),
821
+ bgImage ? /* @__PURE__ */ jsxs2("div", { style: { display: "flex", alignItems: "center", gap: 8 }, children: [
822
+ /* @__PURE__ */ jsx3(
823
+ "img",
824
+ {
825
+ src: bgImage,
826
+ alt: "",
827
+ style: {
828
+ width: 48,
829
+ height: 28,
830
+ borderRadius: 4,
831
+ objectFit: "cover",
832
+ border: "1px solid rgba(255,255,255,0.12)"
833
+ }
834
+ }
835
+ ),
836
+ /* @__PURE__ */ jsxs2(SmallButton, { onClick: () => fileInputRef.current?.click(), children: [
837
+ /* @__PURE__ */ jsx3(Upload, { size: 11, strokeWidth: 2 }),
838
+ "Replace"
839
+ ] }),
840
+ /* @__PURE__ */ jsx3(SmallButton, { onClick: () => onChange({ bgImage: null }), children: /* @__PURE__ */ jsx3(Trash2, { size: 11, strokeWidth: 2 }) })
841
+ ] }) : /* @__PURE__ */ jsxs2(SmallButton, { onClick: () => fileInputRef.current?.click(), children: [
842
+ /* @__PURE__ */ jsx3(Upload, { size: 11, strokeWidth: 2 }),
843
+ "Upload image"
844
+ ] })
845
+ ] })
846
+ ] });
847
+ }
848
+ function SettingsLabel({ children }) {
849
+ return /* @__PURE__ */ jsx3(
850
+ "div",
851
+ {
852
+ style: {
853
+ fontSize: 11,
854
+ color: "rgba(255, 255, 255, 0.42)",
855
+ letterSpacing: "0.02em"
856
+ },
857
+ children
858
+ }
859
+ );
860
+ }
861
+ function SegmentButton({
862
+ children,
863
+ active,
864
+ onClick,
865
+ style
866
+ }) {
867
+ return /* @__PURE__ */ jsx3(
868
+ "button",
869
+ {
870
+ onClick,
871
+ style: {
872
+ display: "flex",
873
+ alignItems: "center",
874
+ gap: 4,
875
+ height: 26,
876
+ padding: "0 10px",
877
+ border: "1px solid rgba(255,255,255,0.1)",
878
+ background: active ? "rgba(255,255,255,0.14)" : "rgba(255,255,255,0.04)",
879
+ color: active ? "rgba(255,255,255,0.92)" : "rgba(255,255,255,0.5)",
880
+ cursor: "pointer",
881
+ fontSize: 11,
882
+ fontFamily: "inherit",
883
+ transition: "background 0.12s ease, color 0.12s ease",
884
+ ...style
885
+ },
886
+ children
887
+ }
888
+ );
889
+ }
890
+ function ColorSwatch({
891
+ color,
892
+ onChange
1196
893
  }) {
1197
- const [hovered, setHovered] = useState5(false);
1198
- const [saveDir, setSaveDir] = useState5(null);
1199
- const [picking, setPicking] = useState5(false);
1200
- useEffect4(() => {
1201
- if (!open) return;
1202
- fetch("/__afterbefore/config").then((r) => r.json()).then((data) => setSaveDir(data.saveDir)).catch(() => {
1203
- });
1204
- }, [open]);
1205
- const handlePickFolder = async () => {
1206
- setPicking(true);
1207
- try {
1208
- const res = await fetch("/__afterbefore/pick-folder", { method: "POST" });
1209
- const data = await res.json();
1210
- if (data.folder) {
1211
- await fetch("/__afterbefore/config", {
1212
- method: "POST",
1213
- headers: { "Content-Type": "application/json" },
1214
- body: JSON.stringify({ saveDir: data.folder })
1215
- });
1216
- setSaveDir(data.folder);
1217
- }
1218
- } catch {
1219
- } finally {
1220
- setPicking(false);
1221
- }
1222
- };
1223
- const shortDir = saveDir ? saveDir.replace(/^\/Users\/[^/]+/, "~") : "~/Desktop";
1224
- return /* @__PURE__ */ jsxs3("div", { style: { position: "relative", marginRight: 6 }, children: [
1225
- /* @__PURE__ */ jsx4(
894
+ const inputRef = useRef2(null);
895
+ return /* @__PURE__ */ jsxs2("div", { style: { position: "relative" }, children: [
896
+ /* @__PURE__ */ jsx3(
1226
897
  "button",
1227
898
  {
1228
- onClick,
1229
- onMouseEnter: () => setHovered(true),
1230
- onMouseLeave: () => setHovered(false),
899
+ onClick: () => inputRef.current?.click(),
1231
900
  style: {
1232
- ...circleButtonStyle(open || hovered, false),
1233
- color: "rgba(255, 255, 255, 0.58)"
1234
- },
1235
- children: /* @__PURE__ */ jsx4(Settings2, { size: 18, strokeWidth: 1.7 })
901
+ width: 24,
902
+ height: 24,
903
+ borderRadius: 6,
904
+ border: "1px solid rgba(255,255,255,0.18)",
905
+ background: color,
906
+ cursor: "pointer",
907
+ padding: 0
908
+ }
1236
909
  }
1237
910
  ),
1238
- open && /* @__PURE__ */ jsxs3(
1239
- "div",
911
+ /* @__PURE__ */ jsx3(
912
+ "input",
1240
913
  {
914
+ ref: inputRef,
915
+ type: "color",
916
+ value: color,
917
+ onChange: (e) => onChange(e.target.value),
1241
918
  style: {
1242
919
  position: "absolute",
1243
- left: "50%",
1244
- bottom: "calc(100% + 12px)",
1245
- transform: "translateX(-50%)",
1246
- minWidth: 210,
1247
- padding: "10px 12px",
1248
- borderRadius: 12,
1249
- background: "rgba(32, 32, 36, 0.96)",
1250
- border: "1px solid rgba(255, 255, 255, 0.1)",
1251
- boxShadow: "0 14px 36px rgba(0, 0, 0, 0.32)",
1252
- backdropFilter: "blur(20px)",
1253
- WebkitBackdropFilter: "blur(20px)"
1254
- },
1255
- children: [
1256
- /* @__PURE__ */ jsx4(
1257
- "div",
1258
- {
1259
- style: {
1260
- fontSize: 11,
1261
- color: "rgba(255, 255, 255, 0.46)",
1262
- letterSpacing: "0.03em",
1263
- textTransform: "uppercase",
1264
- marginBottom: 10
1265
- },
1266
- children: "Settings"
1267
- }
1268
- ),
1269
- selectedMode === "area" && /* @__PURE__ */ jsx4(
1270
- ToggleRow,
1271
- {
1272
- icon: /* @__PURE__ */ jsx4(Magnet, { size: 15, strokeWidth: 1.8 }),
1273
- label: "Magnet snap",
1274
- enabled: magnetEnabled,
1275
- onChange: () => onMagnetChange(!magnetEnabled)
1276
- }
1277
- ),
1278
- selectedMode === "component" && /* @__PURE__ */ jsx4(
1279
- ToggleRow,
1280
- {
1281
- icon: /* @__PURE__ */ jsx4(Frame, { size: 15, strokeWidth: 1.8 }),
1282
- label: "1920 x 1080 frame",
1283
- enabled: frameOnBlackEnabled,
1284
- onChange: () => onFrameOnBlackChange(!frameOnBlackEnabled)
1285
- }
1286
- ),
1287
- (selectedMode === "area" || selectedMode === "component") && /* @__PURE__ */ jsx4("div", { style: { height: 1, background: "rgba(255,255,255,0.08)", margin: "8px 0" } }),
1288
- /* @__PURE__ */ jsx4(
1289
- SaveLocationRow,
1290
- {
1291
- dir: shortDir,
1292
- picking,
1293
- onPick: handlePickFolder
1294
- }
1295
- )
1296
- ]
920
+ top: 0,
921
+ left: 0,
922
+ width: 0,
923
+ height: 0,
924
+ opacity: 0,
925
+ pointerEvents: "none"
926
+ }
1297
927
  }
1298
928
  )
1299
929
  ] });
1300
930
  }
931
+ function SmallButton({
932
+ children,
933
+ onClick
934
+ }) {
935
+ const [hovered, setHovered] = useState3(false);
936
+ return /* @__PURE__ */ jsx3(
937
+ "button",
938
+ {
939
+ onClick,
940
+ onMouseEnter: () => setHovered(true),
941
+ onMouseLeave: () => setHovered(false),
942
+ style: {
943
+ display: "flex",
944
+ alignItems: "center",
945
+ gap: 4,
946
+ padding: "3px 8px",
947
+ borderRadius: 6,
948
+ border: "1px solid rgba(255,255,255,0.12)",
949
+ background: hovered ? "rgba(255,255,255,0.12)" : "rgba(255,255,255,0.06)",
950
+ color: "rgba(255,255,255,0.78)",
951
+ fontSize: 11,
952
+ cursor: "pointer",
953
+ fontFamily: "inherit",
954
+ transition: "background 0.12s ease"
955
+ },
956
+ children
957
+ }
958
+ );
959
+ }
960
+ async function downscaleImage(dataUrl, maxW, maxH) {
961
+ return new Promise((resolve) => {
962
+ const img = new Image();
963
+ img.onload = () => {
964
+ const canvas = document.createElement("canvas");
965
+ const scale = Math.min(maxW / img.width, maxH / img.height, 1);
966
+ canvas.width = img.width * scale;
967
+ canvas.height = img.height * scale;
968
+ const ctx = canvas.getContext("2d");
969
+ ctx.drawImage(img, 0, 0, canvas.width, canvas.height);
970
+ resolve(canvas.toDataURL("image/jpeg", 0.85));
971
+ };
972
+ img.onerror = () => resolve(dataUrl);
973
+ img.src = dataUrl;
974
+ });
975
+ }
1301
976
  function SaveLocationRow({
1302
977
  dir,
1303
978
  picking,
1304
979
  onPick
1305
980
  }) {
1306
- const [btnHovered, setBtnHovered] = useState5(false);
1307
- return /* @__PURE__ */ jsx4("div", { style: { display: "flex", flexDirection: "column", gap: 6 }, children: /* @__PURE__ */ jsxs3(
981
+ const [btnHovered, setBtnHovered] = useState3(false);
982
+ return /* @__PURE__ */ jsx3("div", { style: { display: "flex", flexDirection: "column", gap: 6 }, children: /* @__PURE__ */ jsxs2(
1308
983
  "div",
1309
984
  {
1310
985
  style: {
@@ -1315,8 +990,8 @@ function SaveLocationRow({
1315
990
  fontSize: 13
1316
991
  },
1317
992
  children: [
1318
- /* @__PURE__ */ jsx4(FolderOpen, { size: 15, strokeWidth: 1.8, style: { flexShrink: 0 } }),
1319
- /* @__PURE__ */ jsx4(
993
+ /* @__PURE__ */ jsx3(FolderOpen, { size: 15, strokeWidth: 1.8, style: { flexShrink: 0 } }),
994
+ /* @__PURE__ */ jsx3(
1320
995
  "span",
1321
996
  {
1322
997
  style: {
@@ -1330,7 +1005,7 @@ function SaveLocationRow({
1330
1005
  children: dir
1331
1006
  }
1332
1007
  ),
1333
- /* @__PURE__ */ jsx4(
1008
+ /* @__PURE__ */ jsx3(
1334
1009
  "button",
1335
1010
  {
1336
1011
  onClick: onPick,
@@ -1362,7 +1037,7 @@ function ToggleRow({
1362
1037
  enabled,
1363
1038
  onChange
1364
1039
  }) {
1365
- return /* @__PURE__ */ jsxs3(
1040
+ return /* @__PURE__ */ jsxs2(
1366
1041
  "label",
1367
1042
  {
1368
1043
  style: {
@@ -1373,7 +1048,7 @@ function ToggleRow({
1373
1048
  cursor: "pointer"
1374
1049
  },
1375
1050
  children: [
1376
- /* @__PURE__ */ jsxs3(
1051
+ /* @__PURE__ */ jsxs2(
1377
1052
  "span",
1378
1053
  {
1379
1054
  style: {
@@ -1390,7 +1065,7 @@ function ToggleRow({
1390
1065
  ]
1391
1066
  }
1392
1067
  ),
1393
- /* @__PURE__ */ jsx4(
1068
+ /* @__PURE__ */ jsx3(
1394
1069
  "button",
1395
1070
  {
1396
1071
  type: "button",
@@ -1407,7 +1082,7 @@ function ToggleRow({
1407
1082
  flexShrink: 0,
1408
1083
  transition: "background 0.12s ease"
1409
1084
  },
1410
- children: /* @__PURE__ */ jsx4(
1085
+ children: /* @__PURE__ */ jsx3(
1411
1086
  "span",
1412
1087
  {
1413
1088
  style: {
@@ -1428,49 +1103,18 @@ function ToggleRow({
1428
1103
  }
1429
1104
  );
1430
1105
  }
1431
- function ControlGroup({
1432
- label,
1433
- children
1434
- }) {
1435
- return /* @__PURE__ */ jsxs3(
1436
- "div",
1437
- {
1438
- style: {
1439
- display: "flex",
1440
- alignItems: "center",
1441
- gap: 6,
1442
- padding: "0 4px"
1443
- },
1444
- children: [
1445
- /* @__PURE__ */ jsx4(
1446
- "span",
1447
- {
1448
- style: {
1449
- fontSize: 11,
1450
- color: "rgba(255, 255, 255, 0.42)",
1451
- textTransform: "uppercase",
1452
- letterSpacing: "0.03em"
1453
- },
1454
- children: label
1455
- }
1456
- ),
1457
- children
1458
- ]
1459
- }
1460
- );
1461
- }
1462
1106
  function NumInput({
1463
1107
  value,
1464
1108
  onChange
1465
1109
  }) {
1466
- const [editing, setEditing] = useState5(false);
1467
- const [text, setText] = useState5(String(value));
1468
- useEffect4(() => {
1110
+ const [editing, setEditing] = useState3(false);
1111
+ const [text, setText] = useState3(String(value));
1112
+ useEffect2(() => {
1469
1113
  if (!editing) {
1470
1114
  setText(String(value));
1471
1115
  }
1472
1116
  }, [editing, value]);
1473
- return /* @__PURE__ */ jsx4(
1117
+ return /* @__PURE__ */ jsx3(
1474
1118
  "input",
1475
1119
  {
1476
1120
  type: "text",
@@ -1505,7 +1149,7 @@ function NumInput({
1505
1149
  );
1506
1150
  }
1507
1151
  function StaticText({ children }) {
1508
- return /* @__PURE__ */ jsx4(
1152
+ return /* @__PURE__ */ jsx3(
1509
1153
  "span",
1510
1154
  {
1511
1155
  style: {
@@ -1518,62 +1162,13 @@ function StaticText({ children }) {
1518
1162
  }
1519
1163
  );
1520
1164
  }
1521
- function DropButton({
1522
- children,
1523
- onClick,
1524
- active
1525
- }) {
1526
- return /* @__PURE__ */ jsx4(
1527
- "button",
1528
- {
1529
- onClick,
1530
- style: {
1531
- display: "flex",
1532
- alignItems: "center",
1533
- gap: 6,
1534
- height: 34,
1535
- padding: "0 10px",
1536
- borderRadius: 10,
1537
- border: "1px solid rgba(255,255,255,0.08)",
1538
- background: "rgba(255,255,255,0.04)",
1539
- color: active ? "rgba(125, 211, 252, 0.96)" : "rgba(255,255,255,0.78)",
1540
- cursor: "pointer",
1541
- fontSize: 12,
1542
- fontFamily: "inherit"
1543
- },
1544
- children
1545
- }
1546
- );
1547
- }
1548
- function DropMenu({ children }) {
1549
- return /* @__PURE__ */ jsx4(
1550
- "div",
1551
- {
1552
- style: {
1553
- position: "absolute",
1554
- bottom: "calc(100% + 8px)",
1555
- left: "50%",
1556
- transform: "translateX(-50%)",
1557
- minWidth: 120,
1558
- background: "rgba(32, 32, 36, 0.96)",
1559
- border: "1px solid rgba(255, 255, 255, 0.1)",
1560
- borderRadius: 10,
1561
- padding: "4px 0",
1562
- boxShadow: "0 10px 30px rgba(0, 0, 0, 0.3)",
1563
- backdropFilter: "blur(20px)",
1564
- WebkitBackdropFilter: "blur(20px)"
1565
- },
1566
- children
1567
- }
1568
- );
1569
- }
1570
1165
  function DropItem({
1571
1166
  children,
1572
1167
  onClick,
1573
1168
  active,
1574
1169
  accent
1575
1170
  }) {
1576
- return /* @__PURE__ */ jsx4(
1171
+ return /* @__PURE__ */ jsx3(
1577
1172
  "button",
1578
1173
  {
1579
1174
  onClick,
@@ -1593,56 +1188,29 @@ function DropItem({
1593
1188
  }
1594
1189
  );
1595
1190
  }
1596
- function MenuDivider() {
1597
- return /* @__PURE__ */ jsx4(
1598
- "div",
1599
- {
1600
- style: {
1601
- height: 1,
1602
- background: "rgba(255,255,255,0.08)",
1603
- margin: "4px 0"
1604
- }
1605
- }
1606
- );
1607
- }
1608
1191
  function Separator({ vertical = true }) {
1609
- return /* @__PURE__ */ jsx4(
1192
+ return /* @__PURE__ */ jsx3(
1610
1193
  "div",
1611
1194
  {
1612
1195
  style: {
1613
1196
  width: vertical ? 1 : 24,
1614
- height: vertical ? 24 : 1,
1197
+ height: vertical ? 18 : 1,
1615
1198
  background: "rgba(255,255,255,0.12)",
1616
- flexShrink: 0
1199
+ flexShrink: 0,
1200
+ margin: vertical ? "0 6px" : "6px 0"
1617
1201
  }
1618
1202
  }
1619
1203
  );
1620
1204
  }
1621
- function circleButtonStyle(active, round) {
1622
- return {
1623
- width: 40,
1624
- height: 40,
1625
- borderRadius: round ? "50%" : 12,
1626
- border: "none",
1627
- background: active ? "rgba(255, 255, 255, 0.12)" : "transparent",
1628
- display: "flex",
1629
- alignItems: "center",
1630
- justifyContent: "center",
1631
- cursor: "pointer",
1632
- padding: 0,
1633
- transition: "background 0.12s ease, color 0.12s ease",
1634
- marginRight: round ? 6 : 0
1635
- };
1636
- }
1637
1205
 
1638
1206
  // src/overlay/ui/inspector.tsx
1639
- import { useEffect as useEffect5, useRef as useRef3, useCallback as useCallback4, useState as useState6 } from "react";
1640
- import { Fragment as Fragment3, jsx as jsx5, jsxs as jsxs4 } from "react/jsx-runtime";
1207
+ import { useEffect as useEffect3, useRef as useRef3, useCallback as useCallback3, useState as useState4 } from "react";
1208
+ import { Fragment as Fragment2, jsx as jsx4, jsxs as jsxs3 } from "react/jsx-runtime";
1641
1209
  function Inspector({ onSelect, onCancel }) {
1642
- const [highlight, setHighlight] = useState6(null);
1210
+ const [highlight, setHighlight] = useState4(null);
1643
1211
  const hoveredEl = useRef3(null);
1644
1212
  const styleEl = useRef3(null);
1645
- useEffect5(() => {
1213
+ useEffect3(() => {
1646
1214
  const style = document.createElement("style");
1647
1215
  style.setAttribute("data-afterbefore", "true");
1648
1216
  style.textContent = "*, *::before, *::after { cursor: crosshair !important; }";
@@ -1652,7 +1220,7 @@ function Inspector({ onSelect, onCancel }) {
1652
1220
  style.remove();
1653
1221
  };
1654
1222
  }, []);
1655
- const isOverlayElement = useCallback4((el) => {
1223
+ const isOverlayElement = useCallback3((el) => {
1656
1224
  let node = el;
1657
1225
  while (node) {
1658
1226
  if (node instanceof HTMLElement && node.dataset.afterbefore) return true;
@@ -1660,7 +1228,7 @@ function Inspector({ onSelect, onCancel }) {
1660
1228
  }
1661
1229
  return false;
1662
1230
  }, []);
1663
- const handleMouseMove = useCallback4(
1231
+ const handleMouseMove = useCallback3(
1664
1232
  (e) => {
1665
1233
  const el = document.elementFromPoint(e.clientX, e.clientY);
1666
1234
  if (!el || !(el instanceof HTMLElement) || isOverlayElement(el)) {
@@ -1680,8 +1248,9 @@ function Inspector({ onSelect, onCancel }) {
1680
1248
  },
1681
1249
  [isOverlayElement]
1682
1250
  );
1683
- const handleClick = useCallback4(
1251
+ const handleClick = useCallback3(
1684
1252
  (e) => {
1253
+ if (isOverlayElement(e.target)) return;
1685
1254
  e.preventDefault();
1686
1255
  e.stopPropagation();
1687
1256
  e.stopImmediatePropagation();
@@ -1689,9 +1258,9 @@ function Inspector({ onSelect, onCancel }) {
1689
1258
  onSelect(hoveredEl.current);
1690
1259
  }
1691
1260
  },
1692
- [onSelect]
1261
+ [onSelect, isOverlayElement]
1693
1262
  );
1694
- const handleKeyDown = useCallback4(
1263
+ const handleKeyDown = useCallback3(
1695
1264
  (e) => {
1696
1265
  if (e.key === "Escape") {
1697
1266
  onCancel();
@@ -1699,7 +1268,7 @@ function Inspector({ onSelect, onCancel }) {
1699
1268
  },
1700
1269
  [onCancel]
1701
1270
  );
1702
- useEffect5(() => {
1271
+ useEffect3(() => {
1703
1272
  document.addEventListener("mousemove", handleMouseMove, true);
1704
1273
  document.addEventListener("click", handleClick, true);
1705
1274
  document.addEventListener("keydown", handleKeyDown);
@@ -1709,81 +1278,59 @@ function Inspector({ onSelect, onCancel }) {
1709
1278
  document.removeEventListener("keydown", handleKeyDown);
1710
1279
  };
1711
1280
  }, [handleMouseMove, handleClick, handleKeyDown]);
1712
- return /* @__PURE__ */ jsxs4("div", { "data-afterbefore": "true", style: { position: "fixed", inset: 0, zIndex: 2147483646, pointerEvents: "none" }, children: [
1713
- highlight && /* @__PURE__ */ jsxs4(Fragment3, { children: [
1714
- /* @__PURE__ */ jsx5(
1715
- "div",
1716
- {
1717
- style: {
1718
- position: "fixed",
1719
- left: highlight.x,
1720
- top: highlight.y,
1721
- width: highlight.width,
1722
- height: highlight.height,
1723
- background: "rgba(59, 130, 246, 0.15)",
1724
- border: "2px solid rgba(59, 130, 246, 0.7)",
1725
- borderRadius: 2,
1726
- pointerEvents: "none"
1727
- }
1728
- }
1729
- ),
1730
- /* @__PURE__ */ jsx5(
1731
- "div",
1732
- {
1733
- style: {
1734
- position: "fixed",
1735
- left: highlight.x,
1736
- top: Math.max(0, highlight.y - 24),
1737
- background: "rgba(59, 130, 246, 0.9)",
1738
- color: "#fff",
1739
- fontSize: 11,
1740
- fontFamily: "system-ui, -apple-system, monospace",
1741
- padding: "2px 6px",
1742
- borderRadius: 3,
1743
- pointerEvents: "none",
1744
- whiteSpace: "nowrap",
1745
- lineHeight: "18px"
1746
- },
1747
- children: highlight.tag
1281
+ return /* @__PURE__ */ jsx4("div", { "data-afterbefore": "true", style: { position: "fixed", inset: 0, zIndex: 2147483646, pointerEvents: "none" }, children: highlight && /* @__PURE__ */ jsxs3(Fragment2, { children: [
1282
+ /* @__PURE__ */ jsx4(
1283
+ "div",
1284
+ {
1285
+ style: {
1286
+ position: "fixed",
1287
+ left: highlight.x,
1288
+ top: highlight.y,
1289
+ width: highlight.width,
1290
+ height: highlight.height,
1291
+ background: "rgba(59, 130, 246, 0.15)",
1292
+ border: "2px solid rgba(59, 130, 246, 0.7)",
1293
+ borderRadius: 2,
1294
+ pointerEvents: "none"
1748
1295
  }
1749
- )
1750
- ] }),
1751
- !highlight && /* @__PURE__ */ jsx5(
1296
+ }
1297
+ ),
1298
+ /* @__PURE__ */ jsx4(
1752
1299
  "div",
1753
1300
  {
1754
1301
  style: {
1755
1302
  position: "fixed",
1756
- top: "50%",
1757
- left: "50%",
1758
- transform: "translate(-50%, -50%)",
1759
- color: "rgba(255, 255, 255, 0.7)",
1760
- fontSize: 14,
1761
- fontFamily: "system-ui, -apple-system, sans-serif",
1303
+ left: highlight.x,
1304
+ top: Math.max(0, highlight.y - 24),
1305
+ background: "rgba(59, 130, 246, 0.9)",
1306
+ color: "#fff",
1307
+ fontSize: 11,
1308
+ fontFamily: "system-ui, -apple-system, monospace",
1309
+ padding: "2px 6px",
1310
+ borderRadius: 3,
1762
1311
  pointerEvents: "none",
1763
- textShadow: "0 1px 4px rgba(0, 0, 0, 0.5)",
1764
- background: "rgba(0, 0, 0, 0.5)",
1765
- padding: "8px 16px",
1766
- borderRadius: 8
1312
+ whiteSpace: "nowrap",
1313
+ lineHeight: "18px"
1767
1314
  },
1768
- children: "Hover to inspect \xB7 Click to capture \xB7 Esc to cancel"
1315
+ children: highlight.tag
1769
1316
  }
1770
1317
  )
1771
- ] });
1318
+ ] }) });
1772
1319
  }
1773
1320
 
1774
1321
  // src/overlay/ui/status.tsx
1775
- import { useState as useState7, useRef as useRef4, useEffect as useEffect6, useCallback as useCallback5 } from "react";
1776
- import { jsx as jsx6, jsxs as jsxs5 } from "react/jsx-runtime";
1322
+ import { useState as useState5, useRef as useRef4, useEffect as useEffect4, useCallback as useCallback4 } from "react";
1323
+ import { jsx as jsx5, jsxs as jsxs4 } from "react/jsx-runtime";
1777
1324
  var PANEL_WIDTH = 220;
1778
1325
  function Status({ onReset, position, onClose }) {
1779
1326
  const panelRef = useRef4(null);
1780
- const [toast, setToast] = useState7(null);
1781
- const [pushing, setPushing] = useState7(false);
1782
- const showToast = useCallback5((message, type) => {
1327
+ const [toast, setToast] = useState5(null);
1328
+ const [pushing, setPushing] = useState5(false);
1329
+ const showToast = useCallback4((message, type) => {
1783
1330
  setToast({ message, type });
1784
1331
  setTimeout(() => setToast(null), 3e3);
1785
1332
  }, []);
1786
- useEffect6(() => {
1333
+ useEffect4(() => {
1787
1334
  const handler = (e) => {
1788
1335
  if (panelRef.current && !panelRef.current.contains(e.target)) {
1789
1336
  onClose();
@@ -1792,7 +1339,7 @@ function Status({ onReset, position, onClose }) {
1792
1339
  document.addEventListener("mousedown", handler);
1793
1340
  return () => document.removeEventListener("mousedown", handler);
1794
1341
  }, [onClose]);
1795
- useEffect6(() => {
1342
+ useEffect4(() => {
1796
1343
  const handler = (e) => {
1797
1344
  if (e.key === "Escape") onClose();
1798
1345
  };
@@ -1868,7 +1415,7 @@ function Status({ onReset, position, onClose }) {
1868
1415
  const onLeave = (e) => {
1869
1416
  e.currentTarget.style.background = "transparent";
1870
1417
  };
1871
- return /* @__PURE__ */ jsxs5(
1418
+ return /* @__PURE__ */ jsxs4(
1872
1419
  "div",
1873
1420
  {
1874
1421
  ref: panelRef,
@@ -1887,7 +1434,7 @@ function Status({ onReset, position, onClose }) {
1887
1434
  overflow: "hidden"
1888
1435
  },
1889
1436
  children: [
1890
- /* @__PURE__ */ jsx6(
1437
+ /* @__PURE__ */ jsx5(
1891
1438
  "div",
1892
1439
  {
1893
1440
  style: {
@@ -1897,11 +1444,11 @@ function Status({ onReset, position, onClose }) {
1897
1444
  color: "rgba(255,255,255,0.5)",
1898
1445
  letterSpacing: "0.02em"
1899
1446
  },
1900
- children: "Before & After captured"
1447
+ children: "Screenshot captured"
1901
1448
  }
1902
1449
  ),
1903
- /* @__PURE__ */ jsxs5("div", { style: { padding: "0 4px 4px" }, children: [
1904
- /* @__PURE__ */ jsxs5(
1450
+ /* @__PURE__ */ jsxs4("div", { style: { padding: "0 4px 4px" }, children: [
1451
+ /* @__PURE__ */ jsxs4(
1905
1452
  "button",
1906
1453
  {
1907
1454
  style: buttonStyle,
@@ -1909,12 +1456,12 @@ function Status({ onReset, position, onClose }) {
1909
1456
  onMouseEnter: onEnter,
1910
1457
  onMouseLeave: onLeave,
1911
1458
  children: [
1912
- /* @__PURE__ */ jsx6(FolderIcon, {}),
1459
+ /* @__PURE__ */ jsx5(FolderIcon, {}),
1913
1460
  "Open Folder"
1914
1461
  ]
1915
1462
  }
1916
1463
  ),
1917
- /* @__PURE__ */ jsxs5(
1464
+ /* @__PURE__ */ jsxs4(
1918
1465
  "button",
1919
1466
  {
1920
1467
  style: buttonStyle,
@@ -1922,12 +1469,12 @@ function Status({ onReset, position, onClose }) {
1922
1469
  onMouseEnter: onEnter,
1923
1470
  onMouseLeave: onLeave,
1924
1471
  children: [
1925
- /* @__PURE__ */ jsx6(CopyIcon, {}),
1472
+ /* @__PURE__ */ jsx5(CopyIcon, {}),
1926
1473
  "Copy Markdown"
1927
1474
  ]
1928
1475
  }
1929
1476
  ),
1930
- /* @__PURE__ */ jsxs5(
1477
+ /* @__PURE__ */ jsxs4(
1931
1478
  "button",
1932
1479
  {
1933
1480
  style: buttonStyle,
@@ -1936,12 +1483,12 @@ function Status({ onReset, position, onClose }) {
1936
1483
  onMouseEnter: onEnter,
1937
1484
  onMouseLeave: onLeave,
1938
1485
  children: [
1939
- /* @__PURE__ */ jsx6(PushIcon, {}),
1486
+ /* @__PURE__ */ jsx5(PushIcon, {}),
1940
1487
  pushing ? "Pushing..." : "Push to PR"
1941
1488
  ]
1942
1489
  }
1943
1490
  ),
1944
- /* @__PURE__ */ jsx6(
1491
+ /* @__PURE__ */ jsx5(
1945
1492
  "div",
1946
1493
  {
1947
1494
  style: {
@@ -1951,7 +1498,7 @@ function Status({ onReset, position, onClose }) {
1951
1498
  }
1952
1499
  }
1953
1500
  ),
1954
- /* @__PURE__ */ jsxs5(
1501
+ /* @__PURE__ */ jsxs4(
1955
1502
  "button",
1956
1503
  {
1957
1504
  style: { ...buttonStyle, color: "rgba(255,255,255,0.5)" },
@@ -1959,13 +1506,13 @@ function Status({ onReset, position, onClose }) {
1959
1506
  onMouseEnter: onEnter,
1960
1507
  onMouseLeave: onLeave,
1961
1508
  children: [
1962
- /* @__PURE__ */ jsx6(ResetIcon, {}),
1509
+ /* @__PURE__ */ jsx5(ResetIcon, {}),
1963
1510
  "Reset"
1964
1511
  ]
1965
1512
  }
1966
1513
  )
1967
1514
  ] }),
1968
- toast && /* @__PURE__ */ jsx6(
1515
+ toast && /* @__PURE__ */ jsx5(
1969
1516
  "div",
1970
1517
  {
1971
1518
  style: {
@@ -1991,14 +1538,14 @@ function Status({ onReset, position, onClose }) {
1991
1538
  );
1992
1539
  }
1993
1540
  function FolderIcon() {
1994
- return /* @__PURE__ */ jsx6(
1541
+ return /* @__PURE__ */ jsx5(
1995
1542
  "svg",
1996
1543
  {
1997
1544
  width: "14",
1998
1545
  height: "14",
1999
1546
  viewBox: "0 0 14 14",
2000
1547
  style: { color: "rgba(255,255,255,0.5)" },
2001
- children: /* @__PURE__ */ jsx6(
1548
+ children: /* @__PURE__ */ jsx5(
2002
1549
  "path",
2003
1550
  {
2004
1551
  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",
@@ -2011,7 +1558,7 @@ function FolderIcon() {
2011
1558
  );
2012
1559
  }
2013
1560
  function CopyIcon() {
2014
- return /* @__PURE__ */ jsxs5(
1561
+ return /* @__PURE__ */ jsxs4(
2015
1562
  "svg",
2016
1563
  {
2017
1564
  width: "14",
@@ -2019,7 +1566,7 @@ function CopyIcon() {
2019
1566
  viewBox: "0 0 14 14",
2020
1567
  style: { color: "rgba(255,255,255,0.5)" },
2021
1568
  children: [
2022
- /* @__PURE__ */ jsx6(
1569
+ /* @__PURE__ */ jsx5(
2023
1570
  "rect",
2024
1571
  {
2025
1572
  x: "4",
@@ -2032,7 +1579,7 @@ function CopyIcon() {
2032
1579
  strokeWidth: "1.3"
2033
1580
  }
2034
1581
  ),
2035
- /* @__PURE__ */ jsx6(
1582
+ /* @__PURE__ */ jsx5(
2036
1583
  "path",
2037
1584
  {
2038
1585
  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",
@@ -2046,14 +1593,14 @@ function CopyIcon() {
2046
1593
  );
2047
1594
  }
2048
1595
  function PushIcon() {
2049
- return /* @__PURE__ */ jsx6(
1596
+ return /* @__PURE__ */ jsx5(
2050
1597
  "svg",
2051
1598
  {
2052
1599
  width: "14",
2053
1600
  height: "14",
2054
1601
  viewBox: "0 0 14 14",
2055
1602
  style: { color: "rgba(255,255,255,0.5)" },
2056
- children: /* @__PURE__ */ jsx6(
1603
+ children: /* @__PURE__ */ jsx5(
2057
1604
  "path",
2058
1605
  {
2059
1606
  d: "M7 11V3m0 0L4 6m3-3l3 3",
@@ -2068,7 +1615,7 @@ function PushIcon() {
2068
1615
  );
2069
1616
  }
2070
1617
  function ResetIcon() {
2071
- return /* @__PURE__ */ jsxs5(
1618
+ return /* @__PURE__ */ jsxs4(
2072
1619
  "svg",
2073
1620
  {
2074
1621
  width: "14",
@@ -2076,7 +1623,7 @@ function ResetIcon() {
2076
1623
  viewBox: "0 0 14 14",
2077
1624
  style: { color: "rgba(255,255,255,0.4)" },
2078
1625
  children: [
2079
- /* @__PURE__ */ jsx6(
1626
+ /* @__PURE__ */ jsx5(
2080
1627
  "path",
2081
1628
  {
2082
1629
  d: "M2.5 7a4.5 4.5 0 118 2.5",
@@ -2086,7 +1633,7 @@ function ResetIcon() {
2086
1633
  strokeLinecap: "round"
2087
1634
  }
2088
1635
  ),
2089
- /* @__PURE__ */ jsx6(
1636
+ /* @__PURE__ */ jsx5(
2090
1637
  "path",
2091
1638
  {
2092
1639
  d: "M2.5 3v4h4",
@@ -2103,55 +1650,47 @@ function ResetIcon() {
2103
1650
  }
2104
1651
 
2105
1652
  // src/overlay/index.tsx
2106
- import { jsx as jsx7, jsxs as jsxs6 } from "react/jsx-runtime";
2107
- async function saveCapture(type, mode, dataUrl) {
1653
+ import { jsx as jsx6, jsxs as jsxs5 } from "react/jsx-runtime";
1654
+ async function saveCapture(mode, dataUrl) {
2108
1655
  try {
2109
1656
  const res = await fetch("/__afterbefore/save", {
2110
1657
  method: "POST",
2111
1658
  headers: { "Content-Type": "application/json" },
2112
- body: JSON.stringify({ type, mode, image: dataUrl })
1659
+ body: JSON.stringify({ mode, image: dataUrl })
2113
1660
  });
2114
1661
  if (!res.ok) throw new Error("Save failed");
2115
1662
  } catch {
2116
1663
  const link = document.createElement("a");
2117
- link.download = `${type}.png`;
1664
+ link.download = "screenshot.png";
2118
1665
  link.href = dataUrl;
2119
1666
  link.click();
2120
1667
  }
2121
1668
  }
2122
1669
  function AfterBefore() {
2123
1670
  const { state, captureComplete, reset } = useOverlayState();
2124
- const [statusOpen, setStatusOpen] = useState8(false);
2125
- const [toolbarActive, setToolbarActive] = useState8(false);
2126
- const [selectorActive, setSelectorActive] = useState8(false);
2127
- const [inspectorActive, setInspectorActive] = useState8(false);
2128
- const [loading, setLoading] = useState8(false);
2129
- const [selectedMode, setSelectedMode] = useState8("component");
2130
- const [magnetEnabled, setMagnetEnabled] = useState8(true);
2131
- const [areaRect, setAreaRect] = useState8(null);
2132
- const [areaAspect, setAreaAspect] = useState8(DEFAULT_AREA_ASPECT);
2133
- const [areaPresets, setAreaPresets] = useState8([]);
2134
- const [frameOnBlackEnabled, setFrameOnBlackEnabled] = useState8(false);
1671
+ const [statusOpen, setStatusOpen] = useState6(false);
1672
+ const [toolbarActive, setToolbarActive] = useState6(false);
1673
+ const [inspectorActive, setInspectorActive] = useState6(false);
1674
+ const [loading, setLoading] = useState6(false);
1675
+ const [selectedMode, setSelectedMode] = useState6("component");
1676
+ const [frameSettings, setFrameSettings] = useState6(DEFAULT_FRAME_SETTINGS);
2135
1677
  const iconPos = useRef5({ x: 24, y: 0 });
2136
- useEffect7(() => {
2137
- try {
2138
- setMagnetEnabled(localStorage.getItem("ab-magnet") !== "false");
2139
- } catch {
2140
- setMagnetEnabled(true);
2141
- }
2142
- try {
2143
- const savedPresets = localStorage.getItem("ab-area-presets");
2144
- setAreaPresets(savedPresets ? JSON.parse(savedPresets) : []);
2145
- } catch {
2146
- setAreaPresets([]);
2147
- }
1678
+ useEffect5(() => {
2148
1679
  try {
2149
- setFrameOnBlackEnabled(localStorage.getItem("ab-frame-black") === "true");
1680
+ const stored = localStorage.getItem("ab-frame-settings");
1681
+ if (stored) {
1682
+ setFrameSettings({ ...DEFAULT_FRAME_SETTINGS, ...JSON.parse(stored) });
1683
+ } else if (localStorage.getItem("ab-frame-black") === "true") {
1684
+ const migrated = { ...DEFAULT_FRAME_SETTINGS, enabled: true };
1685
+ setFrameSettings(migrated);
1686
+ localStorage.setItem("ab-frame-settings", JSON.stringify(migrated));
1687
+ localStorage.removeItem("ab-frame-black");
1688
+ }
2150
1689
  } catch {
2151
- setFrameOnBlackEnabled(false);
1690
+ setFrameSettings(DEFAULT_FRAME_SETTINGS);
2152
1691
  }
2153
1692
  }, []);
2154
- useEffect7(() => {
1693
+ useEffect5(() => {
2155
1694
  if (state.phase === "ready") {
2156
1695
  const timer = setTimeout(() => {
2157
1696
  reset();
@@ -2160,43 +1699,37 @@ function AfterBefore() {
2160
1699
  return () => clearTimeout(timer);
2161
1700
  }
2162
1701
  }, [state.phase, reset]);
2163
- const handlePositionChange = useCallback6(
1702
+ const handlePositionChange = useCallback5(
2164
1703
  (pos) => {
2165
1704
  iconPos.current = pos;
2166
1705
  },
2167
1706
  []
2168
1707
  );
2169
- const handleIconClick = useCallback6(() => {
1708
+ const handleIconClick = useCallback5(() => {
2170
1709
  if (loading) return;
2171
1710
  if (state.phase === "ready") {
2172
1711
  setStatusOpen((prev) => !prev);
2173
1712
  } else if (toolbarActive || inspectorActive) {
2174
1713
  setToolbarActive(false);
2175
- setSelectorActive(false);
2176
1714
  setInspectorActive(false);
2177
1715
  setStatusOpen(false);
2178
1716
  } else {
2179
1717
  setStatusOpen(false);
2180
1718
  if (selectedMode === "component") {
1719
+ setToolbarActive(true);
2181
1720
  setInspectorActive(true);
2182
1721
  } else {
2183
1722
  setToolbarActive(true);
2184
1723
  setInspectorActive(false);
2185
- if (selectedMode === "area") {
2186
- setSelectorActive(true);
2187
- setAreaRect(createInitialAreaRect());
2188
- setAreaAspect(DEFAULT_AREA_ASPECT);
2189
- }
2190
1724
  }
2191
1725
  }
2192
1726
  }, [state.phase, loading, toolbarActive, inspectorActive, selectedMode]);
2193
- const performCapture = useCallback6(
2194
- async (mode, area, element) => {
1727
+ const performCapture = useCallback5(
1728
+ async (mode, element) => {
2195
1729
  setLoading(true);
2196
1730
  try {
2197
- const dataUrl = await capture({ mode, area, element, frameOnBlack: frameOnBlackEnabled });
2198
- const type = state.phase === "idle" ? "before" : "after";
2199
- await saveCapture(type, mode, dataUrl);
1731
+ const dataUrl = await capture({ mode, element, frameSettings });
1732
+ await saveCapture(mode, dataUrl);
2200
1733
  captureComplete({
2201
1734
  dataUrl,
2202
1735
  mode,
@@ -2208,164 +1741,59 @@ function AfterBefore() {
2208
1741
  setLoading(false);
2209
1742
  }
2210
1743
  },
2211
- [state.phase, captureComplete, frameOnBlackEnabled]
1744
+ [captureComplete, frameSettings]
2212
1745
  );
2213
- const handleToolbarCapture = useCallback6(
1746
+ const handleToolbarCapture = useCallback5(
2214
1747
  (mode) => {
2215
- if (mode === "area") {
2216
- if (areaRect) {
2217
- setSelectorActive(false);
2218
- setToolbarActive(false);
2219
- performCapture("area", {
2220
- x: Math.round(areaRect.x),
2221
- y: Math.round(areaRect.y),
2222
- width: Math.round(areaRect.w),
2223
- height: Math.round(areaRect.h)
2224
- });
2225
- }
2226
- return;
2227
- } else if (mode === "viewport") {
1748
+ if (mode === "viewport") {
2228
1749
  setToolbarActive(false);
2229
1750
  performCapture("viewport");
2230
1751
  } else if (mode === "fullpage") {
2231
1752
  setToolbarActive(false);
2232
1753
  performCapture("fullpage");
2233
1754
  } else if (mode === "component") {
2234
- setToolbarActive(false);
2235
1755
  setInspectorActive(true);
2236
1756
  }
2237
1757
  },
2238
- [areaRect, performCapture]
1758
+ [performCapture]
2239
1759
  );
2240
- const handleToolbarCancel = useCallback6(() => {
1760
+ const handleToolbarCancel = useCallback5(() => {
2241
1761
  setToolbarActive(false);
2242
- setSelectorActive(false);
2243
1762
  }, []);
2244
- const handleComponentSelect = useCallback6(
1763
+ const handleComponentSelect = useCallback5(
2245
1764
  (element) => {
2246
1765
  setInspectorActive(false);
2247
- performCapture("component", void 0, element);
2248
- },
2249
- [performCapture]
2250
- );
2251
- const handleComponentCancel = useCallback6(() => {
2252
- setInspectorActive(false);
2253
- setToolbarActive(true);
2254
- }, []);
2255
- const handleAreaSelect = useCallback6(
2256
- (area) => {
2257
- setSelectorActive(false);
2258
1766
  setToolbarActive(false);
2259
- performCapture("area", area);
1767
+ performCapture("component", element);
2260
1768
  },
2261
1769
  [performCapture]
2262
1770
  );
2263
- const handleAreaCancel = useCallback6(() => {
2264
- setSelectorActive(false);
1771
+ const handleComponentCancel = useCallback5(() => {
1772
+ setInspectorActive(false);
2265
1773
  setToolbarActive(true);
2266
1774
  }, []);
2267
- const handleMagnetChange = useCallback6((enabled) => {
2268
- setMagnetEnabled(enabled);
2269
- try {
2270
- localStorage.setItem("ab-magnet", String(enabled));
2271
- } catch {
2272
- }
2273
- }, []);
2274
- const handleFrameOnBlackChange = useCallback6((enabled) => {
2275
- setFrameOnBlackEnabled(enabled);
1775
+ const handleFrameSettingsChange = useCallback5((next) => {
1776
+ setFrameSettings(next);
2276
1777
  try {
2277
- localStorage.setItem("ab-frame-black", String(enabled));
1778
+ localStorage.setItem("ab-frame-settings", JSON.stringify(next));
2278
1779
  } catch {
2279
1780
  }
2280
1781
  }, []);
2281
- const handleModeChange = useCallback6((mode) => {
1782
+ const handleModeChange = useCallback5((mode) => {
2282
1783
  setSelectedMode(mode);
2283
- if (mode === "area") {
2284
- setSelectorActive(true);
2285
- setAreaRect(createInitialAreaRect());
2286
- setAreaAspect(DEFAULT_AREA_ASPECT);
2287
- } else if (mode === "component") {
2288
- setSelectorActive(false);
2289
- setToolbarActive(false);
1784
+ if (mode === "component") {
2290
1785
  setInspectorActive(true);
2291
- } else {
2292
- setSelectorActive(false);
2293
- }
2294
- }, []);
2295
- const handleAreaRectChange = useCallback6((nextRect) => {
2296
- setAreaRect(nextRect);
2297
- }, []);
2298
- const handleAreaSizeChange = useCallback6(
2299
- (field, value) => {
2300
- const nextValue = parseInt(value, 10);
2301
- if (Number.isNaN(nextValue) || nextValue < 20 || !areaRect) {
2302
- return;
2303
- }
2304
- 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];
2305
- if (field === "w") {
2306
- setAreaRect({
2307
- ...areaRect,
2308
- w: nextValue,
2309
- h: aspectRatio > 0 ? nextValue / aspectRatio : areaRect.h
2310
- });
2311
- } else {
2312
- setAreaRect({
2313
- ...areaRect,
2314
- h: nextValue,
2315
- w: aspectRatio > 0 ? nextValue * aspectRatio : areaRect.w
2316
- });
2317
- }
2318
- },
2319
- [areaAspect, areaRect]
2320
- );
2321
- const handleAreaPositionChange = useCallback6(
2322
- (field, value) => {
2323
- const nextValue = parseInt(value, 10);
2324
- if (Number.isNaN(nextValue) || !areaRect) {
2325
- return;
2326
- }
2327
- setAreaRect({ ...areaRect, [field]: nextValue });
2328
- },
2329
- [areaRect]
2330
- );
2331
- const handleAreaAspectChange = useCallback6(
2332
- (nextAspect) => {
2333
- setAreaAspect(nextAspect);
2334
- if (!areaRect || nextAspect === "Free") {
2335
- return;
2336
- }
2337
- const aspectRatio = { "16:9": 16 / 9, "4:3": 4 / 3, "1:1": 1, "3:2": 3 / 2, "21:9": 21 / 9 }[nextAspect];
2338
- setAreaRect({ ...areaRect, h: areaRect.w / aspectRatio });
2339
- },
2340
- [areaRect]
2341
- );
2342
- const handleAreaSavePreset = useCallback6(() => {
2343
- if (!areaRect) {
2344
- return;
2345
- }
2346
- const label = `${Math.round(areaRect.w)}x${Math.round(areaRect.h)}`;
2347
- const nextPresets = [
2348
- ...areaPresets.filter((preset) => preset.label !== label),
2349
- { label, rect: { ...areaRect } }
2350
- ];
2351
- setAreaPresets(nextPresets);
2352
- try {
2353
- localStorage.setItem("ab-area-presets", JSON.stringify(nextPresets));
2354
- } catch {
2355
1786
  }
2356
- }, [areaPresets, areaRect]);
2357
- const handleAreaLoadPreset = useCallback6((preset) => {
2358
- setAreaRect({ ...preset.rect });
2359
1787
  }, []);
2360
- const handleReset = useCallback6(() => {
1788
+ const handleReset = useCallback5(() => {
2361
1789
  reset();
2362
1790
  setStatusOpen(false);
2363
1791
  }, [reset]);
2364
- const handleStatusClose = useCallback6(() => {
1792
+ const handleStatusClose = useCallback5(() => {
2365
1793
  setStatusOpen(false);
2366
1794
  }, []);
2367
- return /* @__PURE__ */ jsxs6("div", { "data-afterbefore": "true", children: [
2368
- /* @__PURE__ */ jsx7(
1795
+ return /* @__PURE__ */ jsxs5("div", { "data-afterbefore": "true", children: [
1796
+ /* @__PURE__ */ jsx6(
2369
1797
  Icon,
2370
1798
  {
2371
1799
  phase: state.phase,
@@ -2374,48 +1802,26 @@ function AfterBefore() {
2374
1802
  onPositionChange: handlePositionChange
2375
1803
  }
2376
1804
  ),
2377
- toolbarActive && !selectorActive && !inspectorActive && !loading && /* @__PURE__ */ jsx7(
1805
+ toolbarActive && !inspectorActive && !loading && /* @__PURE__ */ jsx6(
2378
1806
  CapturePreview,
2379
1807
  {
2380
1808
  mode: selectedMode,
2381
1809
  onClick: () => handleToolbarCapture(selectedMode)
2382
1810
  }
2383
1811
  ),
2384
- toolbarActive && !inspectorActive && /* @__PURE__ */ jsx7(
1812
+ toolbarActive && /* @__PURE__ */ jsx6(
2385
1813
  Toolbar,
2386
1814
  {
2387
1815
  selectedMode,
2388
1816
  onModeChange: handleModeChange,
2389
1817
  onCapture: handleToolbarCapture,
2390
1818
  onCancel: handleToolbarCancel,
2391
- magnetEnabled,
2392
- onMagnetChange: handleMagnetChange,
2393
- areaSelectionActive: selectorActive,
2394
- areaRect,
2395
- areaAspect,
2396
- areaPresets,
2397
- onAreaSizeChange: handleAreaSizeChange,
2398
- onAreaPositionChange: handleAreaPositionChange,
2399
- onAreaAspectChange: handleAreaAspectChange,
2400
- onAreaSavePreset: handleAreaSavePreset,
2401
- onAreaLoadPreset: handleAreaLoadPreset,
2402
- frameOnBlackEnabled,
2403
- onFrameOnBlackChange: handleFrameOnBlackChange
2404
- }
2405
- ),
2406
- selectorActive && /* @__PURE__ */ jsx7(
2407
- Selector,
2408
- {
2409
- rect: areaRect,
2410
- aspect: areaAspect,
2411
- onRectChange: handleAreaRectChange,
2412
- onSelect: handleAreaSelect,
2413
- onCancel: handleAreaCancel,
2414
- magnetEnabled
1819
+ frameSettings,
1820
+ onFrameSettingsChange: handleFrameSettingsChange
2415
1821
  }
2416
1822
  ),
2417
- inspectorActive && /* @__PURE__ */ jsx7(Inspector, { onSelect: handleComponentSelect, onCancel: handleComponentCancel }),
2418
- statusOpen && state.phase === "ready" && /* @__PURE__ */ jsx7(
1823
+ inspectorActive && /* @__PURE__ */ jsx6(Inspector, { onSelect: handleComponentSelect, onCancel: handleComponentCancel }),
1824
+ statusOpen && state.phase === "ready" && /* @__PURE__ */ jsx6(
2419
1825
  Status,
2420
1826
  {
2421
1827
  onReset: handleReset,