afterbefore 0.2.23 → 0.2.25

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,15 +25,32 @@ 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,
36
- browserTheme: "dark"
51
+ browserTheme: "dark",
52
+ browserPadding: 20,
53
+ browserUrl: "localhost"
37
54
  };
38
55
  var FRAME_SIZE_PRESETS = [
39
56
  { label: "1920 x 1080", hint: "Desktop / HD", w: 1920, h: 1080 },
@@ -101,9 +118,9 @@ async function captureViewport(frameSettings) {
101
118
  return viewportDataUrl;
102
119
  }
103
120
  const viewportImg = await loadImage(viewportDataUrl);
104
- return drawBrowserChrome(viewportImg, frameSettings.browserTheme, dpr);
121
+ return drawBrowserChrome(viewportImg, frameSettings.browserTheme, dpr, frameSettings.browserUrl);
105
122
  }
106
- function drawBrowserChrome(img, theme, dpr) {
123
+ function drawBrowserChrome(img, theme, dpr, url = "localhost") {
107
124
  const TITLE_BAR_H = 40;
108
125
  const URL_BAR_H = 28;
109
126
  const URL_BAR_MARGIN_TOP = 6;
@@ -150,7 +167,7 @@ function drawBrowserChrome(img, theme, dpr) {
150
167
  ctx.font = `${11 * dpr}px -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif`;
151
168
  ctx.textAlign = "center";
152
169
  ctx.textBaseline = "middle";
153
- ctx.fillText("localhost", canvas.width / 2, urlBarY + urlBarH / 2);
170
+ ctx.fillText(url, canvas.width / 2, urlBarY + urlBarH / 2);
154
171
  ctx.drawImage(img, 0, CHROME_H * dpr);
155
172
  return canvas.toDataURL("image/png");
156
173
  }
@@ -202,12 +219,12 @@ async function captureComponent(element, frameSettings) {
202
219
  const dpr = window.devicePixelRatio || 1;
203
220
  const FRAME_W = frameSettings.size.w;
204
221
  const FRAME_H = frameSettings.size.h;
205
- const padding = frameSettings.padding;
222
+ const pad = frameSettings.browserChrome ? frameSettings.browserPadding ?? 20 : frameSettings.padding ?? 40;
206
223
  const compW = img.width / dpr;
207
224
  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);
225
+ const maxW = FRAME_W - pad * 2;
226
+ const maxH = FRAME_H - pad * 2;
227
+ const scale = Math.min(maxW / compW, maxH / compH, 1);
211
228
  const drawW = compW * scale * dpr;
212
229
  const drawH = compH * scale * dpr;
213
230
  const canvas = document.createElement("canvas");
@@ -222,7 +239,9 @@ async function captureComponent(element, frameSettings) {
222
239
  ctx.fillStyle = frameSettings.bgColor;
223
240
  ctx.fillRect(0, 0, canvas.width, canvas.height);
224
241
  }
225
- } else {
242
+ } else if (frameSettings.bgType === "gradient") {
243
+ fillCssGradient(ctx, frameSettings.bgGradient, canvas.width, canvas.height);
244
+ } else if (frameSettings.bgColor !== "transparent") {
226
245
  ctx.fillStyle = frameSettings.bgColor;
227
246
  ctx.fillRect(0, 0, canvas.width, canvas.height);
228
247
  }
@@ -247,6 +266,34 @@ function loadImage(src) {
247
266
  img.src = src;
248
267
  });
249
268
  }
