afterbefore 0.2.26 → 0.2.28

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.
@@ -46,12 +46,13 @@ var DEFAULT_FRAME_SETTINGS = {
46
46
  bgColor: "#F5F5F5",
47
47
  bgGradient: GRADIENT_PRESETS[0],
48
48
  bgImage: null,
49
- padding: 40,
49
+ padding: 192,
50
50
  browserChrome: false,
51
51
  browserTheme: "dark",
52
52
  browserUrl: "localhost",
53
53
  browserShadow: 50,
54
- browserRadius: 8
54
+ browserRadius: 8,
55
+ afterbefore: false
55
56
  };
56
57
  var FRAME_SIZE_PRESETS = [
57
58
  { label: "1920 x 1080", hint: "Desktop / HD", w: 1920, h: 1080 },
@@ -70,6 +71,10 @@ var DEV_UI_SELECTORS = [
70
71
  "[data-next-badge]",
71
72
  "[data-next-mark]"
72
73
  ];
74
+ var MIN_DPR = 2;
75
+ function getDpr() {
76
+ return Math.max(window.devicePixelRatio || 1, MIN_DPR);
77
+ }
73
78
  var SNAPDOM_BASE = {
74
79
  exclude: DEV_UI_SELECTORS,
75
80
  excludeMode: "remove"
@@ -94,17 +99,32 @@ async function capture(options) {
94
99
  }
95
100
  throw new Error(`Invalid capture mode: ${mode}`);
96
101
  }
102
+ function getContentBottom() {
103
+ let maxBottom = 0;
104
+ for (const child of document.body.children) {
105
+ if (child.dataset?.afterbefore != null) continue;
106
+ if (child.id === "afterbefore-root") continue;
107
+ const rect = child.getBoundingClientRect();
108
+ if (rect.height > 0) {
109
+ maxBottom = Math.max(maxBottom, rect.bottom);
110
+ }
111
+ }
112
+ return maxBottom + window.scrollY;
113
+ }
97
114
  async function captureRawViewport() {
98
115
  const dpr = window.devicePixelRatio || 1;
99
116
  const vw = window.innerWidth;
100
- const vh = window.innerHeight;
101
117
  const scrollY = window.scrollY;
118
+ const contentBottom = getContentBottom();
119
+ const vh = Math.min(window.innerHeight, Math.ceil(contentBottom - scrollY));
102
120
  const fullDataUrl = await captureFullPage();
103
121
  const fullImg = await loadImage(fullDataUrl);
104
122
  const canvas = document.createElement("canvas");
105
123
  canvas.width = vw * dpr;
106
124
  canvas.height = vh * dpr;
107
125
  const ctx = canvas.getContext("2d");
126
+ ctx.fillStyle = "#ffffff";
127
+ ctx.fillRect(0, 0, canvas.width, canvas.height);
108
128
  ctx.drawImage(
109
129
  fullImg,
110
130
  0,
@@ -119,11 +139,14 @@ async function captureRawViewport() {
119
139
  return canvas.toDataURL("image/png");
120
140
  }
121
141
  function drawBrowserChrome(img, theme, dpr, url = "localhost") {
122
- const TITLE_BAR_H = 40;
123
- const URL_BAR_H = 28;
124
- const URL_BAR_MARGIN_TOP = 6;
125
- const URL_BAR_MARGIN_BOTTOM = 6;
126
- const CHROME_H = TITLE_BAR_H + URL_BAR_H + URL_BAR_MARGIN_TOP + URL_BAR_MARGIN_BOTTOM;
142
+ const CHROME_H = 28;
143
+ const BAR_PAD_X = 10;
144
+ const DOT_R = 3.5;
145
+ const DOT_START_X = 12;
146
+ const DOT_GAP = 12;
147
+ const DOTS_END = DOT_START_X + DOT_GAP * 2 + DOT_R + 6;
148
+ const URL_BAR_H = 16;
149
+ const URL_BAR_R = 4;
127
150
  const imgW = img.width;
128
151
  const imgH = img.height;
129
152
  const canvas = document.createElement("canvas");
@@ -135,37 +158,32 @@ function drawBrowserChrome(img, theme, dpr, url = "localhost") {
135
158
  ctx.fillRect(0, 0, canvas.width, CHROME_H * dpr);
136
159
  ctx.fillStyle = colors.border;
137
160
  ctx.fillRect(0, (CHROME_H - 1) * dpr, canvas.width, dpr);
138
- const dotY = TITLE_BAR_H / 2 * dpr;
139
- const dotR = 6 * dpr;
140
- const dotStartX = 18 * dpr;
141
- const dotGap = 20 * dpr;
161
+ const dotY = CHROME_H / 2 * dpr;
142
162
  const dotColors = ["#FF5F57", "#FEBC2E", "#28C840"];
143
163
  for (let i = 0; i < 3; i++) {
144
164
  ctx.beginPath();
145
- ctx.arc(dotStartX + i * dotGap, dotY, dotR, 0, Math.PI * 2);
165
+ ctx.arc((DOT_START_X + i * DOT_GAP) * dpr, dotY, DOT_R * dpr, 0, Math.PI * 2);
146
166
  ctx.fillStyle = dotColors[i];
147
167
  ctx.fill();
148
168
  }
149
- const urlBarY = (TITLE_BAR_H + URL_BAR_MARGIN_TOP) * dpr;
150
- const urlBarPadX = 80 * dpr;
151
- const urlBarX = urlBarPadX;
152
- const urlBarW = canvas.width - urlBarPadX * 2;
169
+ const urlBarX = DOTS_END * dpr;
170
+ const urlBarY = (CHROME_H - URL_BAR_H) / 2 * dpr;
171
+ const urlBarW = canvas.width - urlBarX - BAR_PAD_X * dpr;
153
172
  const urlBarH = URL_BAR_H * dpr;
154
- const urlBarR = 6 * dpr;
155
173
  ctx.fillStyle = colors.urlBar;
156
- roundRect(ctx, urlBarX, urlBarY, urlBarW, urlBarH, urlBarR);
174
+ roundRect(ctx, urlBarX, urlBarY, urlBarW, urlBarH, URL_BAR_R * dpr);
157
175
  ctx.fill();
158
176
  if (theme === "light") {
159
177
  ctx.strokeStyle = colors.border;
160
178
  ctx.lineWidth = dpr;
161
- roundRect(ctx, urlBarX, urlBarY, urlBarW, urlBarH, urlBarR);
179
+ roundRect(ctx, urlBarX, urlBarY, urlBarW, urlBarH, URL_BAR_R * dpr);
162
180
  ctx.stroke();
163
181
  }
164
182
  ctx.fillStyle = colors.text;
165
- ctx.font = `${11 * dpr}px -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif`;
166
- ctx.textAlign = "center";
183
+ ctx.font = `${9 * dpr}px -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif`;
184
+ ctx.textAlign = "left";
167
185
  ctx.textBaseline = "middle";
168
- ctx.fillText(url, canvas.width / 2, urlBarY + urlBarH / 2);
186
+ ctx.fillText(url, urlBarX + 8 * dpr, urlBarY + urlBarH / 2);
169
187
  ctx.drawImage(img, 0, CHROME_H * dpr);
170
188
  return canvas.toDataURL("image/png");
171
189
  }
@@ -213,7 +231,7 @@ async function renderFrame(imageDataUrl, frameSettings) {
213
231
  return imageDataUrl;
214
232
  }
215
233
  const img = await loadImage(imageDataUrl);
216
- const dpr = window.devicePixelRatio || 1;
234
+ const dpr = getDpr();
217
235
  const FRAME_W = frameSettings.size.w;
218
236
  const FRAME_H = frameSettings.size.h;
219
237
  const pad = frameSettings.padding ?? 40;
@@ -233,34 +251,93 @@ async function renderFrame(imageDataUrl, frameSettings) {
233
251
  canvas.width = FRAME_W * dpr;
234
252
  canvas.height = FRAME_H * dpr;
235
253
  const ctx = canvas.getContext("2d");
254
+ await drawBackground(ctx, frameSettings, canvas.width, canvas.height);
255
+ const dx = (canvas.width - drawW) / 2;
256
+ const shadowShift = frameSettings.browserChrome ? (frameSettings.browserShadow ?? 50) / 100 * 12 * dpr : 0;
257
+ const dy = (canvas.height - drawH) / 2 - shadowShift;
258
+ if (frameSettings.browserChrome) {
259
+ const radius = (frameSettings.browserRadius ?? 8) * scale * dpr;
260
+ const shadowVal = frameSettings.browserShadow ?? 50;
261
+ if (shadowVal > 0) {
262
+ ctx.save();
263
+ ctx.shadowColor = `rgba(0, 0, 0, ${shadowVal / 100 * 0.6})`;
264
+ ctx.shadowBlur = shadowVal / 100 * 40 * dpr;
265
+ ctx.shadowOffsetY = shadowVal / 100 * 8 * dpr;
266
+ ctx.shadowOffsetX = 0;
267
+ ctx.fillStyle = "rgba(0,0,0,1)";
268
+ roundRect(ctx, dx, dy, drawW, drawH, radius);
269
+ ctx.fill();
270
+ ctx.restore();
271
+ }
272
+ ctx.save();
273
+ roundRect(ctx, dx, dy, drawW, drawH, radius);
274
+ ctx.clip();
275
+ ctx.fillStyle = "#ffffff";
276
+ ctx.fillRect(dx, dy, drawW, drawH);
277
+ ctx.drawImage(contentImg, dx, dy, drawW, drawH);
278
+ ctx.restore();
279
+ } else {
280
+ ctx.drawImage(contentImg, dx, dy, drawW, drawH);
281
+ }
282
+ return canvas.toDataURL("image/png");
283
+ }
284
+ async function drawBackground(ctx, frameSettings, w, h) {
236
285
  if (frameSettings.bgType === "image" && frameSettings.bgImage) {
237
286
  try {
238
287
  const bgImg = await loadImage(frameSettings.bgImage);
239
- drawCover(ctx, bgImg, canvas.width, canvas.height);
288
+ drawCover(ctx, bgImg, w, h);
240
289
  } catch {
241
290
  ctx.fillStyle = frameSettings.bgColor;
242
- ctx.fillRect(0, 0, canvas.width, canvas.height);
291
+ ctx.fillRect(0, 0, w, h);
243
292
  }
244
293
  } else if (frameSettings.bgType === "gradient") {
245
- fillCssGradient(ctx, frameSettings.bgGradient, canvas.width, canvas.height);
294
+ fillCssGradient(ctx, frameSettings.bgGradient, w, h);
246
295
  } else if (frameSettings.bgColor !== "transparent") {
247
296
  ctx.fillStyle = frameSettings.bgColor;
248
- ctx.fillRect(0, 0, canvas.width, canvas.height);
297
+ ctx.fillRect(0, 0, w, h);
249
298
  }
250
- const dx = (canvas.width - drawW) / 2;
251
- const dy = (canvas.height - drawH) / 2;
252
- const shadowVal = frameSettings.browserShadow ?? 50;
253
- if (frameSettings.browserChrome && shadowVal > 0) {
254
- const alpha = shadowVal / 100 * 0.6;
255
- ctx.shadowColor = `rgba(0, 0, 0, ${alpha})`;
256
- ctx.shadowBlur = shadowVal / 100 * 40 * dpr;
257
- ctx.shadowOffsetY = shadowVal / 100 * 8 * dpr;
258
- ctx.shadowOffsetX = 0;
299
+ }
300
+ async function renderAfterbeforeFrame(beforeDataUrl, afterDataUrl, frameSettings) {
301
+ const dpr = getDpr();
302
+ const FRAME_W = 1920;
303
+ const FRAME_H = 1080;
304
+ const pad = frameSettings.padding ?? 192;
305
+ const gap = 40;
306
+ const labelSpace = 48;
307
+ const canvas = document.createElement("canvas");
308
+ canvas.width = FRAME_W * dpr;
309
+ canvas.height = FRAME_H * dpr;
310
+ const ctx = canvas.getContext("2d");
311
+ await drawBackground(ctx, frameSettings, canvas.width, canvas.height);
312
+ const beforeImg = await loadImage(beforeDataUrl);
313
+ const afterImg = await loadImage(afterDataUrl);
314
+ const availW = FRAME_W - pad * 2;
315
+ const availH = FRAME_H - pad * 2 - labelSpace;
316
+ const slotW = (availW - gap) / 2;
317
+ for (const [i, img] of [beforeImg, afterImg].entries()) {
318
+ const imgW = img.width / dpr;
319
+ const imgH = img.height / dpr;
320
+ const s = Math.min(slotW / imgW, availH / imgH, 1);
321
+ const drawW = imgW * s * dpr;
322
+ const drawH = imgH * s * dpr;
323
+ const slotX = (pad + i * (slotW + gap)) * dpr;
324
+ const dx = slotX + (slotW * dpr - drawW) / 2;
325
+ const dy = pad * dpr + (availH * dpr - drawH) / 2;
326
+ ctx.fillStyle = "#ffffff";
327
+ ctx.fillRect(dx, dy, drawW, drawH);
328
+ ctx.drawImage(img, dx, dy, drawW, drawH);
329
+ }
330
+ ctx.fillStyle = frameSettings.bgColor === "#FFFFFF" || frameSettings.bgColor === "#F5F5F5" || frameSettings.bgColor === "#F8F4ED" ? "#666666" : "#ffffff";
331
+ ctx.font = `400 ${11 * dpr}px -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif`;
332
+ ctx.textAlign = "center";
333
+ ctx.textBaseline = "top";
334
+ const labelY = (FRAME_H - pad - labelSpace + 16) * dpr;
335
+ const labels = ["Before", "After"];
336
+ for (let i = 0; i < 2; i++) {
337
+ const slotX = (pad + i * (slotW + gap)) * dpr;
338
+ const centerX = slotX + slotW * dpr / 2;
339
+ ctx.fillText(labels[i], centerX, labelY);
259
340
  }
260
- ctx.drawImage(contentImg, dx, dy, drawW, drawH);
261
- ctx.shadowColor = "transparent";
262
- ctx.shadowBlur = 0;
263
- ctx.shadowOffsetY = 0;
264
341
  return canvas.toDataURL("image/png");
265
342
  }
266
343
  function drawCover(ctx, img, cw, ch) {
@@ -374,63 +451,7 @@ import {
374
451
  FileText,
375
452
  Monitor
376
453
  } from "lucide-react";
377
-
378
- // src/overlay/ui/icons.tsx
379
- import { jsx, jsxs } from "react/jsx-runtime";
380
- function CameraIcon({ size = 32, color }) {
381
- return /* @__PURE__ */ jsx("svg", { width: size, height: size, viewBox: "0 0 32 32", fill: "none", style: { color }, children: /* @__PURE__ */ jsx(
382
- "path",
383
- {
384
- 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",
385
- fill: "currentColor"
386
- }
387
- ) });
388
- }
389
- function ImageIcon({ size = 32, color }) {
390
- return /* @__PURE__ */ jsxs("svg", { width: size, height: size, viewBox: "0 0 32 32", fill: "none", style: { color }, children: [
391
- /* @__PURE__ */ jsx(
392
- "path",
393
- {
394
- 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",
395
- fill: "currentColor"
396
- }
397
- ),
398
- /* @__PURE__ */ jsx(
399
- "path",
400
- {
401
- 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",
402
- fill: "currentColor"
403
- }
404
- )
405
- ] });
406
- }
407
- function CheckmarkIcon({ size = 32, color }) {
408
- return /* @__PURE__ */ jsxs("svg", { width: size, height: size, viewBox: "0 0 32 32", fill: "none", style: { color }, children: [
409
- /* @__PURE__ */ jsx(
410
- "path",
411
- {
412
- 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",
413
- fill: "currentColor"
414
- }
415
- ),
416
- /* @__PURE__ */ jsx(
417
- "path",
418
- {
419
- 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",
420
- fill: "currentColor"
421
- }
422
- )
423
- ] });
424
- }
425
- function CloseIcon({ size = 32, color }) {
426
- return /* @__PURE__ */ jsx("svg", { width: size, height: size, viewBox: "0 0 32 32", fill: "none", style: { color }, children: /* @__PURE__ */ jsx(
427
- "path",
428
- {
429
- 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",
430
- fill: "currentColor"
431
- }
432
- ) });
433
- }
454
+ import { Camera, Check as Check2, X as X2 } from "lucide-react";
434
455
 
435
456
  // src/overlay/color.ts
436
457
  var gray = {
@@ -507,19 +528,18 @@ import {
507
528
  Trash2 as Trash22,
508
529
  X
509
530
  } from "lucide-react";
531
+ import { Image as ImageIcon } from "lucide-react";
510
532
 
511
533
  // src/overlay/ui/settings-panel.tsx
512
534
  import { useEffect, useRef, useState as useState2 } from "react";
513
535
  import {
514
- AppWindow,
515
536
  Check,
516
537
  ChevronDown,
517
- LayoutGrid,
518
538
  Pipette,
519
539
  Trash2,
520
540
  Upload
521
541
  } from "lucide-react";
522
- import { Fragment, jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
542
+ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
523
543
  function SettingsContent({
524
544
  frameSettings,
525
545
  onFrameSettingsChange,
@@ -527,36 +547,71 @@ function SettingsContent({
527
547
  picking,
528
548
  onPickFolder
529
549
  }) {
530
- return /* @__PURE__ */ jsxs2(Fragment, { children: [
531
- /* @__PURE__ */ jsx2(
550
+ const [autoOpenPanel, setAutoOpenPanel] = useState2(() => {
551
+ try {
552
+ return localStorage.getItem("ab-auto-open-panel") !== "false";
553
+ } catch {
554
+ return true;
555
+ }
556
+ });
557
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
558
+ /* @__PURE__ */ jsx(
532
559
  BackgroundSetting,
533
560
  {
534
561
  frameSettings,
535
562
  onFrameSettingsChange
536
563
  }
537
564
  ),
538
- /* @__PURE__ */ jsx2(SettingsDivider, {}),
539
- /* @__PURE__ */ jsx2(
540
- BrowserFrameSetting,
565
+ /* @__PURE__ */ jsx(SettingsDivider, {}),
566
+ /* @__PURE__ */ jsx(
567
+ SettingsRow,
541
568
  {
542
- enabled: frameSettings.browserChrome,
543
- theme: frameSettings.browserTheme,
544
- shadow: frameSettings.browserShadow ?? 50,
545
- radius: frameSettings.browserRadius ?? 8,
546
- url: frameSettings.browserUrl ?? "localhost",
547
- onToggle: () => onFrameSettingsChange({ ...frameSettings, browserChrome: !frameSettings.browserChrome }),
548
- onSelect: (theme) => onFrameSettingsChange({ ...frameSettings, browserChrome: true, browserTheme: theme }),
549
- onShadowChange: (v) => onFrameSettingsChange({ ...frameSettings, browserShadow: v }),
550
- onRadiusChange: (v) => onFrameSettingsChange({ ...frameSettings, browserRadius: v }),
551
- onUrlChange: (v) => onFrameSettingsChange({ ...frameSettings, browserUrl: v })
569
+ title: "Comparison",
570
+ description: /* @__PURE__ */ jsxs(Fragment, { children: [
571
+ "Side-by-side before & after",
572
+ frameSettings.afterbefore && /* @__PURE__ */ jsx("span", { style: { display: "block", color: fg.muted, marginTop: 1 }, children: "Fixed at 1920 \xD7 1080" })
573
+ ] }),
574
+ control: /* @__PURE__ */ jsx(
575
+ ToggleSwitch,
576
+ {
577
+ enabled: frameSettings.afterbefore,
578
+ onChange: () => onFrameSettingsChange({
579
+ ...frameSettings,
580
+ afterbefore: !frameSettings.afterbefore,
581
+ ...!frameSettings.afterbefore ? { size: { w: 1920, h: 1080 } } : {}
582
+ })
583
+ }
584
+ )
585
+ }
586
+ ),
587
+ /* @__PURE__ */ jsx(SettingsDivider, {}),
588
+ /* @__PURE__ */ jsx(
589
+ SettingsRow,
590
+ {
591
+ title: "Open after capture",
592
+ description: "Show screenshots panel after taking a screenshot",
593
+ control: /* @__PURE__ */ jsx(
594
+ ToggleSwitch,
595
+ {
596
+ enabled: autoOpenPanel,
597
+ onChange: () => {
598
+ const next = !autoOpenPanel;
599
+ setAutoOpenPanel(next);
600
+ try {
601
+ localStorage.setItem("ab-auto-open-panel", String(next));
602
+ } catch {
603
+ }
604
+ }
605
+ }
606
+ )
552
607
  }
553
608
  ),
554
- /* @__PURE__ */ jsx2(SettingsDivider, {}),
555
- saveDir !== void 0 && onPickFolder && /* @__PURE__ */ jsx2(
609
+ /* @__PURE__ */ jsx(SettingsDivider, {}),
610
+ saveDir !== void 0 && onPickFolder && /* @__PURE__ */ jsx(
556
611
  SettingsRow,
557
612
  {
558
- title: "Save Location",
559
- description: /* @__PURE__ */ jsx2(
613
+ title: "Save location",
614
+ description: /* @__PURE__ */ jsx(
560
615
  "span",
561
616
  {
562
617
  style: {
@@ -569,7 +624,7 @@ function SettingsContent({
569
624
  children: saveDir
570
625
  }
571
626
  ),
572
- control: /* @__PURE__ */ jsx2(SmallButton, { onClick: onPickFolder, children: picking ? "..." : "Change" })
627
+ control: /* @__PURE__ */ jsx(SmallButton, { onClick: onPickFolder, children: picking ? "..." : "Change" })
573
628
  }
574
629
  )
575
630
  ] });
@@ -579,16 +634,16 @@ function SettingsRow({
579
634
  description,
580
635
  control
581
636
  }) {
582
- return /* @__PURE__ */ jsxs2("div", { style: { display: "flex", alignItems: "center", justifyContent: "space-between", gap: 16, padding: "14px 0" }, children: [
583
- /* @__PURE__ */ jsxs2("div", { style: { display: "flex", flexDirection: "column", gap: 2, flex: 1, minWidth: 0 }, children: [
584
- /* @__PURE__ */ jsx2("span", { style: { ...text.label.sm, color: fg.strong }, children: title }),
585
- description && /* @__PURE__ */ jsx2("span", { style: { ...text.paragraph.xs, color: fg.sub }, children: description })
637
+ return /* @__PURE__ */ jsxs("div", { style: { display: "flex", alignItems: "center", justifyContent: "space-between", gap: 16, padding: "14px 0" }, children: [
638
+ /* @__PURE__ */ jsxs("div", { style: { display: "flex", flexDirection: "column", gap: 2, flex: 1, minWidth: 0 }, children: [
639
+ /* @__PURE__ */ jsx("span", { style: { ...text.label.xs, color: fg.strong }, children: title }),
640
+ description && /* @__PURE__ */ jsx("span", { style: { ...text.paragraph.xs, color: fg.sub }, children: description })
586
641
  ] }),
587
- /* @__PURE__ */ jsx2("div", { style: { flexShrink: 0 }, children: control })
642
+ /* @__PURE__ */ jsx("div", { style: { flexShrink: 0 }, children: control })
588
643
  ] });
589
644
  }
590
645
  function SettingsDivider() {
591
- return /* @__PURE__ */ jsx2(
646
+ return /* @__PURE__ */ jsx(
592
647
  "div",
593
648
  {
594
649
  style: {
@@ -602,7 +657,7 @@ function ToggleSwitch({
602
657
  enabled,
603
658
  onChange
604
659
  }) {
605
- return /* @__PURE__ */ jsx2(
660
+ return /* @__PURE__ */ jsx(
606
661
  "button",
607
662
  {
608
663
  type: "button",
@@ -619,7 +674,7 @@ function ToggleSwitch({
619
674
  flexShrink: 0,
620
675
  transition: "background 0.12s ease"
621
676
  },
622
- children: /* @__PURE__ */ jsx2(
677
+ children: /* @__PURE__ */ jsx(
623
678
  "span",
624
679
  {
625
680
  style: {
@@ -662,9 +717,9 @@ function FrameSizeControl({
662
717
  outline: "none",
663
718
  padding: "0 2px 0 6px"
664
719
  };
665
- return /* @__PURE__ */ jsxs2("div", { style: { marginTop: 16, paddingBottom: 14 }, children: [
666
- /* @__PURE__ */ jsx2("span", { style: { ...text.label.sm, color: fg.strong }, children: "Size" }),
667
- /* @__PURE__ */ jsx2("div", { style: { display: "flex", gap: 6, marginTop: 8 }, children: ["w", "h"].map((field) => /* @__PURE__ */ jsxs2(
720
+ return /* @__PURE__ */ jsxs("div", { style: { marginTop: 16, paddingBottom: 14 }, children: [
721
+ /* @__PURE__ */ jsx("span", { style: { ...text.label.xs, color: fg.strong }, children: "Size" }),
722
+ /* @__PURE__ */ jsx("div", { style: { display: "flex", gap: 6, marginTop: 8 }, children: ["w", "h"].map((field) => /* @__PURE__ */ jsxs(
668
723
  "div",
669
724
  {
670
725
  style: {
@@ -678,8 +733,8 @@ function FrameSizeControl({
678
733
  borderRadius: 7
679
734
  },
680
735
  children: [
681
- /* @__PURE__ */ jsx2("span", { style: { ...text.label.xs, color: fg.sub, flexShrink: 0, userSelect: "none" }, children: field.toUpperCase() }),
682
- /* @__PURE__ */ jsx2(
736
+ /* @__PURE__ */ jsx("span", { style: { ...text.label.xs, color: fg.sub, flexShrink: 0, userSelect: "none" }, children: field.toUpperCase() }),
737
+ /* @__PURE__ */ jsx(
683
738
  "input",
684
739
  {
685
740
  type: "text",
@@ -703,10 +758,10 @@ function FrameSizeControl({
703
758
  },
704
759
  field
705
760
  )) }),
706
- /* @__PURE__ */ jsxs2("div", { style: { marginTop: 16 }, children: [
707
- /* @__PURE__ */ jsx2("span", { style: { ...text.label.sm, color: fg.strong, display: "block", marginBottom: 8 }, children: "Presets" }),
708
- /* @__PURE__ */ jsxs2("div", { style: { position: "relative" }, children: [
709
- /* @__PURE__ */ jsxs2(
761
+ /* @__PURE__ */ jsxs("div", { style: { marginTop: 16 }, children: [
762
+ /* @__PURE__ */ jsx("span", { style: { ...text.label.xs, color: fg.strong, display: "block", marginBottom: 8 }, children: "Presets" }),
763
+ /* @__PURE__ */ jsxs("div", { style: { position: "relative" }, children: [
764
+ /* @__PURE__ */ jsxs(
710
765
  "button",
711
766
  {
712
767
  onClick: () => setSizeOpen((prev) => !prev),
@@ -729,11 +784,11 @@ function FrameSizeControl({
729
784
  },
730
785
  children: [
731
786
  currentPreset ? currentPreset.label : "Custom",
732
- /* @__PURE__ */ jsx2(ChevronDown, { size: 12, strokeWidth: 2 })
787
+ /* @__PURE__ */ jsx(ChevronDown, { size: 12, strokeWidth: 2 })
733
788
  ]
734
789
  }
735
790
  ),
736
- sizeOpen && /* @__PURE__ */ jsx2(
791
+ sizeOpen && /* @__PURE__ */ jsx(
737
792
  "div",
738
793
  {
739
794
  style: {
@@ -748,7 +803,7 @@ function FrameSizeControl({
748
803
  boxShadow: shadow.dropdown,
749
804
  zIndex: 1
750
805
  },
751
- children: FRAME_SIZE_PRESETS.map((preset) => /* @__PURE__ */ jsxs2(
806
+ children: FRAME_SIZE_PRESETS.map((preset) => /* @__PURE__ */ jsxs(
752
807
  DropItem,
753
808
  {
754
809
  active: !isCustom && preset.w === size.w && preset.h === size.h,
@@ -757,8 +812,8 @@ function FrameSizeControl({
757
812
  setSizeOpen(false);
758
813
  },
759
814
  children: [
760
- /* @__PURE__ */ jsx2("span", { children: preset.label }),
761
- /* @__PURE__ */ jsx2("span", { style: { marginLeft: 6, fontSize: 10, color: fg.muted }, children: preset.hint })
815
+ /* @__PURE__ */ jsx("span", { children: preset.label }),
816
+ /* @__PURE__ */ jsx("span", { style: { marginLeft: 6, fontSize: 10, color: fg.muted }, children: preset.hint })
762
817
  ]
763
818
  },
764
819
  preset.label
@@ -802,13 +857,10 @@ function BackgroundSetting({
802
857
  onFrameSettingsChange({ ...frameSettings, bgType: "color", bgColor: c });
803
858
  };
804
859
  const currentBg = frameSettings.bgType === "gradient" ? frameSettings.bgGradient : frameSettings.bgColor;
805
- return /* @__PURE__ */ jsxs2("div", { children: [
806
- /* @__PURE__ */ jsxs2("div", { style: { display: "flex", alignItems: "center", justifyContent: "space-between", gap: 16 }, children: [
807
- /* @__PURE__ */ jsxs2("div", { style: { display: "flex", alignItems: "center", gap: 8 }, children: [
808
- /* @__PURE__ */ jsx2(LayoutGrid, { size: 16, strokeWidth: 1.6, color: fg.sub }),
809
- /* @__PURE__ */ jsx2("span", { style: { ...text.label.sm, color: fg.strong }, children: "Background" })
810
- ] }),
811
- /* @__PURE__ */ jsx2(
860
+ return /* @__PURE__ */ jsxs("div", { children: [
861
+ /* @__PURE__ */ jsxs("div", { style: { display: "flex", alignItems: "center", justifyContent: "space-between", gap: 16 }, children: [
862
+ /* @__PURE__ */ jsx("span", { style: { ...text.label.xs, color: fg.strong }, children: "Background" }),
863
+ /* @__PURE__ */ jsx(
812
864
  ToggleSwitch,
813
865
  {
814
866
  enabled: frameSettings.enabled,
@@ -816,7 +868,7 @@ function BackgroundSetting({
816
868
  }
817
869
  )
818
870
  ] }),
819
- /* @__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(
871
+ /* @__PURE__ */ jsx("div", { style: { display: "flex", gap: 2, marginTop: 12, background: state.subtle, borderRadius: 8, padding: 2 }, children: ["solid", "gradient", "image"].map((t) => /* @__PURE__ */ jsx(
820
872
  "button",
821
873
  {
822
874
  onClick: () => setTab(t),
@@ -837,9 +889,9 @@ function BackgroundSetting({
837
889
  },
838
890
  t
839
891
  )) }),
840
- tab === "gradient" && /* @__PURE__ */ jsx2("div", { style: { display: "flex", gap: 8, marginTop: 12, padding: "4px 4px 14px", flexWrap: "wrap" }, children: GRADIENT_PRESETS.map((g) => {
892
+ tab === "gradient" && /* @__PURE__ */ jsx("div", { style: { display: "flex", gap: 8, marginTop: 12, padding: "4px 4px 14px", flexWrap: "wrap" }, children: GRADIENT_PRESETS.map((g) => {
841
893
  const selected = frameSettings.bgType === "gradient" && frameSettings.bgGradient === g;
842
- return /* @__PURE__ */ jsx2(
894
+ return /* @__PURE__ */ jsx(
843
895
  PresetSwatch,
844
896
  {
845
897
  background: g,
@@ -849,11 +901,11 @@ function BackgroundSetting({
849
901
  g
850
902
  );
851
903
  }) }),
852
- tab === "solid" && /* @__PURE__ */ jsxs2(Fragment, { children: [
853
- /* @__PURE__ */ jsxs2("div", { style: { display: "flex", gap: 8, marginTop: 12, padding: "4px 4px 14px", flexWrap: "wrap" }, children: [
904
+ tab === "solid" && /* @__PURE__ */ jsxs(Fragment, { children: [
905
+ /* @__PURE__ */ jsxs("div", { style: { display: "flex", gap: 8, marginTop: 12, padding: "4px 4px 14px", flexWrap: "wrap" }, children: [
854
906
  COLOR_PRESETS.map((c) => {
855
907
  const selected = frameSettings.bgType === "color" && frameSettings.bgColor === c;
856
- return /* @__PURE__ */ jsx2(
908
+ return /* @__PURE__ */ jsx(
857
909
  PresetSwatch,
858
910
  {
859
911
  background: c,
@@ -868,7 +920,7 @@ function BackgroundSetting({
868
920
  const isCustom = !COLOR_PRESETS.includes(customColor.toUpperCase());
869
921
  const bg2 = isCustom ? customColor : "transparent";
870
922
  const iconColor = isCustom ? hexLuminance(customColor) > 0.4 ? "rgba(0,0,0,0.5)" : "rgba(255,255,255,0.7)" : fg.sub;
871
- return /* @__PURE__ */ jsx2(
923
+ return /* @__PURE__ */ jsx(
872
924
  "button",
873
925
  {
874
926
  onClick: async () => {
@@ -893,15 +945,15 @@ function BackgroundSetting({
893
945
  justifyContent: "center",
894
946
  color: iconColor
895
947
  },
896
- children: /* @__PURE__ */ jsx2(Pipette, { size: 14, strokeWidth: 2 })
948
+ children: /* @__PURE__ */ jsx(Pipette, { size: 14, strokeWidth: 2 })
897
949
  }
898
950
  );
899
951
  })()
900
952
  ] }),
901
- /* @__PURE__ */ jsx2("div", { style: { paddingBottom: 4 }, children: /* @__PURE__ */ jsx2(ColorHexInput, { value: frameSettings.bgColor, onChange: selectColor }) })
953
+ /* @__PURE__ */ jsx("div", { style: { paddingBottom: 4 }, children: /* @__PURE__ */ jsx(ColorHexInput, { value: frameSettings.bgColor, onChange: selectColor }) })
902
954
  ] }),
903
- tab === "image" && /* @__PURE__ */ jsxs2("div", { style: { marginTop: 12 }, children: [
904
- /* @__PURE__ */ jsx2(
955
+ tab === "image" && /* @__PURE__ */ jsxs("div", { style: { marginTop: 12 }, children: [
956
+ /* @__PURE__ */ jsx(
905
957
  "input",
906
958
  {
907
959
  ref: fileInputRef,
@@ -911,8 +963,8 @@ function BackgroundSetting({
911
963
  style: { display: "none" }
912
964
  }
913
965
  ),
914
- frameSettings.bgImage ? /* @__PURE__ */ jsxs2("div", { style: { display: "flex", alignItems: "center", gap: 8 }, children: [
915
- /* @__PURE__ */ jsx2(
966
+ frameSettings.bgImage ? /* @__PURE__ */ jsxs("div", { style: { display: "flex", alignItems: "center", gap: 8 }, children: [
967
+ /* @__PURE__ */ jsx(
916
968
  "img",
917
969
  {
918
970
  src: frameSettings.bgImage,
@@ -926,17 +978,17 @@ function BackgroundSetting({
926
978
  }
927
979
  }
928
980
  ),
929
- /* @__PURE__ */ jsxs2(SmallButton, { onClick: () => fileInputRef.current?.click(), children: [
930
- /* @__PURE__ */ jsx2(Upload, { size: 11, strokeWidth: 2 }),
981
+ /* @__PURE__ */ jsxs(SmallButton, { onClick: () => fileInputRef.current?.click(), children: [
982
+ /* @__PURE__ */ jsx(Upload, { size: 11, strokeWidth: 2 }),
931
983
  "Replace"
932
984
  ] }),
933
- /* @__PURE__ */ jsx2(SmallButton, { onClick: () => onFrameSettingsChange({ ...frameSettings, bgImage: null }), children: /* @__PURE__ */ jsx2(Trash2, { size: 11, strokeWidth: 2 }) })
934
- ] }) : /* @__PURE__ */ jsxs2(SmallButton, { onClick: () => fileInputRef.current?.click(), children: [
935
- /* @__PURE__ */ jsx2(Upload, { size: 11, strokeWidth: 2 }),
985
+ /* @__PURE__ */ jsx(SmallButton, { onClick: () => onFrameSettingsChange({ ...frameSettings, bgImage: null }), children: /* @__PURE__ */ jsx(Trash2, { size: 11, strokeWidth: 2 }) })
986
+ ] }) : /* @__PURE__ */ jsxs(SmallButton, { onClick: () => fileInputRef.current?.click(), children: [
987
+ /* @__PURE__ */ jsx(Upload, { size: 11, strokeWidth: 2 }),
936
988
  "Upload image"
937
989
  ] })
938
990
  ] }),
939
- /* @__PURE__ */ jsxs2(
991
+ /* @__PURE__ */ jsxs(
940
992
  "button",
941
993
  {
942
994
  onClick: () => setMoreOpen(!moreOpen),
@@ -955,7 +1007,7 @@ function BackgroundSetting({
955
1007
  padding: 0
956
1008
  },
957
1009
  children: [
958
- /* @__PURE__ */ jsx2(
1010
+ /* @__PURE__ */ jsx(
959
1011
  ChevronDown,
960
1012
  {
961
1013
  size: 12,
@@ -970,37 +1022,65 @@ function BackgroundSetting({
970
1022
  ]
971
1023
  }
972
1024
  ),
973
- moreOpen && /* @__PURE__ */ jsxs2("div", { style: { marginTop: 8 }, children: [
974
- /* @__PURE__ */ jsxs2("div", { children: [
975
- /* @__PURE__ */ jsx2("span", { style: { ...text.label.sm, color: fg.strong }, children: "Padding" }),
976
- /* @__PURE__ */ jsxs2("div", { style: { display: "flex", alignItems: "center", gap: 8, marginTop: 8, paddingLeft: 4 }, children: [
977
- /* @__PURE__ */ jsx2(
1025
+ moreOpen && /* @__PURE__ */ jsxs("div", { style: { marginTop: 8 }, children: [
1026
+ /* @__PURE__ */ jsxs("div", { children: [
1027
+ /* @__PURE__ */ jsx("span", { style: { ...text.label.xs, color: fg.strong }, children: "Size" }),
1028
+ /* @__PURE__ */ jsxs("div", { style: { display: "flex", alignItems: "center", gap: 8, marginTop: 8, paddingLeft: 4 }, children: [
1029
+ /* @__PURE__ */ jsx(
978
1030
  SlimSlider,
979
1031
  {
980
- min: 0,
981
- max: 200,
982
- step: 5,
983
- value: frameSettings.padding,
984
- onChange: (v) => onFrameSettingsChange({ ...frameSettings, padding: v })
1032
+ min: 30,
1033
+ max: 100,
1034
+ step: 1,
1035
+ value: Math.round((frameSettings.size.w - frameSettings.padding * 2) / frameSettings.size.w * 100),
1036
+ onChange: (v) => {
1037
+ const pad = Math.round(frameSettings.size.w * (1 - v / 100) / 2);
1038
+ onFrameSettingsChange({ ...frameSettings, padding: pad });
1039
+ }
985
1040
  }
986
1041
  ),
987
- /* @__PURE__ */ jsx2(
1042
+ /* @__PURE__ */ jsx(
988
1043
  SliderInput,
989
1044
  {
990
- value: frameSettings.padding,
991
- onChange: (n) => onFrameSettingsChange({ ...frameSettings, padding: n })
1045
+ min: 30,
1046
+ max: 100,
1047
+ suffix: "%",
1048
+ value: Math.round((frameSettings.size.w - frameSettings.padding * 2) / frameSettings.size.w * 100),
1049
+ onChange: (n) => {
1050
+ const pad = Math.round(frameSettings.size.w * (1 - n / 100) / 2);
1051
+ onFrameSettingsChange({ ...frameSettings, padding: pad });
1052
+ }
992
1053
  }
993
1054
  )
994
1055
  ] })
995
1056
  ] }),
996
- /* @__PURE__ */ jsx2(
1057
+ /* @__PURE__ */ jsx(
997
1058
  FrameSizeControl,
998
1059
  {
999
1060
  size: frameSettings.size,
1000
1061
  onChange: (size) => onFrameSettingsChange({ ...frameSettings, size })
1001
1062
  }
1002
1063
  )
1003
- ] })
1064
+ ] }),
1065
+ /* @__PURE__ */ jsx("div", { style: { borderTop: `1px solid ${stroke.soft}`, marginTop: 14 }, children: /* @__PURE__ */ jsx(
1066
+ BrowserFrameSetting,
1067
+ {
1068
+ enabled: frameSettings.browserChrome,
1069
+ theme: frameSettings.browserTheme,
1070
+ shadow: frameSettings.browserShadow ?? 50,
1071
+ radius: frameSettings.browserRadius ?? 8,
1072
+ url: frameSettings.browserUrl ?? "localhost",
1073
+ onToggle: () => onFrameSettingsChange({
1074
+ ...frameSettings,
1075
+ browserChrome: !frameSettings.browserChrome,
1076
+ ...!frameSettings.browserChrome ? { enabled: true } : {}
1077
+ }),
1078
+ onSelect: (theme) => onFrameSettingsChange({ ...frameSettings, browserChrome: true, browserTheme: theme, enabled: true }),
1079
+ onShadowChange: (v) => onFrameSettingsChange({ ...frameSettings, browserShadow: v }),
1080
+ onRadiusChange: (v) => onFrameSettingsChange({ ...frameSettings, browserRadius: v }),
1081
+ onUrlChange: (v) => onFrameSettingsChange({ ...frameSettings, browserUrl: v })
1082
+ }
1083
+ ) })
1004
1084
  ] });
1005
1085
  }
1006
1086
  function SlimSlider({
@@ -1028,7 +1108,7 @@ function SlimSlider({
1028
1108
  if (e.buttons === 0) return;
1029
1109
  update(e.clientX);
1030
1110
  };
1031
- return /* @__PURE__ */ jsx2(
1111
+ return /* @__PURE__ */ jsx(
1032
1112
  "div",
1033
1113
  {
1034
1114
  ref: trackRef,
@@ -1043,9 +1123,9 @@ function SlimSlider({
1043
1123
  cursor: "pointer",
1044
1124
  touchAction: "none"
1045
1125
  },
1046
- children: /* @__PURE__ */ jsxs2("div", { style: { position: "relative", width: "100%", height: 3, borderRadius: 2, background: stroke.default }, children: [
1047
- /* @__PURE__ */ jsx2("div", { style: { position: "absolute", left: 0, top: 0, height: "100%", width: `${pct}%`, borderRadius: 2, background: fg.sub } }),
1048
- /* @__PURE__ */ jsx2(
1126
+ children: /* @__PURE__ */ jsxs("div", { style: { position: "relative", width: "100%", height: 3, borderRadius: 2, background: stroke.default }, children: [
1127
+ /* @__PURE__ */ jsx("div", { style: { position: "absolute", left: 0, top: 0, height: "100%", width: `${pct}%`, borderRadius: 2, background: fg.sub } }),
1128
+ /* @__PURE__ */ jsx(
1049
1129
  "div",
1050
1130
  {
1051
1131
  style: {
@@ -1069,7 +1149,7 @@ function PresetSwatch({
1069
1149
  selected,
1070
1150
  onClick
1071
1151
  }) {
1072
- return /* @__PURE__ */ jsx2(
1152
+ return /* @__PURE__ */ jsx(
1073
1153
  "div",
1074
1154
  {
1075
1155
  onClick,
@@ -1086,7 +1166,7 @@ function PresetSwatch({
1086
1166
  transition: "outline-color 0.12s ease",
1087
1167
  flexShrink: 0
1088
1168
  },
1089
- children: selected && /* @__PURE__ */ jsx2(
1169
+ children: selected && /* @__PURE__ */ jsx(
1090
1170
  "div",
1091
1171
  {
1092
1172
  style: {
@@ -1098,7 +1178,7 @@ function PresetSwatch({
1098
1178
  justifyContent: "center",
1099
1179
  background: "rgba(0,0,0,0.2)"
1100
1180
  },
1101
- children: /* @__PURE__ */ jsx2(Check, { size: 16, strokeWidth: 3, color: "#fff" })
1181
+ children: /* @__PURE__ */ jsx(Check, { size: 16, strokeWidth: 3, color: "#fff" })
1102
1182
  }
1103
1183
  )
1104
1184
  }
@@ -1124,22 +1204,19 @@ function BrowserFrameSetting({
1124
1204
  onUrlChange
1125
1205
  }) {
1126
1206
  const [moreOpen, setMoreOpen] = useState2(false);
1127
- 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)) });
1207
+ const trafficLights = /* @__PURE__ */ jsx("div", { style: { display: "flex", gap: 4 }, children: ["#FF5F57", "#FEBC2E", "#28C840"].map((c) => /* @__PURE__ */ jsx("div", { style: { width: 5, height: 5, borderRadius: "50%", background: c } }, c)) });
1128
1208
  const themes = [
1129
1209
  { value: "dark", label: "macOS Dark", titleBar: "#1C1C1C", urlBar: "#262626", border: "#333333" },
1130
1210
  { value: "light", label: "macOS Light", titleBar: "#F5F5F5", urlBar: "#FFFFFF", border: "#EBEBEB" }
1131
1211
  ];
1132
- return /* @__PURE__ */ jsxs2("div", { style: { padding: "14px 0" }, children: [
1133
- /* @__PURE__ */ jsxs2("div", { style: { display: "flex", alignItems: "center", justifyContent: "space-between", gap: 16 }, children: [
1134
- /* @__PURE__ */ jsxs2("div", { style: { display: "flex", alignItems: "center", gap: 8 }, children: [
1135
- /* @__PURE__ */ jsx2(AppWindow, { size: 16, strokeWidth: 1.6, color: fg.sub }),
1136
- /* @__PURE__ */ jsx2("span", { style: { ...text.label.sm, color: fg.strong }, children: "Browser Frame" })
1137
- ] }),
1138
- /* @__PURE__ */ jsx2(ToggleSwitch, { enabled, onChange: onToggle })
1212
+ return /* @__PURE__ */ jsxs("div", { style: { padding: "14px 0" }, children: [
1213
+ /* @__PURE__ */ jsxs("div", { style: { display: "flex", alignItems: "center", justifyContent: "space-between", gap: 16 }, children: [
1214
+ /* @__PURE__ */ jsx("span", { style: { ...text.label.xs, color: fg.strong }, children: "Browser frame" }),
1215
+ /* @__PURE__ */ jsx(ToggleSwitch, { enabled, onChange: onToggle })
1139
1216
  ] }),
1140
- /* @__PURE__ */ jsx2("div", { style: { display: "flex", gap: 10, marginTop: 12 }, children: themes.map((t) => {
1217
+ /* @__PURE__ */ jsx("div", { style: { display: "flex", gap: 10, marginTop: 12 }, children: themes.map((t) => {
1141
1218
  const selected = enabled && theme === t.value;
1142
- return /* @__PURE__ */ jsxs2(
1219
+ return /* @__PURE__ */ jsxs(
1143
1220
  "div",
1144
1221
  {
1145
1222
  onClick: () => onSelect(t.value),
@@ -1154,7 +1231,7 @@ function BrowserFrameSetting({
1154
1231
  transition: "border-color 0.12s ease"
1155
1232
  },
1156
1233
  children: [
1157
- /* @__PURE__ */ jsxs2(
1234
+ /* @__PURE__ */ jsxs(
1158
1235
  "div",
1159
1236
  {
1160
1237
  style: {
@@ -1169,7 +1246,7 @@ function BrowserFrameSetting({
1169
1246
  },
1170
1247
  children: [
1171
1248
  trafficLights,
1172
- /* @__PURE__ */ jsx2(
1249
+ /* @__PURE__ */ jsx(
1173
1250
  "div",
1174
1251
  {
1175
1252
  style: {
@@ -1184,8 +1261,8 @@ function BrowserFrameSetting({
1184
1261
  ]
1185
1262
  }
1186
1263
  ),
1187
- /* @__PURE__ */ jsx2("div", { style: { textAlign: "center", marginTop: 8, ...text.label.xs, color: fg.default }, children: t.label }),
1188
- selected && /* @__PURE__ */ jsx2(
1264
+ /* @__PURE__ */ jsx("div", { style: { textAlign: "center", marginTop: 8, ...text.label.xs, color: fg.default }, children: t.label }),
1265
+ selected && /* @__PURE__ */ jsx(
1189
1266
  "div",
1190
1267
  {
1191
1268
  style: {
@@ -1200,7 +1277,7 @@ function BrowserFrameSetting({
1200
1277
  alignItems: "center",
1201
1278
  justifyContent: "center"
1202
1279
  },
1203
- children: /* @__PURE__ */ jsx2(Check, { size: 10, strokeWidth: 3, color: "#fff" })
1280
+ children: /* @__PURE__ */ jsx(Check, { size: 10, strokeWidth: 3, color: "#fff" })
1204
1281
  }
1205
1282
  )
1206
1283
  ]
@@ -1208,7 +1285,7 @@ function BrowserFrameSetting({
1208
1285
  t.value
1209
1286
  );
1210
1287
  }) }),
1211
- /* @__PURE__ */ jsxs2(
1288
+ /* @__PURE__ */ jsxs(
1212
1289
  "button",
1213
1290
  {
1214
1291
  onClick: () => setMoreOpen(!moreOpen),
@@ -1226,7 +1303,7 @@ function BrowserFrameSetting({
1226
1303
  padding: 0
1227
1304
  },
1228
1305
  children: [
1229
- /* @__PURE__ */ jsx2(
1306
+ /* @__PURE__ */ jsx(
1230
1307
  ChevronDown,
1231
1308
  {
1232
1309
  size: 12,
@@ -1241,10 +1318,10 @@ function BrowserFrameSetting({
1241
1318
  ]
1242
1319
  }
1243
1320
  ),
1244
- moreOpen && /* @__PURE__ */ jsxs2("div", { style: { marginTop: 8 }, children: [
1245
- /* @__PURE__ */ jsxs2("div", { children: [
1246
- /* @__PURE__ */ jsx2("span", { style: { ...text.label.sm, color: fg.strong }, children: "URL" }),
1247
- /* @__PURE__ */ jsx2(
1321
+ moreOpen && /* @__PURE__ */ jsxs("div", { style: { marginTop: 8 }, children: [
1322
+ /* @__PURE__ */ jsxs("div", { children: [
1323
+ /* @__PURE__ */ jsx("span", { style: { ...text.label.xs, color: fg.strong }, children: "URL" }),
1324
+ /* @__PURE__ */ jsx(
1248
1325
  "div",
1249
1326
  {
1250
1327
  style: {
@@ -1256,7 +1333,7 @@ function BrowserFrameSetting({
1256
1333
  borderRadius: 7,
1257
1334
  marginTop: 8
1258
1335
  },
1259
- children: /* @__PURE__ */ jsx2(
1336
+ children: /* @__PURE__ */ jsx(
1260
1337
  "input",
1261
1338
  {
1262
1339
  type: "text",
@@ -1279,10 +1356,10 @@ function BrowserFrameSetting({
1279
1356
  }
1280
1357
  )
1281
1358
  ] }),
1282
- /* @__PURE__ */ jsxs2("div", { style: { marginTop: 12 }, children: [
1283
- /* @__PURE__ */ jsx2("span", { style: { ...text.label.sm, color: fg.strong }, children: "Shadow" }),
1284
- /* @__PURE__ */ jsxs2("div", { style: { display: "flex", alignItems: "center", gap: 8, marginTop: 8, paddingLeft: 4 }, children: [
1285
- /* @__PURE__ */ jsx2(
1359
+ /* @__PURE__ */ jsxs("div", { style: { marginTop: 12 }, children: [
1360
+ /* @__PURE__ */ jsx("span", { style: { ...text.label.xs, color: fg.strong }, children: "Shadow" }),
1361
+ /* @__PURE__ */ jsxs("div", { style: { display: "flex", alignItems: "center", gap: 8, marginTop: 8, paddingLeft: 4 }, children: [
1362
+ /* @__PURE__ */ jsx(
1286
1363
  SlimSlider,
1287
1364
  {
1288
1365
  min: 0,
@@ -1292,7 +1369,7 @@ function BrowserFrameSetting({
1292
1369
  onChange: onShadowChange
1293
1370
  }
1294
1371
  ),
1295
- /* @__PURE__ */ jsx2(
1372
+ /* @__PURE__ */ jsx(
1296
1373
  SliderInput,
1297
1374
  {
1298
1375
  value: shadow2,
@@ -1304,10 +1381,10 @@ function BrowserFrameSetting({
1304
1381
  )
1305
1382
  ] })
1306
1383
  ] }),
1307
- /* @__PURE__ */ jsxs2("div", { style: { marginTop: 12 }, children: [
1308
- /* @__PURE__ */ jsx2("span", { style: { ...text.label.sm, color: fg.strong }, children: "Rounding" }),
1309
- /* @__PURE__ */ jsxs2("div", { style: { display: "flex", alignItems: "center", gap: 8, marginTop: 8, paddingLeft: 4 }, children: [
1310
- /* @__PURE__ */ jsx2(
1384
+ /* @__PURE__ */ jsxs("div", { style: { marginTop: 12 }, children: [
1385
+ /* @__PURE__ */ jsx("span", { style: { ...text.label.xs, color: fg.strong }, children: "Rounding" }),
1386
+ /* @__PURE__ */ jsxs("div", { style: { display: "flex", alignItems: "center", gap: 8, marginTop: 8, paddingLeft: 4 }, children: [
1387
+ /* @__PURE__ */ jsx(
1311
1388
  SlimSlider,
1312
1389
  {
1313
1390
  min: 0,
@@ -1317,7 +1394,7 @@ function BrowserFrameSetting({
1317
1394
  onChange: onRadiusChange
1318
1395
  }
1319
1396
  ),
1320
- /* @__PURE__ */ jsx2(
1397
+ /* @__PURE__ */ jsx(
1321
1398
  SliderInput,
1322
1399
  {
1323
1400
  value: radius,
@@ -1337,7 +1414,7 @@ function SmallButton({
1337
1414
  onClick
1338
1415
  }) {
1339
1416
  const [hovered, setHovered] = useState2(false);
1340
- return /* @__PURE__ */ jsx2(
1417
+ return /* @__PURE__ */ jsx(
1341
1418
  "button",
1342
1419
  {
1343
1420
  onClick,
@@ -1373,7 +1450,7 @@ function SliderInput({
1373
1450
  useEffect(() => {
1374
1451
  if (!editing) setText(String(value));
1375
1452
  }, [editing, value]);
1376
- return /* @__PURE__ */ jsxs2(
1453
+ return /* @__PURE__ */ jsxs(
1377
1454
  "div",
1378
1455
  {
1379
1456
  style: {
@@ -1387,7 +1464,7 @@ function SliderInput({
1387
1464
  width: 68
1388
1465
  },
1389
1466
  children: [
1390
- /* @__PURE__ */ jsx2(
1467
+ /* @__PURE__ */ jsx(
1391
1468
  "input",
1392
1469
  {
1393
1470
  type: "text",
@@ -1419,7 +1496,7 @@ function SliderInput({
1419
1496
  }
1420
1497
  }
1421
1498
  ),
1422
- suffix && /* @__PURE__ */ jsx2("span", { style: { ...text.label.xs, color: fg.sub, flexShrink: 0, userSelect: "none", marginLeft: 4 }, children: suffix })
1499
+ suffix && /* @__PURE__ */ jsx("span", { style: { ...text.label.xs, color: fg.sub, flexShrink: 0, userSelect: "none", marginLeft: 4 }, children: suffix })
1423
1500
  ]
1424
1501
  }
1425
1502
  );
@@ -1437,7 +1514,7 @@ function ColorHexInput({
1437
1514
  const hex = raw.startsWith("#") ? raw : `#${raw}`;
1438
1515
  if (/^#[0-9a-fA-F]{6}$/.test(hex)) onChange(hex);
1439
1516
  };
1440
- return /* @__PURE__ */ jsxs2(
1517
+ return /* @__PURE__ */ jsxs(
1441
1518
  "div",
1442
1519
  {
1443
1520
  style: {
@@ -1452,8 +1529,8 @@ function ColorHexInput({
1452
1529
  gap: 6
1453
1530
  },
1454
1531
  children: [
1455
- /* @__PURE__ */ jsx2("span", { style: { ...text.label.xs, color: fg.sub, flexShrink: 0, userSelect: "none" }, children: "#" }),
1456
- /* @__PURE__ */ jsx2(
1532
+ /* @__PURE__ */ jsx("span", { style: { ...text.label.xs, color: fg.sub, flexShrink: 0, userSelect: "none" }, children: "#" }),
1533
+ /* @__PURE__ */ jsx(
1457
1534
  "input",
1458
1535
  {
1459
1536
  type: "text",
@@ -1493,7 +1570,7 @@ function DropItem({
1493
1570
  onClick,
1494
1571
  active
1495
1572
  }) {
1496
- return /* @__PURE__ */ jsx2(
1573
+ return /* @__PURE__ */ jsx(
1497
1574
  "button",
1498
1575
  {
1499
1576
  onClick,
@@ -1531,30 +1608,31 @@ async function downscaleImage(dataUrl, maxW, maxH) {
1531
1608
  }
1532
1609
 
1533
1610
  // src/overlay/ui/screenshots-panel.tsx
1534
- import { Fragment as Fragment2, jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
1535
- function BrowserChromeBar({ theme, url = "localhost" }) {
1611
+ import { Fragment as Fragment2, jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
1612
+ function BrowserChromeBar({ theme, url = "localhost", scale = 1 }) {
1613
+ const s = scale;
1536
1614
  const colors = theme === "dark" ? { titleBar: "#1C1C1C", urlBar: "#262626", text: "#7B7B7B", border: "#333333" } : { titleBar: "#F5F5F5", urlBar: "#FFFFFF", text: "#999999", border: "#EBEBEB" };
1537
- 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: [
1538
- /* @__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)) }),
1539
- /* @__PURE__ */ jsx3(
1615
+ return /* @__PURE__ */ jsx2("div", { style: { background: colors.titleBar, flexShrink: 0, padding: `${4 * s}px ${6 * s}px` }, children: /* @__PURE__ */ jsxs2("div", { style: { display: "flex", alignItems: "center", gap: 6 * s, height: 14 * s }, children: [
1616
+ /* @__PURE__ */ jsx2("div", { style: { display: "flex", gap: 5 * s, flexShrink: 0 }, children: ["#FF5F57", "#FEBC2E", "#28C840"].map((c) => /* @__PURE__ */ jsx2("div", { style: { width: 6 * s, height: 6 * s, borderRadius: "50%", background: c } }, c)) }),
1617
+ /* @__PURE__ */ jsx2(
1540
1618
  "div",
1541
1619
  {
1542
1620
  style: {
1543
1621
  flex: 1,
1544
- height: 14,
1622
+ height: 14 * s,
1545
1623
  background: colors.urlBar,
1546
- borderRadius: 3,
1624
+ borderRadius: 3 * s,
1547
1625
  display: "flex",
1548
1626
  alignItems: "center",
1549
- paddingLeft: 6,
1627
+ paddingLeft: 6 * s,
1550
1628
  ...theme === "light" ? { border: `1px solid ${colors.border}` } : {},
1551
1629
  boxSizing: "border-box"
1552
1630
  },
1553
- children: /* @__PURE__ */ jsx3(
1631
+ children: /* @__PURE__ */ jsx2(
1554
1632
  "span",
1555
1633
  {
1556
1634
  style: {
1557
- fontSize: 7,
1635
+ fontSize: 7 * s,
1558
1636
  lineHeight: 1,
1559
1637
  color: colors.text,
1560
1638
  fontFamily: '-apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif'
@@ -1570,7 +1648,12 @@ function FramePreview({
1570
1648
  componentUrl,
1571
1649
  viewportUrl,
1572
1650
  frameSettings,
1573
- loading
1651
+ loading,
1652
+ afterbeforeMode,
1653
+ beforeUrl,
1654
+ afterUrl,
1655
+ activeSlot: activeSlotProp,
1656
+ onSlotClick
1574
1657
  }) {
1575
1658
  const { enabled: frameEnabled, browserChrome, browserTheme, browserUrl, browserShadow = 50, browserRadius = 8, size, bgType, bgColor, bgGradient, bgImage, padding } = frameSettings;
1576
1659
  const [viewportAvailable, setViewportAvailable] = useState3(true);
@@ -1580,8 +1663,9 @@ function FramePreview({
1580
1663
  const previewUrl = browserChrome && viewportUrl && viewportAvailable ? viewportUrl : componentUrl;
1581
1664
  const aspectRatio = frameEnabled ? `${size.w} / ${size.h}` : "16 / 10";
1582
1665
  const outerBg = frameEnabled ? bgType === "image" && bgImage ? { backgroundImage: `url(${bgImage})`, backgroundSize: "cover", backgroundPosition: "center" } : bgType === "gradient" ? { background: bgGradient } : { background: bgColor } : { background: bg.elevated };
1583
- const emptyState = /* @__PURE__ */ jsx3("span", { style: { ...text.paragraph.xs, color: fg.sub }, children: loading ? "Loading..." : "No screenshots yet" });
1584
- const imageEl = previewUrl ? /* @__PURE__ */ jsx3(
1666
+ const emptyState = /* @__PURE__ */ jsx2("span", { style: { ...text.paragraph.xs, color: fg.sub }, children: loading ? "Loading..." : "No screenshots yet" });
1667
+ const imageStyle = { maxWidth: "100%", maxHeight: "100%", objectFit: "contain", display: "block" };
1668
+ const imageEl = previewUrl ? /* @__PURE__ */ jsx2(
1585
1669
  "img",
1586
1670
  {
1587
1671
  src: previewUrl,
@@ -1591,20 +1675,79 @@ function FramePreview({
1591
1675
  setViewportAvailable(false);
1592
1676
  }
1593
1677
  },
1594
- style: {
1595
- maxWidth: "100%",
1596
- maxHeight: "100%",
1597
- objectFit: "contain",
1598
- display: "block"
1599
- }
1678
+ style: imageStyle
1600
1679
  }
1601
1680
  ) : null;
1602
- const padPct = frameEnabled ? `${padding / size.h * 100}%` : void 0;
1681
+ const padStyle = frameEnabled && !browserChrome ? `${padding / size.h * 100}%` : void 0;
1682
+ const chromeScale = frameEnabled ? Math.max(0.3, (size.w - padding * 2) / size.w) : 1;
1683
+ const chromeWidthPct = frameEnabled ? `${chromeScale * 100}%` : "100%";
1684
+ const chromePadY = frameEnabled ? `${padding / size.w * 100}%` : void 0;
1603
1685
  const shadowAlpha = browserShadow / 100 * 0.6;
1604
1686
  const shadowBlur = browserShadow / 100 * 40;
1605
1687
  const shadowY = browserShadow / 100 * 8;
1606
1688
  const browserBoxShadow = browserChrome && browserShadow > 0 ? `0 ${shadowY}px ${shadowBlur}px rgba(0, 0, 0, ${shadowAlpha})` : "none";
1607
- return /* @__PURE__ */ jsx3(
1689
+ if (afterbeforeMode) {
1690
+ return /* @__PURE__ */ jsx2(
1691
+ "div",
1692
+ {
1693
+ style: {
1694
+ width: "100%",
1695
+ aspectRatio: "1920 / 1080",
1696
+ borderRadius: 10,
1697
+ overflow: "hidden",
1698
+ border: `1px solid ${stroke.soft}`,
1699
+ marginBottom: 10,
1700
+ display: "flex",
1701
+ alignItems: "center",
1702
+ justifyContent: "center",
1703
+ gap: "2%",
1704
+ padding: "5%",
1705
+ ...outerBg
1706
+ },
1707
+ children: ["before", "after"].map((slot) => {
1708
+ const url = slot === "before" ? beforeUrl : afterUrl;
1709
+ const isActive = activeSlotProp === slot;
1710
+ return /* @__PURE__ */ jsxs2(
1711
+ "div",
1712
+ {
1713
+ onClick: () => onSlotClick?.(slot),
1714
+ style: {
1715
+ flex: 1,
1716
+ display: "flex",
1717
+ flexDirection: "column",
1718
+ alignItems: "center",
1719
+ gap: 8,
1720
+ cursor: "pointer"
1721
+ },
1722
+ children: [
1723
+ /* @__PURE__ */ jsx2(
1724
+ "div",
1725
+ {
1726
+ style: {
1727
+ width: "100%",
1728
+ aspectRatio: "16 / 10",
1729
+ borderRadius: 6,
1730
+ border: isActive ? `2px solid ${accent.highlight}` : url ? `1px solid ${stroke.soft}` : `2px dashed ${stroke.interactive}`,
1731
+ overflow: "hidden",
1732
+ display: "flex",
1733
+ alignItems: "center",
1734
+ justifyContent: "center",
1735
+ background: "rgba(0, 0, 0, 0.1)",
1736
+ transition: "border-color 0.12s ease"
1737
+ },
1738
+ children: url ? /* @__PURE__ */ jsx2("img", { src: url, alt: "", style: { width: "100%", height: "100%", objectFit: "contain", display: "block" } }) : /* @__PURE__ */ jsx2("span", { style: { ...text.paragraph.xs, color: fg.muted }, children: "Click to assign" })
1739
+ }
1740
+ ),
1741
+ /* @__PURE__ */ jsx2("span", { style: { ...text.paragraph.xs, color: fg.muted }, children: slot === "before" ? "Before" : "After" })
1742
+ ]
1743
+ },
1744
+ slot
1745
+ );
1746
+ })
1747
+ }
1748
+ );
1749
+ }
1750
+ return /* @__PURE__ */ jsx2(
1608
1751
  "div",
1609
1752
  {
1610
1753
  style: {
@@ -1617,24 +1760,25 @@ function FramePreview({
1617
1760
  display: "flex",
1618
1761
  flexDirection: "column",
1619
1762
  ...outerBg,
1620
- ...padPct ? { padding: padPct } : {}
1763
+ ...padStyle ? { padding: padStyle } : {}
1621
1764
  },
1622
- children: browserChrome ? /* @__PURE__ */ jsxs3("div", { style: { flex: 1, display: "flex", flexDirection: "column", borderRadius: browserRadius, overflow: "hidden", minHeight: 0, boxShadow: browserBoxShadow }, children: [
1623
- /* @__PURE__ */ jsx3(BrowserChromeBar, { theme: browserTheme, url: browserUrl }),
1624
- /* @__PURE__ */ jsx3(
1625
- "div",
1626
- {
1627
- style: {
1628
- flex: 1,
1629
- display: "flex",
1630
- alignItems: "center",
1631
- justifyContent: "center",
1632
- minHeight: 0
1633
- },
1634
- children: imageEl || emptyState
1635
- }
1636
- )
1637
- ] }) : /* @__PURE__ */ jsx3(
1765
+ children: browserChrome ? /* @__PURE__ */ jsx2(
1766
+ "div",
1767
+ {
1768
+ style: {
1769
+ display: "flex",
1770
+ flexDirection: "column",
1771
+ alignItems: "center",
1772
+ justifyContent: "center",
1773
+ flex: 1,
1774
+ minHeight: 0
1775
+ },
1776
+ children: /* @__PURE__ */ jsxs2("div", { style: { width: chromeWidthPct, display: "flex", flexDirection: "column", borderRadius: browserRadius * chromeScale, overflow: "hidden", boxShadow: browserBoxShadow }, children: [
1777
+ /* @__PURE__ */ jsx2(BrowserChromeBar, { theme: browserTheme, url: browserUrl, scale: chromeScale }),
1778
+ /* @__PURE__ */ jsx2("div", { style: { display: "flex", alignItems: "center", justifyContent: "center" }, children: imageEl || emptyState })
1779
+ ] })
1780
+ }
1781
+ ) : /* @__PURE__ */ jsx2(
1638
1782
  "div",
1639
1783
  {
1640
1784
  style: {
@@ -1685,11 +1829,29 @@ function ScreenshotsPanel({
1685
1829
  const [editingFile, setEditingFile] = useState3(null);
1686
1830
  const [editValue, setEditValue] = useState3("");
1687
1831
  const [selectedFile, setSelectedFile] = useState3(null);
1832
+ const [beforeFile, setBeforeFile] = useState3(null);
1833
+ const [afterFile, setAfterFile] = useState3(null);
1834
+ const [activeSlot, setActiveSlot] = useState3("before");
1835
+ useEffect2(() => {
1836
+ if (!frameSettings.afterbefore) {
1837
+ setBeforeFile(null);
1838
+ setAfterFile(null);
1839
+ setActiveSlot("before");
1840
+ }
1841
+ }, [frameSettings.afterbefore]);
1688
1842
  useEffect2(() => {
1689
1843
  if (!open) return;
1690
1844
  fetch("/__afterbefore/config").then((r) => r.json()).then((data) => setSaveDir(data.saveDir)).catch(() => {
1691
1845
  });
1692
1846
  }, [open]);
1847
+ useEffect2(() => {
1848
+ if (!open) return;
1849
+ const prev = document.body.style.overflow;
1850
+ document.body.style.overflow = "hidden";
1851
+ return () => {
1852
+ document.body.style.overflow = prev;
1853
+ };
1854
+ }, [open]);
1693
1855
  const handlePickFolder = async () => {
1694
1856
  setPicking(true);
1695
1857
  try {
@@ -1792,12 +1954,74 @@ function ScreenshotsPanel({
1792
1954
  showToast("Delete failed", "error");
1793
1955
  }
1794
1956
  };
1957
+ const [saving, setSaving] = useState3(false);
1958
+ const toDataUrl = async (url) => {
1959
+ const r = await fetch(url);
1960
+ if (!r.ok) throw new Error("Failed to load image");
1961
+ const b = await r.blob();
1962
+ return new Promise((resolve) => {
1963
+ const reader = new FileReader();
1964
+ reader.onloadend = () => resolve(reader.result);
1965
+ reader.readAsDataURL(b);
1966
+ });
1967
+ };
1968
+ const makeImageUrl = (file) => `/__afterbefore/history/image?repo=${encodeURIComponent(selectedRepo || "")}&branch=${encodeURIComponent(selectedBranch || "")}&file=${encodeURIComponent(file)}`;
1969
+ const handleSave = async () => {
1970
+ if (saving) return;
1971
+ if (frameSettings.afterbefore) {
1972
+ if (!beforeFile || !afterFile) return;
1973
+ setSaving(true);
1974
+ try {
1975
+ const beforeDataUrl = await toDataUrl(makeImageUrl(beforeFile));
1976
+ const afterDataUrl = await toDataUrl(makeImageUrl(afterFile));
1977
+ const renderedDataUrl = await renderAfterbeforeFrame(beforeDataUrl, afterDataUrl, frameSettings);
1978
+ const saveRes = await fetch("/__afterbefore/save", {
1979
+ method: "POST",
1980
+ headers: { "Content-Type": "application/json" },
1981
+ body: JSON.stringify({ mode: "resave", image: renderedDataUrl })
1982
+ });
1983
+ if (!saveRes.ok) throw new Error("Save failed");
1984
+ showToast("Saved to desktop", "success");
1985
+ } catch (err) {
1986
+ console.error("[afterbefore] Comparison save failed:", err);
1987
+ showToast("Save failed", "error");
1988
+ } finally {
1989
+ setSaving(false);
1990
+ }
1991
+ return;
1992
+ }
1993
+ if (!selectedFile) return;
1994
+ setSaving(true);
1995
+ try {
1996
+ const baseUrl = makeImageUrl(selectedFile);
1997
+ const rawDataUrl = await toDataUrl(baseUrl);
1998
+ let primaryDataUrl = rawDataUrl;
1999
+ if (frameSettings.browserChrome) {
2000
+ try {
2001
+ primaryDataUrl = await toDataUrl(`${baseUrl}&variant=viewport`);
2002
+ } catch {
2003
+ }
2004
+ }
2005
+ const renderedDataUrl = await renderFrame(primaryDataUrl, frameSettings);
2006
+ const saveRes = await fetch("/__afterbefore/save", {
2007
+ method: "POST",
2008
+ headers: { "Content-Type": "application/json" },
2009
+ body: JSON.stringify({ mode: "resave", image: renderedDataUrl, rawImage: rawDataUrl })
2010
+ });
2011
+ if (!saveRes.ok) throw new Error("Save failed");
2012
+ showToast("Saved to desktop", "success");
2013
+ } catch {
2014
+ showToast("Save failed", "error");
2015
+ } finally {
2016
+ setSaving(false);
2017
+ }
2018
+ };
1795
2019
  const baseImageUrl = selectedFile ? `/__afterbefore/history/image?repo=${encodeURIComponent(selectedRepo || "")}&branch=${encodeURIComponent(selectedBranch || "")}&file=${encodeURIComponent(selectedFile)}` : null;
1796
2020
  const viewportUrl = baseImageUrl ? `${baseImageUrl}&variant=viewport` : null;
1797
- return /* @__PURE__ */ jsxs3(Fragment2, { children: [
1798
- /* @__PURE__ */ jsx3(IconButton, { active: open, tooltipSide, tooltip: !open ? "Screenshots" : void 0, onClick, children: /* @__PURE__ */ jsx3(ImageIcon, { size: 20 }) }),
2021
+ return /* @__PURE__ */ jsxs2(Fragment2, { children: [
2022
+ /* @__PURE__ */ jsx2(IconButton, { active: open, tooltipSide, tooltip: !open ? "Screenshots" : void 0, onClick, children: /* @__PURE__ */ jsx2(ImageIcon, { size: 18, strokeWidth: 1.7 }) }),
1799
2023
  open && createPortal(
1800
- /* @__PURE__ */ jsxs3(
2024
+ /* @__PURE__ */ jsxs2(
1801
2025
  "div",
1802
2026
  {
1803
2027
  "data-afterbefore": "true",
@@ -1813,7 +2037,7 @@ function ScreenshotsPanel({
1813
2037
  ...fontBase
1814
2038
  },
1815
2039
  children: [
1816
- /* @__PURE__ */ jsx3(
2040
+ /* @__PURE__ */ jsx2(
1817
2041
  "style",
1818
2042
  {
1819
2043
  dangerouslySetInnerHTML: {
@@ -1826,13 +2050,13 @@ function ScreenshotsPanel({
1826
2050
  }
1827
2051
  }
1828
2052
  ),
1829
- /* @__PURE__ */ jsxs3(
2053
+ /* @__PURE__ */ jsxs2(
1830
2054
  "div",
1831
2055
  {
1832
2056
  onClick: (e) => e.stopPropagation(),
1833
2057
  style: {
1834
2058
  width: 900,
1835
- height: "70vh",
2059
+ height: "60vh",
1836
2060
  borderRadius: 14,
1837
2061
  background: bg.base,
1838
2062
  border: `1px solid ${stroke.default}`,
@@ -1843,18 +2067,18 @@ function ScreenshotsPanel({
1843
2067
  animation: "ab-panel-in 150ms cubic-bezier(0.23, 1, 0.32, 1)"
1844
2068
  },
1845
2069
  children: [
1846
- /* @__PURE__ */ jsxs3("div", { style: {
2070
+ /* @__PURE__ */ jsxs2("div", { style: {
1847
2071
  display: "flex",
1848
2072
  alignItems: "center",
1849
2073
  justifyContent: "space-between",
1850
2074
  padding: "16px 20px",
1851
2075
  borderBottom: `1px solid ${stroke.soft}`
1852
2076
  }, children: [
1853
- /* @__PURE__ */ jsxs3("div", { children: [
1854
- /* @__PURE__ */ jsx3("div", { style: { ...text.label.sm, color: fg.strong }, children: "Screenshots" }),
1855
- /* @__PURE__ */ jsx3("div", { style: { ...text.paragraph.xs, color: fg.sub, marginTop: 2 }, children: "Capture history & settings" })
2077
+ /* @__PURE__ */ jsxs2("div", { children: [
2078
+ /* @__PURE__ */ jsx2("div", { style: { ...text.label.sm, color: fg.strong }, children: "Screenshots" }),
2079
+ /* @__PURE__ */ jsx2("div", { style: { ...text.paragraph.xs, color: fg.sub, marginTop: 2 }, children: "Capture history & settings" })
1856
2080
  ] }),
1857
- /* @__PURE__ */ jsx3(
2081
+ /* @__PURE__ */ jsx2(
1858
2082
  "button",
1859
2083
  {
1860
2084
  onClick,
@@ -1880,28 +2104,33 @@ function ScreenshotsPanel({
1880
2104
  e.currentTarget.style.background = "transparent";
1881
2105
  e.currentTarget.style.color = fg.sub;
1882
2106
  },
1883
- children: /* @__PURE__ */ jsx3(X, { size: 14, strokeWidth: 2 })
2107
+ children: /* @__PURE__ */ jsx2(X, { size: 14, strokeWidth: 2 })
1884
2108
  }
1885
2109
  )
1886
2110
  ] }),
1887
- /* @__PURE__ */ jsxs3("div", { style: { display: "flex", flex: 1, overflow: "hidden", minHeight: 0 }, children: [
1888
- /* @__PURE__ */ jsxs3("div", { style: { flex: 1, overflowY: "auto", padding: "16px 20px 20px", minHeight: 0 }, children: [
1889
- /* @__PURE__ */ jsx3(
2111
+ /* @__PURE__ */ jsxs2("div", { style: { display: "flex", flex: 1, overflow: "hidden", minHeight: 0, position: "relative" }, children: [
2112
+ /* @__PURE__ */ jsxs2("div", { style: { flex: 1, overflowY: "auto", padding: "16px 20px 20px", minHeight: 0, marginRight: 301 }, children: [
2113
+ /* @__PURE__ */ jsx2(
1890
2114
  FramePreview,
1891
2115
  {
1892
2116
  componentUrl: baseImageUrl,
1893
2117
  viewportUrl,
1894
2118
  frameSettings,
1895
- loading
2119
+ loading,
2120
+ afterbeforeMode: frameSettings.afterbefore,
2121
+ beforeUrl: beforeFile ? `/__afterbefore/history/image?repo=${encodeURIComponent(selectedRepo || "")}&branch=${encodeURIComponent(selectedBranch || "")}&file=${encodeURIComponent(beforeFile)}` : null,
2122
+ afterUrl: afterFile ? `/__afterbefore/history/image?repo=${encodeURIComponent(selectedRepo || "")}&branch=${encodeURIComponent(selectedBranch || "")}&file=${encodeURIComponent(afterFile)}` : null,
2123
+ activeSlot,
2124
+ onSlotClick: setActiveSlot
1896
2125
  }
1897
2126
  ),
1898
- selectedFile && !loading && /* @__PURE__ */ jsxs3("div", { style: {
2127
+ !frameSettings.afterbefore && selectedFile && !loading && /* @__PURE__ */ jsxs2("div", { style: {
1899
2128
  display: "flex",
1900
2129
  alignItems: "center",
1901
2130
  gap: 8,
1902
2131
  marginBottom: 12
1903
2132
  }, children: [
1904
- /* @__PURE__ */ jsx3("div", { style: { flex: 1, minWidth: 0 }, children: editingFile === selectedFile ? /* @__PURE__ */ jsx3(
2133
+ /* @__PURE__ */ jsx2("div", { style: { flex: 1, minWidth: 0 }, children: editingFile === selectedFile ? /* @__PURE__ */ jsx2(
1905
2134
  "input",
1906
2135
  {
1907
2136
  autoFocus: true,
@@ -1924,7 +2153,7 @@ function ScreenshotsPanel({
1924
2153
  fontFamily: "inherit"
1925
2154
  }
1926
2155
  }
1927
- ) : /* @__PURE__ */ jsx3(
2156
+ ) : /* @__PURE__ */ jsx2(
1928
2157
  "div",
1929
2158
  {
1930
2159
  onClick: () => {
@@ -1943,7 +2172,7 @@ function ScreenshotsPanel({
1943
2172
  children: formatTimestamp(selectedFile)
1944
2173
  }
1945
2174
  ) }),
1946
- /* @__PURE__ */ jsx3(
2175
+ /* @__PURE__ */ jsx2(
1947
2176
  "button",
1948
2177
  {
1949
2178
  onClick: () => handleDelete(selectedFile),
@@ -1970,12 +2199,12 @@ function ScreenshotsPanel({
1970
2199
  e.currentTarget.style.color = fg.muted;
1971
2200
  e.currentTarget.style.background = "transparent";
1972
2201
  },
1973
- children: /* @__PURE__ */ jsx3(Trash22, { size: 14, strokeWidth: 1.8 })
2202
+ children: /* @__PURE__ */ jsx2(Trash22, { size: 14, strokeWidth: 1.8 })
1974
2203
  }
1975
2204
  )
1976
2205
  ] }),
1977
- /* @__PURE__ */ jsxs3("div", { style: { display: "flex", gap: 10, marginBottom: 10 }, children: [
1978
- /* @__PURE__ */ jsx3("div", { style: { flex: 1, position: "relative" }, children: /* @__PURE__ */ jsx3(
2206
+ /* @__PURE__ */ jsxs2("div", { style: { display: "flex", gap: 10, marginBottom: 10 }, children: [
2207
+ /* @__PURE__ */ jsx2("div", { style: { flex: 1, position: "relative" }, children: /* @__PURE__ */ jsx2(
1979
2208
  FilterDropdown,
1980
2209
  {
1981
2210
  label: "Project",
@@ -1993,7 +2222,7 @@ function ScreenshotsPanel({
1993
2222
  }
1994
2223
  }
1995
2224
  ) }),
1996
- /* @__PURE__ */ jsx3("div", { style: { flex: 1, position: "relative" }, children: /* @__PURE__ */ jsx3(
2225
+ /* @__PURE__ */ jsx2("div", { style: { flex: 1, position: "relative" }, children: /* @__PURE__ */ jsx2(
1997
2226
  FilterDropdown,
1998
2227
  {
1999
2228
  label: "Branch",
@@ -2011,7 +2240,7 @@ function ScreenshotsPanel({
2011
2240
  }
2012
2241
  ) })
2013
2242
  ] }),
2014
- screenshots.length > 0 && /* @__PURE__ */ jsx3(
2243
+ screenshots.length > 0 && /* @__PURE__ */ jsx2(
2015
2244
  "div",
2016
2245
  {
2017
2246
  style: {
@@ -2023,12 +2252,25 @@ function ScreenshotsPanel({
2023
2252
  },
2024
2253
  children: screenshots.map((shot) => {
2025
2254
  const thumbUrl = `/__afterbefore/history/image?repo=${encodeURIComponent(selectedRepo || "")}&branch=${encodeURIComponent(selectedBranch || "")}&file=${encodeURIComponent(shot.filename)}`;
2026
- const isSelected = selectedFile === shot.filename;
2027
- return /* @__PURE__ */ jsx3(
2255
+ const isSelected = frameSettings.afterbefore ? shot.filename === beforeFile || shot.filename === afterFile : selectedFile === shot.filename;
2256
+ const slotBadge = frameSettings.afterbefore ? shot.filename === beforeFile ? "B" : shot.filename === afterFile ? "A" : null : null;
2257
+ return /* @__PURE__ */ jsxs2(
2028
2258
  "div",
2029
2259
  {
2030
- onClick: () => setSelectedFile(shot.filename),
2260
+ onClick: () => {
2261
+ if (frameSettings.afterbefore) {
2262
+ if (activeSlot === "before") {
2263
+ setBeforeFile(shot.filename);
2264
+ setActiveSlot("after");
2265
+ } else {
2266
+ setAfterFile(shot.filename);
2267
+ }
2268
+ } else {
2269
+ setSelectedFile(shot.filename);
2270
+ }
2271
+ },
2031
2272
  style: {
2273
+ position: "relative",
2032
2274
  width: 64,
2033
2275
  height: 44,
2034
2276
  flexShrink: 0,
@@ -2039,19 +2281,36 @@ function ScreenshotsPanel({
2039
2281
  opacity: isSelected ? 1 : 0.7,
2040
2282
  transition: "opacity 0.12s ease, border-color 0.12s ease"
2041
2283
  },
2042
- children: /* @__PURE__ */ jsx3(
2043
- "img",
2044
- {
2045
- src: thumbUrl,
2046
- alt: "",
2047
- style: {
2048
- width: "100%",
2049
- height: "100%",
2050
- objectFit: "cover",
2051
- display: "block"
2284
+ children: [
2285
+ /* @__PURE__ */ jsx2(
2286
+ "img",
2287
+ {
2288
+ src: thumbUrl,
2289
+ alt: "",
2290
+ style: {
2291
+ width: "100%",
2292
+ height: "100%",
2293
+ objectFit: "cover",
2294
+ display: "block"
2295
+ }
2052
2296
  }
2053
- }
2054
- )
2297
+ ),
2298
+ slotBadge && /* @__PURE__ */ jsx2("div", { style: {
2299
+ position: "absolute",
2300
+ top: 2,
2301
+ left: 2,
2302
+ width: 16,
2303
+ height: 16,
2304
+ borderRadius: 4,
2305
+ background: accent.highlight,
2306
+ color: "#fff",
2307
+ fontSize: 9,
2308
+ fontWeight: 600,
2309
+ display: "flex",
2310
+ alignItems: "center",
2311
+ justifyContent: "center"
2312
+ }, children: slotBadge })
2313
+ ]
2055
2314
  },
2056
2315
  shot.filename
2057
2316
  );
@@ -2059,59 +2318,46 @@ function ScreenshotsPanel({
2059
2318
  }
2060
2319
  )
2061
2320
  ] }),
2062
- /* @__PURE__ */ jsx3("div", { style: { width: 1, flexShrink: 0, background: stroke.soft } }),
2063
- /* @__PURE__ */ jsxs3("div", { style: { width: 300, flexShrink: 0, overflowY: "auto", padding: "16px 16px 12px", minHeight: 0, display: "flex", flexDirection: "column" }, children: [
2064
- /* @__PURE__ */ jsx3("div", { style: { flex: 1 }, children: /* @__PURE__ */ jsx3(
2065
- SettingsContent,
2066
- {
2067
- frameSettings,
2068
- onFrameSettingsChange,
2069
- saveDir: shortDir,
2070
- picking,
2071
- onPickFolder: handlePickFolder
2072
- }
2073
- ) }),
2074
- /* @__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: [
2075
- /* @__PURE__ */ jsx3("span", { style: { ...text.paragraph.xs, color: fg.muted }, children: "made by Paulius Kairevicius" }),
2076
- /* @__PURE__ */ jsx3(
2077
- "a",
2321
+ /* @__PURE__ */ jsxs2("div", { style: { position: "absolute", top: 0, right: 0, bottom: 0, width: 301, display: "flex", overflow: "hidden" }, children: [
2322
+ /* @__PURE__ */ jsx2("div", { style: { width: 1, flexShrink: 0, background: stroke.soft } }),
2323
+ /* @__PURE__ */ jsxs2("div", { style: { flex: 1, display: "flex", flexDirection: "column", overflow: "hidden" }, children: [
2324
+ /* @__PURE__ */ jsx2("div", { style: { flex: 1, overflowY: "auto", padding: "16px 16px 12px", minHeight: 0 }, children: /* @__PURE__ */ jsx2(
2325
+ SettingsContent,
2078
2326
  {
2079
- href: "https://github.com/kairevicius",
2080
- target: "_blank",
2081
- rel: "noopener noreferrer",
2082
- style: { color: fg.muted, display: "flex", transition: "color 0.12s" },
2083
- onMouseEnter: (e) => {
2084
- e.currentTarget.style.color = fg.strong;
2085
- },
2086
- onMouseLeave: (e) => {
2087
- e.currentTarget.style.color = fg.muted;
2088
- },
2089
- 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" }) })
2327
+ frameSettings,
2328
+ onFrameSettingsChange,
2329
+ saveDir: shortDir,
2330
+ picking,
2331
+ onPickFolder: handlePickFolder
2090
2332
  }
2091
- ),
2092
- /* @__PURE__ */ jsx3(
2093
- "a",
2333
+ ) }),
2334
+ (frameSettings.afterbefore ? beforeFile && afterFile : selectedFile) && /* @__PURE__ */ jsx2("div", { style: { flexShrink: 0, padding: "12px 16px", borderTop: `1px solid ${stroke.soft}` }, children: /* @__PURE__ */ jsx2(
2335
+ "button",
2094
2336
  {
2095
- href: "https://x.com/kairevicius",
2096
- target: "_blank",
2097
- rel: "noopener noreferrer",
2098
- style: { color: fg.muted, display: "flex", transition: "color 0.12s" },
2099
- onMouseEnter: (e) => {
2100
- e.currentTarget.style.color = fg.strong;
2101
- },
2102
- onMouseLeave: (e) => {
2103
- e.currentTarget.style.color = fg.muted;
2337
+ onClick: handleSave,
2338
+ disabled: saving,
2339
+ style: {
2340
+ width: "100%",
2341
+ padding: "8px 0",
2342
+ borderRadius: 8,
2343
+ border: "none",
2344
+ background: accent.toggle,
2345
+ color: "#fff",
2346
+ ...text.label.xs,
2347
+ cursor: saving ? "default" : "pointer",
2348
+ opacity: saving ? 0.6 : 1,
2349
+ transition: "opacity 0.12s ease"
2104
2350
  },
2105
- 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" }) })
2351
+ children: saving ? "Saving..." : "Save"
2106
2352
  }
2107
- )
2353
+ ) })
2108
2354
  ] })
2109
2355
  ] })
2110
2356
  ] })
2111
2357
  ]
2112
2358
  }
2113
2359
  ),
2114
- toast && /* @__PURE__ */ jsx3(
2360
+ toast && /* @__PURE__ */ jsx2(
2115
2361
  "div",
2116
2362
  {
2117
2363
  onClick: (e) => e.stopPropagation(),
@@ -2140,7 +2386,7 @@ function ScreenshotsPanel({
2140
2386
  }
2141
2387
 
2142
2388
  // src/overlay/ui/toolbar.tsx
2143
- import { Fragment as Fragment3, jsx as jsx4, jsxs as jsxs4 } from "react/jsx-runtime";
2389
+ import { Fragment as Fragment3, jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
2144
2390
  var TOOLTIP_DELAY = 500;
2145
2391
  var TOOLTIP_COOLDOWN = 300;
2146
2392
  var tooltipWarm = false;
@@ -2224,11 +2470,6 @@ function getCornerPosition(corner, w, h) {
2224
2470
  return { x: EDGE_MARGIN, y: EDGE_MARGIN };
2225
2471
  }
2226
2472
  }
2227
- var MODES = [
2228
- { mode: "component", label: "Component", icon: CameraIcon },
2229
- { mode: "viewport", label: "Viewport", icon: Monitor },
2230
- { mode: "fullpage", label: "Full Page", icon: FileText }
2231
- ];
2232
2473
  function Toolbar({
2233
2474
  expanded,
2234
2475
  onToggle,
@@ -2243,6 +2484,16 @@ function Toolbar({
2243
2484
  }) {
2244
2485
  const [historyOpen, setHistoryOpen] = useState4(false);
2245
2486
  const [modesExpanded, setModesExpanded] = useState4(false);
2487
+ useEffect3(() => {
2488
+ if (phase === "ready") {
2489
+ try {
2490
+ if (localStorage.getItem("ab-auto-open-panel") !== "false") {
2491
+ setHistoryOpen(true);
2492
+ }
2493
+ } catch {
2494
+ }
2495
+ }
2496
+ }, [phase]);
2246
2497
  const [buttonsVisible, setButtonsVisible] = useState4(expanded);
2247
2498
  const [animIn, setAnimIn] = useState4(expanded);
2248
2499
  const [animDone, setAnimDone] = useState4(expanded);
@@ -2388,8 +2639,8 @@ function Toolbar({
2388
2639
  } : getCornerStyle(corner);
2389
2640
  const cameraTooltipLabel = expanded ? "Close" : void 0;
2390
2641
  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;
2391
- const cameraButton = /* @__PURE__ */ jsxs4("div", { style: { position: "relative" }, children: [
2392
- cameraTooltipLabel && cameraTooltipVisible && !dragging && /* @__PURE__ */ jsx4(
2642
+ const cameraButton = /* @__PURE__ */ jsxs3("div", { style: { position: "relative" }, children: [
2643
+ cameraTooltipLabel && cameraTooltipVisible && !dragging && /* @__PURE__ */ jsx3(
2393
2644
  "div",
2394
2645
  {
2395
2646
  style: {
@@ -2411,7 +2662,7 @@ function Toolbar({
2411
2662
  children: cameraTooltipLabel
2412
2663
  }
2413
2664
  ),
2414
- /* @__PURE__ */ jsxs4(
2665
+ /* @__PURE__ */ jsxs3(
2415
2666
  "div",
2416
2667
  {
2417
2668
  onMouseDown: handleMouseDown,
@@ -2430,7 +2681,7 @@ function Toolbar({
2430
2681
  transition: "background 0.12s ease"
2431
2682
  },
2432
2683
  children: [
2433
- /* @__PURE__ */ jsx4(
2684
+ /* @__PURE__ */ jsx3(
2434
2685
  "style",
2435
2686
  {
2436
2687
  dangerouslySetInnerHTML: {
@@ -2447,23 +2698,25 @@ function Toolbar({
2447
2698
  }
2448
2699
  }
2449
2700
  ),
2450
- loading ? /* @__PURE__ */ jsx4(
2701
+ loading ? /* @__PURE__ */ jsx3(
2451
2702
  LoaderCircle,
2452
2703
  {
2453
2704
  size: 16,
2454
2705
  strokeWidth: 2,
2455
2706
  style: { animation: "ab-spin 0.8s linear infinite", color: "white" }
2456
2707
  }
2457
- ) : phase === "ready" ? /* @__PURE__ */ jsx4(CheckmarkIcon, { size: 20, color: fg.default }) : expanded ? /* @__PURE__ */ jsx4(
2458
- CloseIcon,
2708
+ ) : phase === "ready" ? /* @__PURE__ */ jsx3(Check2, { size: 18, strokeWidth: 1.7, color: fg.default }) : expanded ? /* @__PURE__ */ jsx3(
2709
+ X2,
2459
2710
  {
2460
- size: 20,
2711
+ size: 18,
2712
+ strokeWidth: 1.7,
2461
2713
  color: cameraHovered ? fg.strong : fg.default
2462
2714
  }
2463
- ) : /* @__PURE__ */ jsx4(
2464
- CameraIcon,
2715
+ ) : /* @__PURE__ */ jsx3(
2716
+ Camera,
2465
2717
  {
2466
- size: 20,
2718
+ size: 18,
2719
+ strokeWidth: 1.7,
2467
2720
  color: cameraHovered ? fg.strong : fg.default
2468
2721
  }
2469
2722
  )
@@ -2471,7 +2724,7 @@ function Toolbar({
2471
2724
  }
2472
2725
  )
2473
2726
  ] });
2474
- const toolbarButtons = buttonsVisible ? /* @__PURE__ */ jsx4(
2727
+ const toolbarButtons = buttonsVisible ? /* @__PURE__ */ jsx3(
2475
2728
  "div",
2476
2729
  {
2477
2730
  style: {
@@ -2479,7 +2732,7 @@ function Toolbar({
2479
2732
  maxHeight: animIn ? 195 : 0,
2480
2733
  transition: animIn ? "max-height 250ms cubic-bezier(0.23, 1, 0.32, 1)" : "max-height 150ms cubic-bezier(0.23, 1, 0.32, 1)"
2481
2734
  },
2482
- children: /* @__PURE__ */ jsxs4(
2735
+ children: /* @__PURE__ */ jsxs3(
2483
2736
  "div",
2484
2737
  {
2485
2738
  style: {
@@ -2492,7 +2745,7 @@ function Toolbar({
2492
2745
  willChange: "transform, opacity"
2493
2746
  },
2494
2747
  children: [
2495
- /* @__PURE__ */ jsx4("div", { style: { paddingBottom: 2 }, children: /* @__PURE__ */ jsx4(
2748
+ /* @__PURE__ */ jsx3("div", { style: { paddingBottom: 2 }, children: /* @__PURE__ */ jsx3(
2496
2749
  IconButton,
2497
2750
  {
2498
2751
  active: selectedMode === "component" && !historyOpen,
@@ -2502,42 +2755,10 @@ function Toolbar({
2502
2755
  setHistoryOpen(false);
2503
2756
  onModeChange("component");
2504
2757
  },
2505
- children: /* @__PURE__ */ jsx4(CameraIcon, { size: 20 })
2758
+ children: /* @__PURE__ */ jsx3(Camera, { size: 18, strokeWidth: 1.7 })
2506
2759
  }
2507
2760
  ) }),
2508
- MODES.filter((m) => m.mode !== "component").map(({ mode, label, icon: ModeIcon }) => /* @__PURE__ */ jsx4(
2509
- "div",
2510
- {
2511
- style: {
2512
- maxHeight: modesExpanded ? 34 : 0,
2513
- opacity: modesExpanded ? 1 : 0,
2514
- transition: "max-height 200ms cubic-bezier(0.23, 1, 0.32, 1), opacity 150ms cubic-bezier(0.23, 1, 0.32, 1)"
2515
- },
2516
- children: /* @__PURE__ */ jsx4(
2517
- IconButton,
2518
- {
2519
- active: selectedMode === mode && !historyOpen,
2520
- tooltipSide,
2521
- tooltip: label,
2522
- onClick: () => {
2523
- setHistoryOpen(false);
2524
- onModeChange(mode);
2525
- onCapture(mode);
2526
- },
2527
- children: /* @__PURE__ */ jsx4(ModeIcon, { size: 16, strokeWidth: 1.7 })
2528
- }
2529
- )
2530
- },
2531
- mode
2532
- )),
2533
- /* @__PURE__ */ jsx4(
2534
- Separator,
2535
- {
2536
- vertical: false,
2537
- onClick: () => setModesExpanded((p) => !p)
2538
- }
2539
- ),
2540
- /* @__PURE__ */ jsx4(
2761
+ /* @__PURE__ */ jsx3(
2541
2762
  ScreenshotsPanel,
2542
2763
  {
2543
2764
  open: historyOpen,
@@ -2555,7 +2776,7 @@ function Toolbar({
2555
2776
  )
2556
2777
  }
2557
2778
  ) : null;
2558
- return /* @__PURE__ */ jsx4(
2779
+ return /* @__PURE__ */ jsx3(
2559
2780
  "div",
2560
2781
  {
2561
2782
  ref: toolbarRef,
@@ -2574,10 +2795,10 @@ function Toolbar({
2574
2795
  ...fontBase,
2575
2796
  userSelect: "none"
2576
2797
  },
2577
- children: bottom ? /* @__PURE__ */ jsxs4(Fragment3, { children: [
2798
+ children: bottom ? /* @__PURE__ */ jsxs3(Fragment3, { children: [
2578
2799
  toolbarButtons,
2579
2800
  cameraButton
2580
- ] }) : /* @__PURE__ */ jsxs4(Fragment3, { children: [
2801
+ ] }) : /* @__PURE__ */ jsxs3(Fragment3, { children: [
2581
2802
  cameraButton,
2582
2803
  toolbarButtons
2583
2804
  ] })
@@ -2602,8 +2823,8 @@ function IconButton({
2602
2823
  top: "50%",
2603
2824
  transform: "translateY(-50%)"
2604
2825
  };
2605
- return /* @__PURE__ */ jsxs4("div", { style: { position: "relative" }, children: [
2606
- tooltip && tooltipVisible && /* @__PURE__ */ jsx4(
2826
+ return /* @__PURE__ */ jsxs3("div", { style: { position: "relative" }, children: [
2827
+ tooltip && tooltipVisible && /* @__PURE__ */ jsx3(
2607
2828
  "div",
2608
2829
  {
2609
2830
  style: {
@@ -2625,7 +2846,7 @@ function IconButton({
2625
2846
  children: tooltip
2626
2847
  }
2627
2848
  ),
2628
- /* @__PURE__ */ jsx4(
2849
+ /* @__PURE__ */ jsx3(
2629
2850
  "button",
2630
2851
  {
2631
2852
  onClick,
@@ -2656,7 +2877,7 @@ function DropItem2({
2656
2877
  active,
2657
2878
  accent: accent2
2658
2879
  }) {
2659
- return /* @__PURE__ */ jsx4(
2880
+ return /* @__PURE__ */ jsx3(
2660
2881
  "button",
2661
2882
  {
2662
2883
  onClick,
@@ -2676,27 +2897,6 @@ function DropItem2({
2676
2897
  }
2677
2898
  );
2678
2899
  }
2679
- function Separator({
2680
- vertical = true,
2681
- onClick
2682
- }) {
2683
- return /* @__PURE__ */ jsx4(
2684
- "div",
2685
- {
2686
- onClick,
2687
- style: {
2688
- position: "relative",
2689
- width: vertical ? 1 : 24,
2690
- height: vertical ? 18 : 1,
2691
- background: stroke.strong,
2692
- flexShrink: 0,
2693
- margin: vertical ? "0 6px" : "6px 0",
2694
- cursor: onClick ? "pointer" : void 0
2695
- },
2696
- children: onClick && /* @__PURE__ */ jsx4("div", { style: { position: "absolute", inset: vertical ? "0 -8px" : "-8px 0" } })
2697
- }
2698
- );
2699
- }
2700
2900
  function FilterDropdown({
2701
2901
  label,
2702
2902
  value,
@@ -2705,20 +2905,20 @@ function FilterDropdown({
2705
2905
  onToggle,
2706
2906
  onSelect
2707
2907
  }) {
2708
- return /* @__PURE__ */ jsxs4("div", { children: [
2709
- /* @__PURE__ */ jsx4(
2908
+ return /* @__PURE__ */ jsxs3("div", { children: [
2909
+ /* @__PURE__ */ jsx3(
2710
2910
  "div",
2711
2911
  {
2712
2912
  style: {
2713
- ...text.subheading.xxs,
2913
+ ...text.label.xs,
2714
2914
  color: fg.sub,
2715
2915
  marginBottom: 3
2716
2916
  },
2717
2917
  children: label
2718
2918
  }
2719
2919
  ),
2720
- /* @__PURE__ */ jsxs4("div", { style: { position: "relative" }, children: [
2721
- /* @__PURE__ */ jsxs4(
2920
+ /* @__PURE__ */ jsxs3("div", { style: { position: "relative" }, children: [
2921
+ /* @__PURE__ */ jsxs3(
2722
2922
  "button",
2723
2923
  {
2724
2924
  onClick: onToggle,
@@ -2739,7 +2939,7 @@ function FilterDropdown({
2739
2939
  fontFamily: "inherit"
2740
2940
  },
2741
2941
  children: [
2742
- /* @__PURE__ */ jsx4(
2942
+ /* @__PURE__ */ jsx3(
2743
2943
  "span",
2744
2944
  {
2745
2945
  style: {
@@ -2750,11 +2950,11 @@ function FilterDropdown({
2750
2950
  children: value || "\u2014"
2751
2951
  }
2752
2952
  ),
2753
- /* @__PURE__ */ jsx4(ChevronDown2, { size: 12, strokeWidth: 2 })
2953
+ /* @__PURE__ */ jsx3(ChevronDown2, { size: 12, strokeWidth: 2 })
2754
2954
  ]
2755
2955
  }
2756
2956
  ),
2757
- isOpen && options.length > 0 && /* @__PURE__ */ jsx4(
2957
+ isOpen && options.length > 0 && /* @__PURE__ */ jsx3(
2758
2958
  "div",
2759
2959
  {
2760
2960
  style: {
@@ -2771,7 +2971,7 @@ function FilterDropdown({
2771
2971
  boxShadow: shadow.dropdown,
2772
2972
  zIndex: 1
2773
2973
  },
2774
- children: options.map((opt) => /* @__PURE__ */ jsx4(
2974
+ children: options.map((opt) => /* @__PURE__ */ jsx3(
2775
2975
  DropItem2,
2776
2976
  {
2777
2977
  active: opt === value,
@@ -2788,7 +2988,7 @@ function FilterDropdown({
2788
2988
 
2789
2989
  // src/overlay/ui/inspector.tsx
2790
2990
  import { useEffect as useEffect4, useRef as useRef3, useCallback as useCallback4, useState as useState5 } from "react";
2791
- import { jsx as jsx5 } from "react/jsx-runtime";
2991
+ import { jsx as jsx4 } from "react/jsx-runtime";
2792
2992
  function Inspector({ onSelect, onCancel }) {
2793
2993
  const [highlight, setHighlight] = useState5(null);
2794
2994
  const hoveredEl = useRef3(null);
@@ -2865,7 +3065,7 @@ function Inspector({ onSelect, onCancel }) {
2865
3065
  document.removeEventListener("keydown", handleKeyDown);
2866
3066
  };
2867
3067
  }, [handleMouseMove, handleClick, handleKeyDown]);
2868
- return /* @__PURE__ */ jsx5("div", { "data-afterbefore": "true", style: { position: "fixed", inset: 0, zIndex: 2147483646, pointerEvents: "none" }, children: highlight && /* @__PURE__ */ jsx5(
3068
+ return /* @__PURE__ */ jsx4("div", { "data-afterbefore": "true", style: { position: "fixed", inset: 0, zIndex: 2147483646, pointerEvents: "none" }, children: highlight && /* @__PURE__ */ jsx4(
2869
3069
  "div",
2870
3070
  {
2871
3071
  style: {
@@ -2885,7 +3085,7 @@ function Inspector({ onSelect, onCancel }) {
2885
3085
  }
2886
3086
 
2887
3087
  // src/overlay/index.tsx
2888
- import { jsx as jsx6, jsxs as jsxs5 } from "react/jsx-runtime";
3088
+ import { jsx as jsx5, jsxs as jsxs4 } from "react/jsx-runtime";
2889
3089
  async function saveCapture(mode, renderedDataUrl, rawDataUrl, viewportDataUrl) {
2890
3090
  try {
2891
3091
  const res = await fetch("/__afterbefore/save", {
@@ -2970,6 +3170,12 @@ function AfterBefore() {
2970
3170
  mode,
2971
3171
  timestamp: Date.now()
2972
3172
  });
3173
+ try {
3174
+ if (localStorage.getItem("ab-auto-open-panel") !== "false") {
3175
+ setToolbarActive(true);
3176
+ }
3177
+ } catch {
3178
+ }
2973
3179
  } catch (err) {
2974
3180
  console.error("[afterbefore] Capture failed:", err);
2975
3181
  } finally {
@@ -3019,8 +3225,8 @@ function AfterBefore() {
3019
3225
  setSelectedMode(mode);
3020
3226
  setInspectorActive(mode === "component");
3021
3227
  }, []);
3022
- return /* @__PURE__ */ jsxs5("div", { "data-afterbefore": "true", children: [
3023
- /* @__PURE__ */ jsx6(
3228
+ return /* @__PURE__ */ jsxs4("div", { "data-afterbefore": "true", children: [
3229
+ /* @__PURE__ */ jsx5(
3024
3230
  Toolbar,
3025
3231
  {
3026
3232
  expanded: toolbarActive,
@@ -3035,7 +3241,7 @@ function AfterBefore() {
3035
3241
  onFrameSettingsChange: handleFrameSettingsChange
3036
3242
  }
3037
3243
  ),
3038
- inspectorActive && /* @__PURE__ */ jsx6(Inspector, { onSelect: handleComponentSelect, onCancel: handleComponentCancel })
3244
+ inspectorActive && /* @__PURE__ */ jsx5(Inspector, { onSelect: handleComponentSelect, onCancel: handleComponentCancel })
3039
3245
  ] });
3040
3246
  }
3041
3247
  export {