afterbefore 0.2.22 → 0.2.24

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,7 +1,7 @@
1
1
  "use client";
2
2
 
3
3
  // src/overlay/index.tsx
4
- import { useState as useState5, useCallback as useCallback4, useEffect as useEffect4 } from "react";
4
+ import { useState as useState6, useCallback as useCallback5, useEffect as useEffect5 } from "react";
5
5
 
6
6
  // src/overlay/state.ts
7
7
  import { useState, useCallback } from "react";
@@ -25,11 +25,26 @@ function useOverlayState() {
25
25
 
26
26
  // src/overlay/capture.ts
27
27
  import { snapdom } from "@zumer/snapdom";
28
+ var GRADIENT_PRESETS = [
29
+ "linear-gradient(135deg, #a78bfa 0%, #f472b6 100%)",
30
+ "linear-gradient(135deg, #3b82f6 0%, #06b6d4 100%)",
31
+ "linear-gradient(135deg, #34d399 0%, #6ee7b7 100%)",
32
+ "linear-gradient(135deg, #f97316 0%, #ef4444 100%)",
33
+ "linear-gradient(180deg, #1e293b 0%, #0f172a 100%)"
34
+ ];
35
+ var COLOR_PRESETS = [
36
+ "#FFFFFF",
37
+ "#F5F5F5",
38
+ "#F8F4ED",
39
+ "#1E1E1E",
40
+ "#000000"
41
+ ];
28
42
  var DEFAULT_FRAME_SETTINGS = {
29
43
  enabled: false,
30
44
  size: { w: 1920, h: 1080 },
31
45
  bgType: "color",
32
- bgColor: "#000000",
46
+ bgColor: "#F5F5F5",
47
+ bgGradient: GRADIENT_PRESETS[0],
33
48
  bgImage: null,
34
49
  padding: 40,
35
50
  browserChrome: false,
@@ -202,12 +217,12 @@ async function captureComponent(element, frameSettings) {
202
217
  const dpr = window.devicePixelRatio || 1;
203
218
  const FRAME_W = frameSettings.size.w;
204
219
  const FRAME_H = frameSettings.size.h;
205
- const padding = frameSettings.padding;
220
+ const pad = frameSettings.padding ?? 40;
206
221
  const compW = img.width / dpr;
207
222
  const compH = img.height / dpr;
208
- const maxW = FRAME_W - padding * 2;
209
- const maxH = FRAME_H - padding * 2;
210
- const scale = Math.min(1, maxW / compW, maxH / compH);
223
+ const maxW = FRAME_W - pad * 2;
224
+ const maxH = FRAME_H - pad * 2;
225
+ const scale = Math.min(maxW / compW, maxH / compH, 1);
211
226
  const drawW = compW * scale * dpr;
212
227
  const drawH = compH * scale * dpr;
213
228
  const canvas = document.createElement("canvas");
@@ -222,7 +237,9 @@ async function captureComponent(element, frameSettings) {
222
237
  ctx.fillStyle = frameSettings.bgColor;
223
238
  ctx.fillRect(0, 0, canvas.width, canvas.height);
224
239
  }
225
- } else {
240
+ } else if (frameSettings.bgType === "gradient") {
241
+ fillCssGradient(ctx, frameSettings.bgGradient, canvas.width, canvas.height);
242
+ } else if (frameSettings.bgColor !== "transparent") {
226
243
  ctx.fillStyle = frameSettings.bgColor;
227
244
  ctx.fillRect(0, 0, canvas.width, canvas.height);
228
245
  }
@@ -247,6 +264,34 @@ function loadImage(src) {
247
264
  img.src = src;
248
265
  });
249
266
  }