269
+ function fillCssGradient(ctx, css, w, h) {
270
+ const match = css.match(
271
+ /linear-gradient\(\s*([\d.]+)deg\s*,\s*(.+)\)/
272
+ );
273
+ if (!match) {
274
+ ctx.fillStyle = "#000";
275
+ ctx.fillRect(0, 0, w, h);
276
+ return;
277
+ }
278
+ const angle = parseFloat(match[1]) * Math.PI / 180;
279
+ const stopsRaw = match[2].split(/,\s*(?=#|\w)/).map((s) => s.trim());
280
+ const cx = w / 2;
281
+ const cy = h / 2;
282
+ const len = Math.abs(w * Math.sin(angle)) + Math.abs(h * Math.cos(angle));
283
+ const x0 = cx - Math.sin(angle) * len / 2;
284
+ const y0 = cy + Math.cos(angle) * len / 2;
285
+ const x1 = cx + Math.sin(angle) * len / 2;
286
+ const y1 = cy - Math.cos(angle) * len / 2;
287
+ const grad = ctx.createLinearGradient(x0, y0, x1, y1);
288
+ for (const stop of stopsRaw) {
289
+ const parts = stop.match(/^(.+?)\s+([\d.]+)%$/);
290
+ if (parts) {
291
+ grad.addColorStop(parseFloat(parts[2]) / 100, parts[1]);
292
+ }
293
+ }
294
+ ctx.fillStyle = grad;
295
+ ctx.fillRect(0, 0, w, h);
296
+ }
250
297
 
251
298
  // src/overlay/font.ts
252
299
  var FONT_FAMILY = "'Inter var', 'Inter', system-ui, -apple-system, sans-serif";
@@ -307,37 +354,85 @@ function injectInterFont() {
307
354
  }
308
355
 
309
356
  // 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";
357
+ import { useCallback as useCallback3, useEffect as useEffect3, useRef as useRef2, useState as useState4 } from "react";
312
358
  import {
313
- ArrowUp,
314
- Camera,
315
- Check,
316
359
  ChevronDown as ChevronDown2,
317
- Image as Image2,
318
- FolderOpen,
319
360
  LoaderCircle,
320
361
  FileText,
321
- Monitor,
322
- MousePointer2,
323
- Trash2 as Trash22,
324
- X as X2
362
+ Monitor
325
363
  } from "lucide-react";
326
364
 
365
+ // src/overlay/ui/icons.tsx
366
+ import { jsx, jsxs } from "react/jsx-runtime";
367
+ function CameraIcon({ size = 32, color }) {
368
+ return /* @__PURE__ */ jsx("svg", { width: size, height: size, viewBox: "0 0 32 32", fill: "none", style: { color }, children: /* @__PURE__ */ jsx(
369
+ "path",
370
+ {
371
+ d: "M23.2119 13.8662C23.8759 13.8662 24.4228 13.3193 24.4228 12.6553C24.4228 11.9814 23.8759 11.4443 23.2119 11.4443C22.538 11.4443 22.0009 11.9814 22.0009 12.6553C22.0009 13.3193 22.538 13.8662 23.2119 13.8662ZM6.89356 24.999H24.7451C26.7763 24.999 27.8115 23.9834 27.8115 21.9717V11.7471C27.8115 9.73536 26.7763 8.7295 24.7451 8.7295H21.9521C21.1806 8.7295 20.9462 8.57325 20.5068 8.08496L19.7255 7.20605C19.2373 6.66895 18.7392 6.37598 17.7333 6.37598H13.8369C12.8311 6.37598 12.333 6.66895 11.8447 7.20605L11.0635 8.08496C10.624 8.56348 10.3799 8.7295 9.61817 8.7295H6.89356C4.85254 8.7295 3.82715 9.73536 3.82715 11.7471V21.9717C3.82715 23.9834 4.85254 24.999 6.89356 24.999ZM6.91309 23.4268C5.95606 23.4268 5.39942 22.9092 5.39942 21.8936V11.835C5.39942 10.8193 5.95606 10.3018 6.91309 10.3018H10.0088C10.8877 10.3018 11.3662 10.1357 11.8545 9.58887L12.6162 8.7295C13.1728 8.1045 13.4658 7.94825 14.3251 7.94825H17.2451C18.1044 7.94825 18.3974 8.1045 18.9541 8.7295L19.7158 9.58887C20.2041 10.1357 20.6826 10.3018 21.5615 10.3018H24.7158C25.6826 10.3018 26.2392 10.8193 26.2392 11.835V21.8936C26.2392 22.9092 25.6826 23.4268 24.7158 23.4268H6.91309ZM15.8193 22.0205C18.7685 22.0205 21.1416 19.6572 21.1416 16.6787C21.1416 13.71 18.7783 11.3467 15.8193 11.3467C12.8603 11.3467 10.4873 13.71 10.4873 16.6787C10.4873 19.6572 12.8603 22.0205 15.8193 22.0205ZM15.8193 20.5362C13.7002 20.5362 11.9619 18.8272 11.9619 16.6787C11.9619 14.54 13.6904 12.8213 15.8193 12.8213C17.9482 12.8213 19.6669 14.54 19.6669 16.6787C19.6669 18.8272 17.9482 20.5362 15.8193 20.5362Z",
372
+ fill: "currentColor"
373
+ }
374
+ ) });
375
+ }
376
+ function ImageIcon({ size = 32, color }) {
377
+ return /* @__PURE__ */ jsxs("svg", { width: size, height: size, viewBox: "0 0 32 32", fill: "none", style: { color }, children: [
378
+ /* @__PURE__ */ jsx(
379
+ "path",
380
+ {
381
+ d: "M7.30371 24.3056H23.6709C25.4385 24.3056 26.3272 23.3193 26.3272 21.4443L20.5264 15.9951C20.0967 15.5947 19.5791 15.3897 19.0518 15.3897C18.5147 15.3897 18.0362 15.5752 17.5869 15.9756L13.1729 19.9209L11.3662 18.29C10.9561 17.9189 10.5068 17.7334 10.0479 17.7334C9.60839 17.7334 9.18847 17.9091 8.78808 18.2802L5.06738 21.6396C5.12598 23.4072 5.84863 24.3056 7.30371 24.3056ZM7.37207 24.9892H24.2666C26.3174 24.9892 27.333 23.9834 27.333 21.9716V10.0381C27.333 8.02636 26.3174 7.01074 24.2666 7.01074H7.37207C5.33105 7.01074 4.30566 8.02636 4.30566 10.0381V21.9716C4.30566 23.9834 5.33105 24.9892 7.37207 24.9892ZM7.3916 23.4169C6.41504 23.4169 5.87793 22.8994 5.87793 21.8837V10.126C5.87793 9.11035 6.41504 8.58301 7.3916 8.58301H24.2471C25.2139 8.58301 25.7608 9.11035 25.7608 10.126V21.8837C25.7608 22.8994 25.2139 23.4169 24.2471 23.4169H7.3916Z",
382
+ fill: "currentColor"
383
+ }
384
+ ),
385
+ /* @__PURE__ */ jsx(
386
+ "path",
387
+ {
388
+ d: "M11.5811 16.0928C12.8408 16.0928 13.876 15.0576 13.876 13.7881C13.876 12.5283 12.8408 11.4834 11.5811 11.4834C10.3115 11.4834 9.27637 12.5283 9.27637 13.7881C9.27637 15.0576 10.3115 16.0928 11.5811 16.0928Z",
389
+ fill: "currentColor"
390
+ }
391
+ )
392
+ ] });
393
+ }
394
+ function CheckmarkIcon({ size = 32, color }) {
395
+ return /* @__PURE__ */ jsxs("svg", { width: size, height: size, viewBox: "0 0 32 32", fill: "none", style: { color }, children: [
396
+ /* @__PURE__ */ jsx(
397
+ "path",
398
+ {
399
+ d: "M15.8193 25.9561C21.3174 25.9561 25.7803 21.4932 25.7803 15.9951C25.7803 10.4971 21.3174 6.03418 15.8193 6.03418C10.3213 6.03418 5.8584 10.4971 5.8584 15.9951C5.8584 21.4932 10.3213 25.9561 15.8193 25.9561ZM15.8193 24.2959C11.2295 24.2959 7.51856 20.585 7.51856 15.9951C7.51856 11.4053 11.2295 7.69434 15.8193 7.69434C20.4092 7.69434 24.1201 11.4053 24.1201 15.9951C24.1201 20.585 20.4092 24.2959 15.8193 24.2959Z",
400
+ fill: "currentColor"
401
+ }
402
+ ),
403
+ /* @__PURE__ */ jsx(
404
+ "path",
405
+ {
406
+ d: "M14.7451 20.6338C15.0674 20.6338 15.3408 20.4776 15.5361 20.1748L19.999 13.1533C20.1064 12.958 20.2334 12.7432 20.2334 12.5283C20.2334 12.0889 19.8428 11.8057 19.4326 11.8057C19.1885 11.8057 18.9443 11.9619 18.7588 12.2451L14.7061 18.749L12.7822 16.2588C12.5478 15.9463 12.333 15.8682 12.0596 15.8682C11.6396 15.8682 11.3076 16.21 11.3076 16.6397C11.3076 16.8545 11.3955 17.0596 11.5322 17.2451L13.915 20.1748C14.1592 20.4971 14.4228 20.6338 14.7451 20.6338Z",
407
+ fill: "currentColor"
408
+ }
409
+ )
410
+ ] });
411
+ }
412
+ function CloseIcon({ size = 32, color }) {
413
+ return /* @__PURE__ */ jsx("svg", { width: size, height: size, viewBox: "0 0 32 32", fill: "none", style: { color }, children: /* @__PURE__ */ jsx(
414
+ "path",
415
+ {
416
+ d: "M22.0693 8.52467L8.32911 22.2649C7.99708 22.5969 7.98732 23.1633 8.32911 23.4953C8.67091 23.8274 9.23732 23.8274 9.56935 23.4953L23.3096 9.75514C23.6416 9.42311 23.6514 8.8567 23.3096 8.52467C22.9678 8.18287 22.4111 8.17311 22.0693 8.52467ZM23.3096 22.2649L9.56935 8.52467C9.23732 8.18287 8.66115 8.17311 8.32911 8.52467C7.99708 8.86647 7.99708 9.42311 8.32911 9.75514L22.0693 23.4953C22.4014 23.8274 22.9775 23.8371 23.3096 23.4953C23.6416 23.1535 23.6416 22.5969 23.3096 22.2649Z",
417
+ fill: "currentColor"
418
+ }
419
+ ) });
420
+ }
421
+
327
422
  // src/overlay/color.ts
328
423
  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"
424
+ 950: "oklch(0.162 0 0)",
425
+ 900: "oklch(0.195 0 0)",
426
+ 800: "oklch(0.254 0 0)",
427
+ 700: "oklch(0.302 0 0)",
428
+ 600: "oklch(0.348 0 0)",
429
+ 500: "oklch(0.396 0 0)",
430
+ 400: "oklch(0.459 0 0)",
431
+ 300: "oklch(0.549 0 0)",
432
+ 200: "oklch(0.649 0 0)",
433
+ 100: "oklch(0.72 0 0)",
434
+ 50: "oklch(0.863 0 0)",
435
+ 0: "oklch(0.933 0 0)"
341
436
  };
342
437
  var bg = {
343
438
  base: gray[900],
@@ -346,10 +441,15 @@ var bg = {
346
441
  };
347
442
  var fg = {
348
443
  strong: gray[200],
349
- default: gray[300],
350
- sub: gray[500],
351
- muted: gray[600],
352
- faint: gray[600]
444
+ // ~5.6:1 — headings, input values
445
+ default: "oklch(0.6 0 0)",
446
+ // ~4.6:1 — body text, interactive labels (AA)
447
+ sub: gray[300],
448
+ // ~3.8:1 — secondary labels, icons
449
+ muted: "oklch(0.5 0 0)",
450
+ // ~3:1 — inline labels, decorative
451
+ faint: gray[500]
452
+ // ~2:1 — non-essential hints only
353
453
  };
354
454
  var stroke = {
355
455
  soft: "rgba(255, 255, 255, 0.08)",
@@ -387,17 +487,26 @@ var shadow = {
387
487
  status: "0 4px 20px rgba(0, 0, 0, 0.4), 0 0 0 1px rgba(255, 255, 255, 0.08)"
388
488
  };
389
489
 
490
+ // src/overlay/ui/screenshots-panel.tsx
491
+ import { useCallback as useCallback2, useEffect as useEffect2, useState as useState3 } from "react";
492
+ import { createPortal } from "react-dom";
493
+ import {
494
+ Trash2 as Trash22,
495
+ X
496
+ } from "lucide-react";
497
+
390
498
  // src/overlay/ui/settings-panel.tsx
391
499
  import { useEffect, useRef, useState as useState2 } from "react";
392
500
  import {
501
+ AppWindow,
502
+ Check,
393
503
  ChevronDown,
394
- ImageIcon,
395
- Palette,
504
+ LayoutGrid,
505
+ Pipette,
396
506
  Trash2,
397
- Upload,
398
- X
507
+ Upload
399
508
  } from "lucide-react";
400
- import { Fragment, jsx, jsxs } from "react/jsx-runtime";
509
+ import { Fragment, jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
401
510
  function SettingsContent({
402
511
  frameSettings,
403
512
  onFrameSettingsChange,
@@ -405,90 +514,41 @@ function SettingsContent({
405
514
  picking,
406
515
  onPickFolder
407
516
  }) {
408
- return /* @__PURE__ */ jsxs(Fragment, { children: [
409
- /* @__PURE__ */ jsx(
410
- SettingsRow,
517
+ return /* @__PURE__ */ jsxs2(Fragment, { children: [
518
+ /* @__PURE__ */ jsx2(
519
+ BackgroundSetting,
411
520
  {
412
- title: "Frame",
413
- control: /* @__PURE__ */ jsx(
414
- ToggleSwitch,
415
- {
416
- enabled: frameSettings.enabled,
417
- onChange: () => onFrameSettingsChange({ ...frameSettings, enabled: !frameSettings.enabled })
418
- }
419
- )
521
+ frameSettings,
522
+ onFrameSettingsChange
420
523
  }
421
524
  ),
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
- /* @__PURE__ */ jsx(SettingsDivider, {}),
444
- /* @__PURE__ */ jsx(
445
- SettingsRow,
525
+ /* @__PURE__ */ jsx2(
526
+ FrameSizeControl,
446
527
  {
447
- title: "Browser Chrome",
448
- control: /* @__PURE__ */ jsx(
449
- ToggleSwitch,
450
- {
451
- enabled: frameSettings.browserChrome,
452
- onChange: () => onFrameSettingsChange({ ...frameSettings, browserChrome: !frameSettings.browserChrome })
453
- }
454
- )
528
+ size: frameSettings.size,
529
+ onChange: (size) => onFrameSettingsChange({ ...frameSettings, size })
455
530
  }
456
531
  ),
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
- /* @__PURE__ */ jsx(SettingsDivider, {}),
487
- saveDir !== void 0 && onPickFolder && /* @__PURE__ */ jsx(
532
+ /* @__PURE__ */ jsx2(SettingsDivider, {}),
533
+ /* @__PURE__ */ jsx2(
534
+ BrowserFrameSetting,
535
+ {
536
+ enabled: frameSettings.browserChrome,
537
+ theme: frameSettings.browserTheme,
538
+ padding: frameSettings.browserPadding ?? 20,
539
+ url: frameSettings.browserUrl ?? "localhost",
540
+ onToggle: () => onFrameSettingsChange({ ...frameSettings, browserChrome: !frameSettings.browserChrome }),
541
+ onSelect: (theme) => onFrameSettingsChange({ ...frameSettings, browserChrome: true, browserTheme: theme }),
542
+ onPaddingChange: (v) => onFrameSettingsChange({ ...frameSettings, browserPadding: v }),
543
+ onUrlChange: (v) => onFrameSettingsChange({ ...frameSettings, browserUrl: v })
544
+ }
545
+ ),
546
+ /* @__PURE__ */ jsx2(SettingsDivider, {}),
547
+ saveDir !== void 0 && onPickFolder && /* @__PURE__ */ jsx2(
488
548
  SettingsRow,
489
549
  {
490
550
  title: "Save Location",
491
- description: /* @__PURE__ */ jsx(
551
+ description: /* @__PURE__ */ jsx2(
492
552
  "span",
493
553
  {
494
554
  style: {
@@ -501,7 +561,7 @@ function SettingsContent({
501
561
  children: saveDir
502
562
  }
503
563
  ),
504
- control: /* @__PURE__ */ jsx(SmallButton, { onClick: onPickFolder, children: picking ? "..." : "Change" })
564
+ control: /* @__PURE__ */ jsx2(SmallButton, { onClick: onPickFolder, children: picking ? "..." : "Change" })
505
565
  }
506
566
  )
507
567
  ] });
@@ -511,20 +571,21 @@ function SettingsRow({
511
571
  description,
512
572
  control
513
573
  }) {
514
- return /* @__PURE__ */ jsxs("div", { style: { display: "flex", alignItems: "center", justifyContent: "space-between", gap: 16, padding: "10px 0" }, children: [
515
- /* @__PURE__ */ jsxs("div", { style: { display: "flex", flexDirection: "column", gap: 2, flex: 1, minWidth: 0 }, children: [
516
- /* @__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 })
574
+ return /* @__PURE__ */ jsxs2("div", { style: { display: "flex", alignItems: "center", justifyContent: "space-between", gap: 16, padding: "14px 0" }, children: [
575
+ /* @__PURE__ */ jsxs2("div", { style: { display: "flex", flexDirection: "column", gap: 2, flex: 1, minWidth: 0 }, children: [
576
+ /* @__PURE__ */ jsx2("span", { style: { ...text.label.sm, color: fg.strong }, children: title }),
577
+ description && /* @__PURE__ */ jsx2("span", { style: { ...text.paragraph.xs, color: fg.sub }, children: description })
518
578
  ] }),
519
- /* @__PURE__ */ jsx("div", { style: { flexShrink: 0 }, children: control })
579
+ /* @__PURE__ */ jsx2("div", { style: { flexShrink: 0 }, children: control })
520
580
  ] });
521
581
  }
522
582
  function SettingsDivider() {
523
- return /* @__PURE__ */ jsx(
583
+ return /* @__PURE__ */ jsx2(
524
584
  "div",
525
585
  {
526
586
  style: {
527
- borderBottom: `1px solid ${stroke.soft}`
587
+ borderBottom: `1px solid ${stroke.soft}`,
588
+ margin: "0 -16px"
528
589
  }
529
590
  }
530
591
  );
@@ -533,7 +594,7 @@ function ToggleSwitch({
533
594
  enabled,
534
595
  onChange
535
596
  }) {
536
- return /* @__PURE__ */ jsx(
597
+ return /* @__PURE__ */ jsx2(
537
598
  "button",
538
599
  {
539
600
  type: "button",
@@ -550,7 +611,7 @@ function ToggleSwitch({
550
611
  flexShrink: 0,
551
612
  transition: "background 0.12s ease"
552
613
  },
553
- children: /* @__PURE__ */ jsx(
614
+ children: /* @__PURE__ */ jsx2(
554
615
  "span",
555
616
  {
556
617
  style: {
@@ -573,13 +634,71 @@ function FrameSizeControl({
573
634
  onChange
574
635
  }) {
575
636
  const [sizeOpen, setSizeOpen] = useState2(false);
637
+ const [editing, setEditing] = useState2(null);
638
+ const [text2, setText] = useState2("");
576
639
  const currentPreset = FRAME_SIZE_PRESETS.find((p) => p.w === size.w && p.h === size.h);
577
640
  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" }) }),
581
- /* @__PURE__ */ jsxs("div", { style: { position: "relative", flexShrink: 0 }, children: [
582
- /* @__PURE__ */ jsxs(
641
+ const commitValue = (field, raw) => {
642
+ const n = parseInt(raw, 10);
643
+ if (!Number.isNaN(n) && n > 0) onChange({ ...size, [field]: n });
644
+ };
645
+ const inputStyle = {
646
+ flex: 1,
647
+ minWidth: 0,
648
+ border: "none",
649
+ background: "transparent",
650
+ color: fg.strong,
651
+ ...text.label.xs,
652
+ fontFamily: "inherit",
653
+ textAlign: "left",
654
+ outline: "none",
655
+ padding: "0 2px 0 6px"
656
+ };
657
+ return /* @__PURE__ */ jsxs2("div", { style: { marginTop: 16, paddingBottom: 14 }, children: [
658
+ /* @__PURE__ */ jsx2("span", { style: { ...text.label.sm, color: fg.strong }, children: "Size" }),
659
+ /* @__PURE__ */ jsx2("div", { style: { display: "flex", gap: 6, marginTop: 8 }, children: ["w", "h"].map((field) => /* @__PURE__ */ jsxs2(
660
+ "div",
661
+ {
662
+ style: {
663
+ flex: 1,
664
+ minWidth: 0,
665
+ display: "flex",
666
+ alignItems: "center",
667
+ height: 30,
668
+ padding: "0 8px",
669
+ background: state.input,
670
+ borderRadius: 7
671
+ },
672
+ children: [
673
+ /* @__PURE__ */ jsx2("span", { style: { ...text.label.xs, color: fg.sub, flexShrink: 0, userSelect: "none" }, children: field.toUpperCase() }),
674
+ /* @__PURE__ */ jsx2(
675
+ "input",
676
+ {
677
+ type: "text",
678
+ value: editing === field ? text2 : String(size[field]),
679
+ onFocus: () => {
680
+ setEditing(field);
681
+ setText(String(size[field]));
682
+ },
683
+ onBlur: () => {
684
+ setEditing(null);
685
+ commitValue(field, text2);
686
+ },
687
+ onChange: (e) => setText(e.target.value),
688
+ onKeyDown: (e) => {
689
+ if (e.key === "Enter") e.target.blur();
690
+ },
691
+ style: inputStyle
692
+ }
693
+ )
694
+ ]
695
+ },
696
+ field
697
+ )) }),
698
+ /* @__PURE__ */ jsxs2("div", { style: { display: "flex", alignItems: "center", justifyContent: "space-between", gap: 16, marginTop: 16 }, children: [
699
+ /* @__PURE__ */ jsx2("span", { style: { ...text.label.sm, color: fg.strong }, children: "Presets" }),
700
+ /* @__PURE__ */ jsxs2("div", { style: { position: "relative", flexShrink: 0 }, children: [
701
+ /* @__PURE__ */ jsxs2(
583
702
  "button",
584
703
  {
585
704
  onClick: () => setSizeOpen((prev) => !prev),
@@ -587,12 +706,12 @@ function FrameSizeControl({
587
706
  display: "flex",
588
707
  alignItems: "center",
589
708
  gap: 6,
590
- height: 30,
591
- padding: "0 10px",
709
+ height: 28,
710
+ padding: "0 8px",
592
711
  borderRadius: 7,
593
712
  border: `1px solid ${stroke.default}`,
594
713
  background: state.input,
595
- color: fg.strong,
714
+ color: fg.sub,
596
715
  cursor: "pointer",
597
716
  ...text.label.xs,
598
717
  fontFamily: "inherit",
@@ -600,11 +719,11 @@ function FrameSizeControl({
600
719
  },
601
720
  children: [
602
721
  currentPreset ? currentPreset.label : "Custom",
603
- /* @__PURE__ */ jsx(ChevronDown, { size: 12, strokeWidth: 2 })
722
+ /* @__PURE__ */ jsx2(ChevronDown, { size: 12, strokeWidth: 2 })
604
723
  ]
605
724
  }
606
725
  ),
607
- sizeOpen && /* @__PURE__ */ jsx(
726
+ sizeOpen && /* @__PURE__ */ jsx2(
608
727
  "div",
609
728
  {
610
729
  style: {
@@ -619,7 +738,7 @@ function FrameSizeControl({
619
738
  boxShadow: shadow.dropdown,
620
739
  zIndex: 1
621
740
  },
622
- children: FRAME_SIZE_PRESETS.map((preset) => /* @__PURE__ */ jsxs(
741
+ children: FRAME_SIZE_PRESETS.map((preset) => /* @__PURE__ */ jsxs2(
623
742
  DropItem,
624
743
  {
625
744
  active: !isCustom && preset.w === size.w && preset.h === size.h,
@@ -628,8 +747,8 @@ function FrameSizeControl({
628
747
  setSizeOpen(false);
629
748
  },
630
749
  children: [
631
- /* @__PURE__ */ jsx("span", { children: preset.label }),
632
- /* @__PURE__ */ jsx("span", { style: { marginLeft: 6, fontSize: 10, color: fg.faint }, children: preset.hint })
750
+ /* @__PURE__ */ jsx2("span", { children: preset.label }),
751
+ /* @__PURE__ */ jsx2("span", { style: { marginLeft: 6, fontSize: 10, color: fg.muted }, children: preset.hint })
633
752
  ]
634
753
  },
635
754
  preset.label
@@ -637,39 +756,16 @@ function FrameSizeControl({
637
756
  }
638
757
  )
639
758
  ] })
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
759
  ] })
664
760
  ] });
665
761
  }
666
- function FrameBackgroundControl({
667
- bgType,
668
- bgColor,
669
- bgImage,
670
- frameSize,
671
- onChange
762
+ function BackgroundSetting({
763
+ frameSettings,
764
+ onFrameSettingsChange
672
765
  }) {
766
+ const [tab, setTab] = useState2(
767
+ frameSettings.bgType === "image" ? "image" : frameSettings.bgType === "gradient" ? "gradient" : "solid"
768
+ );
673
769
  const fileInputRef = useRef(null);
674
770
  const handleFileSelect = (e) => {
675
771
  const file = e.target.files?.[0];
@@ -678,52 +774,123 @@ function FrameBackgroundControl({
678
774
  reader.onload = () => {
679
775
  const dataUrl = reader.result;
680
776
  if (dataUrl.length > 2 * 1024 * 1024) {
681
- downscaleImage(dataUrl, frameSize.w, frameSize.h).then((scaled) => {
682
- onChange({ bgType: "image", bgImage: scaled });
777
+ downscaleImage(dataUrl, frameSettings.size.w, frameSettings.size.h).then((scaled) => {
778
+ onFrameSettingsChange({ ...frameSettings, bgType: "image", bgImage: scaled });
683
779
  });
684
780
  } else {
685
- onChange({ bgType: "image", bgImage: dataUrl });
781
+ onFrameSettingsChange({ ...frameSettings, bgType: "image", bgImage: dataUrl });
686
782
  }
687
783
  };
688
784
  reader.readAsDataURL(file);
689
785
  e.target.value = "";
690
786
  };
691
- return /* @__PURE__ */ jsxs("div", { style: { padding: "10px 0" }, children: [
692
- /* @__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
- ] })
787
+ const selectGradient = (g) => {
788
+ onFrameSettingsChange({ ...frameSettings, bgType: "gradient", bgGradient: g });
789
+ };
790
+ const selectColor = (c) => {
791
+ onFrameSettingsChange({ ...frameSettings, bgType: "color", bgColor: c });
792
+ };
793
+ const currentBg = frameSettings.bgType === "gradient" ? frameSettings.bgGradient : frameSettings.bgColor;
794
+ return /* @__PURE__ */ jsxs2("div", { children: [
795
+ /* @__PURE__ */ jsxs2("div", { style: { display: "flex", alignItems: "center", justifyContent: "space-between", gap: 16 }, children: [
796
+ /* @__PURE__ */ jsxs2("div", { style: { display: "flex", alignItems: "center", gap: 8 }, children: [
797
+ /* @__PURE__ */ jsx2(LayoutGrid, { size: 16, strokeWidth: 1.6, color: fg.sub }),
798
+ /* @__PURE__ */ jsx2("span", { style: { ...text.label.sm, color: fg.strong }, children: "Background" })
799
+ ] }),
800
+ /* @__PURE__ */ jsx2(
801
+ ToggleSwitch,
802
+ {
803
+ enabled: frameSettings.enabled,
804
+ onChange: () => onFrameSettingsChange({ ...frameSettings, enabled: !frameSettings.enabled })
805
+ }
806
+ )
720
807
  ] }),
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 }) })
808
+ /* @__PURE__ */ jsx2("div", { style: { display: "flex", gap: 2, marginTop: 12, background: state.subtle, borderRadius: 8, padding: 2 }, children: ["solid", "gradient", "image"].map((t) => /* @__PURE__ */ jsx2(
809
+ "button",
810
+ {
811
+ onClick: () => setTab(t),
812
+ style: {
813
+ flex: 1,
814
+ padding: "6px 0",
815
+ border: "none",
816
+ borderRadius: 6,
817
+ background: tab === t ? state.button : "transparent",
818
+ color: tab === t ? fg.strong : fg.default,
819
+ ...text.label.xs,
820
+ fontFamily: "inherit",
821
+ cursor: "pointer",
822
+ transition: "background 0.12s ease, color 0.12s ease",
823
+ textTransform: "capitalize"
824
+ },
825
+ children: t
826
+ },
827
+ t
828
+ )) }),
829
+ tab === "gradient" && /* @__PURE__ */ jsx2("div", { style: { display: "flex", gap: 8, marginTop: 12, paddingBottom: 10, flexWrap: "wrap" }, children: GRADIENT_PRESETS.map((g) => {
830
+ const selected = frameSettings.bgType === "gradient" && frameSettings.bgGradient === g;
831
+ return /* @__PURE__ */ jsx2(
832
+ PresetSwatch,
833
+ {
834
+ background: g,
835
+ selected,
836
+ onClick: () => selectGradient(g)
837
+ },
838
+ g
839
+ );
840
+ }) }),
841
+ tab === "solid" && /* @__PURE__ */ jsxs2(Fragment, { children: [
842
+ /* @__PURE__ */ jsxs2("div", { style: { display: "flex", gap: 8, marginTop: 12, paddingBottom: 10, flexWrap: "wrap" }, children: [
843
+ COLOR_PRESETS.map((c) => {
844
+ const selected = frameSettings.bgType === "color" && frameSettings.bgColor === c;
845
+ return /* @__PURE__ */ jsx2(
846
+ PresetSwatch,
847
+ {
848
+ background: c,
849
+ selected,
850
+ onClick: () => selectColor(c)
851
+ },
852
+ c
853
+ );
854
+ }),
855
+ (() => {
856
+ const customColor = frameSettings.bgColor;
857
+ const isCustom = !COLOR_PRESETS.includes(customColor.toUpperCase());
858
+ const bg2 = isCustom ? customColor : "transparent";
859
+ const iconColor = isCustom ? hexLuminance(customColor) > 0.4 ? "rgba(0,0,0,0.5)" : "rgba(255,255,255,0.7)" : fg.sub;
860
+ return /* @__PURE__ */ jsx2(
861
+ "button",
862
+ {
863
+ onClick: async () => {
864
+ if (!("EyeDropper" in window)) return;
865
+ try {
866
+ const result = await new window.EyeDropper().open();
867
+ selectColor(result.sRGBHex);
868
+ } catch {
869
+ }
870
+ },
871
+ style: {
872
+ width: 32,
873
+ height: 32,
874
+ borderRadius: "50%",
875
+ border: isCustom ? `1px solid ${stroke.soft}` : `1px dashed ${stroke.default}`,
876
+ outline: isCustom ? `2px solid ${accent.toggle}` : "none",
877
+ outlineOffset: 1,
878
+ background: bg2,
879
+ cursor: "pointer",
880
+ display: "flex",
881
+ alignItems: "center",
882
+ justifyContent: "center",
883
+ color: iconColor
884
+ },
885
+ children: /* @__PURE__ */ jsx2(Pipette, { size: 14, strokeWidth: 2 })
886
+ }
887
+ );
888
+ })()
889
+ ] }),
890
+ /* @__PURE__ */ jsx2("div", { style: { paddingBottom: 4 }, children: /* @__PURE__ */ jsx2(ColorHexInput, { value: frameSettings.bgColor, onChange: selectColor }) })
724
891
  ] }),
725
- bgType === "image" && /* @__PURE__ */ jsxs("div", { style: { marginTop: 10 }, children: [
726
- /* @__PURE__ */ jsx(
892
+ tab === "image" && /* @__PURE__ */ jsxs2("div", { style: { marginTop: 12 }, children: [
893
+ /* @__PURE__ */ jsx2(
727
894
  "input",
728
895
  {
729
896
  ref: fileInputRef,
@@ -733,11 +900,11 @@ function FrameBackgroundControl({
733
900
  style: { display: "none" }
734
901
  }
735
902
  ),
736
- bgImage ? /* @__PURE__ */ jsxs("div", { style: { display: "flex", alignItems: "center", gap: 8 }, children: [
737
- /* @__PURE__ */ jsx(
903
+ frameSettings.bgImage ? /* @__PURE__ */ jsxs2("div", { style: { display: "flex", alignItems: "center", gap: 8 }, children: [
904
+ /* @__PURE__ */ jsx2(
738
905
  "img",
739
906
  {
740
- src: bgImage,
907
+ src: frameSettings.bgImage,
741
908
  alt: "",
742
909
  style: {
743
910
  width: 48,
@@ -748,224 +915,453 @@ function FrameBackgroundControl({
748
915
  }
749
916
  }
750
917
  ),
751
- /* @__PURE__ */ jsxs(SmallButton, { onClick: () => fileInputRef.current?.click(), children: [
752
- /* @__PURE__ */ jsx(Upload, { size: 11, strokeWidth: 2 }),
918
+ /* @__PURE__ */ jsxs2(SmallButton, { onClick: () => fileInputRef.current?.click(), children: [
919
+ /* @__PURE__ */ jsx2(Upload, { size: 11, strokeWidth: 2 }),
753
920
  "Replace"
754
921
  ] }),
755
- /* @__PURE__ */ jsx(SmallButton, { onClick: () => onChange({ bgImage: null }), children: /* @__PURE__ */ jsx(Trash2, { size: 11, strokeWidth: 2 }) })
756
- ] }) : /* @__PURE__ */ jsxs(SmallButton, { onClick: () => fileInputRef.current?.click(), children: [
757
- /* @__PURE__ */ jsx(Upload, { size: 11, strokeWidth: 2 }),
922
+ /* @__PURE__ */ jsx2(SmallButton, { onClick: () => onFrameSettingsChange({ ...frameSettings, bgImage: null }), children: /* @__PURE__ */ jsx2(Trash2, { size: 11, strokeWidth: 2 }) })
923
+ ] }) : /* @__PURE__ */ jsxs2(SmallButton, { onClick: () => fileInputRef.current?.click(), children: [
924
+ /* @__PURE__ */ jsx2(Upload, { size: 11, strokeWidth: 2 }),
758
925
  "Upload image"
759
926
  ] })
927
+ ] }),
928
+ /* @__PURE__ */ jsxs2("div", { style: { marginTop: 16 }, children: [
929
+ /* @__PURE__ */ jsx2("span", { style: { ...text.label.sm, color: fg.strong }, children: "Padding" }),
930
+ /* @__PURE__ */ jsxs2("div", { style: { display: "flex", alignItems: "center", gap: 8, marginTop: 8 }, children: [
931
+ /* @__PURE__ */ jsx2(
932
+ SlimSlider,
933
+ {
934
+ min: 0,
935
+ max: 200,
936
+ step: 5,
937
+ value: frameSettings.padding,
938
+ onChange: (v) => onFrameSettingsChange({ ...frameSettings, padding: v })
939
+ }
940
+ ),
941
+ /* @__PURE__ */ jsx2(
942
+ PaddingInput,
943
+ {
944
+ value: frameSettings.padding,
945
+ onChange: (n) => onFrameSettingsChange({ ...frameSettings, padding: n })
946
+ }
947
+ )
948
+ ] })
760
949
  ] })
761
950
  ] });
762
951
  }
763
- function SegmentButton({
764
- children,
765
- active,
766
- onClick,
767
- style: style2
952
+ function SlimSlider({
953
+ min,
954
+ max,
955
+ step,
956
+ value,
957
+ onChange
768
958
  }) {
769
- return /* @__PURE__ */ jsx(
770
- "button",
959
+ const trackRef = useRef(null);
960
+ const pct = (value - min) / (max - min) * 100;
961
+ const update = (clientX) => {
962
+ const rect = trackRef.current.getBoundingClientRect();
963
+ const ratio = Math.max(0, Math.min(1, (clientX - rect.left) / rect.width));
964
+ const raw = min + ratio * (max - min);
965
+ const snapped = Math.round(raw / step) * step;
966
+ onChange(Math.max(min, Math.min(max, snapped)));
967
+ };
968
+ const onPointerDown = (e) => {
969
+ e.preventDefault();
970
+ e.target.setPointerCapture(e.pointerId);
971
+ update(e.clientX);
972
+ };
973
+ const onPointerMove = (e) => {
974
+ if (e.buttons === 0) return;
975
+ update(e.clientX);
976
+ };
977
+ return /* @__PURE__ */ jsx2(
978
+ "div",
771
979
  {
772
- onClick,
980
+ ref: trackRef,
981
+ onPointerDown,
982
+ onPointerMove,
773
983
  style: {
984
+ flex: 1,
985
+ minWidth: 0,
986
+ height: 20,
774
987
  display: "flex",
775
988
  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
989
  cursor: "pointer",
783
- ...text.label.xs,
784
- fontFamily: "inherit",
785
- transition: "background 0.12s ease, color 0.12s ease",
786
- ...style2
990
+ touchAction: "none"
787
991
  },
788
- children
992
+ children: /* @__PURE__ */ jsxs2("div", { style: { position: "relative", width: "100%", height: 3, borderRadius: 2, background: stroke.default }, children: [
993
+ /* @__PURE__ */ jsx2("div", { style: { position: "absolute", left: 0, top: 0, height: "100%", width: `${pct}%`, borderRadius: 2, background: fg.sub } }),
994
+ /* @__PURE__ */ jsx2(
995
+ "div",
996
+ {
997
+ style: {
998
+ position: "absolute",
999
+ top: "50%",
1000
+ left: `${pct}%`,
1001
+ width: 10,
1002
+ height: 10,
1003
+ borderRadius: "50%",
1004
+ background: fg.strong,
1005
+ transform: "translate(-50%, -50%)"
1006
+ }
1007
+ }
1008
+ )
1009
+ ] })
789
1010
  }
790
1011
  );
791
1012
  }
792
- function ColorSwatch({
793
- color,
794
- onChange
795
- }) {
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"
828
- }
829
- }
830
- )
831
- ] });
832
- }
833
- function SmallButton({
834
- children,
1013
+ function PresetSwatch({
1014
+ background,
1015
+ selected,
835
1016
  onClick
836
1017
  }) {
837
- const [hovered, setHovered] = useState2(false);
838
- return /* @__PURE__ */ jsx(
839
- "button",
1018
+ return /* @__PURE__ */ jsx2(
1019
+ "div",
840
1020
  {
841
1021
  onClick,
842
- onMouseEnter: () => setHovered(true),
843
- onMouseLeave: () => setHovered(false),
844
1022
  style: {
845
- display: "flex",
846
- alignItems: "center",
847
- gap: 4,
848
- padding: "4px 10px",
849
- borderRadius: 6,
850
- border: `1px solid ${stroke.strong}`,
851
- background: hovered ? state.hoverStrong : state.button,
852
- color: fg.default,
853
- ...text.label.xs,
1023
+ width: 32,
1024
+ height: 32,
1025
+ borderRadius: "50%",
1026
+ background,
854
1027
  cursor: "pointer",
855
- fontFamily: "inherit",
856
- transition: "background 0.12s ease"
1028
+ border: `1px solid ${stroke.soft}`,
1029
+ outline: selected ? `2px solid ${accent.toggle}` : "none",
1030
+ outlineOffset: 1,
1031
+ position: "relative",
1032
+ transition: "outline-color 0.12s ease",
1033
+ flexShrink: 0
857
1034
  },
858
- children
1035
+ children: selected && /* @__PURE__ */ jsx2(
1036
+ "div",
1037
+ {
1038
+ style: {
1039
+ position: "absolute",
1040
+ inset: 0,
1041
+ borderRadius: "50%",
1042
+ display: "flex",
1043
+ alignItems: "center",
1044
+ justifyContent: "center",
1045
+ background: "rgba(0,0,0,0.2)"
1046
+ },
1047
+ children: /* @__PURE__ */ jsx2(Check, { size: 16, strokeWidth: 3, color: "#fff" })
1048
+ }
1049
+ )
859
1050
  }
860
1051
  );
861
1052
  }
862
- function NumInput({
863
- value,
864
- onChange
1053
+ function hexLuminance(hex) {
1054
+ const n = parseInt(hex.replace("#", ""), 16);
1055
+ const r = (n >> 16 & 255) / 255;
1056
+ const g = (n >> 8 & 255) / 255;
1057
+ const b = (n & 255) / 255;
1058
+ return 0.2126 * r + 0.7152 * g + 0.0722 * b;
1059
+ }
1060
+ function BrowserFrameSetting({
1061
+ enabled,
1062
+ theme,
1063
+ padding,
1064
+ url,
1065
+ onToggle,
1066
+ onSelect,
1067
+ onPaddingChange,
1068
+ onUrlChange
865
1069
  }) {
866
- const [editing, setEditing] = useState2(false);
867
- const [text2, setText] = useState2(String(value));
868
- useEffect(() => {
869
- if (!editing) {
870
- setText(String(value));
871
- }
872
- }, [editing, value]);
873
- return /* @__PURE__ */ jsx(
874
- "input",
875
- {
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();
1070
+ const trafficLights = /* @__PURE__ */ jsx2("div", { style: { display: "flex", gap: 4 }, children: ["#FF5F57", "#FEBC2E", "#28C840"].map((c) => /* @__PURE__ */ jsx2("div", { style: { width: 5, height: 5, borderRadius: "50%", background: c } }, c)) });
1071
+ const themes = [
1072
+ { value: "dark", label: "macOS Dark", titleBar: "#1C1C1C", urlBar: "#262626", border: "#333333" },
1073
+ { value: "light", label: "macOS Light", titleBar: "#F5F5F5", urlBar: "#FFFFFF", border: "#EBEBEB" }
1074
+ ];
1075
+ return /* @__PURE__ */ jsxs2("div", { style: { padding: "14px 0" }, children: [
1076
+ /* @__PURE__ */ jsxs2("div", { style: { display: "flex", alignItems: "center", justifyContent: "space-between", gap: 16 }, children: [
1077
+ /* @__PURE__ */ jsxs2("div", { style: { display: "flex", alignItems: "center", gap: 8 }, children: [
1078
+ /* @__PURE__ */ jsx2(AppWindow, { size: 16, strokeWidth: 1.6, color: fg.sub }),
1079
+ /* @__PURE__ */ jsx2("span", { style: { ...text.label.sm, color: fg.strong }, children: "Browser Frame" })
1080
+ ] }),
1081
+ /* @__PURE__ */ jsx2(ToggleSwitch, { enabled, onChange: onToggle })
1082
+ ] }),
1083
+ /* @__PURE__ */ jsx2("div", { style: { display: "flex", gap: 10, marginTop: 12 }, children: themes.map((t) => {
1084
+ const selected = enabled && theme === t.value;
1085
+ return /* @__PURE__ */ jsxs2(
1086
+ "div",
1087
+ {
1088
+ onClick: () => onSelect(t.value),
1089
+ style: {
1090
+ flex: 1,
1091
+ cursor: "pointer",
1092
+ borderRadius: 10,
1093
+ border: selected ? `2px solid ${accent.toggle}` : `1px solid ${stroke.soft}`,
1094
+ padding: selected ? 7 : 8,
1095
+ position: "relative",
1096
+ background: state.subtle,
1097
+ transition: "border-color 0.12s ease"
1098
+ },
1099
+ children: [
1100
+ /* @__PURE__ */ jsxs2(
1101
+ "div",
1102
+ {
1103
+ style: {
1104
+ background: t.titleBar,
1105
+ borderRadius: 6,
1106
+ overflow: "hidden",
1107
+ border: t.value === "light" ? `1px solid ${t.border}` : "none",
1108
+ display: "flex",
1109
+ alignItems: "center",
1110
+ gap: 6,
1111
+ padding: "6px 8px"
1112
+ },
1113
+ children: [
1114
+ trafficLights,
1115
+ /* @__PURE__ */ jsx2(
1116
+ "div",
1117
+ {
1118
+ style: {
1119
+ flex: 1,
1120
+ height: 8,
1121
+ background: t.urlBar,
1122
+ borderRadius: 2,
1123
+ ...t.value === "light" ? { border: `1px solid ${t.border}` } : {}
1124
+ }
1125
+ }
1126
+ )
1127
+ ]
1128
+ }
1129
+ ),
1130
+ /* @__PURE__ */ jsx2("div", { style: { textAlign: "center", marginTop: 8, ...text.label.xs, color: fg.default }, children: t.label }),
1131
+ selected && /* @__PURE__ */ jsx2(
1132
+ "div",
1133
+ {
1134
+ style: {
1135
+ position: "absolute",
1136
+ top: -5,
1137
+ right: -5,
1138
+ width: 18,
1139
+ height: 18,
1140
+ borderRadius: "50%",
1141
+ background: accent.toggle,
1142
+ display: "flex",
1143
+ alignItems: "center",
1144
+ justifyContent: "center"
1145
+ },
1146
+ children: /* @__PURE__ */ jsx2(Check, { size: 10, strokeWidth: 3, color: "#fff" })
1147
+ }
1148
+ )
1149
+ ]
1150
+ },
1151
+ t.value
1152
+ );
1153
+ }) }),
1154
+ /* @__PURE__ */ jsxs2("div", { style: { marginTop: 12 }, children: [
1155
+ /* @__PURE__ */ jsx2("span", { style: { ...text.label.sm, color: fg.strong }, children: "URL" }),
1156
+ /* @__PURE__ */ jsx2(
1157
+ "div",
1158
+ {
1159
+ style: {
1160
+ display: "flex",
1161
+ alignItems: "center",
1162
+ height: 30,
1163
+ padding: "0 8px",
1164
+ background: state.input,
1165
+ borderRadius: 7,
1166
+ marginTop: 8
1167
+ },
1168
+ children: /* @__PURE__ */ jsx2(
1169
+ "input",
1170
+ {
1171
+ type: "text",
1172
+ value: url,
1173
+ onChange: (e) => onUrlChange(e.target.value),
1174
+ placeholder: "localhost",
1175
+ style: {
1176
+ flex: 1,
1177
+ minWidth: 0,
1178
+ border: "none",
1179
+ background: "transparent",
1180
+ color: fg.strong,
1181
+ ...text.label.xs,
1182
+ fontFamily: "inherit",
1183
+ outline: "none",
1184
+ padding: 0
1185
+ }
1186
+ }
1187
+ )
890
1188
  }
891
- },
1189
+ )
1190
+ ] }),
1191
+ /* @__PURE__ */ jsxs2("div", { style: { marginTop: 12 }, children: [
1192
+ /* @__PURE__ */ jsx2("span", { style: { ...text.label.sm, color: fg.strong }, children: "Padding" }),
1193
+ /* @__PURE__ */ jsxs2("div", { style: { display: "flex", alignItems: "center", gap: 8, marginTop: 8 }, children: [
1194
+ /* @__PURE__ */ jsx2(
1195
+ SlimSlider,
1196
+ {
1197
+ min: 0,
1198
+ max: 200,
1199
+ step: 5,
1200
+ value: padding,
1201
+ onChange: onPaddingChange
1202
+ }
1203
+ ),
1204
+ /* @__PURE__ */ jsx2(
1205
+ PaddingInput,
1206
+ {
1207
+ value: padding,
1208
+ onChange: onPaddingChange
1209
+ }
1210
+ )
1211
+ ] })
1212
+ ] })
1213
+ ] });
1214
+ }
1215
+ function SmallButton({
1216
+ children,
1217
+ onClick
1218
+ }) {
1219
+ const [hovered, setHovered] = useState2(false);
1220
+ return /* @__PURE__ */ jsx2(
1221
+ "button",
1222
+ {
1223
+ onClick,
1224
+ onMouseEnter: () => setHovered(true),
1225
+ onMouseLeave: () => setHovered(false),
892
1226
  style: {
893
- width: 54,
894
- padding: "4px 6px",
895
- background: state.input,
896
- border: `1px solid ${stroke.default}`,
897
- borderRadius: 7,
898
- color: fg.strong,
1227
+ display: "flex",
1228
+ alignItems: "center",
1229
+ gap: 4,
1230
+ padding: "4px 10px",
1231
+ borderRadius: 6,
1232
+ border: `1px solid ${stroke.strong}`,
1233
+ background: hovered ? state.hoverStrong : state.button,
1234
+ color: fg.default,
899
1235
  ...text.label.xs,
1236
+ cursor: "pointer",
900
1237
  fontFamily: "inherit",
901
- textAlign: "center",
902
- outline: "none"
903
- }
1238
+ transition: "background 0.12s ease"
1239
+ },
1240
+ children
904
1241
  }
905
1242
  );
906
1243
  }
907
- function HexInput({
1244
+ function PaddingInput({
908
1245
  value,
909
1246
  onChange
910
1247
  }) {
911
1248
  const [editing, setEditing] = useState2(false);
912
- const [text2, setText] = useState2(value);
1249
+ const [text2, setText] = useState2(String(value));
913
1250
  useEffect(() => {
914
- if (!editing) {
915
- setText(value);
916
- }
1251
+ if (!editing) setText(String(value));
917
1252
  }, [editing, value]);
918
- const commit = (raw) => {
919
- const hex = raw.startsWith("#") ? raw : `#${raw}`;
920
- if (/^#[0-9a-fA-F]{6}$/.test(hex)) {
921
- onChange(hex);
922
- }
923
- };
924
- return /* @__PURE__ */ jsx(
925
- "input",
1253
+ return /* @__PURE__ */ jsxs2(
1254
+ "div",
926
1255
  {
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
1256
  style: {
944
- width: 72,
945
- padding: "4px 6px",
1257
+ display: "flex",
1258
+ alignItems: "center",
1259
+ height: 30,
1260
+ padding: "0 8px",
946
1261
  background: state.input,
947
- border: `1px solid ${stroke.default}`,
948
1262
  borderRadius: 7,
949
- color: fg.strong,
950
- ...text.label.xs,
951
- fontFamily: "inherit",
952
- textAlign: "left",
953
- outline: "none"
954
- }
1263
+ flexShrink: 0,
1264
+ width: 68
1265
+ },
1266
+ children: [
1267
+ /* @__PURE__ */ jsx2(
1268
+ "input",
1269
+ {
1270
+ type: "text",
1271
+ value: editing ? text2 : String(value),
1272
+ onFocus: () => {
1273
+ setEditing(true);
1274
+ setText(String(value));
1275
+ },
1276
+ onBlur: () => {
1277
+ setEditing(false);
1278
+ const n = parseInt(text2, 10);
1279
+ if (!Number.isNaN(n) && n >= 0 && n <= 200) onChange(n);
1280
+ },
1281
+ onChange: (e) => setText(e.target.value),
1282
+ onKeyDown: (e) => {
1283
+ if (e.key === "Enter") e.target.blur();
1284
+ },
1285
+ style: {
1286
+ flex: 1,
1287
+ minWidth: 0,
1288
+ border: "none",
1289
+ background: "transparent",
1290
+ color: fg.strong,
1291
+ ...text.label.xs,
1292
+ fontFamily: "inherit",
1293
+ textAlign: "left",
1294
+ outline: "none",
1295
+ padding: 0
1296
+ }
1297
+ }
1298
+ ),
1299
+ /* @__PURE__ */ jsx2("span", { style: { ...text.label.xs, color: fg.sub, flexShrink: 0, userSelect: "none", marginLeft: 4 }, children: "px" })
1300
+ ]
955
1301
  }
956
1302
  );
957
1303
  }
958
- function StaticText({ children }) {
959
- return /* @__PURE__ */ jsx(
960
- "span",
1304
+ function ColorHexInput({
1305
+ value,
1306
+ onChange
1307
+ }) {
1308
+ const [editing, setEditing] = useState2(false);
1309
+ const [text2, setText] = useState2(value.replace("#", ""));
1310
+ useEffect(() => {
1311
+ if (!editing) setText(value.replace("#", ""));
1312
+ }, [editing, value]);
1313
+ const commit = (raw) => {
1314
+ const hex = raw.startsWith("#") ? raw : `#${raw}`;
1315
+ if (/^#[0-9a-fA-F]{6}$/.test(hex)) onChange(hex);
1316
+ };
1317
+ return /* @__PURE__ */ jsxs2(
1318
+ "div",
961
1319
  {
962
1320
  style: {
963
- fontSize: 11,
964
- color: fg.faint,
965
- minWidth: 8,
966
- textAlign: "center"
1321
+ flex: 1,
1322
+ minWidth: 0,
1323
+ display: "flex",
1324
+ alignItems: "center",
1325
+ height: 30,
1326
+ padding: "0 8px",
1327
+ background: state.input,
1328
+ borderRadius: 7,
1329
+ gap: 6
967
1330
  },
968
- children
1331
+ children: [
1332
+ /* @__PURE__ */ jsx2("span", { style: { ...text.label.xs, color: fg.sub, flexShrink: 0, userSelect: "none" }, children: "#" }),
1333
+ /* @__PURE__ */ jsx2(
1334
+ "input",
1335
+ {
1336
+ type: "text",
1337
+ value: editing ? text2 : value.replace("#", ""),
1338
+ onFocus: () => {
1339
+ setEditing(true);
1340
+ setText(value.replace("#", ""));
1341
+ },
1342
+ onBlur: () => {
1343
+ setEditing(false);
1344
+ commit(text2);
1345
+ },
1346
+ onChange: (e) => setText(e.target.value),
1347
+ onKeyDown: (e) => {
1348
+ if (e.key === "Enter") e.target.blur();
1349
+ },
1350
+ style: {
1351
+ flex: 1,
1352
+ minWidth: 0,
1353
+ border: "none",
1354
+ background: "transparent",
1355
+ color: fg.strong,
1356
+ ...text.label.xs,
1357
+ fontFamily: "inherit",
1358
+ textAlign: "left",
1359
+ outline: "none",
1360
+ padding: 0
1361
+ }
1362
+ }
1363
+ )
1364
+ ]
969
1365
  }
970
1366
  );
971
1367
  }
@@ -974,7 +1370,7 @@ function DropItem({
974
1370
  onClick,
975
1371
  active
976
1372
  }) {
977
- return /* @__PURE__ */ jsx(
1373
+ return /* @__PURE__ */ jsx2(
978
1374
  "button",
979
1375
  {
980
1376
  onClick,
@@ -1011,641 +1407,126 @@ async function downscaleImage(dataUrl, maxW, maxH) {
1011
1407
  });
1012
1408
  }
1013
1409
 
1014
- // src/overlay/ui/toolbar.tsx
1015
- 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";
1035
- }
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";
1043
- }
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
- }
1410
+ // src/overlay/ui/screenshots-panel.tsx
1411
+ import { Fragment as Fragment2, jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
1412
+ function BrowserChromeBar({ theme, url = "localhost" }) {
1413
+ const colors = theme === "dark" ? { titleBar: "#1C1C1C", urlBar: "#262626", text: "#7B7B7B", border: "#333333" } : { titleBar: "#F5F5F5", urlBar: "#FFFFFF", text: "#999999", border: "#EBEBEB" };
1414
+ return /* @__PURE__ */ jsx3("div", { style: { background: colors.titleBar, flexShrink: 0, padding: "4px 6px" }, children: /* @__PURE__ */ jsxs3("div", { style: { display: "flex", alignItems: "center", gap: 6, height: 14 }, children: [
1415
+ /* @__PURE__ */ jsx3("div", { style: { display: "flex", gap: 5, flexShrink: 0 }, children: ["#FF5F57", "#FEBC2E", "#28C840"].map((c) => /* @__PURE__ */ jsx3("div", { style: { width: 6, height: 6, borderRadius: "50%", background: c } }, c)) }),
1416
+ /* @__PURE__ */ jsx3(
1417
+ "div",
1418
+ {
1419
+ style: {
1420
+ flex: 1,
1421
+ height: 14,
1422
+ background: colors.urlBar,
1423
+ borderRadius: 3,
1424
+ display: "flex",
1425
+ alignItems: "center",
1426
+ paddingLeft: 6,
1427
+ ...theme === "light" ? { border: `1px solid ${colors.border}` } : {},
1428
+ boxSizing: "border-box"
1429
+ },
1430
+ children: /* @__PURE__ */ jsx3(
1431
+ "span",
1432
+ {
1433
+ style: {
1434
+ fontSize: 7,
1435
+ lineHeight: 1,
1436
+ color: colors.text,
1437
+ fontFamily: '-apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif'
1438
+ },
1439
+ children: url
1440
+ }
1441
+ )
1442
+ }
1443
+ )
1444
+ ] }) });
1057
1445
  }
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,
1068
- selectedMode,
1069
- onModeChange,
1070
- onCapture,
1071
- onCancel,
1446
+ function FramePreview({
1447
+ previewUrl,
1072
1448
  frameSettings,
1073
- onFrameSettingsChange
1449
+ loading
1074
1450
  }) {
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);
1080
- 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(() => {
1099
- 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;
1103
- }
1104
- } catch {
1105
- }
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);
1114
- 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;
1175
- } 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
- });
1451
+ const { enabled: frameEnabled, browserChrome, browserTheme, browserUrl, size, bgType, bgColor, bgGradient, bgImage, padding, browserPadding = 20 } = frameSettings;
1452
+ const aspectRatio = frameEnabled ? `${size.w} / ${size.h}` : "16 / 10";
1453
+ const outerBg = frameEnabled ? bgType === "image" && bgImage ? { backgroundImage: `url(${bgImage})`, backgroundSize: "cover", backgroundPosition: "center" } : bgType === "gradient" ? { background: bgGradient } : { background: bgColor } : { background: bg.elevated };
1454
+ const emptyState = /* @__PURE__ */ jsx3("span", { style: { ...text.paragraph.xs, color: fg.sub }, children: loading ? "Loading..." : "No screenshots yet" });
1455
+ const imageEl = previewUrl ? /* @__PURE__ */ jsx3(
1456
+ "img",
1457
+ {
1458
+ src: previewUrl,
1459
+ alt: "",
1460
+ style: {
1461
+ maxWidth: "100%",
1462
+ maxHeight: "100%",
1463
+ objectFit: "contain",
1464
+ display: "block"
1200
1465
  }
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)"
1217
1466
  }
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
1242
- }
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
- }
1272
- @keyframes ab-panel-in {
1273
- from { opacity: 0; transform: translateY(4px); }
1274
- to { opacity: 1; transform: translateY(0); }
1275
- }
1276
- `
1277
- }
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(
1467
+ ) : null;
1468
+ const padPct = frameEnabled ? `${(browserChrome ? browserPadding : padding) / size.h * 100}%` : void 0;
1469
+ return /* @__PURE__ */ jsx3(
1307
1470
  "div",
1308
1471
  {
1309
1472
  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)"
1473
+ width: "100%",
1474
+ aspectRatio,
1475
+ borderRadius: 10,
1476
+ overflow: "hidden",
1477
+ border: `1px solid ${stroke.soft}`,
1478
+ marginBottom: 10,
1479
+ display: "flex",
1480
+ flexDirection: "column",
1481
+ ...outerBg,
1482
+ ...browserChrome && padPct ? { padding: padPct } : {}
1313
1483
  },
1314
- children: /* @__PURE__ */ jsxs2(
1484
+ children: browserChrome ? /* @__PURE__ */ jsxs3("div", { style: { flex: 1, display: "flex", flexDirection: "column", borderRadius: 8, overflow: "hidden", minHeight: 0 }, children: [
1485
+ /* @__PURE__ */ jsx3(BrowserChromeBar, { theme: browserTheme, url: browserUrl }),
1486
+ /* @__PURE__ */ jsx3(
1487
+ "div",
1488
+ {
1489
+ style: {
1490
+ flex: 1,
1491
+ display: "flex",
1492
+ alignItems: "center",
1493
+ justifyContent: "center",
1494
+ minHeight: 0
1495
+ },
1496
+ children: imageEl || emptyState
1497
+ }
1498
+ )
1499
+ ] }) : /* @__PURE__ */ jsx3(
1315
1500
  "div",
1316
1501
  {
1317
1502
  style: {
1503
+ flex: 1,
1318
1504
  display: "flex",
1319
- flexDirection: "column",
1320
1505
  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"
1506
+ justifyContent: "center",
1507
+ minHeight: 0,
1508
+ ...padPct ? { padding: padPct } : {}
1325
1509
  },
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(
1341
- "div",
1342
- {
1343
- 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)"
1347
- },
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)
1370
- }
1371
- ),
1372
- /* @__PURE__ */ jsx2(
1373
- HistoryButton,
1374
- {
1375
- open: historyOpen,
1376
- onClick: () => {
1377
- setHistoryOpen((prev) => !prev);
1378
- },
1379
- selectedMode,
1380
- frameSettings,
1381
- onFrameSettingsChange,
1382
- tooltipSide
1383
- }
1384
- )
1385
- ]
1510
+ children: imageEl || emptyState
1386
1511
  }
1387
1512
  )
1388
1513
  }
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
- ] })
1416
- }
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
1457
- }
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
- ] });
1483
- }
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
- );
1509
- }
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
1514
  );
1530
1515
  }
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
- ] });
1560
- }
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
- );
1516
+ function formatTimestamp(filename) {
1517
+ const iso = filename.replace(/\.png$/, "").replace(/T(\d{2})-(\d{2})-(\d{2})-(\d+)Z$/, "T$1:$2:$3.$4Z");
1518
+ const date = new Date(iso);
1519
+ if (Number.isNaN(date.getTime())) return filename.replace(/\.png$/, "");
1520
+ const now = /* @__PURE__ */ new Date();
1521
+ const diffMs = now.getTime() - date.getTime();
1522
+ const diffMin = Math.floor(diffMs / 6e4);
1523
+ if (diffMin < 1) return "Just now";
1524
+ if (diffMin < 60) return `${diffMin}m ago`;
1525
+ const diffHr = Math.floor(diffMin / 60);
1526
+ if (diffHr < 24) return `${diffHr}h ago`;
1527
+ return date.toLocaleDateString(void 0, { month: "short", day: "numeric", hour: "2-digit", minute: "2-digit" });
1647
1528
  }
1648
- function HistoryButton({
1529
+ function ScreenshotsPanel({
1649
1530
  open,
1650
1531
  onClick,
1651
1532
  selectedMode,
@@ -1654,7 +1535,6 @@ function HistoryButton({
1654
1535
  tooltipSide
1655
1536
  }) {
1656
1537
  const [toast, setToast] = useState3(null);
1657
- const [pushing, setPushing] = useState3(false);
1658
1538
  const [saveDir, setSaveDir] = useState3(null);
1659
1539
  const [picking, setPicking] = useState3(false);
1660
1540
  const [repos, setRepos] = useState3([]);
@@ -1665,7 +1545,6 @@ function HistoryButton({
1665
1545
  const [loading, setLoading] = useState3(false);
1666
1546
  const [repoDropOpen, setRepoDropOpen] = useState3(false);
1667
1547
  const [branchDropOpen, setBranchDropOpen] = useState3(false);
1668
- const [lightboxSrc, setLightboxSrc] = useState3(null);
1669
1548
  const [editingFile, setEditingFile] = useState3(null);
1670
1549
  const [editValue, setEditValue] = useState3("");
1671
1550
  const [selectedFile, setSelectedFile] = useState3(null);
@@ -1724,27 +1603,13 @@ function HistoryButton({
1724
1603
  setToast({ message, type });
1725
1604
  setTimeout(() => setToast(null), 3e3);
1726
1605
  }, []);
1727
- const handleOpenFolder = async () => {
1606
+ const handleRename = async (oldName, newName) => {
1607
+ if (!newName.trim() || newName.trim() === oldName.replace(/\.png$/, "")) {
1608
+ setEditingFile(null);
1609
+ return;
1610
+ }
1728
1611
  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", {
1612
+ const res = await fetch("/__afterbefore/history/rename", {
1748
1613
  method: "POST",
1749
1614
  headers: { "Content-Type": "application/json" },
1750
1615
  body: JSON.stringify({
@@ -1790,29 +1655,11 @@ function HistoryButton({
1790
1655
  showToast("Delete failed", "error");
1791
1656
  }
1792
1657
  };
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");
1802
- } else {
1803
- showToast("No PR found", "error");
1804
- }
1805
- } catch {
1806
- showToast("Push failed", "error");
1807
- } finally {
1808
- setPushing(false);
1809
- }
1810
- };
1811
1658
  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 }) }),
1659
+ return /* @__PURE__ */ jsxs3(Fragment2, { children: [
1660
+ /* @__PURE__ */ jsx3(IconButton, { active: open, tooltipSide, tooltip: !open ? "Screenshots" : void 0, onClick, children: /* @__PURE__ */ jsx3(ImageIcon, { size: 20 }) }),
1814
1661
  open && createPortal(
1815
- /* @__PURE__ */ jsxs2(
1662
+ /* @__PURE__ */ jsxs3(
1816
1663
  "div",
1817
1664
  {
1818
1665
  "data-afterbefore": "true",
@@ -1828,13 +1675,26 @@ function HistoryButton({
1828
1675
  ...fontBase
1829
1676
  },
1830
1677
  children: [
1831
- /* @__PURE__ */ jsxs2(
1678
+ /* @__PURE__ */ jsx3(
1679
+ "style",
1680
+ {
1681
+ dangerouslySetInnerHTML: {
1682
+ __html: `
1683
+ @keyframes ab-panel-in {
1684
+ from { opacity: 0; transform: translateY(4px); }
1685
+ to { opacity: 1; transform: translateY(0); }
1686
+ }
1687
+ `
1688
+ }
1689
+ }
1690
+ ),
1691
+ /* @__PURE__ */ jsxs3(
1832
1692
  "div",
1833
1693
  {
1834
1694
  onClick: (e) => e.stopPropagation(),
1835
1695
  style: {
1836
- width: 580,
1837
- maxHeight: "80vh",
1696
+ width: 900,
1697
+ height: "70vh",
1838
1698
  borderRadius: 14,
1839
1699
  background: bg.base,
1840
1700
  border: `1px solid ${stroke.default}`,
@@ -1845,17 +1705,18 @@ function HistoryButton({
1845
1705
  animation: "ab-panel-in 150ms cubic-bezier(0.23, 1, 0.32, 1)"
1846
1706
  },
1847
1707
  children: [
1848
- /* @__PURE__ */ jsxs2("div", { style: {
1708
+ /* @__PURE__ */ jsxs3("div", { style: {
1849
1709
  display: "flex",
1850
1710
  alignItems: "center",
1851
1711
  justifyContent: "space-between",
1852
- padding: "16px 20px 0"
1712
+ padding: "16px 20px",
1713
+ borderBottom: `1px solid ${stroke.soft}`
1853
1714
  }, 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" })
1715
+ /* @__PURE__ */ jsxs3("div", { children: [
1716
+ /* @__PURE__ */ jsx3("div", { style: { ...text.label.sm, color: fg.strong }, children: "Screenshots" }),
1717
+ /* @__PURE__ */ jsx3("div", { style: { ...text.paragraph.xs, color: fg.sub, marginTop: 2 }, children: "Capture history & settings" })
1857
1718
  ] }),
1858
- /* @__PURE__ */ jsx2(
1719
+ /* @__PURE__ */ jsx3(
1859
1720
  "button",
1860
1721
  {
1861
1722
  onClick,
@@ -1869,222 +1730,249 @@ function HistoryButton({
1869
1730
  alignItems: "center",
1870
1731
  justifyContent: "center",
1871
1732
  cursor: "pointer",
1872
- color: fg.muted,
1733
+ color: fg.sub,
1873
1734
  padding: 0,
1874
1735
  transition: "background 0.12s ease, color 0.12s ease"
1875
1736
  },
1876
1737
  onMouseEnter: (e) => {
1877
1738
  e.currentTarget.style.background = state.hover;
1878
- e.currentTarget.style.color = fg.default;
1739
+ e.currentTarget.style.color = fg.strong;
1879
1740
  },
1880
1741
  onMouseLeave: (e) => {
1881
1742
  e.currentTarget.style.background = "transparent";
1882
- e.currentTarget.style.color = fg.muted;
1743
+ e.currentTarget.style.color = fg.sub;
1883
1744
  },
1884
- children: /* @__PURE__ */ jsx2(X2, { size: 14, strokeWidth: 2 })
1745
+ children: /* @__PURE__ */ jsx3(X, { size: 14, strokeWidth: 2 })
1885
1746
  }
1886
1747
  )
1887
1748
  ] }),
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",
1749
+ /* @__PURE__ */ jsxs3("div", { style: { display: "flex", flex: 1, overflow: "hidden", minHeight: 0 }, children: [
1750
+ /* @__PURE__ */ jsxs3("div", { style: { flex: 1, overflowY: "auto", padding: "16px 20px 20px", minHeight: 0 }, children: [
1751
+ /* @__PURE__ */ jsx3(
1752
+ FramePreview,
1908
1753
  {
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
- }
1754
+ previewUrl,
1755
+ frameSettings,
1756
+ loading
1928
1757
  }
1929
- ) : /* @__PURE__ */ jsx2(
1758
+ ),
1759
+ selectedFile && !loading && /* @__PURE__ */ jsxs3("div", { style: {
1760
+ display: "flex",
1761
+ alignItems: "center",
1762
+ gap: 8,
1763
+ marginBottom: 12
1764
+ }, children: [
1765
+ /* @__PURE__ */ jsx3("div", { style: { flex: 1, minWidth: 0 }, children: editingFile === selectedFile ? /* @__PURE__ */ jsx3(
1766
+ "input",
1767
+ {
1768
+ autoFocus: true,
1769
+ value: editValue,
1770
+ onChange: (e) => setEditValue(e.target.value),
1771
+ onKeyDown: (e) => {
1772
+ if (e.key === "Enter") handleRename(selectedFile, editValue);
1773
+ if (e.key === "Escape") setEditingFile(null);
1774
+ },
1775
+ onBlur: () => handleRename(selectedFile, editValue),
1776
+ style: {
1777
+ width: "100%",
1778
+ ...text.label.xs,
1779
+ color: fg.strong,
1780
+ background: state.hover,
1781
+ border: `1px solid ${stroke.interactive}`,
1782
+ borderRadius: 4,
1783
+ padding: "2px 6px",
1784
+ outline: "none",
1785
+ fontFamily: "inherit"
1786
+ }
1787
+ }
1788
+ ) : /* @__PURE__ */ jsx3(
1789
+ "div",
1790
+ {
1791
+ onClick: () => {
1792
+ setEditingFile(selectedFile);
1793
+ setEditValue(selectedFile.replace(/\.png$/, ""));
1794
+ },
1795
+ style: {
1796
+ ...text.label.xs,
1797
+ color: fg.strong,
1798
+ cursor: "pointer",
1799
+ overflow: "hidden",
1800
+ textOverflow: "ellipsis",
1801
+ whiteSpace: "nowrap"
1802
+ },
1803
+ title: "Click to rename",
1804
+ children: formatTimestamp(selectedFile)
1805
+ }
1806
+ ) }),
1807
+ /* @__PURE__ */ jsx3(
1808
+ "button",
1809
+ {
1810
+ onClick: () => handleDelete(selectedFile),
1811
+ title: "Delete screenshot",
1812
+ style: {
1813
+ flexShrink: 0,
1814
+ width: 28,
1815
+ height: 28,
1816
+ borderRadius: 6,
1817
+ border: "none",
1818
+ background: "transparent",
1819
+ color: fg.muted,
1820
+ cursor: "pointer",
1821
+ display: "flex",
1822
+ alignItems: "center",
1823
+ justifyContent: "center",
1824
+ padding: 0
1825
+ },
1826
+ onMouseEnter: (e) => {
1827
+ e.currentTarget.style.color = feedback.error;
1828
+ e.currentTarget.style.background = feedback.errorBg;
1829
+ },
1830
+ onMouseLeave: (e) => {
1831
+ e.currentTarget.style.color = fg.muted;
1832
+ e.currentTarget.style.background = "transparent";
1833
+ },
1834
+ children: /* @__PURE__ */ jsx3(Trash22, { size: 14, strokeWidth: 1.8 })
1835
+ }
1836
+ )
1837
+ ] }),
1838
+ /* @__PURE__ */ jsxs3("div", { style: { display: "flex", gap: 10, marginBottom: 10 }, children: [
1839
+ /* @__PURE__ */ jsx3("div", { style: { flex: 1, position: "relative" }, children: /* @__PURE__ */ jsx3(
1840
+ FilterDropdown,
1841
+ {
1842
+ label: "Project",
1843
+ value: selectedRepo,
1844
+ options: repos,
1845
+ isOpen: repoDropOpen,
1846
+ onToggle: () => {
1847
+ setRepoDropOpen((p) => !p);
1848
+ setBranchDropOpen(false);
1849
+ },
1850
+ onSelect: (repo) => {
1851
+ setSelectedRepo(repo);
1852
+ setSelectedBranch(null);
1853
+ setRepoDropOpen(false);
1854
+ }
1855
+ }
1856
+ ) }),
1857
+ /* @__PURE__ */ jsx3("div", { style: { flex: 1, position: "relative" }, children: /* @__PURE__ */ jsx3(
1858
+ FilterDropdown,
1859
+ {
1860
+ label: "Branch",
1861
+ value: selectedBranch,
1862
+ options: branches,
1863
+ isOpen: branchDropOpen,
1864
+ onToggle: () => {
1865
+ setBranchDropOpen((p) => !p);
1866
+ setRepoDropOpen(false);
1867
+ },
1868
+ onSelect: (branch) => {
1869
+ setSelectedBranch(branch);
1870
+ setBranchDropOpen(false);
1871
+ }
1872
+ }
1873
+ ) })
1874
+ ] }),
1875
+ screenshots.length > 0 && /* @__PURE__ */ jsx3(
1930
1876
  "div",
1931
1877
  {
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
1878
  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
1879
  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;
1880
+ gap: 6,
1881
+ overflowX: "auto",
1882
+ paddingBottom: 4,
1883
+ marginBottom: 10
1970
1884
  },
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 })
1885
+ children: screenshots.map((shot) => {
1886
+ const thumbUrl = `/__afterbefore/history/image?repo=${encodeURIComponent(selectedRepo || "")}&branch=${encodeURIComponent(selectedBranch || "")}&file=${encodeURIComponent(shot.filename)}`;
1887
+ const isSelected = selectedFile === shot.filename;
1888
+ return /* @__PURE__ */ jsx3(
1889
+ "div",
1890
+ {
1891
+ onClick: () => setSelectedFile(shot.filename),
1892
+ style: {
1893
+ width: 64,
1894
+ height: 44,
1895
+ flexShrink: 0,
1896
+ borderRadius: 6,
1897
+ overflow: "hidden",
1898
+ cursor: "pointer",
1899
+ border: isSelected ? `2px solid ${accent.highlight}` : `1px solid ${stroke.soft}`,
1900
+ opacity: isSelected ? 1 : 0.7,
1901
+ transition: "opacity 0.12s ease, border-color 0.12s ease"
1902
+ },
1903
+ children: /* @__PURE__ */ jsx3(
1904
+ "img",
1905
+ {
1906
+ src: thumbUrl,
1907
+ alt: "",
1908
+ style: {
1909
+ width: "100%",
1910
+ height: "100%",
1911
+ objectFit: "cover",
1912
+ display: "block"
1913
+ }
1914
+ }
1915
+ )
1916
+ },
1917
+ shot.filename
1918
+ );
1919
+ })
1976
1920
  }
1977
1921
  )
1978
1922
  ] }),
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,
1923
+ /* @__PURE__ */ jsx3("div", { style: { width: 1, flexShrink: 0, background: stroke.soft } }),
1924
+ /* @__PURE__ */ jsxs3("div", { style: { width: 300, flexShrink: 0, overflowY: "auto", padding: "16px 16px 12px", minHeight: 0, display: "flex", flexDirection: "column" }, children: [
1925
+ /* @__PURE__ */ jsx3("div", { style: { flex: 1 }, children: /* @__PURE__ */ jsx3(
1926
+ SettingsContent,
1992
1927
  {
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
- }
1928
+ frameSettings,
1929
+ onFrameSettingsChange,
1930
+ saveDir: shortDir,
1931
+ picking,
1932
+ onPickFolder: handlePickFolder
2006
1933
  }
2007
1934
  ) }),
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);
1935
+ /* @__PURE__ */ jsxs3("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: [
1936
+ /* @__PURE__ */ jsx3("span", { style: { ...text.paragraph.xs, color: fg.muted }, children: "Paulius Kairevicius" }),
1937
+ /* @__PURE__ */ jsx3(
1938
+ "a",
1939
+ {
1940
+ href: "https://github.com/kairevicius",
1941
+ target: "_blank",
1942
+ rel: "noopener noreferrer",
1943
+ style: { color: fg.muted, display: "flex", transition: "color 0.12s" },
1944
+ onMouseEnter: (e) => {
1945
+ e.currentTarget.style.color = fg.strong;
1946
+ },
1947
+ onMouseLeave: (e) => {
1948
+ e.currentTarget.style.color = fg.muted;
1949
+ },
1950
+ children: /* @__PURE__ */ jsx3("svg", { width: "13", height: "13", viewBox: "0 0 24 24", fill: "currentColor", children: /* @__PURE__ */ jsx3("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" }) })
2022
1951
  }
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
- )
1952
+ ),
1953
+ /* @__PURE__ */ jsx3(
1954
+ "a",
1955
+ {
1956
+ href: "https://x.com/kairevicius",
1957
+ target: "_blank",
1958
+ rel: "noopener noreferrer",
1959
+ style: { color: fg.muted, display: "flex", transition: "color 0.12s" },
1960
+ onMouseEnter: (e) => {
1961
+ e.currentTarget.style.color = fg.strong;
2067
1962
  },
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"
1963
+ onMouseLeave: (e) => {
1964
+ e.currentTarget.style.color = fg.muted;
1965
+ },
1966
+ children: /* @__PURE__ */ jsx3("svg", { width: "12", height: "12", viewBox: "0 0 24 24", fill: "currentColor", children: /* @__PURE__ */ jsx3("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" }) })
1967
+ }
1968
+ )
2081
1969
  ] })
2082
1970
  ] })
2083
1971
  ] })
2084
1972
  ]
2085
1973
  }
2086
1974
  ),
2087
- toast && /* @__PURE__ */ jsx2(
1975
+ toast && /* @__PURE__ */ jsx3(
2088
1976
  "div",
2089
1977
  {
2090
1978
  onClick: (e) => e.stopPropagation(),
@@ -2108,72 +1996,568 @@ function HistoryButton({
2108
1996
  }
2109
1997
  ),
2110
1998
  document.body
1999
+ )
2000
+ ] });
2001
+ }
2002
+
2003
+ // src/overlay/ui/toolbar.tsx
2004
+ import { Fragment as Fragment3, jsx as jsx4, jsxs as jsxs4 } from "react/jsx-runtime";
2005
+ var TOOLTIP_DELAY = 500;
2006
+ var TOOLTIP_COOLDOWN = 300;
2007
+ var tooltipWarm = false;
2008
+ var tooltipCooldownTimer = null;
2009
+ function useTooltipDelay(hovered) {
2010
+ const [visible, setVisible] = useState4(false);
2011
+ const delayRef = useRef2(null);
2012
+ useEffect3(() => {
2013
+ if (!hovered) {
2014
+ setVisible(false);
2015
+ if (delayRef.current) {
2016
+ clearTimeout(delayRef.current);
2017
+ delayRef.current = null;
2018
+ }
2019
+ if (tooltipCooldownTimer) clearTimeout(tooltipCooldownTimer);
2020
+ tooltipCooldownTimer = setTimeout(() => {
2021
+ tooltipWarm = false;
2022
+ }, TOOLTIP_COOLDOWN);
2023
+ return;
2024
+ }
2025
+ if (tooltipCooldownTimer) {
2026
+ clearTimeout(tooltipCooldownTimer);
2027
+ tooltipCooldownTimer = null;
2028
+ }
2029
+ if (tooltipWarm) {
2030
+ setVisible(true);
2031
+ } else {
2032
+ delayRef.current = setTimeout(() => {
2033
+ tooltipWarm = true;
2034
+ setVisible(true);
2035
+ }, TOOLTIP_DELAY);
2036
+ }
2037
+ return () => {
2038
+ if (delayRef.current) {
2039
+ clearTimeout(delayRef.current);
2040
+ delayRef.current = null;
2041
+ }
2042
+ };
2043
+ }, [hovered]);
2044
+ return visible;
2045
+ }
2046
+ var EDGE_MARGIN = 24;
2047
+ var CONTAINER_SIZE = 38;
2048
+ function getCornerStyle(corner) {
2049
+ switch (corner) {
2050
+ case "bottom-right":
2051
+ return { bottom: EDGE_MARGIN, right: EDGE_MARGIN };
2052
+ case "bottom-left":
2053
+ return { bottom: EDGE_MARGIN, left: EDGE_MARGIN };
2054
+ case "top-right":
2055
+ return { top: EDGE_MARGIN, right: EDGE_MARGIN };
2056
+ case "top-left":
2057
+ return { top: EDGE_MARGIN, left: EDGE_MARGIN };
2058
+ }
2059
+ }
2060
+ function isBottomCorner(corner) {
2061
+ return corner === "bottom-right" || corner === "bottom-left";
2062
+ }
2063
+ function isRightCorner(corner) {
2064
+ return corner === "bottom-right" || corner === "top-right";
2065
+ }
2066
+ function snapToCorner(x, y) {
2067
+ const cx = window.innerWidth / 2;
2068
+ const cy = window.innerHeight / 2;
2069
+ if (x < cx) {
2070
+ return y < cy ? "top-left" : "bottom-left";
2071
+ }
2072
+ return y < cy ? "top-right" : "bottom-right";
2073
+ }
2074
+ function getCornerPosition(corner, w, h) {
2075
+ const vw = window.innerWidth;
2076
+ const vh = window.innerHeight;
2077
+ switch (corner) {
2078
+ case "bottom-right":
2079
+ return { x: vw - EDGE_MARGIN - w, y: vh - EDGE_MARGIN - h };
2080
+ case "bottom-left":
2081
+ return { x: EDGE_MARGIN, y: vh - EDGE_MARGIN - h };
2082
+ case "top-right":
2083
+ return { x: vw - EDGE_MARGIN - w, y: EDGE_MARGIN };
2084
+ case "top-left":
2085
+ return { x: EDGE_MARGIN, y: EDGE_MARGIN };
2086
+ }
2087
+ }
2088
+ var MODES = [
2089
+ { mode: "component", label: "Component", icon: CameraIcon },
2090
+ { mode: "viewport", label: "Viewport", icon: Monitor },
2091
+ { mode: "fullpage", label: "Full Page", icon: FileText }
2092
+ ];
2093
+ function Toolbar({
2094
+ expanded,
2095
+ onToggle,
2096
+ phase,
2097
+ loading,
2098
+ selectedMode,
2099
+ onModeChange,
2100
+ onCapture,
2101
+ onCancel,
2102
+ frameSettings,
2103
+ onFrameSettingsChange
2104
+ }) {
2105
+ const [historyOpen, setHistoryOpen] = useState4(false);
2106
+ const [modesExpanded, setModesExpanded] = useState4(false);
2107
+ const [buttonsVisible, setButtonsVisible] = useState4(expanded);
2108
+ const [animIn, setAnimIn] = useState4(expanded);
2109
+ const [animDone, setAnimDone] = useState4(expanded);
2110
+ useEffect3(() => {
2111
+ if (expanded) {
2112
+ setAnimDone(false);
2113
+ setButtonsVisible(true);
2114
+ setAnimIn(false);
2115
+ requestAnimationFrame(() => {
2116
+ requestAnimationFrame(() => setAnimIn(true));
2117
+ });
2118
+ const timer = setTimeout(() => setAnimDone(true), 250);
2119
+ return () => clearTimeout(timer);
2120
+ } else if (buttonsVisible) {
2121
+ setHistoryOpen(false);
2122
+ setAnimDone(false);
2123
+ setAnimIn(false);
2124
+ const timer = setTimeout(() => setButtonsVisible(false), 150);
2125
+ return () => clearTimeout(timer);
2126
+ }
2127
+ }, [expanded]);
2128
+ const [corner, setCorner] = useState4(() => {
2129
+ try {
2130
+ const stored = localStorage.getItem("ab-toolbar-corner");
2131
+ if (stored && ["bottom-right", "bottom-left", "top-right", "top-left"].includes(stored)) {
2132
+ return stored;
2133
+ }
2134
+ } catch {
2135
+ }
2136
+ return "bottom-right";
2137
+ });
2138
+ const [dragging, setDragging] = useState4(false);
2139
+ const [dragPos, setDragPos] = useState4(null);
2140
+ const [snapAnim, setSnapAnim] = useState4(null);
2141
+ const dragState = useRef2(null);
2142
+ const toolbarRef = useRef2(null);
2143
+ const [cameraHovered, setCameraHovered] = useState4(false);
2144
+ const cameraTooltipVisible = useTooltipDelay(cameraHovered && !!expanded);
2145
+ useEffect3(() => {
2146
+ if (!expanded) return;
2147
+ const onKey = (e) => {
2148
+ if (e.target?.tagName === "INPUT") {
2149
+ if (e.key === "Escape") {
2150
+ e.target.blur();
2151
+ }
2152
+ return;
2153
+ }
2154
+ if (e.key === "Escape") {
2155
+ if (historyOpen) {
2156
+ setHistoryOpen(false);
2157
+ return;
2158
+ }
2159
+ onCancel();
2160
+ } else if (e.key === "Enter") {
2161
+ onCapture(selectedMode);
2162
+ }
2163
+ };
2164
+ document.addEventListener("keydown", onKey);
2165
+ return () => document.removeEventListener("keydown", onKey);
2166
+ }, [expanded, onCancel, onCapture, selectedMode, historyOpen]);
2167
+ const handleMouseDown = useCallback3(
2168
+ (e) => {
2169
+ e.preventDefault();
2170
+ const el = toolbarRef.current;
2171
+ if (!el) return;
2172
+ const rect = el.getBoundingClientRect();
2173
+ setDragging(true);
2174
+ setDragPos({ x: rect.left, y: rect.top });
2175
+ dragState.current = {
2176
+ dragging: true,
2177
+ startX: e.clientX,
2178
+ startY: e.clientY,
2179
+ origX: rect.left,
2180
+ origY: rect.top,
2181
+ distance: 0
2182
+ };
2183
+ },
2184
+ []
2185
+ );
2186
+ useEffect3(() => {
2187
+ const handleMouseMove = (e) => {
2188
+ const ds = dragState.current;
2189
+ if (!ds || !ds.dragging) return;
2190
+ const dx = e.clientX - ds.startX;
2191
+ const dy = e.clientY - ds.startY;
2192
+ ds.distance = Math.sqrt(dx * dx + dy * dy);
2193
+ setDragPos({
2194
+ x: ds.origX + dx,
2195
+ y: ds.origY + dy
2196
+ });
2197
+ };
2198
+ const handleMouseUp = (e) => {
2199
+ const ds = dragState.current;
2200
+ if (!ds) return;
2201
+ if (ds.distance < 5) {
2202
+ onToggle();
2203
+ setDragging(false);
2204
+ setDragPos(null);
2205
+ dragState.current = null;
2206
+ } else {
2207
+ const el = toolbarRef.current;
2208
+ const w = el?.offsetWidth ?? CONTAINER_SIZE;
2209
+ const h = el?.offsetHeight ?? CONTAINER_SIZE;
2210
+ const currentX = ds.origX + (e.clientX - ds.startX);
2211
+ const currentY = ds.origY + (e.clientY - ds.startY);
2212
+ const centerX = currentX + w / 2;
2213
+ const centerY = currentY + h / 2;
2214
+ const newCorner = snapToCorner(centerX, centerY);
2215
+ setCorner(newCorner);
2216
+ try {
2217
+ localStorage.setItem("ab-toolbar-corner", newCorner);
2218
+ } catch {
2219
+ }
2220
+ const targetPos = getCornerPosition(newCorner, w, h);
2221
+ setDragging(false);
2222
+ setDragPos(null);
2223
+ setSnapAnim({ x: currentX, y: currentY, animate: false });
2224
+ dragState.current = null;
2225
+ requestAnimationFrame(() => {
2226
+ requestAnimationFrame(() => {
2227
+ setSnapAnim({ ...targetPos, animate: true });
2228
+ setTimeout(() => setSnapAnim(null), 300);
2229
+ });
2230
+ });
2231
+ }
2232
+ };
2233
+ window.addEventListener("mousemove", handleMouseMove);
2234
+ window.addEventListener("mouseup", handleMouseUp);
2235
+ return () => {
2236
+ window.removeEventListener("mousemove", handleMouseMove);
2237
+ window.removeEventListener("mouseup", handleMouseUp);
2238
+ };
2239
+ }, [onToggle]);
2240
+ const panelSide = isRightCorner(corner) ? "left" : "right";
2241
+ const tooltipSide = panelSide;
2242
+ const bottom = isBottomCorner(corner);
2243
+ const positionStyle = dragging && dragPos ? { left: dragPos.x, top: dragPos.y } : snapAnim ? {
2244
+ left: snapAnim.x,
2245
+ top: snapAnim.y,
2246
+ ...snapAnim.animate && {
2247
+ transition: "left 0.3s cubic-bezier(0.23, 1, 0.32, 1), top 0.3s cubic-bezier(0.23, 1, 0.32, 1)"
2248
+ }
2249
+ } : getCornerStyle(corner);
2250
+ const cameraTooltipLabel = expanded ? "Close" : void 0;
2251
+ 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;
2252
+ const cameraButton = /* @__PURE__ */ jsxs4("div", { style: { position: "relative" }, children: [
2253
+ cameraTooltipLabel && cameraTooltipVisible && !dragging && /* @__PURE__ */ jsx4(
2254
+ "div",
2255
+ {
2256
+ style: {
2257
+ position: "absolute",
2258
+ ...cameraTooltipStyle,
2259
+ background: bg.base,
2260
+ border: `1px solid ${stroke.default}`,
2261
+ borderRadius: 6,
2262
+ padding: "0 8px",
2263
+ height: 24,
2264
+ display: "flex",
2265
+ alignItems: "center",
2266
+ color: fg.strong,
2267
+ ...text.label.xs,
2268
+ whiteSpace: "nowrap",
2269
+ boxShadow: shadow.tooltip,
2270
+ pointerEvents: "none"
2271
+ },
2272
+ children: cameraTooltipLabel
2273
+ }
2111
2274
  ),
2112
- lightboxSrc && createPortal(
2113
- /* @__PURE__ */ jsxs2(
2275
+ /* @__PURE__ */ jsxs4(
2276
+ "div",
2277
+ {
2278
+ onMouseDown: handleMouseDown,
2279
+ onMouseEnter: () => setCameraHovered(true),
2280
+ onMouseLeave: () => setCameraHovered(false),
2281
+ style: {
2282
+ width: 32,
2283
+ height: 32,
2284
+ padding: 0,
2285
+ borderRadius: "50%",
2286
+ display: "flex",
2287
+ alignItems: "center",
2288
+ justifyContent: "center",
2289
+ cursor: dragging ? "grabbing" : "pointer",
2290
+ background: expanded && cameraHovered ? state.hoverStrong : "transparent",
2291
+ transition: "background 0.12s ease"
2292
+ },
2293
+ children: [
2294
+ /* @__PURE__ */ jsx4(
2295
+ "style",
2296
+ {
2297
+ dangerouslySetInnerHTML: {
2298
+ __html: `
2299
+ @keyframes ab-spin {
2300
+ 0% { transform: rotate(0deg); }
2301
+ 100% { transform: rotate(360deg); }
2302
+ }
2303
+ @keyframes ab-panel-in {
2304
+ from { opacity: 0; transform: translateY(4px); }
2305
+ to { opacity: 1; transform: translateY(0); }
2306
+ }
2307
+ `
2308
+ }
2309
+ }
2310
+ ),
2311
+ loading ? /* @__PURE__ */ jsx4(
2312
+ LoaderCircle,
2313
+ {
2314
+ size: 16,
2315
+ strokeWidth: 2,
2316
+ style: { animation: "ab-spin 0.8s linear infinite", color: "white" }
2317
+ }
2318
+ ) : phase === "ready" ? /* @__PURE__ */ jsx4(CheckmarkIcon, { size: 20, color: fg.default }) : expanded ? /* @__PURE__ */ jsx4(
2319
+ CloseIcon,
2320
+ {
2321
+ size: 20,
2322
+ color: cameraHovered ? fg.strong : fg.default
2323
+ }
2324
+ ) : /* @__PURE__ */ jsx4(
2325
+ CameraIcon,
2326
+ {
2327
+ size: 20,
2328
+ color: cameraHovered ? fg.strong : fg.default
2329
+ }
2330
+ )
2331
+ ]
2332
+ }
2333
+ )
2334
+ ] });
2335
+ const toolbarButtons = buttonsVisible ? /* @__PURE__ */ jsx4(
2336
+ "div",
2337
+ {
2338
+ style: {
2339
+ overflow: animDone ? "visible" : "hidden",
2340
+ maxHeight: animIn ? 195 : 0,
2341
+ transition: animIn ? "max-height 250ms cubic-bezier(0.23, 1, 0.32, 1)" : "max-height 150ms cubic-bezier(0.23, 1, 0.32, 1)"
2342
+ },
2343
+ children: /* @__PURE__ */ jsxs4(
2114
2344
  "div",
2115
2345
  {
2116
- "data-afterbefore": "true",
2117
- onClick: () => setLightboxSrc(null),
2118
- onKeyDown: (e) => {
2119
- if (e.key === "Escape") setLightboxSrc(null);
2120
- },
2121
2346
  style: {
2122
- position: "fixed",
2123
- inset: 0,
2124
- zIndex: 2147483647,
2125
- background: "rgba(0, 0, 0, 0.85)",
2126
2347
  display: "flex",
2348
+ flexDirection: "column",
2127
2349
  alignItems: "center",
2128
- justifyContent: "center",
2129
- cursor: "zoom-out"
2350
+ opacity: animIn ? 1 : 0,
2351
+ transform: animIn ? "translateY(0)" : `translateY(${bottom ? 6 : -6}px)`,
2352
+ 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)",
2353
+ willChange: "transform, opacity"
2130
2354
  },
2131
2355
  children: [
2132
- /* @__PURE__ */ jsx2(
2133
- "img",
2356
+ /* @__PURE__ */ jsx4("div", { style: { paddingBottom: 2 }, children: /* @__PURE__ */ jsx4(
2357
+ IconButton,
2358
+ {
2359
+ active: selectedMode === "component" && !historyOpen,
2360
+ tooltipSide,
2361
+ tooltip: "Component",
2362
+ onClick: () => {
2363
+ setHistoryOpen(false);
2364
+ onModeChange("component");
2365
+ },
2366
+ children: /* @__PURE__ */ jsx4(CameraIcon, { size: 20 })
2367
+ }
2368
+ ) }),
2369
+ MODES.filter((m) => m.mode !== "component").map(({ mode, label, icon: ModeIcon }) => /* @__PURE__ */ jsx4(
2370
+ "div",
2134
2371
  {
2135
- src: lightboxSrc,
2136
- alt: "",
2137
- onClick: (e) => e.stopPropagation(),
2138
2372
  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
- }
2373
+ maxHeight: modesExpanded ? 34 : 0,
2374
+ opacity: modesExpanded ? 1 : 0,
2375
+ transition: "max-height 200ms cubic-bezier(0.23, 1, 0.32, 1), opacity 150ms cubic-bezier(0.23, 1, 0.32, 1)"
2376
+ },
2377
+ children: /* @__PURE__ */ jsx4(
2378
+ IconButton,
2379
+ {
2380
+ active: selectedMode === mode && !historyOpen,
2381
+ tooltipSide,
2382
+ tooltip: label,
2383
+ onClick: () => {
2384
+ setHistoryOpen(false);
2385
+ onModeChange(mode);
2386
+ onCapture(mode);
2387
+ },
2388
+ children: /* @__PURE__ */ jsx4(ModeIcon, { size: 16, strokeWidth: 1.7 })
2389
+ }
2390
+ )
2391
+ },
2392
+ mode
2393
+ )),
2394
+ /* @__PURE__ */ jsx4(
2395
+ Separator,
2396
+ {
2397
+ vertical: false,
2398
+ onClick: () => setModesExpanded((p) => !p)
2145
2399
  }
2146
2400
  ),
2147
- /* @__PURE__ */ jsx2(
2148
- "button",
2401
+ /* @__PURE__ */ jsx4(
2402
+ ScreenshotsPanel,
2149
2403
  {
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
2404
+ open: historyOpen,
2405
+ onClick: () => {
2406
+ setHistoryOpen((prev) => !prev);
2166
2407
  },
2167
- children: /* @__PURE__ */ jsx2(X2, { size: 18, strokeWidth: 2 })
2408
+ selectedMode,
2409
+ frameSettings,
2410
+ onFrameSettingsChange,
2411
+ tooltipSide
2168
2412
  }
2169
2413
  )
2170
2414
  ]
2171
2415
  }
2172
- ),
2173
- document.body
2416
+ )
2417
+ }
2418
+ ) : null;
2419
+ return /* @__PURE__ */ jsx4(
2420
+ "div",
2421
+ {
2422
+ ref: toolbarRef,
2423
+ "data-afterbefore": "true",
2424
+ style: {
2425
+ position: "fixed",
2426
+ ...positionStyle,
2427
+ zIndex: 2147483647,
2428
+ display: "flex",
2429
+ flexDirection: "column",
2430
+ alignItems: "center",
2431
+ background: bg.base,
2432
+ borderRadius: 999,
2433
+ padding: 6,
2434
+ boxShadow: shadow.toolbar,
2435
+ ...fontBase,
2436
+ userSelect: "none"
2437
+ },
2438
+ children: bottom ? /* @__PURE__ */ jsxs4(Fragment3, { children: [
2439
+ toolbarButtons,
2440
+ cameraButton
2441
+ ] }) : /* @__PURE__ */ jsxs4(Fragment3, { children: [
2442
+ cameraButton,
2443
+ toolbarButtons
2444
+ ] })
2445
+ }
2446
+ );
2447
+ }
2448
+ function IconButton({
2449
+ children,
2450
+ active,
2451
+ tooltip,
2452
+ tooltipSide = "left",
2453
+ onClick
2454
+ }) {
2455
+ const [hovered, setHovered] = useState4(false);
2456
+ const tooltipVisible = useTooltipDelay(hovered && !!tooltip);
2457
+ const tooltipStyle = tooltipSide === "left" ? {
2458
+ right: "calc(100% + 10px)",
2459
+ top: "50%",
2460
+ transform: "translateY(-50%)"
2461
+ } : {
2462
+ left: "calc(100% + 10px)",
2463
+ top: "50%",
2464
+ transform: "translateY(-50%)"
2465
+ };
2466
+ return /* @__PURE__ */ jsxs4("div", { style: { position: "relative" }, children: [
2467
+ tooltip && tooltipVisible && /* @__PURE__ */ jsx4(
2468
+ "div",
2469
+ {
2470
+ style: {
2471
+ position: "absolute",
2472
+ ...tooltipStyle,
2473
+ background: bg.base,
2474
+ border: `1px solid ${stroke.default}`,
2475
+ borderRadius: 6,
2476
+ padding: "0 8px",
2477
+ height: 24,
2478
+ display: "flex",
2479
+ alignItems: "center",
2480
+ color: fg.strong,
2481
+ ...text.label.xs,
2482
+ whiteSpace: "nowrap",
2483
+ boxShadow: shadow.tooltip,
2484
+ pointerEvents: "none"
2485
+ },
2486
+ children: tooltip
2487
+ }
2488
+ ),
2489
+ /* @__PURE__ */ jsx4(
2490
+ "button",
2491
+ {
2492
+ onClick,
2493
+ onMouseEnter: () => setHovered(true),
2494
+ onMouseLeave: () => setHovered(false),
2495
+ style: {
2496
+ width: 32,
2497
+ height: 32,
2498
+ borderRadius: "50%",
2499
+ border: "none",
2500
+ background: active || hovered ? state.hoverStrong : "transparent",
2501
+ display: "flex",
2502
+ alignItems: "center",
2503
+ justifyContent: "center",
2504
+ cursor: "pointer",
2505
+ padding: 0,
2506
+ color: active || hovered ? fg.strong : fg.default,
2507
+ transition: "background 0.12s ease, color 0.12s ease"
2508
+ },
2509
+ children
2510
+ }
2174
2511
  )
2175
2512
  ] });
2176
2513
  }
2514
+ function DropItem2({
2515
+ children,
2516
+ onClick,
2517
+ active,
2518
+ accent: accent2
2519
+ }) {
2520
+ return /* @__PURE__ */ jsx4(
2521
+ "button",
2522
+ {
2523
+ onClick,
2524
+ style: {
2525
+ display: "block",
2526
+ width: "100%",
2527
+ padding: "7px 12px",
2528
+ background: active ? state.active : "transparent",
2529
+ border: "none",
2530
+ color: accent2 ? accent.highlight : fg.strong,
2531
+ textAlign: "left",
2532
+ cursor: "pointer",
2533
+ ...text.paragraph.sm,
2534
+ fontFamily: "inherit"
2535
+ },
2536
+ children
2537
+ }
2538
+ );
2539
+ }
2540
+ function Separator({
2541
+ vertical = true,
2542
+ onClick
2543
+ }) {
2544
+ return /* @__PURE__ */ jsx4(
2545
+ "div",
2546
+ {
2547
+ onClick,
2548
+ style: {
2549
+ position: "relative",
2550
+ width: vertical ? 1 : 24,
2551
+ height: vertical ? 18 : 1,
2552
+ background: stroke.strong,
2553
+ flexShrink: 0,
2554
+ margin: vertical ? "0 6px" : "6px 0",
2555
+ cursor: onClick ? "pointer" : void 0
2556
+ },
2557
+ children: onClick && /* @__PURE__ */ jsx4("div", { style: { position: "absolute", inset: vertical ? "0 -8px" : "-8px 0" } })
2558
+ }
2559
+ );
2560
+ }
2177
2561
  function FilterDropdown({
2178
2562
  label,
2179
2563
  value,
@@ -2182,20 +2566,20 @@ function FilterDropdown({
2182
2566
  onToggle,
2183
2567
  onSelect
2184
2568
  }) {
2185
- return /* @__PURE__ */ jsxs2("div", { children: [
2186
- /* @__PURE__ */ jsx2(
2569
+ return /* @__PURE__ */ jsxs4("div", { children: [
2570
+ /* @__PURE__ */ jsx4(
2187
2571
  "div",
2188
2572
  {
2189
2573
  style: {
2190
2574
  ...text.subheading.xxs,
2191
- color: fg.muted,
2575
+ color: fg.sub,
2192
2576
  marginBottom: 3
2193
2577
  },
2194
2578
  children: label
2195
2579
  }
2196
2580
  ),
2197
- /* @__PURE__ */ jsxs2("div", { style: { position: "relative" }, children: [
2198
- /* @__PURE__ */ jsxs2(
2581
+ /* @__PURE__ */ jsxs4("div", { style: { position: "relative" }, children: [
2582
+ /* @__PURE__ */ jsxs4(
2199
2583
  "button",
2200
2584
  {
2201
2585
  onClick: onToggle,
@@ -2216,7 +2600,7 @@ function FilterDropdown({
2216
2600
  fontFamily: "inherit"
2217
2601
  },
2218
2602
  children: [
2219
- /* @__PURE__ */ jsx2(
2603
+ /* @__PURE__ */ jsx4(
2220
2604
  "span",
2221
2605
  {
2222
2606
  style: {
@@ -2227,11 +2611,11 @@ function FilterDropdown({
2227
2611
  children: value || "\u2014"
2228
2612
  }
2229
2613
  ),
2230
- /* @__PURE__ */ jsx2(ChevronDown2, { size: 12, strokeWidth: 2 })
2614
+ /* @__PURE__ */ jsx4(ChevronDown2, { size: 12, strokeWidth: 2 })
2231
2615
  ]
2232
2616
  }
2233
2617
  ),
2234
- isOpen && options.length > 0 && /* @__PURE__ */ jsx2(
2618
+ isOpen && options.length > 0 && /* @__PURE__ */ jsx4(
2235
2619
  "div",
2236
2620
  {
2237
2621
  style: {
@@ -2248,7 +2632,7 @@ function FilterDropdown({
2248
2632
  boxShadow: shadow.dropdown,
2249
2633
  zIndex: 1
2250
2634
  },
2251
- children: options.map((opt) => /* @__PURE__ */ jsx2(
2635
+ children: options.map((opt) => /* @__PURE__ */ jsx4(
2252
2636
  DropItem2,
2253
2637
  {
2254
2638
  active: opt === value,
@@ -2262,61 +2646,15 @@ function FilterDropdown({
2262
2646
  ] })
2263
2647
  ] });
2264
2648
  }
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
2649
 
2312
2650
  // 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";
2651
+ import { useEffect as useEffect4, useRef as useRef3, useCallback as useCallback4, useState as useState5 } from "react";
2652
+ import { jsx as jsx5 } from "react/jsx-runtime";
2315
2653
  function Inspector({ onSelect, onCancel }) {
2316
- const [highlight, setHighlight] = useState4(null);
2654
+ const [highlight, setHighlight] = useState5(null);
2317
2655
  const hoveredEl = useRef3(null);
2318
2656
  const styleEl = useRef3(null);
2319
- useEffect3(() => {
2657
+ useEffect4(() => {
2320
2658
  const style2 = document.createElement("style");
2321
2659
  style2.setAttribute("data-afterbefore", "true");
2322
2660
  style2.textContent = [
@@ -2331,7 +2669,7 @@ function Inspector({ onSelect, onCancel }) {
2331
2669
  style2.remove();
2332
2670
  };
2333
2671
  }, []);
2334
- const isOverlayElement = useCallback3((el) => {
2672
+ const isOverlayElement = useCallback4((el) => {
2335
2673
  let node = el;
2336
2674
  while (node) {
2337
2675
  if (node instanceof HTMLElement && node.dataset.afterbefore) return true;
@@ -2339,7 +2677,7 @@ function Inspector({ onSelect, onCancel }) {
2339
2677
  }
2340
2678
  return false;
2341
2679
  }, []);
2342
- const handleMouseMove = useCallback3(
2680
+ const handleMouseMove = useCallback4(
2343
2681
  (e) => {
2344
2682
  const el = document.elementFromPoint(e.clientX, e.clientY);
2345
2683
  if (!el || !(el instanceof HTMLElement) || isOverlayElement(el)) {
@@ -2358,7 +2696,7 @@ function Inspector({ onSelect, onCancel }) {
2358
2696
  },
2359
2697
  [isOverlayElement]
2360
2698
  );
2361
- const handleClick = useCallback3(
2699
+ const handleClick = useCallback4(
2362
2700
  (e) => {
2363
2701
  if (isOverlayElement(e.target)) return;
2364
2702
  e.preventDefault();
@@ -2370,7 +2708,7 @@ function Inspector({ onSelect, onCancel }) {
2370
2708
  },
2371
2709
  [onSelect, isOverlayElement]
2372
2710
  );
2373
- const handleKeyDown = useCallback3(
2711
+ const handleKeyDown = useCallback4(
2374
2712
  (e) => {
2375
2713
  if (e.key === "Escape") {
2376
2714
  onCancel();
@@ -2378,7 +2716,7 @@ function Inspector({ onSelect, onCancel }) {
2378
2716
  },
2379
2717
  [onCancel]
2380
2718
  );
2381
- useEffect3(() => {
2719
+ useEffect4(() => {
2382
2720
  document.addEventListener("mousemove", handleMouseMove, true);
2383
2721
  document.addEventListener("click", handleClick, true);
2384
2722
  document.addEventListener("keydown", handleKeyDown);
@@ -2388,7 +2726,7 @@ function Inspector({ onSelect, onCancel }) {
2388
2726
  document.removeEventListener("keydown", handleKeyDown);
2389
2727
  };
2390
2728
  }, [handleMouseMove, handleClick, handleKeyDown]);
2391
- return /* @__PURE__ */ jsx3("div", { "data-afterbefore": "true", style: { position: "fixed", inset: 0, zIndex: 2147483646, pointerEvents: "none" }, children: highlight && /* @__PURE__ */ jsx3(
2729
+ return /* @__PURE__ */ jsx5("div", { "data-afterbefore": "true", style: { position: "fixed", inset: 0, zIndex: 2147483646, pointerEvents: "none" }, children: highlight && /* @__PURE__ */ jsx5(
2392
2730
  "div",
2393
2731
  {
2394
2732
  style: {
@@ -2408,7 +2746,7 @@ function Inspector({ onSelect, onCancel }) {
2408
2746
  }
2409
2747
 
2410
2748
  // src/overlay/index.tsx
2411
- import { jsx as jsx4, jsxs as jsxs3 } from "react/jsx-runtime";
2749
+ import { jsx as jsx6, jsxs as jsxs5 } from "react/jsx-runtime";
2412
2750
  async function saveCapture(mode, dataUrl) {
2413
2751
  try {
2414
2752
  const res = await fetch("/__afterbefore/save", {
@@ -2426,15 +2764,15 @@ async function saveCapture(mode, dataUrl) {
2426
2764
  }
2427
2765
  function AfterBefore() {
2428
2766
  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(() => {
2767
+ const [toolbarActive, setToolbarActive] = useState6(false);
2768
+ const [inspectorActive, setInspectorActive] = useState6(false);
2769
+ const [loading, setLoading] = useState6(false);
2770
+ const [selectedMode, setSelectedMode] = useState6("component");
2771
+ const [frameSettings, setFrameSettings] = useState6(DEFAULT_FRAME_SETTINGS);
2772
+ useEffect5(() => {
2435
2773
  injectInterFont();
2436
2774
  }, []);
2437
- useEffect4(() => {
2775
+ useEffect5(() => {
2438
2776
  try {
2439
2777
  const stored = localStorage.getItem("ab-frame-settings");
2440
2778
  if (stored) {
@@ -2449,7 +2787,7 @@ function AfterBefore() {
2449
2787
  setFrameSettings(DEFAULT_FRAME_SETTINGS);
2450
2788
  }
2451
2789
  }, []);
2452
- useEffect4(() => {
2790
+ useEffect5(() => {
2453
2791
  if (state2.phase === "ready") {
2454
2792
  const timer = setTimeout(() => {
2455
2793
  reset();
@@ -2457,7 +2795,7 @@ function AfterBefore() {
2457
2795
  return () => clearTimeout(timer);
2458
2796
  }
2459
2797
  }, [state2.phase, reset]);
2460
- const handleToggle = useCallback4(() => {
2798
+ const handleToggle = useCallback5(() => {
2461
2799
  if (loading) return;
2462
2800
  if (state2.phase === "ready") {
2463
2801
  reset();
@@ -2475,7 +2813,7 @@ function AfterBefore() {
2475
2813
  }
2476
2814
  }
2477
2815
  }, [state2.phase, loading, toolbarActive, inspectorActive, selectedMode, reset]);
2478
- const performCapture = useCallback4(
2816
+ const performCapture = useCallback5(
2479
2817
  async (mode, element) => {
2480
2818
  setLoading(true);
2481
2819
  try {
@@ -2494,7 +2832,7 @@ function AfterBefore() {
2494
2832
  },
2495
2833
  [captureComplete, frameSettings]
2496
2834
  );
2497
- const handleToolbarCapture = useCallback4(
2835
+ const handleToolbarCapture = useCallback5(
2498
2836
  (mode) => {
2499
2837
  if (mode === "viewport") {
2500
2838
  setToolbarActive(false);
@@ -2508,11 +2846,11 @@ function AfterBefore() {
2508
2846
  },
2509
2847
  [performCapture]
2510
2848
  );
2511
- const handleToolbarCancel = useCallback4(() => {
2849
+ const handleToolbarCancel = useCallback5(() => {
2512
2850
  setToolbarActive(false);
2513
2851
  setInspectorActive(false);
2514
2852
  }, []);
2515
- const handleComponentSelect = useCallback4(
2853
+ const handleComponentSelect = useCallback5(
2516
2854
  (element) => {
2517
2855
  setInspectorActive(false);
2518
2856
  setToolbarActive(false);
@@ -2520,23 +2858,23 @@ function AfterBefore() {
2520
2858
  },
2521
2859
  [performCapture]
2522
2860
  );
2523
- const handleComponentCancel = useCallback4(() => {
2861
+ const handleComponentCancel = useCallback5(() => {
2524
2862
  setInspectorActive(false);
2525
2863
  setToolbarActive(true);
2526
2864
  }, []);
2527
- const handleFrameSettingsChange = useCallback4((next) => {
2865
+ const handleFrameSettingsChange = useCallback5((next) => {
2528
2866
  setFrameSettings(next);
2529
2867
  try {
2530
2868
  localStorage.setItem("ab-frame-settings", JSON.stringify(next));
2531
2869
  } catch {
2532
2870
  }
2533
2871
  }, []);
2534
- const handleModeChange = useCallback4((mode) => {
2872
+ const handleModeChange = useCallback5((mode) => {
2535
2873
  setSelectedMode(mode);
2536
2874
  setInspectorActive(mode === "component");
2537
2875
  }, []);
2538
- return /* @__PURE__ */ jsxs3("div", { "data-afterbefore": "true", children: [
2539
- /* @__PURE__ */ jsx4(
2876
+ return /* @__PURE__ */ jsxs5("div", { "data-afterbefore": "true", children: [
2877
+ /* @__PURE__ */ jsx6(
2540
2878
  Toolbar,
2541
2879
  {
2542
2880
  expanded: toolbarActive,
@@ -2551,7 +2889,7 @@ function AfterBefore() {
2551
2889
  onFrameSettingsChange: handleFrameSettingsChange
2552
2890
  }
2553
2891
  ),
2554
- inspectorActive && /* @__PURE__ */ jsx4(Inspector, { onSelect: handleComponentSelect, onCancel: handleComponentCancel })
2892
+ inspectorActive && /* @__PURE__ */ jsx6(Inspector, { onSelect: handleComponentSelect, onCancel: handleComponentCancel })
2555
2893
  ] });
2556
2894
  }
2557
2895
  export {