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