267
+ function fillCssGradient(ctx, css, w, h) {
268
+ const match = css.match(
269
+ /linear-gradient\(\s*([\d.]+)deg\s*,\s*(.+)\)/
270
+ );
271
+ if (!match) {
272
+ ctx.fillStyle = "#000";
273
+ ctx.fillRect(0, 0, w, h);
274
+ return;
275
+ }
276
+ const angle = parseFloat(match[1]) * Math.PI / 180;
277
+ const stopsRaw = match[2].split(/,\s*(?=#|\w)/).map((s) => s.trim());
278
+ const cx = w / 2;
279
+ const cy = h / 2;
280
+ const len = Math.abs(w * Math.sin(angle)) + Math.abs(h * Math.cos(angle));
281
+ const x0 = cx - Math.sin(angle) * len / 2;
282
+ const y0 = cy + Math.cos(angle) * len / 2;
283
+ const x1 = cx + Math.sin(angle) * len / 2;
284
+ const y1 = cy - Math.cos(angle) * len / 2;
285
+ const grad = ctx.createLinearGradient(x0, y0, x1, y1);
286
+ for (const stop of stopsRaw) {
287
+ const parts = stop.match(/^(.+?)\s+([\d.]+)%$/);
288
+ if (parts) {
289
+ grad.addColorStop(parseFloat(parts[2]) / 100, parts[1]);
290
+ }
291
+ }
292
+ ctx.fillStyle = grad;
293
+ ctx.fillRect(0, 0, w, h);
294
+ }
250
295
 
251
296
  // src/overlay/font.ts
252
297
  var FONT_FAMILY = "'Inter var', 'Inter', system-ui, -apple-system, sans-serif";
@@ -307,38 +352,31 @@ function injectInterFont() {
307
352
  }
308
353
 
309
354
  // src/overlay/ui/toolbar.tsx
310
- import { useCallback as useCallback2, useEffect as useEffect2, useRef as useRef2, useState as useState3 } from "react";
311
- import { createPortal } from "react-dom";
355
+ import { useCallback as useCallback3, useEffect as useEffect3, useRef as useRef2, useState as useState4 } from "react";
312
356
  import {
313
- ArrowUp,
314
357
  Camera,
315
- Check,
358
+ Check as Check2,
316
359
  ChevronDown as ChevronDown2,
317
- Image as Image2,
318
- Eye,
319
- FolderOpen,
320
360
  LoaderCircle,
321
361
  FileText,
322
362
  Monitor,
323
- MousePointer2,
324
- Trash2 as Trash22,
325
363
  X as X2
326
364
  } from "lucide-react";
327
365
 
328
366
  // src/overlay/color.ts
329
367
  var gray = {
330
- 950: "#171717",
331
- 900: "#1C1C1C",
332
- 800: "#262626",
333
- 700: "#333333",
334
- 600: "#5C5C5C",
335
- 500: "#7B7B7B",
336
- 400: "#A3A3A3",
337
- 300: "#D1D1D1",
338
- 200: "#EBEBEB",
339
- 100: "#F5F5F5",
340
- 50: "#F7F7F7",
341
- 0: "#FFFFFF"
368
+ 950: "oklch(0.162 0 0)",
369
+ 900: "oklch(0.195 0 0)",
370
+ 800: "oklch(0.254 0 0)",
371
+ 700: "oklch(0.302 0 0)",
372
+ 600: "oklch(0.348 0 0)",
373
+ 500: "oklch(0.396 0 0)",
374
+ 400: "oklch(0.459 0 0)",
375
+ 300: "oklch(0.549 0 0)",
376
+ 200: "oklch(0.649 0 0)",
377
+ 100: "oklch(0.72 0 0)",
378
+ 50: "oklch(0.863 0 0)",
379
+ 0: "oklch(0.933 0 0)"
342
380
  };
343
381
  var bg = {
344
382
  base: gray[900],
@@ -347,10 +385,15 @@ var bg = {
347
385
  };
348
386
  var fg = {
349
387
  strong: gray[200],
350
- default: gray[300],
351
- sub: gray[500],
352
- muted: gray[600],
353
- faint: gray[600]
388
+ // ~5.6:1 — headings, input values
389
+ default: "oklch(0.6 0 0)",
390
+ // ~4.6:1 — body text, interactive labels (AA)
391
+ sub: gray[300],
392
+ // ~3.8:1 — secondary labels, icons
393
+ muted: "oklch(0.5 0 0)",
394
+ // ~3:1 — inline labels, decorative
395
+ faint: gray[500]
396
+ // ~2:1 — non-essential hints only
354
397
  };
355
398
  var stroke = {
356
399
  soft: "rgba(255, 255, 255, 0.08)",
@@ -388,15 +431,25 @@ var shadow = {
388
431
  status: "0 4px 20px rgba(0, 0, 0, 0.4), 0 0 0 1px rgba(255, 255, 255, 0.08)"
389
432
  };
390
433
 
434
+ // src/overlay/ui/screenshots-panel.tsx
435
+ import { useCallback as useCallback2, useEffect as useEffect2, useState as useState3 } from "react";
436
+ import { createPortal } from "react-dom";
437
+ import {
438
+ Image as Image2,
439
+ Trash2 as Trash22,
440
+ X
441
+ } from "lucide-react";
442
+
391
443
  // src/overlay/ui/settings-panel.tsx
392
444
  import { useEffect, useRef, useState as useState2 } from "react";
393
445
  import {
446
+ AppWindow,
447
+ Check,
394
448
  ChevronDown,
395
- ImageIcon,
396
- Palette,
449
+ LayoutGrid,
450
+ Pipette,
397
451
  Trash2,
398
- Upload,
399
- X
452
+ Upload
400
453
  } from "lucide-react";
401
454
  import { Fragment, jsx, jsxs } from "react/jsx-runtime";
402
455
  function SettingsContent({
@@ -408,82 +461,29 @@ function SettingsContent({
408
461
  }) {
409
462
  return /* @__PURE__ */ jsxs(Fragment, { children: [
410
463
  /* @__PURE__ */ jsx(
411
- SettingsRow,
464
+ BackgroundSetting,
412
465
  {
413
- title: "Frame",
414
- control: /* @__PURE__ */ jsx(
415
- ToggleSwitch,
416
- {
417
- enabled: frameSettings.enabled,
418
- onChange: () => onFrameSettingsChange({ ...frameSettings, enabled: !frameSettings.enabled })
419
- }
420
- )
466
+ frameSettings,
467
+ onFrameSettingsChange
468
+ }
469
+ ),
470
+ /* @__PURE__ */ jsx(
471
+ FrameSizeControl,
472
+ {
473
+ size: frameSettings.size,
474
+ onChange: (size) => onFrameSettingsChange({ ...frameSettings, size })
421
475
  }
422
476
  ),
423
- frameSettings.enabled && /* @__PURE__ */ jsxs(Fragment, { children: [
424
- /* @__PURE__ */ jsx(SettingsDivider, {}),
425
- /* @__PURE__ */ jsx(
426
- FrameSizeControl,
427
- {
428
- size: frameSettings.size,
429
- onChange: (size) => onFrameSettingsChange({ ...frameSettings, size })
430
- }
431
- ),
432
- /* @__PURE__ */ jsx(SettingsDivider, {}),
433
- /* @__PURE__ */ jsx(
434
- FrameBackgroundControl,
435
- {
436
- bgType: frameSettings.bgType,
437
- bgColor: frameSettings.bgColor,
438
- bgImage: frameSettings.bgImage,
439
- frameSize: frameSettings.size,
440
- onChange: (updates) => onFrameSettingsChange({ ...frameSettings, ...updates })
441
- }
442
- )
443
- ] }),
444
477
  /* @__PURE__ */ jsx(SettingsDivider, {}),
445
478
  /* @__PURE__ */ jsx(
446
- SettingsRow,
479
+ BrowserFrameSetting,
447
480
  {
448
- title: "Browser Chrome",
449
- control: /* @__PURE__ */ jsx(
450
- ToggleSwitch,
451
- {
452
- enabled: frameSettings.browserChrome,
453
- onChange: () => onFrameSettingsChange({ ...frameSettings, browserChrome: !frameSettings.browserChrome })
454
- }
455
- )
481
+ enabled: frameSettings.browserChrome,
482
+ theme: frameSettings.browserTheme,
483
+ onToggle: () => onFrameSettingsChange({ ...frameSettings, browserChrome: !frameSettings.browserChrome }),
484
+ onSelect: (theme) => onFrameSettingsChange({ ...frameSettings, browserChrome: true, browserTheme: theme })
456
485
  }
457
486
  ),
458
- frameSettings.browserChrome && /* @__PURE__ */ jsxs(Fragment, { children: [
459
- /* @__PURE__ */ jsx(SettingsDivider, {}),
460
- /* @__PURE__ */ jsx(
461
- SettingsRow,
462
- {
463
- title: "Theme",
464
- control: /* @__PURE__ */ jsxs("div", { style: { display: "flex", gap: 2, flexShrink: 0 }, children: [
465
- /* @__PURE__ */ jsx(
466
- SegmentButton,
467
- {
468
- active: frameSettings.browserTheme === "light",
469
- onClick: () => onFrameSettingsChange({ ...frameSettings, browserTheme: "light" }),
470
- style: { borderRadius: "6px 0 0 6px" },
471
- children: "Light"
472
- }
473
- ),
474
- /* @__PURE__ */ jsx(
475
- SegmentButton,
476
- {
477
- active: frameSettings.browserTheme === "dark",
478
- onClick: () => onFrameSettingsChange({ ...frameSettings, browserTheme: "dark" }),
479
- style: { borderRadius: "0 6px 6px 0" },
480
- children: "Dark"
481
- }
482
- )
483
- ] })
484
- }
485
- )
486
- ] }),
487
487
  /* @__PURE__ */ jsx(SettingsDivider, {}),
488
488
  saveDir !== void 0 && onPickFolder && /* @__PURE__ */ jsx(
489
489
  SettingsRow,
@@ -512,10 +512,10 @@ function SettingsRow({
512
512
  description,
513
513
  control
514
514
  }) {
515
- return /* @__PURE__ */ jsxs("div", { style: { display: "flex", alignItems: "center", justifyContent: "space-between", gap: 16, padding: "10px 0" }, children: [
515
+ return /* @__PURE__ */ jsxs("div", { style: { display: "flex", alignItems: "center", justifyContent: "space-between", gap: 16, padding: "14px 0" }, children: [
516
516
  /* @__PURE__ */ jsxs("div", { style: { display: "flex", flexDirection: "column", gap: 2, flex: 1, minWidth: 0 }, children: [
517
517
  /* @__PURE__ */ jsx("span", { style: { ...text.label.sm, color: fg.strong }, children: title }),
518
- description && /* @__PURE__ */ jsx("span", { style: { ...text.paragraph.xs, color: fg.muted }, children: description })
518
+ description && /* @__PURE__ */ jsx("span", { style: { ...text.paragraph.xs, color: fg.sub }, children: description })
519
519
  ] }),
520
520
  /* @__PURE__ */ jsx("div", { style: { flexShrink: 0 }, children: control })
521
521
  ] });
@@ -525,7 +525,8 @@ function SettingsDivider() {
525
525
  "div",
526
526
  {
527
527
  style: {
528
- borderBottom: `1px solid ${stroke.soft}`
528
+ borderBottom: `1px solid ${stroke.soft}`,
529
+ margin: "0 -16px"
529
530
  }
530
531
  }
531
532
  );
@@ -574,11 +575,69 @@ function FrameSizeControl({
574
575
  onChange
575
576
  }) {
576
577
  const [sizeOpen, setSizeOpen] = useState2(false);
578
+ const [editing, setEditing] = useState2(null);
579
+ const [text2, setText] = useState2("");
577
580
  const currentPreset = FRAME_SIZE_PRESETS.find((p) => p.w === size.w && p.h === size.h);
578
581
  const isCustom = !currentPreset;
579
- return /* @__PURE__ */ jsxs("div", { style: { padding: "10px 0" }, children: [
580
- /* @__PURE__ */ jsxs("div", { style: { display: "flex", alignItems: "center", justifyContent: "space-between", gap: 16 }, children: [
581
- /* @__PURE__ */ jsx("div", { children: /* @__PURE__ */ jsx("span", { style: { ...text.label.sm, color: fg.strong }, children: "Size" }) }),
582
+ const commitValue = (field, raw) => {
583
+ const n = parseInt(raw, 10);
584
+ if (!Number.isNaN(n) && n > 0) onChange({ ...size, [field]: n });
585
+ };
586
+ const inputStyle = {
587
+ flex: 1,
588
+ minWidth: 0,
589
+ border: "none",
590
+ background: "transparent",
591
+ color: fg.strong,
592
+ ...text.label.xs,
593
+ fontFamily: "inherit",
594
+ textAlign: "left",
595
+ outline: "none",
596
+ padding: "0 2px 0 6px"
597
+ };
598
+ return /* @__PURE__ */ jsxs("div", { style: { marginTop: 16, paddingBottom: 14 }, children: [
599
+ /* @__PURE__ */ jsx("span", { style: { ...text.label.sm, color: fg.strong }, children: "Size" }),
600
+ /* @__PURE__ */ jsx("div", { style: { display: "flex", gap: 6, marginTop: 8 }, children: ["w", "h"].map((field) => /* @__PURE__ */ jsxs(
601
+ "div",
602
+ {
603
+ style: {
604
+ flex: 1,
605
+ minWidth: 0,
606
+ display: "flex",
607
+ alignItems: "center",
608
+ height: 30,
609
+ padding: "0 8px",
610
+ background: state.input,
611
+ borderRadius: 7
612
+ },
613
+ children: [
614
+ /* @__PURE__ */ jsx("span", { style: { ...text.label.xs, color: fg.sub, flexShrink: 0, userSelect: "none" }, children: field.toUpperCase() }),
615
+ /* @__PURE__ */ jsx(
616
+ "input",
617
+ {
618
+ type: "text",
619
+ value: editing === field ? text2 : String(size[field]),
620
+ onFocus: () => {
621
+ setEditing(field);
622
+ setText(String(size[field]));
623
+ },
624
+ onBlur: () => {
625
+ setEditing(null);
626
+ commitValue(field, text2);
627
+ },
628
+ onChange: (e) => setText(e.target.value),
629
+ onKeyDown: (e) => {
630
+ if (e.key === "Enter") e.target.blur();
631
+ },
632
+ style: inputStyle
633
+ }
634
+ )
635
+ ]
636
+ },
637
+ field
638
+ )) }),
639
+ /* @__PURE__ */ jsxs("div", { style: { display: "flex", alignItems: "center", justifyContent: "space-between", gap: 16, marginTop: 16 }, children: [
640
+ /* @__PURE__ */ jsx("span", { style: { ...text.label.sm, color: fg.strong }, children: "Presets" }),
582
641
  /* @__PURE__ */ jsxs("div", { style: { position: "relative", flexShrink: 0 }, children: [
583
642
  /* @__PURE__ */ jsxs(
584
643
  "button",
@@ -588,12 +647,12 @@ function FrameSizeControl({
588
647
  display: "flex",
589
648
  alignItems: "center",
590
649
  gap: 6,
591
- height: 30,
592
- padding: "0 10px",
650
+ height: 28,
651
+ padding: "0 8px",
593
652
  borderRadius: 7,
594
653
  border: `1px solid ${stroke.default}`,
595
654
  background: state.input,
596
- color: fg.strong,
655
+ color: fg.sub,
597
656
  cursor: "pointer",
598
657
  ...text.label.xs,
599
658
  fontFamily: "inherit",
@@ -630,7 +689,7 @@ function FrameSizeControl({
630
689
  },
631
690
  children: [
632
691
  /* @__PURE__ */ jsx("span", { children: preset.label }),
633
- /* @__PURE__ */ jsx("span", { style: { marginLeft: 6, fontSize: 10, color: fg.faint }, children: preset.hint })
692
+ /* @__PURE__ */ jsx("span", { style: { marginLeft: 6, fontSize: 10, color: fg.muted }, children: preset.hint })
634
693
  ]
635
694
  },
636
695
  preset.label
@@ -638,39 +697,16 @@ function FrameSizeControl({
638
697
  }
639
698
  )
640
699
  ] })
641
- ] }),
642
- /* @__PURE__ */ jsxs("div", { style: { display: "flex", alignItems: "center", gap: 4, marginTop: 10 }, children: [
643
- /* @__PURE__ */ jsx(
644
- NumInput,
645
- {
646
- value: size.w,
647
- onChange: (v) => {
648
- const n = parseInt(v, 10);
649
- if (!Number.isNaN(n) && n > 0) onChange({ ...size, w: n });
650
- }
651
- }
652
- ),
653
- /* @__PURE__ */ jsx(StaticText, { children: "x" }),
654
- /* @__PURE__ */ jsx(
655
- NumInput,
656
- {
657
- value: size.h,
658
- onChange: (v) => {
659
- const n = parseInt(v, 10);
660
- if (!Number.isNaN(n) && n > 0) onChange({ ...size, h: n });
661
- }
662
- }
663
- )
664
700
  ] })
665
701
  ] });
666
702
  }
667
- function FrameBackgroundControl({
668
- bgType,
669
- bgColor,
670
- bgImage,
671
- frameSize,
672
- onChange
703
+ function BackgroundSetting({
704
+ frameSettings,
705
+ onFrameSettingsChange
673
706
  }) {
707
+ const [tab, setTab] = useState2(
708
+ frameSettings.bgType === "image" ? "image" : frameSettings.bgType === "gradient" ? "gradient" : "solid"
709
+ );
674
710
  const fileInputRef = useRef(null);
675
711
  const handleFileSelect = (e) => {
676
712
  const file = e.target.files?.[0];
@@ -679,51 +715,122 @@ function FrameBackgroundControl({
679
715
  reader.onload = () => {
680
716
  const dataUrl = reader.result;
681
717
  if (dataUrl.length > 2 * 1024 * 1024) {
682
- downscaleImage(dataUrl, frameSize.w, frameSize.h).then((scaled) => {
683
- onChange({ bgType: "image", bgImage: scaled });
718
+ downscaleImage(dataUrl, frameSettings.size.w, frameSettings.size.h).then((scaled) => {
719
+ onFrameSettingsChange({ ...frameSettings, bgType: "image", bgImage: scaled });
684
720
  });
685
721
  } else {
686
- onChange({ bgType: "image", bgImage: dataUrl });
722
+ onFrameSettingsChange({ ...frameSettings, bgType: "image", bgImage: dataUrl });
687
723
  }
688
724
  };
689
725
  reader.readAsDataURL(file);
690
726
  e.target.value = "";
691
727
  };
692
- return /* @__PURE__ */ jsxs("div", { style: { padding: "10px 0" }, children: [
728
+ const selectGradient = (g) => {
729
+ onFrameSettingsChange({ ...frameSettings, bgType: "gradient", bgGradient: g });
730
+ };
731
+ const selectColor = (c) => {
732
+ onFrameSettingsChange({ ...frameSettings, bgType: "color", bgColor: c });
733
+ };
734
+ const currentBg = frameSettings.bgType === "gradient" ? frameSettings.bgGradient : frameSettings.bgColor;
735
+ return /* @__PURE__ */ jsxs("div", { children: [
693
736
  /* @__PURE__ */ jsxs("div", { style: { display: "flex", alignItems: "center", justifyContent: "space-between", gap: 16 }, children: [
694
- /* @__PURE__ */ jsx("div", { children: /* @__PURE__ */ jsx("span", { style: { ...text.label.sm, color: fg.strong }, children: "Background" }) }),
695
- /* @__PURE__ */ jsxs("div", { style: { display: "flex", gap: 2, flexShrink: 0 }, children: [
696
- /* @__PURE__ */ jsxs(
697
- SegmentButton,
698
- {
699
- active: bgType === "color",
700
- onClick: () => onChange({ bgType: "color" }),
701
- style: { borderRadius: "6px 0 0 6px" },
702
- children: [
703
- /* @__PURE__ */ jsx(Palette, { size: 12, strokeWidth: 2 }),
704
- "Color"
705
- ]
706
- }
707
- ),
708
- /* @__PURE__ */ jsxs(
709
- SegmentButton,
710
- {
711
- active: bgType === "image",
712
- onClick: () => onChange({ bgType: "image" }),
713
- style: { borderRadius: "0 6px 6px 0" },
714
- children: [
715
- /* @__PURE__ */ jsx(ImageIcon, { size: 12, strokeWidth: 2 }),
716
- "Image"
717
- ]
718
- }
719
- )
720
- ] })
737
+ /* @__PURE__ */ jsxs("div", { style: { display: "flex", alignItems: "center", gap: 8 }, children: [
738
+ /* @__PURE__ */ jsx(LayoutGrid, { size: 16, strokeWidth: 1.6, color: fg.sub }),
739
+ /* @__PURE__ */ jsx("span", { style: { ...text.label.sm, color: fg.strong }, children: "Background" })
740
+ ] }),
741
+ /* @__PURE__ */ jsx(
742
+ ToggleSwitch,
743
+ {
744
+ enabled: frameSettings.enabled,
745
+ onChange: () => onFrameSettingsChange({ ...frameSettings, enabled: !frameSettings.enabled })
746
+ }
747
+ )
721
748
  ] }),
722
- bgType === "color" && /* @__PURE__ */ jsxs("div", { style: { display: "flex", alignItems: "center", gap: 8, marginTop: 10 }, children: [
723
- /* @__PURE__ */ jsx(ColorSwatch, { color: bgColor, onChange: (c) => onChange({ bgColor: c }) }),
724
- /* @__PURE__ */ jsx(HexInput, { value: bgColor, onChange: (c) => onChange({ bgColor: c }) })
749
+ /* @__PURE__ */ jsx("div", { style: { display: "flex", gap: 2, marginTop: 12, background: state.subtle, borderRadius: 8, padding: 2 }, children: ["solid", "gradient", "image"].map((t) => /* @__PURE__ */ jsx(
750
+ "button",
751
+ {
752
+ onClick: () => setTab(t),
753
+ style: {
754
+ flex: 1,
755
+ padding: "6px 0",
756
+ border: "none",
757
+ borderRadius: 6,
758
+ background: tab === t ? state.button : "transparent",
759
+ color: tab === t ? fg.strong : fg.default,
760
+ ...text.label.xs,
761
+ fontFamily: "inherit",
762
+ cursor: "pointer",
763
+ transition: "background 0.12s ease, color 0.12s ease",
764
+ textTransform: "capitalize"
765
+ },
766
+ children: t
767
+ },
768
+ t
769
+ )) }),
770
+ tab === "gradient" && /* @__PURE__ */ jsx("div", { style: { display: "flex", gap: 8, marginTop: 12, paddingBottom: 10, flexWrap: "wrap" }, children: GRADIENT_PRESETS.map((g) => {
771
+ const selected = frameSettings.bgType === "gradient" && frameSettings.bgGradient === g;
772
+ return /* @__PURE__ */ jsx(
773
+ PresetSwatch,
774
+ {
775
+ background: g,
776
+ selected,
777
+ onClick: () => selectGradient(g)
778
+ },
779
+ g
780
+ );
781
+ }) }),
782
+ tab === "solid" && /* @__PURE__ */ jsxs(Fragment, { children: [
783
+ /* @__PURE__ */ jsxs("div", { style: { display: "flex", gap: 8, marginTop: 12, paddingBottom: 10, flexWrap: "wrap" }, children: [
784
+ COLOR_PRESETS.map((c) => {
785
+ const selected = frameSettings.bgType === "color" && frameSettings.bgColor === c;
786
+ return /* @__PURE__ */ jsx(
787
+ PresetSwatch,
788
+ {
789
+ background: c,
790
+ selected,
791
+ onClick: () => selectColor(c)
792
+ },
793
+ c
794
+ );
795
+ }),
796
+ (() => {
797
+ const customColor = frameSettings.bgColor;
798
+ const isCustom = !COLOR_PRESETS.includes(customColor.toUpperCase());
799
+ const bg2 = isCustom ? customColor : "transparent";
800
+ const iconColor = isCustom ? hexLuminance(customColor) > 0.4 ? "rgba(0,0,0,0.5)" : "rgba(255,255,255,0.7)" : fg.sub;
801
+ return /* @__PURE__ */ jsx(
802
+ "button",
803
+ {
804
+ onClick: async () => {
805
+ if (!("EyeDropper" in window)) return;
806
+ try {
807
+ const result = await new window.EyeDropper().open();
808
+ selectColor(result.sRGBHex);
809
+ } catch {
810
+ }
811
+ },
812
+ style: {
813
+ width: 32,
814
+ height: 32,
815
+ borderRadius: "50%",
816
+ border: isCustom ? `1px solid ${stroke.soft}` : `1px dashed ${stroke.default}`,
817
+ outline: isCustom ? `2px solid ${accent.toggle}` : "none",
818
+ outlineOffset: 1,
819
+ background: bg2,
820
+ cursor: "pointer",
821
+ display: "flex",
822
+ alignItems: "center",
823
+ justifyContent: "center",
824
+ color: iconColor
825
+ },
826
+ children: /* @__PURE__ */ jsx(Pipette, { size: 14, strokeWidth: 2 })
827
+ }
828
+ );
829
+ })()
830
+ ] }),
831
+ /* @__PURE__ */ jsx("div", { style: { paddingBottom: 4 }, children: /* @__PURE__ */ jsx(ColorHexInput, { value: frameSettings.bgColor, onChange: selectColor }) })
725
832
  ] }),
726
- bgType === "image" && /* @__PURE__ */ jsxs("div", { style: { marginTop: 10 }, children: [
833
+ tab === "image" && /* @__PURE__ */ jsxs("div", { style: { marginTop: 12 }, children: [
727
834
  /* @__PURE__ */ jsx(
728
835
  "input",
729
836
  {
@@ -734,11 +841,11 @@ function FrameBackgroundControl({
734
841
  style: { display: "none" }
735
842
  }
736
843
  ),
737
- bgImage ? /* @__PURE__ */ jsxs("div", { style: { display: "flex", alignItems: "center", gap: 8 }, children: [
844
+ frameSettings.bgImage ? /* @__PURE__ */ jsxs("div", { style: { display: "flex", alignItems: "center", gap: 8 }, children: [
738
845
  /* @__PURE__ */ jsx(
739
846
  "img",
740
847
  {
741
- src: bgImage,
848
+ src: frameSettings.bgImage,
742
849
  alt: "",
743
850
  style: {
744
851
  width: 48,
@@ -753,82 +860,234 @@ function FrameBackgroundControl({
753
860
  /* @__PURE__ */ jsx(Upload, { size: 11, strokeWidth: 2 }),
754
861
  "Replace"
755
862
  ] }),
756
- /* @__PURE__ */ jsx(SmallButton, { onClick: () => onChange({ bgImage: null }), children: /* @__PURE__ */ jsx(Trash2, { size: 11, strokeWidth: 2 }) })
863
+ /* @__PURE__ */ jsx(SmallButton, { onClick: () => onFrameSettingsChange({ ...frameSettings, bgImage: null }), children: /* @__PURE__ */ jsx(Trash2, { size: 11, strokeWidth: 2 }) })
757
864
  ] }) : /* @__PURE__ */ jsxs(SmallButton, { onClick: () => fileInputRef.current?.click(), children: [
758
865
  /* @__PURE__ */ jsx(Upload, { size: 11, strokeWidth: 2 }),
759
866
  "Upload image"
760
867
  ] })
868
+ ] }),
869
+ /* @__PURE__ */ jsxs("div", { style: { marginTop: 16 }, children: [
870
+ /* @__PURE__ */ jsx("span", { style: { ...text.label.sm, color: fg.strong }, children: "Padding" }),
871
+ /* @__PURE__ */ jsxs("div", { style: { display: "flex", alignItems: "center", gap: 8, marginTop: 8 }, children: [
872
+ /* @__PURE__ */ jsx(
873
+ SlimSlider,
874
+ {
875
+ min: 0,
876
+ max: 200,
877
+ step: 5,
878
+ value: frameSettings.padding,
879
+ onChange: (v) => onFrameSettingsChange({ ...frameSettings, padding: v })
880
+ }
881
+ ),
882
+ /* @__PURE__ */ jsx(
883
+ PaddingInput,
884
+ {
885
+ value: frameSettings.padding,
886
+ onChange: (n) => onFrameSettingsChange({ ...frameSettings, padding: n })
887
+ }
888
+ )
889
+ ] })
761
890
  ] })
762
891
  ] });
763
892
  }
764
- function SegmentButton({
765
- children,
766
- active,
767
- onClick,
768
- style: style2
893
+ function SlimSlider({
894
+ min,
895
+ max,
896
+ step,
897
+ value,
898
+ onChange
769
899
  }) {
900
+ const trackRef = useRef(null);
901
+ const pct = (value - min) / (max - min) * 100;
902
+ const update = (clientX) => {
903
+ const rect = trackRef.current.getBoundingClientRect();
904
+ const ratio = Math.max(0, Math.min(1, (clientX - rect.left) / rect.width));
905
+ const raw = min + ratio * (max - min);
906
+ const snapped = Math.round(raw / step) * step;
907
+ onChange(Math.max(min, Math.min(max, snapped)));
908
+ };
909
+ const onPointerDown = (e) => {
910
+ e.preventDefault();
911
+ e.target.setPointerCapture(e.pointerId);
912
+ update(e.clientX);
913
+ };
914
+ const onPointerMove = (e) => {
915
+ if (e.buttons === 0) return;
916
+ update(e.clientX);
917
+ };
770
918
  return /* @__PURE__ */ jsx(
771
- "button",
919
+ "div",
772
920
  {
773
- onClick,
921
+ ref: trackRef,
922
+ onPointerDown,
923
+ onPointerMove,
774
924
  style: {
925
+ flex: 1,
926
+ minWidth: 0,
927
+ height: 20,
775
928
  display: "flex",
776
929
  alignItems: "center",
777
- gap: 4,
778
- height: 28,
779
- padding: "0 10px",
780
- border: `1px solid ${stroke.default}`,
781
- background: active ? state.pressed : state.subtle,
782
- color: active ? fg.strong : fg.sub,
783
930
  cursor: "pointer",
784
- ...text.label.xs,
785
- fontFamily: "inherit",
786
- transition: "background 0.12s ease, color 0.12s ease",
787
- ...style2
931
+ touchAction: "none"
788
932
  },
789
- children
933
+ children: /* @__PURE__ */ jsxs("div", { style: { position: "relative", width: "100%", height: 3, borderRadius: 2, background: stroke.default }, children: [
934
+ /* @__PURE__ */ jsx("div", { style: { position: "absolute", left: 0, top: 0, height: "100%", width: `${pct}%`, borderRadius: 2, background: fg.sub } }),
935
+ /* @__PURE__ */ jsx(
936
+ "div",
937
+ {
938
+ style: {
939
+ position: "absolute",
940
+ top: "50%",
941
+ left: `${pct}%`,
942
+ width: 10,
943
+ height: 10,
944
+ borderRadius: "50%",
945
+ background: fg.strong,
946
+ transform: "translate(-50%, -50%)"
947
+ }
948
+ }
949
+ )
950
+ ] })
790
951
  }
791
952
  );
792
953
  }
793
- function ColorSwatch({
794
- color,
795
- onChange
954
+ function PresetSwatch({
955
+ background,
956
+ selected,
957
+ onClick
796
958
  }) {
797
- const inputRef = useRef(null);
798
- return /* @__PURE__ */ jsxs("div", { style: { position: "relative" }, children: [
799
- /* @__PURE__ */ jsx(
800
- "button",
801
- {
802
- onClick: () => inputRef.current?.click(),
803
- style: {
804
- width: 24,
805
- height: 24,
806
- borderRadius: 6,
807
- border: `1px solid ${stroke.interactive}`,
808
- background: color,
809
- cursor: "pointer",
810
- padding: 0
811
- }
812
- }
813
- ),
814
- /* @__PURE__ */ jsx(
815
- "input",
816
- {
817
- ref: inputRef,
818
- type: "color",
819
- value: color,
820
- onChange: (e) => onChange(e.target.value),
821
- style: {
822
- position: "absolute",
823
- top: 0,
824
- left: 0,
825
- width: 0,
826
- height: 0,
827
- opacity: 0,
828
- pointerEvents: "none"
959
+ return /* @__PURE__ */ jsx(
960
+ "div",
961
+ {
962
+ onClick,
963
+ style: {
964
+ width: 32,
965
+ height: 32,
966
+ borderRadius: "50%",
967
+ background,
968
+ cursor: "pointer",
969
+ border: `1px solid ${stroke.soft}`,
970
+ outline: selected ? `2px solid ${accent.toggle}` : "none",
971
+ outlineOffset: 1,
972
+ position: "relative",
973
+ transition: "outline-color 0.12s ease",
974
+ flexShrink: 0
975
+ },
976
+ children: selected && /* @__PURE__ */ jsx(
977
+ "div",
978
+ {
979
+ style: {
980
+ position: "absolute",
981
+ inset: 0,
982
+ borderRadius: "50%",
983
+ display: "flex",
984
+ alignItems: "center",
985
+ justifyContent: "center",
986
+ background: "rgba(0,0,0,0.2)"
987
+ },
988
+ children: /* @__PURE__ */ jsx(Check, { size: 16, strokeWidth: 3, color: "#fff" })
829
989
  }
830
- }
831
- )
990
+ )
991
+ }
992
+ );
993
+ }
994
+ function hexLuminance(hex) {
995
+ const n = parseInt(hex.replace("#", ""), 16);
996
+ const r = (n >> 16 & 255) / 255;
997
+ const g = (n >> 8 & 255) / 255;
998
+ const b = (n & 255) / 255;
999
+ return 0.2126 * r + 0.7152 * g + 0.0722 * b;
1000
+ }
1001
+ function BrowserFrameSetting({
1002
+ enabled,
1003
+ theme,
1004
+ onToggle,
1005
+ onSelect
1006
+ }) {
1007
+ const trafficLights = /* @__PURE__ */ jsx("div", { style: { display: "flex", gap: 4 }, children: ["#FF5F57", "#FEBC2E", "#28C840"].map((c) => /* @__PURE__ */ jsx("div", { style: { width: 5, height: 5, borderRadius: "50%", background: c } }, c)) });
1008
+ const themes = [
1009
+ { value: "dark", label: "macOS Dark", titleBar: "#1C1C1C", urlBar: "#262626", border: "#333333" },
1010
+ { value: "light", label: "macOS Light", titleBar: "#F5F5F5", urlBar: "#FFFFFF", border: "#EBEBEB" }
1011
+ ];
1012
+ return /* @__PURE__ */ jsxs("div", { style: { padding: "14px 0" }, children: [
1013
+ /* @__PURE__ */ jsxs("div", { style: { display: "flex", alignItems: "center", justifyContent: "space-between", gap: 16 }, children: [
1014
+ /* @__PURE__ */ jsxs("div", { style: { display: "flex", alignItems: "center", gap: 8 }, children: [
1015
+ /* @__PURE__ */ jsx(AppWindow, { size: 16, strokeWidth: 1.6, color: fg.sub }),
1016
+ /* @__PURE__ */ jsx("span", { style: { ...text.label.sm, color: fg.strong }, children: "Browser Frame" })
1017
+ ] }),
1018
+ /* @__PURE__ */ jsx(ToggleSwitch, { enabled, onChange: onToggle })
1019
+ ] }),
1020
+ /* @__PURE__ */ jsx("div", { style: { display: "flex", gap: 10, marginTop: 12 }, children: themes.map((t) => {
1021
+ const selected = enabled && theme === t.value;
1022
+ return /* @__PURE__ */ jsxs(
1023
+ "div",
1024
+ {
1025
+ onClick: () => onSelect(t.value),
1026
+ style: {
1027
+ flex: 1,
1028
+ cursor: "pointer",
1029
+ borderRadius: 10,
1030
+ border: selected ? `2px solid ${accent.toggle}` : `1px solid ${stroke.soft}`,
1031
+ padding: selected ? 7 : 8,
1032
+ position: "relative",
1033
+ background: state.subtle,
1034
+ transition: "border-color 0.12s ease"
1035
+ },
1036
+ children: [
1037
+ /* @__PURE__ */ jsxs(
1038
+ "div",
1039
+ {
1040
+ style: {
1041
+ background: t.titleBar,
1042
+ borderRadius: 6,
1043
+ overflow: "hidden",
1044
+ border: t.value === "light" ? `1px solid ${t.border}` : "none",
1045
+ display: "flex",
1046
+ alignItems: "center",
1047
+ gap: 6,
1048
+ padding: "6px 8px"
1049
+ },
1050
+ children: [
1051
+ trafficLights,
1052
+ /* @__PURE__ */ jsx(
1053
+ "div",
1054
+ {
1055
+ style: {
1056
+ flex: 1,
1057
+ height: 8,
1058
+ background: t.urlBar,
1059
+ borderRadius: 2,
1060
+ ...t.value === "light" ? { border: `1px solid ${t.border}` } : {}
1061
+ }
1062
+ }
1063
+ )
1064
+ ]
1065
+ }
1066
+ ),
1067
+ /* @__PURE__ */ jsx("div", { style: { textAlign: "center", marginTop: 8, ...text.label.xs, color: fg.default }, children: t.label }),
1068
+ selected && /* @__PURE__ */ jsx(
1069
+ "div",
1070
+ {
1071
+ style: {
1072
+ position: "absolute",
1073
+ top: -5,
1074
+ right: -5,
1075
+ width: 18,
1076
+ height: 18,
1077
+ borderRadius: "50%",
1078
+ background: accent.toggle,
1079
+ display: "flex",
1080
+ alignItems: "center",
1081
+ justifyContent: "center"
1082
+ },
1083
+ children: /* @__PURE__ */ jsx(Check, { size: 10, strokeWidth: 3, color: "#fff" })
1084
+ }
1085
+ )
1086
+ ]
1087
+ },
1088
+ t.value
1089
+ );
1090
+ }) })
832
1091
  ] });
833
1092
  }
834
1093
  function SmallButton({
@@ -860,113 +1119,127 @@ function SmallButton({
860
1119
  }
861
1120
  );
862
1121
  }
863
- function NumInput({
1122
+ function PaddingInput({
864
1123
  value,
865
1124
  onChange
866
1125
  }) {
867
1126
  const [editing, setEditing] = useState2(false);
868
1127
  const [text2, setText] = useState2(String(value));
869
1128
  useEffect(() => {
870
- if (!editing) {
871
- setText(String(value));
872
- }
1129
+ if (!editing) setText(String(value));
873
1130
  }, [editing, value]);
874
- return /* @__PURE__ */ jsx(
875
- "input",
1131
+ return /* @__PURE__ */ jsxs(
1132
+ "div",
876
1133
  {
877
- type: "text",
878
- value: editing ? text2 : String(value),
879
- onFocus: () => {
880
- setEditing(true);
881
- setText(String(value));
882
- },
883
- onBlur: () => {
884
- setEditing(false);
885
- onChange(text2);
886
- },
887
- onChange: (e) => setText(e.target.value),
888
- onKeyDown: (e) => {
889
- if (e.key === "Enter") {
890
- e.target.blur();
891
- }
892
- },
893
1134
  style: {
894
- width: 54,
895
- padding: "4px 6px",
1135
+ display: "flex",
1136
+ alignItems: "center",
1137
+ height: 30,
1138
+ padding: "0 8px",
896
1139
  background: state.input,
897
- border: `1px solid ${stroke.default}`,
898
1140
  borderRadius: 7,
899
- color: fg.strong,
900
- ...text.label.xs,
901
- fontFamily: "inherit",
902
- textAlign: "center",
903
- outline: "none"
904
- }
905
- }
1141
+ flexShrink: 0,
1142
+ width: 68
1143
+ },
1144
+ children: [
1145
+ /* @__PURE__ */ jsx(
1146
+ "input",
1147
+ {
1148
+ type: "text",
1149
+ value: editing ? text2 : String(value),
1150
+ onFocus: () => {
1151
+ setEditing(true);
1152
+ setText(String(value));
1153
+ },
1154
+ onBlur: () => {
1155
+ setEditing(false);
1156
+ const n = parseInt(text2, 10);
1157
+ if (!Number.isNaN(n) && n >= 0 && n <= 200) onChange(n);
1158
+ },
1159
+ onChange: (e) => setText(e.target.value),
1160
+ onKeyDown: (e) => {
1161
+ if (e.key === "Enter") e.target.blur();
1162
+ },
1163
+ style: {
1164
+ flex: 1,
1165
+ minWidth: 0,
1166
+ border: "none",
1167
+ background: "transparent",
1168
+ color: fg.strong,
1169
+ ...text.label.xs,
1170
+ fontFamily: "inherit",
1171
+ textAlign: "left",
1172
+ outline: "none",
1173
+ padding: 0
1174
+ }
1175
+ }
1176
+ ),
1177
+ /* @__PURE__ */ jsx("span", { style: { ...text.label.xs, color: fg.sub, flexShrink: 0, userSelect: "none", marginLeft: 4 }, children: "px" })
1178
+ ]
1179
+ }
906
1180
  );
907
1181
  }
908
- function HexInput({
1182
+ function ColorHexInput({
909
1183
  value,
910
1184
  onChange
911
1185
  }) {
912
1186
  const [editing, setEditing] = useState2(false);
913
- const [text2, setText] = useState2(value);
1187
+ const [text2, setText] = useState2(value.replace("#", ""));
914
1188
  useEffect(() => {
915
- if (!editing) {
916
- setText(value);
917
- }
1189
+ if (!editing) setText(value.replace("#", ""));
918
1190
  }, [editing, value]);
919
1191
  const commit = (raw) => {
920
1192
  const hex = raw.startsWith("#") ? raw : `#${raw}`;
921
- if (/^#[0-9a-fA-F]{6}$/.test(hex)) {
922
- onChange(hex);
923
- }
1193
+ if (/^#[0-9a-fA-F]{6}$/.test(hex)) onChange(hex);
924
1194
  };
925
- return /* @__PURE__ */ jsx(
926
- "input",
1195
+ return /* @__PURE__ */ jsxs(
1196
+ "div",
927
1197
  {
928
- type: "text",
929
- value: editing ? text2 : value,
930
- onFocus: () => {
931
- setEditing(true);
932
- setText(value);
933
- },
934
- onBlur: () => {
935
- setEditing(false);
936
- commit(text2);
937
- },
938
- onChange: (e) => setText(e.target.value),
939
- onKeyDown: (e) => {
940
- if (e.key === "Enter") {
941
- e.target.blur();
942
- }
943
- },
944
1198
  style: {
945
- width: 72,
946
- padding: "4px 6px",
1199
+ flex: 1,
1200
+ minWidth: 0,
1201
+ display: "flex",
1202
+ alignItems: "center",
1203
+ height: 30,
1204
+ padding: "0 8px",
947
1205
  background: state.input,
948
- border: `1px solid ${stroke.default}`,
949
1206
  borderRadius: 7,
950
- color: fg.strong,
951
- ...text.label.xs,
952
- fontFamily: "inherit",
953
- textAlign: "left",
954
- outline: "none"
955
- }
956
- }
957
- );
958
- }
959
- function StaticText({ children }) {
960
- return /* @__PURE__ */ jsx(
961
- "span",
962
- {
963
- style: {
964
- fontSize: 11,
965
- color: fg.faint,
966
- minWidth: 8,
967
- textAlign: "center"
1207
+ gap: 6
968
1208
  },
969
- children
1209
+ children: [
1210
+ /* @__PURE__ */ jsx("span", { style: { ...text.label.xs, color: fg.sub, flexShrink: 0, userSelect: "none" }, children: "#" }),
1211
+ /* @__PURE__ */ jsx(
1212
+ "input",
1213
+ {
1214
+ type: "text",
1215
+ value: editing ? text2 : value.replace("#", ""),
1216
+ onFocus: () => {
1217
+ setEditing(true);
1218
+ setText(value.replace("#", ""));
1219
+ },
1220
+ onBlur: () => {
1221
+ setEditing(false);
1222
+ commit(text2);
1223
+ },
1224
+ onChange: (e) => setText(e.target.value),
1225
+ onKeyDown: (e) => {
1226
+ if (e.key === "Enter") e.target.blur();
1227
+ },
1228
+ style: {
1229
+ flex: 1,
1230
+ minWidth: 0,
1231
+ border: "none",
1232
+ background: "transparent",
1233
+ color: fg.strong,
1234
+ ...text.label.xs,
1235
+ fontFamily: "inherit",
1236
+ textAlign: "left",
1237
+ outline: "none",
1238
+ padding: 0
1239
+ }
1240
+ }
1241
+ )
1242
+ ]
970
1243
  }
971
1244
  );
972
1245
  }
@@ -1012,615 +1285,175 @@ async function downscaleImage(dataUrl, maxW, maxH) {
1012
1285
  });
1013
1286
  }
1014
1287
 
1015
- // src/overlay/ui/toolbar.tsx
1288
+ // src/overlay/ui/screenshots-panel.tsx
1016
1289
  import { Fragment as Fragment2, jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
1017
- var EDGE_MARGIN = 24;
1018
- var CONTAINER_SIZE = 38;
1019
- function getCornerStyle(corner) {
1020
- switch (corner) {
1021
- case "bottom-right":
1022
- return { bottom: EDGE_MARGIN, right: EDGE_MARGIN };
1023
- case "bottom-left":
1024
- return { bottom: EDGE_MARGIN, left: EDGE_MARGIN };
1025
- case "top-right":
1026
- return { top: EDGE_MARGIN, right: EDGE_MARGIN };
1027
- case "top-left":
1028
- return { top: EDGE_MARGIN, left: EDGE_MARGIN };
1029
- }
1030
- }
1031
- function isBottomCorner(corner) {
1032
- return corner === "bottom-right" || corner === "bottom-left";
1033
- }
1034
- function isRightCorner(corner) {
1035
- return corner === "bottom-right" || corner === "top-right";
1290
+ function BrowserChromeBar({ theme }) {
1291
+ const colors = theme === "dark" ? { titleBar: "#1C1C1C", urlBar: "#262626", text: "#7B7B7B", border: "#333333" } : { titleBar: "#F5F5F5", urlBar: "#FFFFFF", text: "#999999", border: "#EBEBEB" };
1292
+ return /* @__PURE__ */ jsxs2("div", { style: { background: colors.titleBar, flexShrink: 0 }, children: [
1293
+ /* @__PURE__ */ jsx2("div", { style: { display: "flex", alignItems: "center", height: 18, padding: "0 8px" }, children: /* @__PURE__ */ jsx2("div", { style: { display: "flex", gap: 5 }, children: ["#FF5F57", "#FEBC2E", "#28C840"].map((c) => /* @__PURE__ */ jsx2("div", { style: { width: 6, height: 6, borderRadius: "50%", background: c } }, c)) }) }),
1294
+ /* @__PURE__ */ jsx2("div", { style: { padding: "2px 40px 3px" }, children: /* @__PURE__ */ jsx2(
1295
+ "div",
1296
+ {
1297
+ style: {
1298
+ background: colors.urlBar,
1299
+ borderRadius: 3,
1300
+ padding: "2px 0",
1301
+ textAlign: "center",
1302
+ ...theme === "light" ? { border: `1px solid ${colors.border}` } : {}
1303
+ },
1304
+ children: /* @__PURE__ */ jsx2(
1305
+ "span",
1306
+ {
1307
+ style: {
1308
+ fontSize: 7,
1309
+ color: colors.text,
1310
+ fontFamily: '-apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif'
1311
+ },
1312
+ children: "localhost"
1313
+ }
1314
+ )
1315
+ }
1316
+ ) }),
1317
+ /* @__PURE__ */ jsx2("div", { style: { height: 1, background: colors.border } })
1318
+ ] });
1036
1319
  }
1037
- function snapToCorner(x, y) {
1038
- const cx = window.innerWidth / 2;
1039
- const cy = window.innerHeight / 2;
1040
- if (x < cx) {
1041
- return y < cy ? "top-left" : "bottom-left";
1042
- }
1043
- return y < cy ? "top-right" : "bottom-right";
1320
+ function FramePreview({
1321
+ previewUrl,
1322
+ frameSettings,
1323
+ loading
1324
+ }) {
1325
+ const { enabled: frameEnabled, browserChrome, browserTheme, size, bgType, bgColor, bgGradient, bgImage, padding } = frameSettings;
1326
+ const aspectRatio = frameEnabled ? `${size.w} / ${size.h}` : "16 / 10";
1327
+ const outerBg = frameEnabled ? bgType === "image" && bgImage ? { backgroundImage: `url(${bgImage})`, backgroundSize: "cover", backgroundPosition: "center" } : bgType === "gradient" ? { background: bgGradient } : { background: bgColor } : { background: bg.elevated };
1328
+ const emptyState = /* @__PURE__ */ jsx2("span", { style: { ...text.paragraph.xs, color: fg.sub }, children: loading ? "Loading..." : "No screenshots yet" });
1329
+ const imageEl = previewUrl ? /* @__PURE__ */ jsx2(
1330
+ "img",
1331
+ {
1332
+ src: previewUrl,
1333
+ alt: "",
1334
+ style: {
1335
+ maxWidth: "100%",
1336
+ maxHeight: "100%",
1337
+ objectFit: "contain",
1338
+ display: "block"
1339
+ }
1340
+ }
1341
+ ) : null;
1342
+ return /* @__PURE__ */ jsxs2(
1343
+ "div",
1344
+ {
1345
+ style: {
1346
+ width: "100%",
1347
+ aspectRatio,
1348
+ borderRadius: 10,
1349
+ overflow: "hidden",
1350
+ border: `1px solid ${stroke.soft}`,
1351
+ marginBottom: 10,
1352
+ display: "flex",
1353
+ flexDirection: "column",
1354
+ ...outerBg
1355
+ },
1356
+ children: [
1357
+ browserChrome && /* @__PURE__ */ jsx2(BrowserChromeBar, { theme: browserTheme }),
1358
+ /* @__PURE__ */ jsx2(
1359
+ "div",
1360
+ {
1361
+ style: {
1362
+ flex: 1,
1363
+ display: "flex",
1364
+ alignItems: "center",
1365
+ justifyContent: "center",
1366
+ minHeight: 0,
1367
+ ...frameEnabled ? { padding: `${padding / size.h * 100}%` } : {}
1368
+ },
1369
+ children: imageEl || emptyState
1370
+ }
1371
+ )
1372
+ ]
1373
+ }
1374
+ );
1044
1375
  }
1045
- function getCornerPosition(corner, w, h) {
1046
- const vw = window.innerWidth;
1047
- const vh = window.innerHeight;
1048
- switch (corner) {
1049
- case "bottom-right":
1050
- return { x: vw - EDGE_MARGIN - w, y: vh - EDGE_MARGIN - h };
1051
- case "bottom-left":
1052
- return { x: EDGE_MARGIN, y: vh - EDGE_MARGIN - h };
1053
- case "top-right":
1054
- return { x: vw - EDGE_MARGIN - w, y: EDGE_MARGIN };
1055
- case "top-left":
1056
- return { x: EDGE_MARGIN, y: EDGE_MARGIN };
1057
- }
1376
+ function formatTimestamp(filename) {
1377
+ const iso = filename.replace(/\.png$/, "").replace(/T(\d{2})-(\d{2})-(\d{2})-(\d+)Z$/, "T$1:$2:$3.$4Z");
1378
+ const date = new Date(iso);
1379
+ if (Number.isNaN(date.getTime())) return filename.replace(/\.png$/, "");
1380
+ const now = /* @__PURE__ */ new Date();
1381
+ const diffMs = now.getTime() - date.getTime();
1382
+ const diffMin = Math.floor(diffMs / 6e4);
1383
+ if (diffMin < 1) return "Just now";
1384
+ if (diffMin < 60) return `${diffMin}m ago`;
1385
+ const diffHr = Math.floor(diffMin / 60);
1386
+ if (diffHr < 24) return `${diffHr}h ago`;
1387
+ return date.toLocaleDateString(void 0, { month: "short", day: "numeric", hour: "2-digit", minute: "2-digit" });
1058
1388
  }
1059
- var MODES = [
1060
- { mode: "component", label: "Component", icon: MousePointer2 },
1061
- { mode: "viewport", label: "Viewport", icon: Monitor },
1062
- { mode: "fullpage", label: "Full Page", icon: FileText }
1063
- ];
1064
- function Toolbar({
1065
- expanded,
1066
- onToggle,
1067
- phase,
1068
- loading,
1389
+ function ScreenshotsPanel({
1390
+ open,
1391
+ onClick,
1069
1392
  selectedMode,
1070
- onModeChange,
1071
- onCapture,
1072
- onCancel,
1073
1393
  frameSettings,
1074
- onFrameSettingsChange
1394
+ onFrameSettingsChange,
1395
+ tooltipSide
1075
1396
  }) {
1076
- const [historyOpen, setHistoryOpen] = useState3(false);
1077
- const [modesExpanded, setModesExpanded] = useState3(true);
1078
- const [buttonsVisible, setButtonsVisible] = useState3(expanded);
1079
- const [animIn, setAnimIn] = useState3(expanded);
1080
- const [animDone, setAnimDone] = useState3(expanded);
1397
+ const [toast, setToast] = useState3(null);
1398
+ const [saveDir, setSaveDir] = useState3(null);
1399
+ const [picking, setPicking] = useState3(false);
1400
+ const [repos, setRepos] = useState3([]);
1401
+ const [branches, setBranches] = useState3([]);
1402
+ const [screenshots, setScreenshots] = useState3([]);
1403
+ const [selectedRepo, setSelectedRepo] = useState3(null);
1404
+ const [selectedBranch, setSelectedBranch] = useState3(null);
1405
+ const [loading, setLoading] = useState3(false);
1406
+ const [repoDropOpen, setRepoDropOpen] = useState3(false);
1407
+ const [branchDropOpen, setBranchDropOpen] = useState3(false);
1408
+ const [editingFile, setEditingFile] = useState3(null);
1409
+ const [editValue, setEditValue] = useState3("");
1410
+ const [selectedFile, setSelectedFile] = useState3(null);
1081
1411
  useEffect2(() => {
1082
- if (expanded) {
1083
- setAnimDone(false);
1084
- setButtonsVisible(true);
1085
- setAnimIn(false);
1086
- requestAnimationFrame(() => {
1087
- requestAnimationFrame(() => setAnimIn(true));
1088
- });
1089
- const timer = setTimeout(() => setAnimDone(true), 250);
1090
- return () => clearTimeout(timer);
1091
- } else if (buttonsVisible) {
1092
- setHistoryOpen(false);
1093
- setAnimDone(false);
1094
- setAnimIn(false);
1095
- const timer = setTimeout(() => setButtonsVisible(false), 150);
1096
- return () => clearTimeout(timer);
1097
- }
1098
- }, [expanded]);
1099
- const [corner, setCorner] = useState3(() => {
1412
+ if (!open) return;
1413
+ fetch("/__afterbefore/config").then((r) => r.json()).then((data) => setSaveDir(data.saveDir)).catch(() => {
1414
+ });
1415
+ }, [open]);
1416
+ const handlePickFolder = async () => {
1417
+ setPicking(true);
1100
1418
  try {
1101
- const stored = localStorage.getItem("ab-toolbar-corner");
1102
- if (stored && ["bottom-right", "bottom-left", "top-right", "top-left"].includes(stored)) {
1103
- return stored;
1419
+ const res = await fetch("/__afterbefore/pick-folder", { method: "POST" });
1420
+ const data = await res.json();
1421
+ if (data.folder) {
1422
+ await fetch("/__afterbefore/config", {
1423
+ method: "POST",
1424
+ headers: { "Content-Type": "application/json" },
1425
+ body: JSON.stringify({ saveDir: data.folder })
1426
+ });
1427
+ setSaveDir(data.folder);
1104
1428
  }
1105
1429
  } catch {
1430
+ } finally {
1431
+ setPicking(false);
1106
1432
  }
1107
- return "bottom-right";
1108
- });
1109
- const [dragging, setDragging] = useState3(false);
1110
- const [dragPos, setDragPos] = useState3(null);
1111
- const [snapAnim, setSnapAnim] = useState3(null);
1112
- const dragState = useRef2(null);
1113
- const toolbarRef = useRef2(null);
1114
- const [cameraHovered, setCameraHovered] = useState3(false);
1433
+ };
1434
+ const shortDir = saveDir ? saveDir.replace(/^\/Users\/[^/]+/, "~") : "~/Desktop";
1115
1435
  useEffect2(() => {
1116
- if (!expanded) return;
1117
- const onKey = (e) => {
1118
- if (e.target?.tagName === "INPUT") {
1119
- if (e.key === "Escape") {
1120
- e.target.blur();
1121
- }
1122
- return;
1123
- }
1124
- if (e.key === "Escape") {
1125
- if (historyOpen) {
1126
- setHistoryOpen(false);
1127
- return;
1128
- }
1129
- onCancel();
1130
- } else if (e.key === "Enter") {
1131
- onCapture(selectedMode);
1436
+ if (!open) {
1437
+ setRepoDropOpen(false);
1438
+ setBranchDropOpen(false);
1439
+ return;
1440
+ }
1441
+ setLoading(true);
1442
+ const params = new URLSearchParams();
1443
+ if (selectedRepo) params.set("repo", selectedRepo);
1444
+ if (selectedBranch) params.set("branch", selectedBranch);
1445
+ fetch(`/__afterbefore/history?${params}`).then((r) => r.json()).then((data) => {
1446
+ setRepos(data.repos || []);
1447
+ setBranches(data.branches || []);
1448
+ const shots = data.screenshots || [];
1449
+ setScreenshots(shots);
1450
+ if (shots.length > 0) {
1451
+ setSelectedFile(
1452
+ (prev) => prev && shots.some((s) => s.filename === prev) ? prev : shots[0].filename
1453
+ );
1454
+ } else {
1455
+ setSelectedFile(null);
1132
1456
  }
1133
- };
1134
- document.addEventListener("keydown", onKey);
1135
- return () => document.removeEventListener("keydown", onKey);
1136
- }, [expanded, onCancel, onCapture, selectedMode, historyOpen]);
1137
- const handleMouseDown = useCallback2(
1138
- (e) => {
1139
- e.preventDefault();
1140
- const el = toolbarRef.current;
1141
- if (!el) return;
1142
- const rect = el.getBoundingClientRect();
1143
- setDragging(true);
1144
- setDragPos({ x: rect.left, y: rect.top });
1145
- dragState.current = {
1146
- dragging: true,
1147
- startX: e.clientX,
1148
- startY: e.clientY,
1149
- origX: rect.left,
1150
- origY: rect.top,
1151
- distance: 0
1152
- };
1153
- },
1154
- []
1155
- );
1156
- useEffect2(() => {
1157
- const handleMouseMove = (e) => {
1158
- const ds = dragState.current;
1159
- if (!ds || !ds.dragging) return;
1160
- const dx = e.clientX - ds.startX;
1161
- const dy = e.clientY - ds.startY;
1162
- ds.distance = Math.sqrt(dx * dx + dy * dy);
1163
- setDragPos({
1164
- x: ds.origX + dx,
1165
- y: ds.origY + dy
1166
- });
1167
- };
1168
- const handleMouseUp = (e) => {
1169
- const ds = dragState.current;
1170
- if (!ds) return;
1171
- if (ds.distance < 5) {
1172
- onToggle();
1173
- setDragging(false);
1174
- setDragPos(null);
1175
- dragState.current = null;
1176
- } else {
1177
- const el = toolbarRef.current;
1178
- const w = el?.offsetWidth ?? CONTAINER_SIZE;
1179
- const h = el?.offsetHeight ?? CONTAINER_SIZE;
1180
- const currentX = ds.origX + (e.clientX - ds.startX);
1181
- const currentY = ds.origY + (e.clientY - ds.startY);
1182
- const centerX = currentX + w / 2;
1183
- const centerY = currentY + h / 2;
1184
- const newCorner = snapToCorner(centerX, centerY);
1185
- setCorner(newCorner);
1186
- try {
1187
- localStorage.setItem("ab-toolbar-corner", newCorner);
1188
- } catch {
1189
- }
1190
- const targetPos = getCornerPosition(newCorner, w, h);
1191
- setDragging(false);
1192
- setDragPos(null);
1193
- setSnapAnim({ x: currentX, y: currentY, animate: false });
1194
- dragState.current = null;
1195
- requestAnimationFrame(() => {
1196
- requestAnimationFrame(() => {
1197
- setSnapAnim({ ...targetPos, animate: true });
1198
- setTimeout(() => setSnapAnim(null), 300);
1199
- });
1200
- });
1201
- }
1202
- };
1203
- window.addEventListener("mousemove", handleMouseMove);
1204
- window.addEventListener("mouseup", handleMouseUp);
1205
- return () => {
1206
- window.removeEventListener("mousemove", handleMouseMove);
1207
- window.removeEventListener("mouseup", handleMouseUp);
1208
- };
1209
- }, [onToggle]);
1210
- const panelSide = isRightCorner(corner) ? "left" : "right";
1211
- const tooltipSide = panelSide;
1212
- const bottom = isBottomCorner(corner);
1213
- const positionStyle = dragging && dragPos ? { left: dragPos.x, top: dragPos.y } : snapAnim ? {
1214
- left: snapAnim.x,
1215
- top: snapAnim.y,
1216
- ...snapAnim.animate && {
1217
- transition: "left 0.3s cubic-bezier(0.23, 1, 0.32, 1), top 0.3s cubic-bezier(0.23, 1, 0.32, 1)"
1218
- }
1219
- } : getCornerStyle(corner);
1220
- const cameraTooltipLabel = expanded ? "Close" : void 0;
1221
- const cameraTooltipStyle = cameraTooltipLabel ? tooltipSide === "left" ? { right: "calc(100% + 10px)", top: "50%", transform: "translateY(-50%)" } : { left: "calc(100% + 10px)", top: "50%", transform: "translateY(-50%)" } : void 0;
1222
- const cameraButton = /* @__PURE__ */ jsxs2("div", { style: { position: "relative" }, children: [
1223
- cameraTooltipLabel && cameraHovered && !dragging && /* @__PURE__ */ jsx2(
1224
- "div",
1225
- {
1226
- style: {
1227
- position: "absolute",
1228
- ...cameraTooltipStyle,
1229
- background: bg.base,
1230
- border: `1px solid ${stroke.default}`,
1231
- borderRadius: 6,
1232
- padding: "0 8px",
1233
- height: 24,
1234
- display: "flex",
1235
- alignItems: "center",
1236
- color: fg.strong,
1237
- ...text.label.xs,
1238
- whiteSpace: "nowrap",
1239
- boxShadow: shadow.tooltip,
1240
- pointerEvents: "none"
1241
- },
1242
- children: cameraTooltipLabel
1243
- }
1244
- ),
1245
- /* @__PURE__ */ jsxs2(
1246
- "div",
1247
- {
1248
- onMouseDown: handleMouseDown,
1249
- onMouseEnter: () => setCameraHovered(true),
1250
- onMouseLeave: () => setCameraHovered(false),
1251
- style: {
1252
- width: 32,
1253
- height: 32,
1254
- padding: 0,
1255
- borderRadius: "50%",
1256
- display: "flex",
1257
- alignItems: "center",
1258
- justifyContent: "center",
1259
- cursor: dragging ? "grabbing" : "pointer",
1260
- background: expanded && cameraHovered ? state.hoverStrong : "transparent",
1261
- transition: "background 0.12s ease"
1262
- },
1263
- children: [
1264
- /* @__PURE__ */ jsx2(
1265
- "style",
1266
- {
1267
- dangerouslySetInnerHTML: {
1268
- __html: `
1269
- @keyframes ab-spin {
1270
- 0% { transform: rotate(0deg); }
1271
- 100% { transform: rotate(360deg); }
1272
- }
1273
- @keyframes ab-panel-in {
1274
- from { opacity: 0; transform: translateY(4px); }
1275
- to { opacity: 1; transform: translateY(0); }
1276
- }
1277
- `
1278
- }
1279
- }
1280
- ),
1281
- loading ? /* @__PURE__ */ jsx2(
1282
- LoaderCircle,
1283
- {
1284
- size: 16,
1285
- strokeWidth: 2,
1286
- style: { animation: "ab-spin 0.8s linear infinite", color: "white" }
1287
- }
1288
- ) : phase === "ready" ? /* @__PURE__ */ jsx2(Check, { size: 16, strokeWidth: 2.6, color: accent.check }) : expanded ? /* @__PURE__ */ jsx2(
1289
- X2,
1290
- {
1291
- size: 16,
1292
- strokeWidth: 1.7,
1293
- color: cameraHovered ? fg.strong : fg.sub
1294
- }
1295
- ) : /* @__PURE__ */ jsx2(
1296
- Camera,
1297
- {
1298
- size: 16,
1299
- strokeWidth: 1.9,
1300
- color: fg.sub
1301
- }
1302
- )
1303
- ]
1304
- }
1305
- )
1306
- ] });
1307
- const toolbarButtons = buttonsVisible ? /* @__PURE__ */ jsx2(
1308
- "div",
1309
- {
1310
- style: {
1311
- overflow: animDone ? "visible" : "hidden",
1312
- maxHeight: animIn ? 195 : 0,
1313
- transition: animIn ? "max-height 250ms cubic-bezier(0.23, 1, 0.32, 1)" : "max-height 150ms cubic-bezier(0.23, 1, 0.32, 1)"
1314
- },
1315
- children: /* @__PURE__ */ jsxs2(
1316
- "div",
1317
- {
1318
- style: {
1319
- display: "flex",
1320
- flexDirection: "column",
1321
- alignItems: "center",
1322
- opacity: animIn ? 1 : 0,
1323
- transform: animIn ? "translateY(0)" : `translateY(${bottom ? 6 : -6}px)`,
1324
- transition: animIn ? "opacity 200ms cubic-bezier(0.23, 1, 0.32, 1), transform 200ms cubic-bezier(0.23, 1, 0.32, 1)" : "opacity 150ms cubic-bezier(0.23, 1, 0.32, 1), transform 150ms cubic-bezier(0.23, 1, 0.32, 1)",
1325
- willChange: "transform, opacity"
1326
- },
1327
- children: [
1328
- /* @__PURE__ */ jsx2("div", { style: { paddingBottom: 2 }, children: /* @__PURE__ */ jsx2(
1329
- IconButton,
1330
- {
1331
- active: selectedMode === "component" && !historyOpen,
1332
- tooltipSide,
1333
- tooltip: "Component",
1334
- onClick: () => {
1335
- setHistoryOpen(false);
1336
- onModeChange("component");
1337
- },
1338
- children: /* @__PURE__ */ jsx2(MousePointer2, { size: 16, strokeWidth: 1.7 })
1339
- }
1340
- ) }),
1341
- MODES.filter((m) => m.mode !== "component").map(({ mode, label, icon: ModeIcon }) => /* @__PURE__ */ jsx2(
1342
- "div",
1343
- {
1344
- style: {
1345
- maxHeight: modesExpanded ? 34 : 0,
1346
- opacity: modesExpanded ? 1 : 0,
1347
- transition: "max-height 200ms cubic-bezier(0.23, 1, 0.32, 1), opacity 150ms cubic-bezier(0.23, 1, 0.32, 1)"
1348
- },
1349
- children: /* @__PURE__ */ jsx2(
1350
- IconButton,
1351
- {
1352
- active: selectedMode === mode && !historyOpen,
1353
- tooltipSide,
1354
- tooltip: label,
1355
- onClick: () => {
1356
- setHistoryOpen(false);
1357
- onModeChange(mode);
1358
- onCapture(mode);
1359
- },
1360
- children: /* @__PURE__ */ jsx2(ModeIcon, { size: 16, strokeWidth: 1.7 })
1361
- }
1362
- )
1363
- },
1364
- mode
1365
- )),
1366
- /* @__PURE__ */ jsx2(
1367
- Separator,
1368
- {
1369
- vertical: false,
1370
- onClick: () => setModesExpanded((p) => !p)
1371
- }
1372
- ),
1373
- /* @__PURE__ */ jsx2(
1374
- HistoryButton,
1375
- {
1376
- open: historyOpen,
1377
- onClick: () => {
1378
- setHistoryOpen((prev) => !prev);
1379
- },
1380
- selectedMode,
1381
- frameSettings,
1382
- onFrameSettingsChange,
1383
- panelSide,
1384
- tooltipSide,
1385
- bottom
1386
- }
1387
- )
1388
- ]
1389
- }
1390
- )
1391
- }
1392
- ) : null;
1393
- return /* @__PURE__ */ jsx2(
1394
- "div",
1395
- {
1396
- ref: toolbarRef,
1397
- "data-afterbefore": "true",
1398
- style: {
1399
- position: "fixed",
1400
- ...positionStyle,
1401
- zIndex: 2147483647,
1402
- display: "flex",
1403
- flexDirection: "column",
1404
- alignItems: "center",
1405
- background: bg.base,
1406
- borderRadius: 999,
1407
- padding: 6,
1408
- boxShadow: shadow.toolbar,
1409
- ...fontBase,
1410
- userSelect: "none"
1411
- },
1412
- children: bottom ? /* @__PURE__ */ jsxs2(Fragment2, { children: [
1413
- toolbarButtons,
1414
- cameraButton
1415
- ] }) : /* @__PURE__ */ jsxs2(Fragment2, { children: [
1416
- cameraButton,
1417
- toolbarButtons
1418
- ] })
1419
- }
1420
- );
1421
- }
1422
- function IconButton({
1423
- children,
1424
- active,
1425
- tooltip,
1426
- tooltipSide = "left",
1427
- onClick
1428
- }) {
1429
- const [hovered, setHovered] = useState3(false);
1430
- const tooltipStyle = tooltipSide === "left" ? {
1431
- right: "calc(100% + 10px)",
1432
- top: "50%",
1433
- transform: "translateY(-50%)"
1434
- } : {
1435
- left: "calc(100% + 10px)",
1436
- top: "50%",
1437
- transform: "translateY(-50%)"
1438
- };
1439
- return /* @__PURE__ */ jsxs2("div", { style: { position: "relative" }, children: [
1440
- tooltip && hovered && /* @__PURE__ */ jsx2(
1441
- "div",
1442
- {
1443
- style: {
1444
- position: "absolute",
1445
- ...tooltipStyle,
1446
- background: bg.base,
1447
- border: `1px solid ${stroke.default}`,
1448
- borderRadius: 6,
1449
- padding: "0 8px",
1450
- height: 24,
1451
- display: "flex",
1452
- alignItems: "center",
1453
- color: fg.strong,
1454
- ...text.label.xs,
1455
- whiteSpace: "nowrap",
1456
- boxShadow: shadow.tooltip,
1457
- pointerEvents: "none"
1458
- },
1459
- children: tooltip
1460
- }
1461
- ),
1462
- /* @__PURE__ */ jsx2(
1463
- "button",
1464
- {
1465
- onClick,
1466
- onMouseEnter: () => setHovered(true),
1467
- onMouseLeave: () => setHovered(false),
1468
- style: {
1469
- width: 32,
1470
- height: 32,
1471
- borderRadius: "50%",
1472
- border: "none",
1473
- background: active || hovered ? state.hoverStrong : "transparent",
1474
- display: "flex",
1475
- alignItems: "center",
1476
- justifyContent: "center",
1477
- cursor: "pointer",
1478
- padding: 0,
1479
- color: active || hovered ? fg.strong : fg.sub,
1480
- transition: "background 0.12s ease, color 0.12s ease"
1481
- },
1482
- children
1483
- }
1484
- )
1485
- ] });
1486
- }
1487
- function DropItem2({
1488
- children,
1489
- onClick,
1490
- active,
1491
- accent: accent2
1492
- }) {
1493
- return /* @__PURE__ */ jsx2(
1494
- "button",
1495
- {
1496
- onClick,
1497
- style: {
1498
- display: "block",
1499
- width: "100%",
1500
- padding: "7px 12px",
1501
- background: active ? state.active : "transparent",
1502
- border: "none",
1503
- color: accent2 ? accent.highlight : fg.strong,
1504
- textAlign: "left",
1505
- cursor: "pointer",
1506
- ...text.paragraph.sm,
1507
- fontFamily: "inherit"
1508
- },
1509
- children
1510
- }
1511
- );
1512
- }
1513
- function Separator({
1514
- vertical = true,
1515
- onClick
1516
- }) {
1517
- return /* @__PURE__ */ jsx2(
1518
- "div",
1519
- {
1520
- onClick,
1521
- style: {
1522
- position: "relative",
1523
- width: vertical ? 1 : 24,
1524
- height: vertical ? 18 : 1,
1525
- background: stroke.strong,
1526
- flexShrink: 0,
1527
- margin: vertical ? "0 6px" : "6px 0",
1528
- cursor: onClick ? "pointer" : void 0
1529
- },
1530
- children: onClick && /* @__PURE__ */ jsx2("div", { style: { position: "absolute", inset: vertical ? "0 -8px" : "-8px 0" } })
1531
- }
1532
- );
1533
- }
1534
- function PanelTab({
1535
- children,
1536
- active,
1537
- onClick
1538
- }) {
1539
- return /* @__PURE__ */ jsx2(
1540
- "button",
1541
- {
1542
- onClick,
1543
- style: {
1544
- flex: 1,
1545
- padding: "6px 0",
1546
- border: "none",
1547
- borderBottom: `2px solid ${active ? fg.strong : "transparent"}`,
1548
- background: "transparent",
1549
- color: active ? fg.strong : fg.muted,
1550
- ...text.label.xs,
1551
- fontFamily: "inherit",
1552
- cursor: "pointer",
1553
- transition: "color 0.12s ease, border-color 0.12s ease"
1554
- },
1555
- children
1556
- }
1557
- );
1558
- }
1559
- function HistoryButton({
1560
- open,
1561
- onClick,
1562
- selectedMode,
1563
- frameSettings,
1564
- onFrameSettingsChange,
1565
- panelSide,
1566
- tooltipSide,
1567
- bottom
1568
- }) {
1569
- const [activeTab, setActiveTab] = useState3("screenshots");
1570
- const [toast, setToast] = useState3(null);
1571
- const [pushing, setPushing] = useState3(false);
1572
- const [saveDir, setSaveDir] = useState3(null);
1573
- const [picking, setPicking] = useState3(false);
1574
- const [repos, setRepos] = useState3([]);
1575
- const [branches, setBranches] = useState3([]);
1576
- const [screenshots, setScreenshots] = useState3([]);
1577
- const [selectedRepo, setSelectedRepo] = useState3(null);
1578
- const [selectedBranch, setSelectedBranch] = useState3(null);
1579
- const [loading, setLoading] = useState3(false);
1580
- const [repoDropOpen, setRepoDropOpen] = useState3(false);
1581
- const [branchDropOpen, setBranchDropOpen] = useState3(false);
1582
- const [lightboxSrc, setLightboxSrc] = useState3(null);
1583
- const [editingFile, setEditingFile] = useState3(null);
1584
- const [editValue, setEditValue] = useState3("");
1585
- const [hoveredThumb, setHoveredThumb] = useState3(null);
1586
- useEffect2(() => {
1587
- if (!open) return;
1588
- fetch("/__afterbefore/config").then((r) => r.json()).then((data) => setSaveDir(data.saveDir)).catch(() => {
1589
- });
1590
- }, [open]);
1591
- const handlePickFolder = async () => {
1592
- setPicking(true);
1593
- try {
1594
- const res = await fetch("/__afterbefore/pick-folder", { method: "POST" });
1595
- const data = await res.json();
1596
- if (data.folder) {
1597
- await fetch("/__afterbefore/config", {
1598
- method: "POST",
1599
- headers: { "Content-Type": "application/json" },
1600
- body: JSON.stringify({ saveDir: data.folder })
1601
- });
1602
- setSaveDir(data.folder);
1603
- }
1604
- } catch {
1605
- } finally {
1606
- setPicking(false);
1607
- }
1608
- };
1609
- const shortDir = saveDir ? saveDir.replace(/^\/Users\/[^/]+/, "~") : "~/Desktop";
1610
- useEffect2(() => {
1611
- if (!open) {
1612
- setRepoDropOpen(false);
1613
- setBranchDropOpen(false);
1614
- return;
1615
- }
1616
- setLoading(true);
1617
- const params = new URLSearchParams();
1618
- if (selectedRepo) params.set("repo", selectedRepo);
1619
- if (selectedBranch) params.set("branch", selectedBranch);
1620
- fetch(`/__afterbefore/history?${params}`).then((r) => r.json()).then((data) => {
1621
- setRepos(data.repos || []);
1622
- setBranches(data.branches || []);
1623
- setScreenshots(data.screenshots || []);
1624
1457
  if (!selectedRepo && data.currentRepo) setSelectedRepo(data.currentRepo);
1625
1458
  if (!selectedBranch && data.currentBranch) setSelectedBranch(data.currentBranch);
1626
1459
  }).catch(() => {
@@ -1630,20 +1463,6 @@ function HistoryButton({
1630
1463
  setToast({ message, type });
1631
1464
  setTimeout(() => setToast(null), 3e3);
1632
1465
  }, []);
1633
- const handleOpenFolder = async () => {
1634
- try {
1635
- const body = selectedRepo && selectedBranch ? JSON.stringify({ repo: selectedRepo, branch: selectedBranch }) : void 0;
1636
- const res = await fetch("/__afterbefore/open", {
1637
- method: "POST",
1638
- headers: body ? { "Content-Type": "application/json" } : void 0,
1639
- body
1640
- });
1641
- if (!res.ok) throw new Error();
1642
- showToast("Opened folder", "success");
1643
- } catch {
1644
- showToast("Could not open folder", "error");
1645
- }
1646
- };
1647
1466
  const handleRename = async (oldName, newName) => {
1648
1467
  if (!newName.trim() || newName.trim() === oldName.replace(/\.png$/, "")) {
1649
1468
  setEditingFile(null);
@@ -1660,413 +1479,947 @@ function HistoryButton({
1660
1479
  newName: newName.trim()
1661
1480
  })
1662
1481
  });
1663
- if (!res.ok) throw new Error();
1664
- const data = await res.json();
1665
- setScreenshots(
1666
- (prev) => prev.map(
1667
- (s) => s.filename === oldName ? { ...s, filename: data.filename, timestamp: data.filename.replace(/\.png$/, "") } : s
1668
- )
1669
- );
1670
- showToast("Renamed", "success");
1671
- } catch {
1672
- showToast("Rename failed", "error");
1482
+ if (!res.ok) throw new Error();
1483
+ const data = await res.json();
1484
+ setScreenshots(
1485
+ (prev) => prev.map(
1486
+ (s) => s.filename === oldName ? { ...s, filename: data.filename, timestamp: data.filename.replace(/\.png$/, "") } : s
1487
+ )
1488
+ );
1489
+ if (selectedFile === oldName) setSelectedFile(data.filename);
1490
+ showToast("Renamed", "success");
1491
+ } catch {
1492
+ showToast("Rename failed", "error");
1493
+ }
1494
+ setEditingFile(null);
1495
+ };
1496
+ const handleDelete = async (filename) => {
1497
+ try {
1498
+ const res = await fetch("/__afterbefore/history/delete", {
1499
+ method: "POST",
1500
+ headers: { "Content-Type": "application/json" },
1501
+ body: JSON.stringify({
1502
+ repo: selectedRepo,
1503
+ branch: selectedBranch,
1504
+ file: filename
1505
+ })
1506
+ });
1507
+ if (!res.ok) throw new Error();
1508
+ setScreenshots((prev) => prev.filter((s) => s.filename !== filename));
1509
+ if (selectedFile === filename) {
1510
+ const remaining = screenshots.filter((s) => s.filename !== filename);
1511
+ setSelectedFile(remaining.length > 0 ? remaining[0].filename : null);
1512
+ }
1513
+ showToast("Deleted", "success");
1514
+ } catch {
1515
+ showToast("Delete failed", "error");
1516
+ }
1517
+ };
1518
+ const previewUrl = selectedFile ? `/__afterbefore/history/image?repo=${encodeURIComponent(selectedRepo || "")}&branch=${encodeURIComponent(selectedBranch || "")}&file=${encodeURIComponent(selectedFile)}` : null;
1519
+ return /* @__PURE__ */ jsxs2(Fragment2, { children: [
1520
+ /* @__PURE__ */ jsx2(IconButton, { active: open, tooltipSide, tooltip: !open ? "Screenshots" : void 0, onClick, children: /* @__PURE__ */ jsx2(Image2, { size: 16, strokeWidth: 1.7 }) }),
1521
+ open && createPortal(
1522
+ /* @__PURE__ */ jsxs2(
1523
+ "div",
1524
+ {
1525
+ "data-afterbefore": "true",
1526
+ onClick,
1527
+ style: {
1528
+ position: "fixed",
1529
+ inset: 0,
1530
+ zIndex: 2147483647,
1531
+ background: "rgba(0, 0, 0, 0.45)",
1532
+ display: "flex",
1533
+ alignItems: "center",
1534
+ justifyContent: "center",
1535
+ ...fontBase
1536
+ },
1537
+ children: [
1538
+ /* @__PURE__ */ jsx2(
1539
+ "style",
1540
+ {
1541
+ dangerouslySetInnerHTML: {
1542
+ __html: `
1543
+ @keyframes ab-panel-in {
1544
+ from { opacity: 0; transform: translateY(4px); }
1545
+ to { opacity: 1; transform: translateY(0); }
1546
+ }
1547
+ `
1548
+ }
1549
+ }
1550
+ ),
1551
+ /* @__PURE__ */ jsxs2(
1552
+ "div",
1553
+ {
1554
+ onClick: (e) => e.stopPropagation(),
1555
+ style: {
1556
+ width: 900,
1557
+ height: "70vh",
1558
+ borderRadius: 14,
1559
+ background: bg.base,
1560
+ border: `1px solid ${stroke.default}`,
1561
+ boxShadow: shadow.panel,
1562
+ display: "flex",
1563
+ flexDirection: "column",
1564
+ overflow: "hidden",
1565
+ animation: "ab-panel-in 150ms cubic-bezier(0.23, 1, 0.32, 1)"
1566
+ },
1567
+ children: [
1568
+ /* @__PURE__ */ jsxs2("div", { style: {
1569
+ display: "flex",
1570
+ alignItems: "center",
1571
+ justifyContent: "space-between",
1572
+ padding: "16px 20px",
1573
+ borderBottom: `1px solid ${stroke.soft}`
1574
+ }, children: [
1575
+ /* @__PURE__ */ jsxs2("div", { children: [
1576
+ /* @__PURE__ */ jsx2("div", { style: { ...text.label.sm, color: fg.strong }, children: "Screenshots" }),
1577
+ /* @__PURE__ */ jsx2("div", { style: { ...text.paragraph.xs, color: fg.sub, marginTop: 2 }, children: "Capture history & settings" })
1578
+ ] }),
1579
+ /* @__PURE__ */ jsx2(
1580
+ "button",
1581
+ {
1582
+ onClick,
1583
+ style: {
1584
+ width: 28,
1585
+ height: 28,
1586
+ borderRadius: 7,
1587
+ border: "none",
1588
+ background: "transparent",
1589
+ display: "flex",
1590
+ alignItems: "center",
1591
+ justifyContent: "center",
1592
+ cursor: "pointer",
1593
+ color: fg.sub,
1594
+ padding: 0,
1595
+ transition: "background 0.12s ease, color 0.12s ease"
1596
+ },
1597
+ onMouseEnter: (e) => {
1598
+ e.currentTarget.style.background = state.hover;
1599
+ e.currentTarget.style.color = fg.strong;
1600
+ },
1601
+ onMouseLeave: (e) => {
1602
+ e.currentTarget.style.background = "transparent";
1603
+ e.currentTarget.style.color = fg.sub;
1604
+ },
1605
+ children: /* @__PURE__ */ jsx2(X, { size: 14, strokeWidth: 2 })
1606
+ }
1607
+ )
1608
+ ] }),
1609
+ /* @__PURE__ */ jsxs2("div", { style: { display: "flex", flex: 1, overflow: "hidden", minHeight: 0 }, children: [
1610
+ /* @__PURE__ */ jsxs2("div", { style: { flex: 1, overflowY: "auto", padding: "16px 20px 20px", minHeight: 0 }, children: [
1611
+ /* @__PURE__ */ jsx2(
1612
+ FramePreview,
1613
+ {
1614
+ previewUrl,
1615
+ frameSettings,
1616
+ loading
1617
+ }
1618
+ ),
1619
+ selectedFile && !loading && /* @__PURE__ */ jsxs2("div", { style: {
1620
+ display: "flex",
1621
+ alignItems: "center",
1622
+ gap: 8,
1623
+ marginBottom: 12
1624
+ }, children: [
1625
+ /* @__PURE__ */ jsx2("div", { style: { flex: 1, minWidth: 0 }, children: editingFile === selectedFile ? /* @__PURE__ */ jsx2(
1626
+ "input",
1627
+ {
1628
+ autoFocus: true,
1629
+ value: editValue,
1630
+ onChange: (e) => setEditValue(e.target.value),
1631
+ onKeyDown: (e) => {
1632
+ if (e.key === "Enter") handleRename(selectedFile, editValue);
1633
+ if (e.key === "Escape") setEditingFile(null);
1634
+ },
1635
+ onBlur: () => handleRename(selectedFile, editValue),
1636
+ style: {
1637
+ width: "100%",
1638
+ ...text.label.xs,
1639
+ color: fg.strong,
1640
+ background: state.hover,
1641
+ border: `1px solid ${stroke.interactive}`,
1642
+ borderRadius: 4,
1643
+ padding: "2px 6px",
1644
+ outline: "none",
1645
+ fontFamily: "inherit"
1646
+ }
1647
+ }
1648
+ ) : /* @__PURE__ */ jsx2(
1649
+ "div",
1650
+ {
1651
+ onClick: () => {
1652
+ setEditingFile(selectedFile);
1653
+ setEditValue(selectedFile.replace(/\.png$/, ""));
1654
+ },
1655
+ style: {
1656
+ ...text.label.xs,
1657
+ color: fg.strong,
1658
+ cursor: "pointer",
1659
+ overflow: "hidden",
1660
+ textOverflow: "ellipsis",
1661
+ whiteSpace: "nowrap"
1662
+ },
1663
+ title: "Click to rename",
1664
+ children: formatTimestamp(selectedFile)
1665
+ }
1666
+ ) }),
1667
+ /* @__PURE__ */ jsx2(
1668
+ "button",
1669
+ {
1670
+ onClick: () => handleDelete(selectedFile),
1671
+ title: "Delete screenshot",
1672
+ style: {
1673
+ flexShrink: 0,
1674
+ width: 28,
1675
+ height: 28,
1676
+ borderRadius: 6,
1677
+ border: "none",
1678
+ background: "transparent",
1679
+ color: fg.muted,
1680
+ cursor: "pointer",
1681
+ display: "flex",
1682
+ alignItems: "center",
1683
+ justifyContent: "center",
1684
+ padding: 0
1685
+ },
1686
+ onMouseEnter: (e) => {
1687
+ e.currentTarget.style.color = feedback.error;
1688
+ e.currentTarget.style.background = feedback.errorBg;
1689
+ },
1690
+ onMouseLeave: (e) => {
1691
+ e.currentTarget.style.color = fg.muted;
1692
+ e.currentTarget.style.background = "transparent";
1693
+ },
1694
+ children: /* @__PURE__ */ jsx2(Trash22, { size: 14, strokeWidth: 1.8 })
1695
+ }
1696
+ )
1697
+ ] }),
1698
+ /* @__PURE__ */ jsxs2("div", { style: { display: "flex", gap: 10, marginBottom: 10 }, children: [
1699
+ /* @__PURE__ */ jsx2("div", { style: { flex: 1, position: "relative" }, children: /* @__PURE__ */ jsx2(
1700
+ FilterDropdown,
1701
+ {
1702
+ label: "Project",
1703
+ value: selectedRepo,
1704
+ options: repos,
1705
+ isOpen: repoDropOpen,
1706
+ onToggle: () => {
1707
+ setRepoDropOpen((p) => !p);
1708
+ setBranchDropOpen(false);
1709
+ },
1710
+ onSelect: (repo) => {
1711
+ setSelectedRepo(repo);
1712
+ setSelectedBranch(null);
1713
+ setRepoDropOpen(false);
1714
+ }
1715
+ }
1716
+ ) }),
1717
+ /* @__PURE__ */ jsx2("div", { style: { flex: 1, position: "relative" }, children: /* @__PURE__ */ jsx2(
1718
+ FilterDropdown,
1719
+ {
1720
+ label: "Branch",
1721
+ value: selectedBranch,
1722
+ options: branches,
1723
+ isOpen: branchDropOpen,
1724
+ onToggle: () => {
1725
+ setBranchDropOpen((p) => !p);
1726
+ setRepoDropOpen(false);
1727
+ },
1728
+ onSelect: (branch) => {
1729
+ setSelectedBranch(branch);
1730
+ setBranchDropOpen(false);
1731
+ }
1732
+ }
1733
+ ) })
1734
+ ] }),
1735
+ screenshots.length > 0 && /* @__PURE__ */ jsx2(
1736
+ "div",
1737
+ {
1738
+ style: {
1739
+ display: "flex",
1740
+ gap: 6,
1741
+ overflowX: "auto",
1742
+ paddingBottom: 4,
1743
+ marginBottom: 10
1744
+ },
1745
+ children: screenshots.map((shot) => {
1746
+ const thumbUrl = `/__afterbefore/history/image?repo=${encodeURIComponent(selectedRepo || "")}&branch=${encodeURIComponent(selectedBranch || "")}&file=${encodeURIComponent(shot.filename)}`;
1747
+ const isSelected = selectedFile === shot.filename;
1748
+ return /* @__PURE__ */ jsx2(
1749
+ "div",
1750
+ {
1751
+ onClick: () => setSelectedFile(shot.filename),
1752
+ style: {
1753
+ width: 64,
1754
+ height: 44,
1755
+ flexShrink: 0,
1756
+ borderRadius: 6,
1757
+ overflow: "hidden",
1758
+ cursor: "pointer",
1759
+ border: isSelected ? `2px solid ${accent.highlight}` : `1px solid ${stroke.soft}`,
1760
+ opacity: isSelected ? 1 : 0.7,
1761
+ transition: "opacity 0.12s ease, border-color 0.12s ease"
1762
+ },
1763
+ children: /* @__PURE__ */ jsx2(
1764
+ "img",
1765
+ {
1766
+ src: thumbUrl,
1767
+ alt: "",
1768
+ style: {
1769
+ width: "100%",
1770
+ height: "100%",
1771
+ objectFit: "cover",
1772
+ display: "block"
1773
+ }
1774
+ }
1775
+ )
1776
+ },
1777
+ shot.filename
1778
+ );
1779
+ })
1780
+ }
1781
+ )
1782
+ ] }),
1783
+ /* @__PURE__ */ jsx2("div", { style: { width: 1, flexShrink: 0, background: stroke.soft } }),
1784
+ /* @__PURE__ */ jsxs2("div", { style: { width: 300, flexShrink: 0, overflowY: "auto", padding: "16px 16px 12px", minHeight: 0, display: "flex", flexDirection: "column" }, children: [
1785
+ /* @__PURE__ */ jsx2("div", { style: { flex: 1 }, children: /* @__PURE__ */ jsx2(
1786
+ SettingsContent,
1787
+ {
1788
+ frameSettings,
1789
+ onFrameSettingsChange,
1790
+ saveDir: shortDir,
1791
+ picking,
1792
+ onPickFolder: handlePickFolder
1793
+ }
1794
+ ) }),
1795
+ /* @__PURE__ */ jsxs2("div", { style: { display: "flex", alignItems: "center", gap: 8, paddingTop: 12, paddingBottom: 2, marginTop: 12, borderTop: `1px solid ${stroke.soft}`, marginLeft: -16, marginRight: -16, paddingLeft: 16 }, children: [
1796
+ /* @__PURE__ */ jsx2("span", { style: { ...text.paragraph.xs, color: fg.muted }, children: "Paulius Kairevicius" }),
1797
+ /* @__PURE__ */ jsx2(
1798
+ "a",
1799
+ {
1800
+ href: "https://github.com/kairevicius",
1801
+ target: "_blank",
1802
+ rel: "noopener noreferrer",
1803
+ style: { color: fg.muted, display: "flex", transition: "color 0.12s" },
1804
+ onMouseEnter: (e) => {
1805
+ e.currentTarget.style.color = fg.strong;
1806
+ },
1807
+ onMouseLeave: (e) => {
1808
+ e.currentTarget.style.color = fg.muted;
1809
+ },
1810
+ children: /* @__PURE__ */ jsx2("svg", { width: "13", height: "13", viewBox: "0 0 24 24", fill: "currentColor", children: /* @__PURE__ */ jsx2("path", { d: "M12 0C5.37 0 0 5.37 0 12c0 5.31 3.435 9.795 8.205 11.385.6.105.825-.255.825-.57 0-.285-.015-1.23-.015-2.235-3.015.555-3.795-.735-4.035-1.41-.135-.345-.72-1.41-1.23-1.695-.42-.225-1.02-.78-.015-.795.945-.015 1.62.87 1.845 1.23 1.08 1.815 2.805 1.305 3.495.99.105-.78.42-1.305.765-1.605-2.67-.3-5.46-1.335-5.46-5.925 0-1.305.465-2.385 1.23-3.225-.12-.3-.54-1.53.12-3.18 0 0 1.005-.315 3.3 1.23.96-.27 1.98-.405 3-.405s2.04.135 3 .405c2.295-1.56 3.3-1.23 3.3-1.23.66 1.65.24 2.88.12 3.18.765.84 1.23 1.905 1.23 3.225 0 4.605-2.805 5.625-5.475 5.925.435.375.81 1.095.81 2.22 0 1.605-.015 2.895-.015 3.3 0 .315.225.69.825.57A12.02 12.02 0 0 0 24 12c0-6.63-5.37-12-12-12z" }) })
1811
+ }
1812
+ ),
1813
+ /* @__PURE__ */ jsx2(
1814
+ "a",
1815
+ {
1816
+ href: "https://x.com/kairevicius",
1817
+ target: "_blank",
1818
+ rel: "noopener noreferrer",
1819
+ style: { color: fg.muted, display: "flex", transition: "color 0.12s" },
1820
+ onMouseEnter: (e) => {
1821
+ e.currentTarget.style.color = fg.strong;
1822
+ },
1823
+ onMouseLeave: (e) => {
1824
+ e.currentTarget.style.color = fg.muted;
1825
+ },
1826
+ children: /* @__PURE__ */ jsx2("svg", { width: "12", height: "12", viewBox: "0 0 24 24", fill: "currentColor", children: /* @__PURE__ */ jsx2("path", { d: "M18.244 2.25h3.308l-7.227 8.26 8.502 11.24H16.17l-5.214-6.817L4.99 21.75H1.68l7.73-8.835L1.254 2.25H8.08l4.713 6.231zm-1.161 17.52h1.833L7.084 4.126H5.117z" }) })
1827
+ }
1828
+ )
1829
+ ] })
1830
+ ] })
1831
+ ] })
1832
+ ]
1833
+ }
1834
+ ),
1835
+ toast && /* @__PURE__ */ jsx2(
1836
+ "div",
1837
+ {
1838
+ onClick: (e) => e.stopPropagation(),
1839
+ style: {
1840
+ position: "fixed",
1841
+ bottom: 32,
1842
+ left: "50%",
1843
+ transform: "translateX(-50%)",
1844
+ padding: "6px 14px",
1845
+ borderRadius: 8,
1846
+ ...text.label.xs,
1847
+ whiteSpace: "nowrap",
1848
+ color: "white",
1849
+ background: toast.type === "success" ? feedback.success : feedback.error,
1850
+ boxShadow: shadow.toast
1851
+ },
1852
+ children: toast.message
1853
+ }
1854
+ )
1855
+ ]
1856
+ }
1857
+ ),
1858
+ document.body
1859
+ )
1860
+ ] });
1861
+ }
1862
+
1863
+ // src/overlay/ui/toolbar.tsx
1864
+ import { Fragment as Fragment3, jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
1865
+ var TOOLTIP_DELAY = 500;
1866
+ var TOOLTIP_COOLDOWN = 300;
1867
+ var tooltipWarm = false;
1868
+ var tooltipCooldownTimer = null;
1869
+ function useTooltipDelay(hovered) {
1870
+ const [visible, setVisible] = useState4(false);
1871
+ const delayRef = useRef2(null);
1872
+ useEffect3(() => {
1873
+ if (!hovered) {
1874
+ setVisible(false);
1875
+ if (delayRef.current) {
1876
+ clearTimeout(delayRef.current);
1877
+ delayRef.current = null;
1878
+ }
1879
+ if (tooltipCooldownTimer) clearTimeout(tooltipCooldownTimer);
1880
+ tooltipCooldownTimer = setTimeout(() => {
1881
+ tooltipWarm = false;
1882
+ }, TOOLTIP_COOLDOWN);
1883
+ return;
1884
+ }
1885
+ if (tooltipCooldownTimer) {
1886
+ clearTimeout(tooltipCooldownTimer);
1887
+ tooltipCooldownTimer = null;
1888
+ }
1889
+ if (tooltipWarm) {
1890
+ setVisible(true);
1891
+ } else {
1892
+ delayRef.current = setTimeout(() => {
1893
+ tooltipWarm = true;
1894
+ setVisible(true);
1895
+ }, TOOLTIP_DELAY);
1896
+ }
1897
+ return () => {
1898
+ if (delayRef.current) {
1899
+ clearTimeout(delayRef.current);
1900
+ delayRef.current = null;
1901
+ }
1902
+ };
1903
+ }, [hovered]);
1904
+ return visible;
1905
+ }
1906
+ var EDGE_MARGIN = 24;
1907
+ var CONTAINER_SIZE = 38;
1908
+ function getCornerStyle(corner) {
1909
+ switch (corner) {
1910
+ case "bottom-right":
1911
+ return { bottom: EDGE_MARGIN, right: EDGE_MARGIN };
1912
+ case "bottom-left":
1913
+ return { bottom: EDGE_MARGIN, left: EDGE_MARGIN };
1914
+ case "top-right":
1915
+ return { top: EDGE_MARGIN, right: EDGE_MARGIN };
1916
+ case "top-left":
1917
+ return { top: EDGE_MARGIN, left: EDGE_MARGIN };
1918
+ }
1919
+ }
1920
+ function isBottomCorner(corner) {
1921
+ return corner === "bottom-right" || corner === "bottom-left";
1922
+ }
1923
+ function isRightCorner(corner) {
1924
+ return corner === "bottom-right" || corner === "top-right";
1925
+ }
1926
+ function snapToCorner(x, y) {
1927
+ const cx = window.innerWidth / 2;
1928
+ const cy = window.innerHeight / 2;
1929
+ if (x < cx) {
1930
+ return y < cy ? "top-left" : "bottom-left";
1931
+ }
1932
+ return y < cy ? "top-right" : "bottom-right";
1933
+ }
1934
+ function getCornerPosition(corner, w, h) {
1935
+ const vw = window.innerWidth;
1936
+ const vh = window.innerHeight;
1937
+ switch (corner) {
1938
+ case "bottom-right":
1939
+ return { x: vw - EDGE_MARGIN - w, y: vh - EDGE_MARGIN - h };
1940
+ case "bottom-left":
1941
+ return { x: EDGE_MARGIN, y: vh - EDGE_MARGIN - h };
1942
+ case "top-right":
1943
+ return { x: vw - EDGE_MARGIN - w, y: EDGE_MARGIN };
1944
+ case "top-left":
1945
+ return { x: EDGE_MARGIN, y: EDGE_MARGIN };
1946
+ }
1947
+ }
1948
+ var MODES = [
1949
+ { mode: "component", label: "Component", icon: Camera },
1950
+ { mode: "viewport", label: "Viewport", icon: Monitor },
1951
+ { mode: "fullpage", label: "Full Page", icon: FileText }
1952
+ ];
1953
+ function Toolbar({
1954
+ expanded,
1955
+ onToggle,
1956
+ phase,
1957
+ loading,
1958
+ selectedMode,
1959
+ onModeChange,
1960
+ onCapture,
1961
+ onCancel,
1962
+ frameSettings,
1963
+ onFrameSettingsChange
1964
+ }) {
1965
+ const [historyOpen, setHistoryOpen] = useState4(false);
1966
+ const [modesExpanded, setModesExpanded] = useState4(false);
1967
+ const [buttonsVisible, setButtonsVisible] = useState4(expanded);
1968
+ const [animIn, setAnimIn] = useState4(expanded);
1969
+ const [animDone, setAnimDone] = useState4(expanded);
1970
+ useEffect3(() => {
1971
+ if (expanded) {
1972
+ setAnimDone(false);
1973
+ setButtonsVisible(true);
1974
+ setAnimIn(false);
1975
+ requestAnimationFrame(() => {
1976
+ requestAnimationFrame(() => setAnimIn(true));
1977
+ });
1978
+ const timer = setTimeout(() => setAnimDone(true), 250);
1979
+ return () => clearTimeout(timer);
1980
+ } else if (buttonsVisible) {
1981
+ setHistoryOpen(false);
1982
+ setAnimDone(false);
1983
+ setAnimIn(false);
1984
+ const timer = setTimeout(() => setButtonsVisible(false), 150);
1985
+ return () => clearTimeout(timer);
1673
1986
  }
1674
- setEditingFile(null);
1675
- };
1676
- const handleDelete = async (filename) => {
1987
+ }, [expanded]);
1988
+ const [corner, setCorner] = useState4(() => {
1677
1989
  try {
1678
- const res = await fetch("/__afterbefore/history/delete", {
1679
- method: "POST",
1680
- headers: { "Content-Type": "application/json" },
1681
- body: JSON.stringify({
1682
- repo: selectedRepo,
1683
- branch: selectedBranch,
1684
- file: filename
1685
- })
1686
- });
1687
- if (!res.ok) throw new Error();
1688
- setScreenshots((prev) => prev.filter((s) => s.filename !== filename));
1689
- showToast("Deleted", "success");
1990
+ const stored = localStorage.getItem("ab-toolbar-corner");
1991
+ if (stored && ["bottom-right", "bottom-left", "top-right", "top-left"].includes(stored)) {
1992
+ return stored;
1993
+ }
1690
1994
  } catch {
1691
- showToast("Delete failed", "error");
1692
1995
  }
1693
- };
1694
- const handlePush = async () => {
1695
- setPushing(true);
1696
- try {
1697
- const res = await fetch("/__afterbefore/push", { method: "POST" });
1698
- const data = await res.json();
1699
- if (!res.ok) {
1700
- showToast(data.error || "Push failed", "error");
1701
- } else if (data.pr) {
1702
- showToast(`Posted to PR #${data.pr}`, "success");
1996
+ return "bottom-right";
1997
+ });
1998
+ const [dragging, setDragging] = useState4(false);
1999
+ const [dragPos, setDragPos] = useState4(null);
2000
+ const [snapAnim, setSnapAnim] = useState4(null);
2001
+ const dragState = useRef2(null);
2002
+ const toolbarRef = useRef2(null);
2003
+ const [cameraHovered, setCameraHovered] = useState4(false);
2004
+ const cameraTooltipVisible = useTooltipDelay(cameraHovered && !!expanded);
2005
+ useEffect3(() => {
2006
+ if (!expanded) return;
2007
+ const onKey = (e) => {
2008
+ if (e.target?.tagName === "INPUT") {
2009
+ if (e.key === "Escape") {
2010
+ e.target.blur();
2011
+ }
2012
+ return;
2013
+ }
2014
+ if (e.key === "Escape") {
2015
+ if (historyOpen) {
2016
+ setHistoryOpen(false);
2017
+ return;
2018
+ }
2019
+ onCancel();
2020
+ } else if (e.key === "Enter") {
2021
+ onCapture(selectedMode);
2022
+ }
2023
+ };
2024
+ document.addEventListener("keydown", onKey);
2025
+ return () => document.removeEventListener("keydown", onKey);
2026
+ }, [expanded, onCancel, onCapture, selectedMode, historyOpen]);
2027
+ const handleMouseDown = useCallback3(
2028
+ (e) => {
2029
+ e.preventDefault();
2030
+ const el = toolbarRef.current;
2031
+ if (!el) return;
2032
+ const rect = el.getBoundingClientRect();
2033
+ setDragging(true);
2034
+ setDragPos({ x: rect.left, y: rect.top });
2035
+ dragState.current = {
2036
+ dragging: true,
2037
+ startX: e.clientX,
2038
+ startY: e.clientY,
2039
+ origX: rect.left,
2040
+ origY: rect.top,
2041
+ distance: 0
2042
+ };
2043
+ },
2044
+ []
2045
+ );
2046
+ useEffect3(() => {
2047
+ const handleMouseMove = (e) => {
2048
+ const ds = dragState.current;
2049
+ if (!ds || !ds.dragging) return;
2050
+ const dx = e.clientX - ds.startX;
2051
+ const dy = e.clientY - ds.startY;
2052
+ ds.distance = Math.sqrt(dx * dx + dy * dy);
2053
+ setDragPos({
2054
+ x: ds.origX + dx,
2055
+ y: ds.origY + dy
2056
+ });
2057
+ };
2058
+ const handleMouseUp = (e) => {
2059
+ const ds = dragState.current;
2060
+ if (!ds) return;
2061
+ if (ds.distance < 5) {
2062
+ onToggle();
2063
+ setDragging(false);
2064
+ setDragPos(null);
2065
+ dragState.current = null;
1703
2066
  } else {
1704
- showToast("No PR found", "error");
2067
+ const el = toolbarRef.current;
2068
+ const w = el?.offsetWidth ?? CONTAINER_SIZE;
2069
+ const h = el?.offsetHeight ?? CONTAINER_SIZE;
2070
+ const currentX = ds.origX + (e.clientX - ds.startX);
2071
+ const currentY = ds.origY + (e.clientY - ds.startY);
2072
+ const centerX = currentX + w / 2;
2073
+ const centerY = currentY + h / 2;
2074
+ const newCorner = snapToCorner(centerX, centerY);
2075
+ setCorner(newCorner);
2076
+ try {
2077
+ localStorage.setItem("ab-toolbar-corner", newCorner);
2078
+ } catch {
2079
+ }
2080
+ const targetPos = getCornerPosition(newCorner, w, h);
2081
+ setDragging(false);
2082
+ setDragPos(null);
2083
+ setSnapAnim({ x: currentX, y: currentY, animate: false });
2084
+ dragState.current = null;
2085
+ requestAnimationFrame(() => {
2086
+ requestAnimationFrame(() => {
2087
+ setSnapAnim({ ...targetPos, animate: true });
2088
+ setTimeout(() => setSnapAnim(null), 300);
2089
+ });
2090
+ });
1705
2091
  }
1706
- } catch {
1707
- showToast("Push failed", "error");
1708
- } finally {
1709
- setPushing(false);
2092
+ };
2093
+ window.addEventListener("mousemove", handleMouseMove);
2094
+ window.addEventListener("mouseup", handleMouseUp);
2095
+ return () => {
2096
+ window.removeEventListener("mousemove", handleMouseMove);
2097
+ window.removeEventListener("mouseup", handleMouseUp);
2098
+ };
2099
+ }, [onToggle]);
2100
+ const panelSide = isRightCorner(corner) ? "left" : "right";
2101
+ const tooltipSide = panelSide;
2102
+ const bottom = isBottomCorner(corner);
2103
+ const positionStyle = dragging && dragPos ? { left: dragPos.x, top: dragPos.y } : snapAnim ? {
2104
+ left: snapAnim.x,
2105
+ top: snapAnim.y,
2106
+ ...snapAnim.animate && {
2107
+ transition: "left 0.3s cubic-bezier(0.23, 1, 0.32, 1), top 0.3s cubic-bezier(0.23, 1, 0.32, 1)"
1710
2108
  }
1711
- };
1712
- const verticalAlign = bottom ? { bottom: 0 } : { top: 0 };
1713
- const panelStyle = panelSide === "left" ? { right: "calc(100% + 10px)", ...verticalAlign } : { left: "calc(100% + 10px)", ...verticalAlign };
1714
- return /* @__PURE__ */ jsxs2("div", { style: { position: "relative" }, children: [
1715
- /* @__PURE__ */ jsx2(IconButton, { active: open, tooltipSide, tooltip: !open ? "Screenshots" : void 0, onClick, children: /* @__PURE__ */ jsx2(Image2, { size: 16, strokeWidth: 1.7 }) }),
1716
- open && /* @__PURE__ */ jsxs2(
2109
+ } : getCornerStyle(corner);
2110
+ const cameraTooltipLabel = expanded ? "Close" : void 0;
2111
+ const cameraTooltipStyle = cameraTooltipLabel ? tooltipSide === "left" ? { right: "calc(100% + 10px)", top: "50%", transform: "translateY(-50%)" } : { left: "calc(100% + 10px)", top: "50%", transform: "translateY(-50%)" } : void 0;
2112
+ const cameraButton = /* @__PURE__ */ jsxs3("div", { style: { position: "relative" }, children: [
2113
+ cameraTooltipLabel && cameraTooltipVisible && !dragging && /* @__PURE__ */ jsx3(
1717
2114
  "div",
1718
2115
  {
1719
2116
  style: {
1720
2117
  position: "absolute",
1721
- ...panelStyle,
1722
- minWidth: 300,
1723
- maxWidth: 360,
1724
- padding: "10px 12px",
1725
- borderRadius: 12,
2118
+ ...cameraTooltipStyle,
1726
2119
  background: bg.base,
1727
2120
  border: `1px solid ${stroke.default}`,
1728
- boxShadow: shadow.panel,
1729
- animation: "ab-panel-in 150ms cubic-bezier(0.23, 1, 0.32, 1)"
2121
+ borderRadius: 6,
2122
+ padding: "0 8px",
2123
+ height: 24,
2124
+ display: "flex",
2125
+ alignItems: "center",
2126
+ color: fg.strong,
2127
+ ...text.label.xs,
2128
+ whiteSpace: "nowrap",
2129
+ boxShadow: shadow.tooltip,
2130
+ pointerEvents: "none"
2131
+ },
2132
+ children: cameraTooltipLabel
2133
+ }
2134
+ ),
2135
+ /* @__PURE__ */ jsxs3(
2136
+ "div",
2137
+ {
2138
+ onMouseDown: handleMouseDown,
2139
+ onMouseEnter: () => setCameraHovered(true),
2140
+ onMouseLeave: () => setCameraHovered(false),
2141
+ style: {
2142
+ width: 32,
2143
+ height: 32,
2144
+ padding: 0,
2145
+ borderRadius: "50%",
2146
+ display: "flex",
2147
+ alignItems: "center",
2148
+ justifyContent: "center",
2149
+ cursor: dragging ? "grabbing" : "pointer",
2150
+ background: expanded && cameraHovered ? state.hoverStrong : "transparent",
2151
+ transition: "background 0.12s ease"
1730
2152
  },
1731
2153
  children: [
1732
- /* @__PURE__ */ jsxs2("div", { style: { display: "flex", gap: 2, marginBottom: 10 }, children: [
1733
- /* @__PURE__ */ jsx2(PanelTab, { active: activeTab === "screenshots", onClick: () => setActiveTab("screenshots"), children: "Screenshots" }),
1734
- /* @__PURE__ */ jsx2(PanelTab, { active: activeTab === "settings", onClick: () => setActiveTab("settings"), children: "Settings" })
1735
- ] }),
1736
- activeTab === "settings" && /* @__PURE__ */ jsx2(
1737
- SettingsContent,
2154
+ /* @__PURE__ */ jsx3(
2155
+ "style",
1738
2156
  {
1739
- frameSettings,
1740
- onFrameSettingsChange,
1741
- saveDir: shortDir,
1742
- picking,
1743
- onPickFolder: handlePickFolder
2157
+ dangerouslySetInnerHTML: {
2158
+ __html: `
2159
+ @keyframes ab-spin {
2160
+ 0% { transform: rotate(0deg); }
2161
+ 100% { transform: rotate(360deg); }
2162
+ }
2163
+ @keyframes ab-panel-in {
2164
+ from { opacity: 0; transform: translateY(4px); }
2165
+ to { opacity: 1; transform: translateY(0); }
2166
+ }
2167
+ `
2168
+ }
1744
2169
  }
1745
2170
  ),
1746
- activeTab === "screenshots" && /* @__PURE__ */ jsxs2(Fragment2, { children: [
1747
- /* @__PURE__ */ jsxs2("div", { style: { display: "flex", flexDirection: "column", gap: 6, marginBottom: 10 }, children: [
1748
- /* @__PURE__ */ jsx2(
1749
- FilterDropdown,
1750
- {
1751
- label: "Project",
1752
- value: selectedRepo,
1753
- options: repos,
1754
- isOpen: repoDropOpen,
1755
- onToggle: () => {
1756
- setRepoDropOpen((p) => !p);
1757
- setBranchDropOpen(false);
1758
- },
1759
- onSelect: (repo) => {
1760
- setSelectedRepo(repo);
1761
- setSelectedBranch(null);
1762
- setRepoDropOpen(false);
1763
- }
1764
- }
1765
- ),
1766
- /* @__PURE__ */ jsx2(
1767
- FilterDropdown,
1768
- {
1769
- label: "Branch",
1770
- value: selectedBranch,
1771
- options: branches,
1772
- isOpen: branchDropOpen,
1773
- onToggle: () => {
1774
- setBranchDropOpen((p) => !p);
1775
- setRepoDropOpen(false);
1776
- },
1777
- onSelect: (branch) => {
1778
- setSelectedBranch(branch);
1779
- setBranchDropOpen(false);
1780
- }
1781
- }
1782
- )
1783
- ] }),
1784
- loading ? /* @__PURE__ */ jsx2(
1785
- "div",
1786
- {
1787
- style: {
1788
- padding: "12px 0",
1789
- textAlign: "center",
1790
- ...text.paragraph.xs,
1791
- color: fg.faint
1792
- },
1793
- children: "Loading..."
1794
- }
1795
- ) : screenshots.length === 0 ? /* @__PURE__ */ jsx2(
1796
- "div",
1797
- {
1798
- style: {
1799
- padding: "12px 0",
1800
- textAlign: "center",
1801
- ...text.paragraph.xs,
1802
- color: fg.faint
1803
- },
1804
- children: "No screenshots yet"
1805
- }
1806
- ) : /* @__PURE__ */ jsxs2(Fragment2, { children: [
1807
- /* @__PURE__ */ jsx2(
1808
- "div",
1809
- {
1810
- style: {
1811
- maxHeight: 240,
1812
- overflowY: "auto",
1813
- display: "flex",
1814
- flexDirection: "column",
1815
- gap: 8
1816
- },
1817
- children: screenshots.map((shot) => {
1818
- const imgUrl = `/__afterbefore/history/image?repo=${encodeURIComponent(selectedRepo || "")}&branch=${encodeURIComponent(selectedBranch || "")}&file=${encodeURIComponent(shot.filename)}`;
1819
- const isEditing = editingFile === shot.filename;
1820
- return /* @__PURE__ */ jsxs2(
1821
- "div",
1822
- {
1823
- style: {
1824
- display: "flex",
1825
- gap: 10,
1826
- alignItems: "center"
1827
- },
1828
- children: [
1829
- /* @__PURE__ */ jsxs2(
1830
- "div",
1831
- {
1832
- style: {
1833
- position: "relative",
1834
- width: 56,
1835
- height: 36,
1836
- flexShrink: 0,
1837
- borderRadius: 4,
1838
- overflow: "hidden",
1839
- cursor: "pointer"
1840
- },
1841
- onMouseEnter: () => setHoveredThumb(shot.filename),
1842
- onMouseLeave: () => setHoveredThumb(null),
1843
- onClick: () => setLightboxSrc(imgUrl),
1844
- children: [
1845
- /* @__PURE__ */ jsx2(
1846
- "img",
1847
- {
1848
- src: imgUrl,
1849
- alt: "",
1850
- style: {
1851
- width: 56,
1852
- height: 36,
1853
- objectFit: "cover",
1854
- border: `1px solid ${stroke.default}`,
1855
- borderRadius: 4,
1856
- background: state.subtle,
1857
- display: "block"
1858
- }
1859
- }
1860
- ),
1861
- /* @__PURE__ */ jsx2(
1862
- "div",
1863
- {
1864
- style: {
1865
- position: "absolute",
1866
- inset: 0,
1867
- background: "rgba(0, 0, 0, 0.55)",
1868
- display: "flex",
1869
- alignItems: "center",
1870
- justifyContent: "center",
1871
- borderRadius: 4,
1872
- opacity: hoveredThumb === shot.filename ? 1 : 0,
1873
- transition: "opacity 0.15s ease"
1874
- },
1875
- children: /* @__PURE__ */ jsx2(Eye, { size: 16, strokeWidth: 1.8, color: fg.strong })
1876
- }
1877
- )
1878
- ]
1879
- }
1880
- ),
1881
- /* @__PURE__ */ jsx2("div", { style: { flex: 1, minWidth: 0 }, children: isEditing ? /* @__PURE__ */ jsx2(
1882
- "input",
1883
- {
1884
- autoFocus: true,
1885
- value: editValue,
1886
- onChange: (e) => setEditValue(e.target.value),
1887
- onKeyDown: (e) => {
1888
- if (e.key === "Enter") handleRename(shot.filename, editValue);
1889
- if (e.key === "Escape") setEditingFile(null);
1890
- },
1891
- onBlur: () => handleRename(shot.filename, editValue),
1892
- style: {
1893
- width: "100%",
1894
- ...text.label.xs,
1895
- color: fg.strong,
1896
- background: state.hover,
1897
- border: `1px solid ${stroke.interactive}`,
1898
- borderRadius: 4,
1899
- padding: "2px 6px",
1900
- outline: "none",
1901
- fontFamily: "inherit"
1902
- }
1903
- }
1904
- ) : /* @__PURE__ */ jsx2(
1905
- "div",
1906
- {
1907
- onClick: () => {
1908
- setEditingFile(shot.filename);
1909
- setEditValue(shot.filename.replace(/\.png$/, ""));
1910
- },
1911
- style: {
1912
- ...text.label.xs,
1913
- color: fg.strong,
1914
- cursor: "pointer",
1915
- overflow: "hidden",
1916
- textOverflow: "ellipsis",
1917
- whiteSpace: "nowrap"
1918
- },
1919
- title: "Click to rename",
1920
- children: formatTimestamp(shot.filename)
1921
- }
1922
- ) }),
1923
- /* @__PURE__ */ jsx2(
1924
- "button",
1925
- {
1926
- onClick: () => handleDelete(shot.filename),
1927
- title: "Delete screenshot",
1928
- style: {
1929
- flexShrink: 0,
1930
- width: 24,
1931
- height: 24,
1932
- borderRadius: 4,
1933
- border: "none",
1934
- background: "transparent",
1935
- color: fg.faint,
1936
- cursor: "pointer",
1937
- display: "flex",
1938
- alignItems: "center",
1939
- justifyContent: "center",
1940
- padding: 0
1941
- },
1942
- onMouseEnter: (e) => {
1943
- e.currentTarget.style.color = feedback.error;
1944
- e.currentTarget.style.background = feedback.errorBg;
1945
- },
1946
- onMouseLeave: (e) => {
1947
- e.currentTarget.style.color = fg.faint;
1948
- e.currentTarget.style.background = "transparent";
1949
- },
1950
- children: /* @__PURE__ */ jsx2(Trash22, { size: 13, strokeWidth: 1.8 })
1951
- }
1952
- )
1953
- ]
1954
- },
1955
- shot.filename
1956
- );
1957
- })
1958
- }
1959
- ),
1960
- /* @__PURE__ */ jsx2(
1961
- "div",
1962
- {
1963
- style: {
1964
- height: 1,
1965
- background: state.active,
1966
- margin: "8px 0"
1967
- }
1968
- }
1969
- ),
1970
- /* @__PURE__ */ jsxs2("div", { style: { display: "flex", flexDirection: "column", gap: 2 }, children: [
1971
- /* @__PURE__ */ jsxs2(ActionButton, { onClick: handleOpenFolder, children: [
1972
- /* @__PURE__ */ jsx2(FolderOpen, { size: 13, strokeWidth: 1.8 }),
1973
- "Open Folder"
1974
- ] }),
1975
- /* @__PURE__ */ jsxs2(ActionButton, { onClick: handlePush, disabled: pushing, children: [
1976
- /* @__PURE__ */ jsx2(ArrowUp, { size: 13, strokeWidth: 1.8 }),
1977
- pushing ? "Pushing..." : "Push to PR"
1978
- ] })
1979
- ] })
1980
- ] })
1981
- ] }),
1982
- toast && /* @__PURE__ */ jsx2(
1983
- "div",
2171
+ loading ? /* @__PURE__ */ jsx3(
2172
+ LoaderCircle,
1984
2173
  {
1985
- style: {
1986
- position: "absolute",
1987
- bottom: "100%",
1988
- left: "50%",
1989
- transform: "translateX(-50%)",
1990
- marginBottom: 8,
1991
- padding: "6px 12px",
1992
- borderRadius: 6,
1993
- ...text.label.xs,
1994
- whiteSpace: "nowrap",
1995
- color: "white",
1996
- background: toast.type === "success" ? feedback.success : feedback.error,
1997
- boxShadow: shadow.toast
1998
- },
1999
- children: toast.message
2174
+ size: 16,
2175
+ strokeWidth: 2,
2176
+ style: { animation: "ab-spin 0.8s linear infinite", color: "white" }
2177
+ }
2178
+ ) : phase === "ready" ? /* @__PURE__ */ jsx3(Check2, { size: 16, strokeWidth: 2.6, color: accent.check }) : expanded ? /* @__PURE__ */ jsx3(
2179
+ X2,
2180
+ {
2181
+ size: 16,
2182
+ strokeWidth: 1.7,
2183
+ color: cameraHovered ? fg.strong : fg.sub
2184
+ }
2185
+ ) : /* @__PURE__ */ jsx3(
2186
+ Camera,
2187
+ {
2188
+ size: 16,
2189
+ strokeWidth: 1.9,
2190
+ color: fg.sub
2000
2191
  }
2001
2192
  )
2002
2193
  ]
2003
2194
  }
2004
- ),
2005
- lightboxSrc && createPortal(
2006
- /* @__PURE__ */ jsxs2(
2195
+ )
2196
+ ] });
2197
+ const toolbarButtons = buttonsVisible ? /* @__PURE__ */ jsx3(
2198
+ "div",
2199
+ {
2200
+ style: {
2201
+ overflow: animDone ? "visible" : "hidden",
2202
+ maxHeight: animIn ? 195 : 0,
2203
+ transition: animIn ? "max-height 250ms cubic-bezier(0.23, 1, 0.32, 1)" : "max-height 150ms cubic-bezier(0.23, 1, 0.32, 1)"
2204
+ },
2205
+ children: /* @__PURE__ */ jsxs3(
2007
2206
  "div",
2008
2207
  {
2009
- "data-afterbefore": "true",
2010
- onClick: () => setLightboxSrc(null),
2011
- onKeyDown: (e) => {
2012
- if (e.key === "Escape") setLightboxSrc(null);
2013
- },
2014
2208
  style: {
2015
- position: "fixed",
2016
- inset: 0,
2017
- zIndex: 2147483647,
2018
- background: "rgba(0, 0, 0, 0.85)",
2019
2209
  display: "flex",
2210
+ flexDirection: "column",
2020
2211
  alignItems: "center",
2021
- justifyContent: "center",
2022
- cursor: "zoom-out"
2212
+ opacity: animIn ? 1 : 0,
2213
+ transform: animIn ? "translateY(0)" : `translateY(${bottom ? 6 : -6}px)`,
2214
+ transition: animIn ? "opacity 200ms cubic-bezier(0.23, 1, 0.32, 1), transform 200ms cubic-bezier(0.23, 1, 0.32, 1)" : "opacity 150ms cubic-bezier(0.23, 1, 0.32, 1), transform 150ms cubic-bezier(0.23, 1, 0.32, 1)",
2215
+ willChange: "transform, opacity"
2023
2216
  },
2024
2217
  children: [
2025
- /* @__PURE__ */ jsx2(
2026
- "img",
2218
+ /* @__PURE__ */ jsx3("div", { style: { paddingBottom: 2 }, children: /* @__PURE__ */ jsx3(
2219
+ IconButton,
2220
+ {
2221
+ active: selectedMode === "component" && !historyOpen,
2222
+ tooltipSide,
2223
+ tooltip: "Component",
2224
+ onClick: () => {
2225
+ setHistoryOpen(false);
2226
+ onModeChange("component");
2227
+ },
2228
+ children: /* @__PURE__ */ jsx3(Camera, { size: 16, strokeWidth: 1.7 })
2229
+ }
2230
+ ) }),
2231
+ MODES.filter((m) => m.mode !== "component").map(({ mode, label, icon: ModeIcon }) => /* @__PURE__ */ jsx3(
2232
+ "div",
2027
2233
  {
2028
- src: lightboxSrc,
2029
- alt: "",
2030
- onClick: (e) => e.stopPropagation(),
2031
2234
  style: {
2032
- maxWidth: "90vw",
2033
- maxHeight: "calc(100vh - 64px)",
2034
- borderRadius: 8,
2035
- boxShadow: "0 20px 60px rgba(0, 0, 0, 0.5)",
2036
- cursor: "default"
2037
- }
2235
+ maxHeight: modesExpanded ? 34 : 0,
2236
+ opacity: modesExpanded ? 1 : 0,
2237
+ transition: "max-height 200ms cubic-bezier(0.23, 1, 0.32, 1), opacity 150ms cubic-bezier(0.23, 1, 0.32, 1)"
2238
+ },
2239
+ children: /* @__PURE__ */ jsx3(
2240
+ IconButton,
2241
+ {
2242
+ active: selectedMode === mode && !historyOpen,
2243
+ tooltipSide,
2244
+ tooltip: label,
2245
+ onClick: () => {
2246
+ setHistoryOpen(false);
2247
+ onModeChange(mode);
2248
+ onCapture(mode);
2249
+ },
2250
+ children: /* @__PURE__ */ jsx3(ModeIcon, { size: 16, strokeWidth: 1.7 })
2251
+ }
2252
+ )
2253
+ },
2254
+ mode
2255
+ )),
2256
+ /* @__PURE__ */ jsx3(
2257
+ Separator,
2258
+ {
2259
+ vertical: false,
2260
+ onClick: () => setModesExpanded((p) => !p)
2038
2261
  }
2039
2262
  ),
2040
- /* @__PURE__ */ jsx2(
2041
- "button",
2263
+ /* @__PURE__ */ jsx3(
2264
+ ScreenshotsPanel,
2042
2265
  {
2043
- onClick: () => setLightboxSrc(null),
2044
- style: {
2045
- position: "absolute",
2046
- top: 16,
2047
- right: 16,
2048
- width: 32,
2049
- height: 32,
2050
- borderRadius: "50%",
2051
- border: "none",
2052
- background: state.pressed,
2053
- color: "white",
2054
- cursor: "pointer",
2055
- display: "flex",
2056
- alignItems: "center",
2057
- justifyContent: "center",
2058
- padding: 0
2266
+ open: historyOpen,
2267
+ onClick: () => {
2268
+ setHistoryOpen((prev) => !prev);
2059
2269
  },
2060
- children: /* @__PURE__ */ jsx2(X2, { size: 18, strokeWidth: 2 })
2270
+ selectedMode,
2271
+ frameSettings,
2272
+ onFrameSettingsChange,
2273
+ tooltipSide
2061
2274
  }
2062
2275
  )
2063
2276
  ]
2064
2277
  }
2065
- ),
2066
- document.body
2278
+ )
2279
+ }
2280
+ ) : null;
2281
+ return /* @__PURE__ */ jsx3(
2282
+ "div",
2283
+ {
2284
+ ref: toolbarRef,
2285
+ "data-afterbefore": "true",
2286
+ style: {
2287
+ position: "fixed",
2288
+ ...positionStyle,
2289
+ zIndex: 2147483647,
2290
+ display: "flex",
2291
+ flexDirection: "column",
2292
+ alignItems: "center",
2293
+ background: bg.base,
2294
+ borderRadius: 999,
2295
+ padding: 6,
2296
+ boxShadow: shadow.toolbar,
2297
+ ...fontBase,
2298
+ userSelect: "none"
2299
+ },
2300
+ children: bottom ? /* @__PURE__ */ jsxs3(Fragment3, { children: [
2301
+ toolbarButtons,
2302
+ cameraButton
2303
+ ] }) : /* @__PURE__ */ jsxs3(Fragment3, { children: [
2304
+ cameraButton,
2305
+ toolbarButtons
2306
+ ] })
2307
+ }
2308
+ );
2309
+ }
2310
+ function IconButton({
2311
+ children,
2312
+ active,
2313
+ tooltip,
2314
+ tooltipSide = "left",
2315
+ onClick
2316
+ }) {
2317
+ const [hovered, setHovered] = useState4(false);
2318
+ const tooltipVisible = useTooltipDelay(hovered && !!tooltip);
2319
+ const tooltipStyle = tooltipSide === "left" ? {
2320
+ right: "calc(100% + 10px)",
2321
+ top: "50%",
2322
+ transform: "translateY(-50%)"
2323
+ } : {
2324
+ left: "calc(100% + 10px)",
2325
+ top: "50%",
2326
+ transform: "translateY(-50%)"
2327
+ };
2328
+ return /* @__PURE__ */ jsxs3("div", { style: { position: "relative" }, children: [
2329
+ tooltip && tooltipVisible && /* @__PURE__ */ jsx3(
2330
+ "div",
2331
+ {
2332
+ style: {
2333
+ position: "absolute",
2334
+ ...tooltipStyle,
2335
+ background: bg.base,
2336
+ border: `1px solid ${stroke.default}`,
2337
+ borderRadius: 6,
2338
+ padding: "0 8px",
2339
+ height: 24,
2340
+ display: "flex",
2341
+ alignItems: "center",
2342
+ color: fg.strong,
2343
+ ...text.label.xs,
2344
+ whiteSpace: "nowrap",
2345
+ boxShadow: shadow.tooltip,
2346
+ pointerEvents: "none"
2347
+ },
2348
+ children: tooltip
2349
+ }
2350
+ ),
2351
+ /* @__PURE__ */ jsx3(
2352
+ "button",
2353
+ {
2354
+ onClick,
2355
+ onMouseEnter: () => setHovered(true),
2356
+ onMouseLeave: () => setHovered(false),
2357
+ style: {
2358
+ width: 32,
2359
+ height: 32,
2360
+ borderRadius: "50%",
2361
+ border: "none",
2362
+ background: active || hovered ? state.hoverStrong : "transparent",
2363
+ display: "flex",
2364
+ alignItems: "center",
2365
+ justifyContent: "center",
2366
+ cursor: "pointer",
2367
+ padding: 0,
2368
+ color: active || hovered ? fg.strong : fg.sub,
2369
+ transition: "background 0.12s ease, color 0.12s ease"
2370
+ },
2371
+ children
2372
+ }
2067
2373
  )
2068
2374
  ] });
2069
2375
  }
2376
+ function DropItem2({
2377
+ children,
2378
+ onClick,
2379
+ active,
2380
+ accent: accent2
2381
+ }) {
2382
+ return /* @__PURE__ */ jsx3(
2383
+ "button",
2384
+ {
2385
+ onClick,
2386
+ style: {
2387
+ display: "block",
2388
+ width: "100%",
2389
+ padding: "7px 12px",
2390
+ background: active ? state.active : "transparent",
2391
+ border: "none",
2392
+ color: accent2 ? accent.highlight : fg.strong,
2393
+ textAlign: "left",
2394
+ cursor: "pointer",
2395
+ ...text.paragraph.sm,
2396
+ fontFamily: "inherit"
2397
+ },
2398
+ children
2399
+ }
2400
+ );
2401
+ }
2402
+ function Separator({
2403
+ vertical = true,
2404
+ onClick
2405
+ }) {
2406
+ return /* @__PURE__ */ jsx3(
2407
+ "div",
2408
+ {
2409
+ onClick,
2410
+ style: {
2411
+ position: "relative",
2412
+ width: vertical ? 1 : 24,
2413
+ height: vertical ? 18 : 1,
2414
+ background: stroke.strong,
2415
+ flexShrink: 0,
2416
+ margin: vertical ? "0 6px" : "6px 0",
2417
+ cursor: onClick ? "pointer" : void 0
2418
+ },
2419
+ children: onClick && /* @__PURE__ */ jsx3("div", { style: { position: "absolute", inset: vertical ? "0 -8px" : "-8px 0" } })
2420
+ }
2421
+ );
2422
+ }
2070
2423
  function FilterDropdown({
2071
2424
  label,
2072
2425
  value,
@@ -2075,20 +2428,20 @@ function FilterDropdown({
2075
2428
  onToggle,
2076
2429
  onSelect
2077
2430
  }) {
2078
- return /* @__PURE__ */ jsxs2("div", { children: [
2079
- /* @__PURE__ */ jsx2(
2431
+ return /* @__PURE__ */ jsxs3("div", { children: [
2432
+ /* @__PURE__ */ jsx3(
2080
2433
  "div",
2081
2434
  {
2082
2435
  style: {
2083
2436
  ...text.subheading.xxs,
2084
- color: fg.muted,
2437
+ color: fg.sub,
2085
2438
  marginBottom: 3
2086
2439
  },
2087
2440
  children: label
2088
2441
  }
2089
2442
  ),
2090
- /* @__PURE__ */ jsxs2("div", { style: { position: "relative" }, children: [
2091
- /* @__PURE__ */ jsxs2(
2443
+ /* @__PURE__ */ jsxs3("div", { style: { position: "relative" }, children: [
2444
+ /* @__PURE__ */ jsxs3(
2092
2445
  "button",
2093
2446
  {
2094
2447
  onClick: onToggle,
@@ -2109,7 +2462,7 @@ function FilterDropdown({
2109
2462
  fontFamily: "inherit"
2110
2463
  },
2111
2464
  children: [
2112
- /* @__PURE__ */ jsx2(
2465
+ /* @__PURE__ */ jsx3(
2113
2466
  "span",
2114
2467
  {
2115
2468
  style: {
@@ -2120,16 +2473,16 @@ function FilterDropdown({
2120
2473
  children: value || "\u2014"
2121
2474
  }
2122
2475
  ),
2123
- /* @__PURE__ */ jsx2(ChevronDown2, { size: 12, strokeWidth: 2 })
2476
+ /* @__PURE__ */ jsx3(ChevronDown2, { size: 12, strokeWidth: 2 })
2124
2477
  ]
2125
2478
  }
2126
2479
  ),
2127
- isOpen && options.length > 0 && /* @__PURE__ */ jsx2(
2480
+ isOpen && options.length > 0 && /* @__PURE__ */ jsx3(
2128
2481
  "div",
2129
2482
  {
2130
2483
  style: {
2131
2484
  position: "absolute",
2132
- bottom: "calc(100% + 4px)",
2485
+ top: "calc(100% + 4px)",
2133
2486
  left: 0,
2134
2487
  right: 0,
2135
2488
  maxHeight: 160,
@@ -2141,7 +2494,7 @@ function FilterDropdown({
2141
2494
  boxShadow: shadow.dropdown,
2142
2495
  zIndex: 1
2143
2496
  },
2144
- children: options.map((opt) => /* @__PURE__ */ jsx2(
2497
+ children: options.map((opt) => /* @__PURE__ */ jsx3(
2145
2498
  DropItem2,
2146
2499
  {
2147
2500
  active: opt === value,
@@ -2155,61 +2508,15 @@ function FilterDropdown({
2155
2508
  ] })
2156
2509
  ] });
2157
2510
  }
2158
- function ActionButton({
2159
- children,
2160
- onClick,
2161
- disabled
2162
- }) {
2163
- const [hovered, setHovered] = useState3(false);
2164
- return /* @__PURE__ */ jsx2(
2165
- "button",
2166
- {
2167
- onClick,
2168
- disabled,
2169
- onMouseEnter: () => setHovered(true),
2170
- onMouseLeave: () => setHovered(false),
2171
- style: {
2172
- display: "flex",
2173
- alignItems: "center",
2174
- gap: 6,
2175
- width: "100%",
2176
- padding: "6px 8px",
2177
- border: "none",
2178
- background: hovered ? state.active : "transparent",
2179
- color: fg.default,
2180
- ...text.label.xs,
2181
- borderRadius: 6,
2182
- cursor: disabled ? "wait" : "pointer",
2183
- textAlign: "left",
2184
- fontFamily: "inherit",
2185
- transition: "background 0.1s ease"
2186
- },
2187
- children
2188
- }
2189
- );
2190
- }
2191
- function formatTimestamp(filename) {
2192
- const iso = filename.replace(/\.png$/, "").replace(/T(\d{2})-(\d{2})-(\d{2})-(\d+)Z$/, "T$1:$2:$3.$4Z");
2193
- const date = new Date(iso);
2194
- if (Number.isNaN(date.getTime())) return filename.replace(/\.png$/, "");
2195
- const now = /* @__PURE__ */ new Date();
2196
- const diffMs = now.getTime() - date.getTime();
2197
- const diffMin = Math.floor(diffMs / 6e4);
2198
- if (diffMin < 1) return "Just now";
2199
- if (diffMin < 60) return `${diffMin}m ago`;
2200
- const diffHr = Math.floor(diffMin / 60);
2201
- if (diffHr < 24) return `${diffHr}h ago`;
2202
- return date.toLocaleDateString(void 0, { month: "short", day: "numeric", hour: "2-digit", minute: "2-digit" });
2203
- }
2204
2511
 
2205
2512
  // src/overlay/ui/inspector.tsx
2206
- import { useEffect as useEffect3, useRef as useRef3, useCallback as useCallback3, useState as useState4 } from "react";
2207
- import { jsx as jsx3 } from "react/jsx-runtime";
2513
+ import { useEffect as useEffect4, useRef as useRef3, useCallback as useCallback4, useState as useState5 } from "react";
2514
+ import { jsx as jsx4 } from "react/jsx-runtime";
2208
2515
  function Inspector({ onSelect, onCancel }) {
2209
- const [highlight, setHighlight] = useState4(null);
2516
+ const [highlight, setHighlight] = useState5(null);
2210
2517
  const hoveredEl = useRef3(null);
2211
2518
  const styleEl = useRef3(null);
2212
- useEffect3(() => {
2519
+ useEffect4(() => {
2213
2520
  const style2 = document.createElement("style");
2214
2521
  style2.setAttribute("data-afterbefore", "true");
2215
2522
  style2.textContent = [
@@ -2224,7 +2531,7 @@ function Inspector({ onSelect, onCancel }) {
2224
2531
  style2.remove();
2225
2532
  };
2226
2533
  }, []);
2227
- const isOverlayElement = useCallback3((el) => {
2534
+ const isOverlayElement = useCallback4((el) => {
2228
2535
  let node = el;
2229
2536
  while (node) {
2230
2537
  if (node instanceof HTMLElement && node.dataset.afterbefore) return true;
@@ -2232,7 +2539,7 @@ function Inspector({ onSelect, onCancel }) {
2232
2539
  }
2233
2540
  return false;
2234
2541
  }, []);
2235
- const handleMouseMove = useCallback3(
2542
+ const handleMouseMove = useCallback4(
2236
2543
  (e) => {
2237
2544
  const el = document.elementFromPoint(e.clientX, e.clientY);
2238
2545
  if (!el || !(el instanceof HTMLElement) || isOverlayElement(el)) {
@@ -2251,7 +2558,7 @@ function Inspector({ onSelect, onCancel }) {
2251
2558
  },
2252
2559
  [isOverlayElement]
2253
2560
  );
2254
- const handleClick = useCallback3(
2561
+ const handleClick = useCallback4(
2255
2562
  (e) => {
2256
2563
  if (isOverlayElement(e.target)) return;
2257
2564
  e.preventDefault();
@@ -2263,7 +2570,7 @@ function Inspector({ onSelect, onCancel }) {
2263
2570
  },
2264
2571
  [onSelect, isOverlayElement]
2265
2572
  );
2266
- const handleKeyDown = useCallback3(
2573
+ const handleKeyDown = useCallback4(
2267
2574
  (e) => {
2268
2575
  if (e.key === "Escape") {
2269
2576
  onCancel();
@@ -2271,7 +2578,7 @@ function Inspector({ onSelect, onCancel }) {
2271
2578
  },
2272
2579
  [onCancel]
2273
2580
  );
2274
- useEffect3(() => {
2581
+ useEffect4(() => {
2275
2582
  document.addEventListener("mousemove", handleMouseMove, true);
2276
2583
  document.addEventListener("click", handleClick, true);
2277
2584
  document.addEventListener("keydown", handleKeyDown);
@@ -2281,7 +2588,7 @@ function Inspector({ onSelect, onCancel }) {
2281
2588
  document.removeEventListener("keydown", handleKeyDown);
2282
2589
  };
2283
2590
  }, [handleMouseMove, handleClick, handleKeyDown]);
2284
- return /* @__PURE__ */ jsx3("div", { "data-afterbefore": "true", style: { position: "fixed", inset: 0, zIndex: 2147483646, pointerEvents: "none" }, children: highlight && /* @__PURE__ */ jsx3(
2591
+ return /* @__PURE__ */ jsx4("div", { "data-afterbefore": "true", style: { position: "fixed", inset: 0, zIndex: 2147483646, pointerEvents: "none" }, children: highlight && /* @__PURE__ */ jsx4(
2285
2592
  "div",
2286
2593
  {
2287
2594
  style: {
@@ -2301,7 +2608,7 @@ function Inspector({ onSelect, onCancel }) {
2301
2608
  }
2302
2609
 
2303
2610
  // src/overlay/index.tsx
2304
- import { jsx as jsx4, jsxs as jsxs3 } from "react/jsx-runtime";
2611
+ import { jsx as jsx5, jsxs as jsxs4 } from "react/jsx-runtime";
2305
2612
  async function saveCapture(mode, dataUrl) {
2306
2613
  try {
2307
2614
  const res = await fetch("/__afterbefore/save", {
@@ -2319,15 +2626,15 @@ async function saveCapture(mode, dataUrl) {
2319
2626
  }
2320
2627
  function AfterBefore() {
2321
2628
  const { state: state2, captureComplete, reset } = useOverlayState();
2322
- const [toolbarActive, setToolbarActive] = useState5(false);
2323
- const [inspectorActive, setInspectorActive] = useState5(false);
2324
- const [loading, setLoading] = useState5(false);
2325
- const [selectedMode, setSelectedMode] = useState5("component");
2326
- const [frameSettings, setFrameSettings] = useState5(DEFAULT_FRAME_SETTINGS);
2327
- useEffect4(() => {
2629
+ const [toolbarActive, setToolbarActive] = useState6(false);
2630
+ const [inspectorActive, setInspectorActive] = useState6(false);
2631
+ const [loading, setLoading] = useState6(false);
2632
+ const [selectedMode, setSelectedMode] = useState6("component");
2633
+ const [frameSettings, setFrameSettings] = useState6(DEFAULT_FRAME_SETTINGS);
2634
+ useEffect5(() => {
2328
2635
  injectInterFont();
2329
2636
  }, []);
2330
- useEffect4(() => {
2637
+ useEffect5(() => {
2331
2638
  try {
2332
2639
  const stored = localStorage.getItem("ab-frame-settings");
2333
2640
  if (stored) {
@@ -2342,7 +2649,7 @@ function AfterBefore() {
2342
2649
  setFrameSettings(DEFAULT_FRAME_SETTINGS);
2343
2650
  }
2344
2651
  }, []);
2345
- useEffect4(() => {
2652
+ useEffect5(() => {
2346
2653
  if (state2.phase === "ready") {
2347
2654
  const timer = setTimeout(() => {
2348
2655
  reset();
@@ -2350,7 +2657,7 @@ function AfterBefore() {
2350
2657
  return () => clearTimeout(timer);
2351
2658
  }
2352
2659
  }, [state2.phase, reset]);
2353
- const handleToggle = useCallback4(() => {
2660
+ const handleToggle = useCallback5(() => {
2354
2661
  if (loading) return;
2355
2662
  if (state2.phase === "ready") {
2356
2663
  reset();
@@ -2368,7 +2675,7 @@ function AfterBefore() {
2368
2675
  }
2369
2676
  }
2370
2677
  }, [state2.phase, loading, toolbarActive, inspectorActive, selectedMode, reset]);
2371
- const performCapture = useCallback4(
2678
+ const performCapture = useCallback5(
2372
2679
  async (mode, element) => {
2373
2680
  setLoading(true);
2374
2681
  try {
@@ -2387,7 +2694,7 @@ function AfterBefore() {
2387
2694
  },
2388
2695
  [captureComplete, frameSettings]
2389
2696
  );
2390
- const handleToolbarCapture = useCallback4(
2697
+ const handleToolbarCapture = useCallback5(
2391
2698
  (mode) => {
2392
2699
  if (mode === "viewport") {
2393
2700
  setToolbarActive(false);
@@ -2401,11 +2708,11 @@ function AfterBefore() {
2401
2708
  },
2402
2709
  [performCapture]
2403
2710
  );
2404
- const handleToolbarCancel = useCallback4(() => {
2711
+ const handleToolbarCancel = useCallback5(() => {
2405
2712
  setToolbarActive(false);
2406
2713
  setInspectorActive(false);
2407
2714
  }, []);
2408
- const handleComponentSelect = useCallback4(
2715
+ const handleComponentSelect = useCallback5(
2409
2716
  (element) => {
2410
2717
  setInspectorActive(false);
2411
2718
  setToolbarActive(false);
@@ -2413,23 +2720,23 @@ function AfterBefore() {
2413
2720
  },
2414
2721
  [performCapture]
2415
2722
  );
2416
- const handleComponentCancel = useCallback4(() => {
2723
+ const handleComponentCancel = useCallback5(() => {
2417
2724
  setInspectorActive(false);
2418
2725
  setToolbarActive(true);
2419
2726
  }, []);
2420
- const handleFrameSettingsChange = useCallback4((next) => {
2727
+ const handleFrameSettingsChange = useCallback5((next) => {
2421
2728
  setFrameSettings(next);
2422
2729
  try {
2423
2730
  localStorage.setItem("ab-frame-settings", JSON.stringify(next));
2424
2731
  } catch {
2425
2732
  }
2426
2733
  }, []);
2427
- const handleModeChange = useCallback4((mode) => {
2734
+ const handleModeChange = useCallback5((mode) => {
2428
2735
  setSelectedMode(mode);
2429
2736
  setInspectorActive(mode === "component");
2430
2737
  }, []);
2431
- return /* @__PURE__ */ jsxs3("div", { "data-afterbefore": "true", children: [
2432
- /* @__PURE__ */ jsx4(
2738
+ return /* @__PURE__ */ jsxs4("div", { "data-afterbefore": "true", children: [
2739
+ /* @__PURE__ */ jsx5(
2433
2740
  Toolbar,
2434
2741
  {
2435
2742
  expanded: toolbarActive,
@@ -2444,7 +2751,7 @@ function AfterBefore() {
2444
2751
  onFrameSettingsChange: handleFrameSettingsChange
2445
2752
  }
2446
2753
  ),
2447
- inspectorActive && /* @__PURE__ */ jsx4(Inspector, { onSelect: handleComponentSelect, onCancel: handleComponentCancel })
2754
+ inspectorActive && /* @__PURE__ */ jsx5(Inspector, { onSelect: handleComponentSelect, onCancel: handleComponentCancel })
2448
2755
  ] });
2449
2756
  }
2450
2757
  export {