afterbefore 0.2.20 → 0.2.22

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.
@@ -10,7 +10,7 @@ var initialState = {
10
10
  lastCapture: null
11
11
  };
12
12
  function useOverlayState() {
13
- const [state, setState] = useState(initialState);
13
+ const [state2, setState] = useState(initialState);
14
14
  const captureComplete = useCallback(
15
15
  (result) => {
16
16
  setState({ phase: "ready", lastCapture: result });
@@ -20,7 +20,7 @@ function useOverlayState() {
20
20
  const reset = useCallback(() => {
21
21
  setState(initialState);
22
22
  }, []);
23
- return { state, captureComplete, reset };
23
+ return { state: state2, captureComplete, reset };
24
24
  }
25
25
 
26
26
  // src/overlay/capture.ts
@@ -31,7 +31,9 @@ var DEFAULT_FRAME_SETTINGS = {
31
31
  bgType: "color",
32
32
  bgColor: "#000000",
33
33
  bgImage: null,
34
- padding: 40
34
+ padding: 40,
35
+ browserChrome: false,
36
+ browserTheme: "dark"
35
37
  };
36
38
  var FRAME_SIZE_PRESETS = [
37
39
  { label: "1920 x 1080", hint: "Desktop / HD", w: 1920, h: 1080 },
@@ -62,7 +64,7 @@ async function toPngDataUrl(el, opts) {
62
64
  async function capture(options) {
63
65
  const { mode, element } = options;
64
66
  if (mode === "viewport") {
65
- return captureViewport();
67
+ return captureViewport(options.frameSettings);
66
68
  }
67
69
  if (mode === "fullpage") {
68
70
  return captureFullPage();
@@ -72,19 +74,19 @@ async function capture(options) {
72
74
  }
73
75
  throw new Error(`Invalid capture mode: ${mode}`);
74
76
  }
75
- async function captureViewport() {
77
+ async function captureViewport(frameSettings) {
76
78
  const dpr = window.devicePixelRatio || 1;
77
79
  const vw = window.innerWidth;
78
80
  const vh = window.innerHeight;
79
81
  const scrollY = window.scrollY;
80
82
  const fullDataUrl = await captureFullPage();
81
- const img = await loadImage(fullDataUrl);
83
+ const fullImg = await loadImage(fullDataUrl);
82
84
  const canvas = document.createElement("canvas");
83
85
  canvas.width = vw * dpr;
84
86
  canvas.height = vh * dpr;
85
87
  const ctx = canvas.getContext("2d");
86
88
  ctx.drawImage(
87
- img,
89
+ fullImg,
88
90
  0,
89
91
  scrollY * dpr,
90
92
  vw * dpr,
@@ -94,8 +96,77 @@ async function captureViewport() {
94
96
  vw * dpr,
95
97
  vh * dpr
96
98
  );
99
+ const viewportDataUrl = canvas.toDataURL("image/png");
100
+ if (!frameSettings?.browserChrome) {
101
+ return viewportDataUrl;
102
+ }
103
+ const viewportImg = await loadImage(viewportDataUrl);
104
+ return drawBrowserChrome(viewportImg, frameSettings.browserTheme, dpr);
105
+ }
106
+ function drawBrowserChrome(img, theme, dpr) {
107
+ const TITLE_BAR_H = 40;
108
+ const URL_BAR_H = 28;
109
+ const URL_BAR_MARGIN_TOP = 6;
110
+ const URL_BAR_MARGIN_BOTTOM = 6;
111
+ const CHROME_H = TITLE_BAR_H + URL_BAR_H + URL_BAR_MARGIN_TOP + URL_BAR_MARGIN_BOTTOM;
112
+ const imgW = img.width;
113
+ const imgH = img.height;
114
+ const canvas = document.createElement("canvas");
115
+ canvas.width = imgW;
116
+ canvas.height = imgH + CHROME_H * dpr;
117
+ const ctx = canvas.getContext("2d");
118
+ const colors = theme === "dark" ? { titleBar: "#1C1C1C", urlBar: "#262626", text: "#7B7B7B", border: "#333333" } : { titleBar: "#F5F5F5", urlBar: "#FFFFFF", text: "#999999", border: "#EBEBEB" };
119
+ ctx.fillStyle = colors.titleBar;
120
+ ctx.fillRect(0, 0, canvas.width, CHROME_H * dpr);
121
+ ctx.fillStyle = colors.border;
122
+ ctx.fillRect(0, (CHROME_H - 1) * dpr, canvas.width, dpr);
123
+ const dotY = TITLE_BAR_H / 2 * dpr;
124
+ const dotR = 6 * dpr;
125
+ const dotStartX = 18 * dpr;
126
+ const dotGap = 20 * dpr;
127
+ const dotColors = ["#FF5F57", "#FEBC2E", "#28C840"];
128
+ for (let i = 0; i < 3; i++) {
129
+ ctx.beginPath();
130
+ ctx.arc(dotStartX + i * dotGap, dotY, dotR, 0, Math.PI * 2);
131
+ ctx.fillStyle = dotColors[i];
132
+ ctx.fill();
133
+ }
134
+ const urlBarY = (TITLE_BAR_H + URL_BAR_MARGIN_TOP) * dpr;
135
+ const urlBarPadX = 80 * dpr;
136
+ const urlBarX = urlBarPadX;
137
+ const urlBarW = canvas.width - urlBarPadX * 2;
138
+ const urlBarH = URL_BAR_H * dpr;
139
+ const urlBarR = 6 * dpr;
140
+ ctx.fillStyle = colors.urlBar;
141
+ roundRect(ctx, urlBarX, urlBarY, urlBarW, urlBarH, urlBarR);
142
+ ctx.fill();
143
+ if (theme === "light") {
144
+ ctx.strokeStyle = colors.border;
145
+ ctx.lineWidth = dpr;
146
+ roundRect(ctx, urlBarX, urlBarY, urlBarW, urlBarH, urlBarR);
147
+ ctx.stroke();
148
+ }
149
+ ctx.fillStyle = colors.text;
150
+ ctx.font = `${11 * dpr}px -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif`;
151
+ ctx.textAlign = "center";
152
+ ctx.textBaseline = "middle";
153
+ ctx.fillText("localhost", canvas.width / 2, urlBarY + urlBarH / 2);
154
+ ctx.drawImage(img, 0, CHROME_H * dpr);
97
155
  return canvas.toDataURL("image/png");
98
156
  }
157
+ function roundRect(ctx, x, y, w, h, r) {
158
+ ctx.beginPath();
159
+ ctx.moveTo(x + r, y);
160
+ ctx.lineTo(x + w - r, y);
161
+ ctx.quadraticCurveTo(x + w, y, x + w, y + r);
162
+ ctx.lineTo(x + w, y + h - r);
163
+ ctx.quadraticCurveTo(x + w, y + h, x + w - r, y + h);
164
+ ctx.lineTo(x + r, y + h);
165
+ ctx.quadraticCurveTo(x, y + h, x, y + h - r);
166
+ ctx.lineTo(x, y + r);
167
+ ctx.quadraticCurveTo(x, y, x + r, y);
168
+ ctx.closePath();
169
+ }
99
170
  async function captureFullPage() {
100
171
  const scrollY = window.scrollY;
101
172
  const body = document.body;
@@ -177,8 +248,67 @@ function loadImage(src) {
177
248
  });
178
249
  }
179
250
 
251
+ // src/overlay/font.ts
252
+ var FONT_FAMILY = "'Inter var', 'Inter', system-ui, -apple-system, sans-serif";
253
+ var FONT_FEATURE_SETTINGS = "'ss11' 1, 'calt' 1";
254
+ var fontBase = {
255
+ fontFamily: FONT_FAMILY,
256
+ fontFeatureSettings: FONT_FEATURE_SETTINGS
257
+ };
258
+ function style(fontSize, lineHeight, fontWeight, letterSpacing, extra) {
259
+ return { fontSize, lineHeight: `${lineHeight}px`, fontWeight, letterSpacing, ...extra };
260
+ }
261
+ var text = {
262
+ // --- Title (Medium 500) ---
263
+ title: {
264
+ h1: style(56, 64, 500, "-0.01em"),
265
+ h2: style(48, 56, 500, "-0.01em"),
266
+ h3: style(40, 48, 500, "-0.01em"),
267
+ h4: style(32, 40, 500, "-0.005em"),
268
+ h5: style(24, 32, 500, "0em"),
269
+ h6: style(20, 28, 500, "0em")
270
+ },
271
+ // --- Label (Medium 500) ---
272
+ label: {
273
+ xl: style(24, 32, 500, "-0.015em"),
274
+ lg: style(18, 24, 500, "-0.015em"),
275
+ md: style(16, 24, 500, "-0.011em"),
276
+ sm: style(14, 20, 500, "-0.006em"),
277
+ xs: style(12, 16, 500, "0em")
278
+ },
279
+ // --- Paragraph (Regular 400) ---
280
+ paragraph: {
281
+ xl: style(24, 32, 400, "-0.015em"),
282
+ lg: style(18, 24, 400, "-0.015em"),
283
+ md: style(16, 24, 400, "-0.011em"),
284
+ sm: style(14, 20, 400, "-0.006em"),
285
+ xs: style(12, 16, 400, "0em")
286
+ },
287
+ // --- Subheading (Medium 500, uppercase) ---
288
+ subheading: {
289
+ md: style(16, 24, 500, "0.06em", { textTransform: "uppercase" }),
290
+ sm: style(14, 20, 500, "0.06em", { textTransform: "uppercase" }),
291
+ xs: style(12, 16, 500, "0.04em", { textTransform: "uppercase" }),
292
+ xxs: style(11, 12, 500, "0.02em", { textTransform: "uppercase" })
293
+ }
294
+ };
295
+ var INTER_CSS_ID = "afterbefore-inter-font";
296
+ function injectInterFont() {
297
+ if (document.getElementById(INTER_CSS_ID)) return;
298
+ const preconnect = document.createElement("link");
299
+ preconnect.rel = "preconnect";
300
+ preconnect.href = "https://rsms.me/";
301
+ document.head.appendChild(preconnect);
302
+ const stylesheet = document.createElement("link");
303
+ stylesheet.id = INTER_CSS_ID;
304
+ stylesheet.rel = "stylesheet";
305
+ stylesheet.href = "https://rsms.me/inter/inter.css";
306
+ document.head.appendChild(stylesheet);
307
+ }
308
+
180
309
  // src/overlay/ui/toolbar.tsx
181
310
  import { useCallback as useCallback2, useEffect as useEffect2, useRef as useRef2, useState as useState3 } from "react";
311
+ import { createPortal } from "react-dom";
182
312
  import {
183
313
  ArrowUp,
184
314
  Camera,
@@ -191,11 +321,73 @@ import {
191
321
  FileText,
192
322
  Monitor,
193
323
  MousePointer2,
194
- Settings,
195
324
  Trash2 as Trash22,
196
325
  X as X2
197
326
  } from "lucide-react";
198
327
 
328
+ // src/overlay/color.ts
329
+ var gray = {
330
+ 950: "#171717",
331
+ 900: "#1C1C1C",
332
+ 800: "#262626",
333
+ 700: "#333333",
334
+ 600: "#5C5C5C",
335
+ 500: "#7B7B7B",
336
+ 400: "#A3A3A3",
337
+ 300: "#D1D1D1",
338
+ 200: "#EBEBEB",
339
+ 100: "#F5F5F5",
340
+ 50: "#F7F7F7",
341
+ 0: "#FFFFFF"
342
+ };
343
+ var bg = {
344
+ base: gray[900],
345
+ elevated: gray[950],
346
+ dropdown: gray[900]
347
+ };
348
+ var fg = {
349
+ strong: gray[200],
350
+ default: gray[300],
351
+ sub: gray[500],
352
+ muted: gray[600],
353
+ faint: gray[600]
354
+ };
355
+ var stroke = {
356
+ soft: "rgba(255, 255, 255, 0.08)",
357
+ default: "rgba(255, 255, 255, 0.1)",
358
+ strong: "rgba(255, 255, 255, 0.12)",
359
+ interactive: "rgba(255, 255, 255, 0.18)"
360
+ };
361
+ var state = {
362
+ subtle: "rgba(255, 255, 255, 0.04)",
363
+ button: "rgba(255, 255, 255, 0.06)",
364
+ input: "rgba(255, 255, 255, 0.07)",
365
+ active: "rgba(255, 255, 255, 0.08)",
366
+ hover: "rgba(255, 255, 255, 0.1)",
367
+ hoverStrong: "rgba(255, 255, 255, 0.12)",
368
+ pressed: "rgba(255, 255, 255, 0.14)"
369
+ };
370
+ var accent = {
371
+ primary: "#335CFF",
372
+ toggle: "#38bdf8",
373
+ check: "#4ade80",
374
+ highlight: "rgba(125, 211, 252, 0.96)"
375
+ };
376
+ var feedback = {
377
+ success: "rgba(34, 197, 94, 0.9)",
378
+ error: "rgba(239, 68, 68, 0.9)",
379
+ errorText: "rgba(239, 68, 68, 0.9)",
380
+ errorBg: "rgba(239, 68, 68, 0.1)"
381
+ };
382
+ var shadow = {
383
+ toolbar: "0 8px 32px rgba(0, 0, 0, 0.4)",
384
+ panel: "0 14px 36px rgba(0, 0, 0, 0.32)",
385
+ dropdown: "0 10px 30px rgba(0, 0, 0, 0.3)",
386
+ tooltip: "0 8px 28px rgba(0, 0, 0, 0.28)",
387
+ toast: "0 2px 8px rgba(0, 0, 0, 0.3)",
388
+ status: "0 4px 20px rgba(0, 0, 0, 0.4), 0 0 0 1px rgba(255, 255, 255, 0.08)"
389
+ };
390
+
199
391
  // src/overlay/ui/settings-panel.tsx
200
392
  import { useEffect, useRef, useState as useState2 } from "react";
201
393
  import {
@@ -207,140 +399,110 @@ import {
207
399
  X
208
400
  } from "lucide-react";
209
401
  import { Fragment, jsx, jsxs } from "react/jsx-runtime";
210
- function SettingsPanel({
211
- style,
212
- onClose,
213
- selectedMode,
402
+ function SettingsContent({
214
403
  frameSettings,
215
- onFrameSettingsChange
404
+ onFrameSettingsChange,
405
+ saveDir,
406
+ picking,
407
+ onPickFolder
216
408
  }) {
217
- const [saveDir, setSaveDir] = useState2(null);
218
- const [picking, setPicking] = useState2(false);
219
- useEffect(() => {
220
- fetch("/__afterbefore/config").then((r) => r.json()).then((data) => setSaveDir(data.saveDir)).catch(() => {
221
- });
222
- }, []);
223
- const handlePickFolder = async () => {
224
- setPicking(true);
225
- try {
226
- const res = await fetch("/__afterbefore/pick-folder", { method: "POST" });
227
- const data = await res.json();
228
- if (data.folder) {
229
- await fetch("/__afterbefore/config", {
230
- method: "POST",
231
- headers: { "Content-Type": "application/json" },
232
- body: JSON.stringify({ saveDir: data.folder })
233
- });
234
- setSaveDir(data.folder);
409
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
410
+ /* @__PURE__ */ jsx(
411
+ SettingsRow,
412
+ {
413
+ title: "Frame",
414
+ control: /* @__PURE__ */ jsx(
415
+ ToggleSwitch,
416
+ {
417
+ enabled: frameSettings.enabled,
418
+ onChange: () => onFrameSettingsChange({ ...frameSettings, enabled: !frameSettings.enabled })
419
+ }
420
+ )
235
421
  }
236
- } catch {
237
- } finally {
238
- setPicking(false);
239
- }
240
- };
241
- const shortDir = saveDir ? saveDir.replace(/^\/Users\/[^/]+/, "~") : "~/Desktop";
242
- return /* @__PURE__ */ jsxs(
243
- "div",
244
- {
245
- style: {
246
- minWidth: 300,
247
- padding: "12px 16px",
248
- borderRadius: 12,
249
- background: "rgb(32, 32, 36)",
250
- border: "1px solid rgba(255, 255, 255, 0.1)",
251
- boxShadow: "0 14px 36px rgba(0, 0, 0, 0.32)",
252
- ...style
253
- },
254
- children: [
255
- /* @__PURE__ */ jsx(SettingsHeader, { onClose }),
256
- /* @__PURE__ */ jsx(SettingsDivider, {}),
257
- selectedMode === "component" && /* @__PURE__ */ jsxs(Fragment, { children: [
258
- /* @__PURE__ */ jsx(
259
- SettingsRow,
260
- {
261
- title: "Frame",
262
- description: "Wrap in a sized canvas with background",
263
- control: /* @__PURE__ */ jsx(
264
- ToggleSwitch,
265
- {
266
- enabled: frameSettings.enabled,
267
- onChange: () => onFrameSettingsChange({ ...frameSettings, enabled: !frameSettings.enabled })
268
- }
269
- )
270
- }
271
- ),
272
- frameSettings.enabled && /* @__PURE__ */ jsxs(Fragment, { children: [
273
- /* @__PURE__ */ jsx(SettingsDivider, {}),
422
+ ),
423
+ frameSettings.enabled && /* @__PURE__ */ jsxs(Fragment, { children: [
424
+ /* @__PURE__ */ jsx(SettingsDivider, {}),
425
+ /* @__PURE__ */ jsx(
426
+ FrameSizeControl,
427
+ {
428
+ size: frameSettings.size,
429
+ onChange: (size) => onFrameSettingsChange({ ...frameSettings, size })
430
+ }
431
+ ),
432
+ /* @__PURE__ */ jsx(SettingsDivider, {}),
433
+ /* @__PURE__ */ jsx(
434
+ FrameBackgroundControl,
435
+ {
436
+ bgType: frameSettings.bgType,
437
+ bgColor: frameSettings.bgColor,
438
+ bgImage: frameSettings.bgImage,
439
+ frameSize: frameSettings.size,
440
+ onChange: (updates) => onFrameSettingsChange({ ...frameSettings, ...updates })
441
+ }
442
+ )
443
+ ] }),
444
+ /* @__PURE__ */ jsx(SettingsDivider, {}),
445
+ /* @__PURE__ */ jsx(
446
+ SettingsRow,
447
+ {
448
+ title: "Browser Chrome",
449
+ control: /* @__PURE__ */ jsx(
450
+ ToggleSwitch,
451
+ {
452
+ enabled: frameSettings.browserChrome,
453
+ onChange: () => onFrameSettingsChange({ ...frameSettings, browserChrome: !frameSettings.browserChrome })
454
+ }
455
+ )
456
+ }
457
+ ),
458
+ frameSettings.browserChrome && /* @__PURE__ */ jsxs(Fragment, { children: [
459
+ /* @__PURE__ */ jsx(SettingsDivider, {}),
460
+ /* @__PURE__ */ jsx(
461
+ SettingsRow,
462
+ {
463
+ title: "Theme",
464
+ control: /* @__PURE__ */ jsxs("div", { style: { display: "flex", gap: 2, flexShrink: 0 }, children: [
274
465
  /* @__PURE__ */ jsx(
275
- FrameSizeControl,
466
+ SegmentButton,
276
467
  {
277
- size: frameSettings.size,
278
- onChange: (size) => onFrameSettingsChange({ ...frameSettings, size })
468
+ active: frameSettings.browserTheme === "light",
469
+ onClick: () => onFrameSettingsChange({ ...frameSettings, browserTheme: "light" }),
470
+ style: { borderRadius: "6px 0 0 6px" },
471
+ children: "Light"
279
472
  }
280
473
  ),
281
- /* @__PURE__ */ jsx(SettingsDivider, {}),
282
474
  /* @__PURE__ */ jsx(
283
- FrameBackgroundControl,
475
+ SegmentButton,
284
476
  {
285
- bgType: frameSettings.bgType,
286
- bgColor: frameSettings.bgColor,
287
- bgImage: frameSettings.bgImage,
288
- frameSize: frameSettings.size,
289
- onChange: (updates) => onFrameSettingsChange({ ...frameSettings, ...updates })
477
+ active: frameSettings.browserTheme === "dark",
478
+ onClick: () => onFrameSettingsChange({ ...frameSettings, browserTheme: "dark" }),
479
+ style: { borderRadius: "0 6px 6px 0" },
480
+ children: "Dark"
290
481
  }
291
482
  )
292
- ] }),
293
- /* @__PURE__ */ jsx(SettingsDivider, {})
294
- ] }),
295
- /* @__PURE__ */ jsx(
296
- SettingsRow,
483
+ ] })
484
+ }
485
+ )
486
+ ] }),
487
+ /* @__PURE__ */ jsx(SettingsDivider, {}),
488
+ saveDir !== void 0 && onPickFolder && /* @__PURE__ */ jsx(
489
+ SettingsRow,
490
+ {
491
+ title: "Save Location",
492
+ description: /* @__PURE__ */ jsx(
493
+ "span",
297
494
  {
298
- title: "Save Location",
299
- description: /* @__PURE__ */ jsx(
300
- "span",
301
- {
302
- style: {
303
- overflow: "hidden",
304
- textOverflow: "ellipsis",
305
- whiteSpace: "nowrap",
306
- display: "block"
307
- },
308
- title: shortDir,
309
- children: shortDir
310
- }
311
- ),
312
- control: /* @__PURE__ */ jsx(SmallButton, { onClick: handlePickFolder, children: picking ? "..." : "Change" })
495
+ style: {
496
+ overflow: "hidden",
497
+ textOverflow: "ellipsis",
498
+ whiteSpace: "nowrap",
499
+ display: "block"
500
+ },
501
+ title: saveDir,
502
+ children: saveDir
313
503
  }
314
- )
315
- ]
316
- }
317
- );
318
- }
319
- function SettingsHeader({ onClose }) {
320
- const [hovered, setHovered] = useState2(false);
321
- return /* @__PURE__ */ jsxs("div", { style: { display: "flex", alignItems: "center", justifyContent: "space-between", paddingBottom: 8 }, children: [
322
- /* @__PURE__ */ jsx("span", { style: { fontSize: 14, fontWeight: 600, color: "rgba(255, 255, 255, 0.92)" }, children: "Settings" }),
323
- /* @__PURE__ */ jsx(
324
- "button",
325
- {
326
- onClick: onClose,
327
- onMouseEnter: () => setHovered(true),
328
- onMouseLeave: () => setHovered(false),
329
- style: {
330
- width: 24,
331
- height: 24,
332
- borderRadius: 6,
333
- border: "none",
334
- background: hovered ? "rgba(255, 255, 255, 0.1)" : "transparent",
335
- display: "flex",
336
- alignItems: "center",
337
- justifyContent: "center",
338
- cursor: "pointer",
339
- color: hovered ? "rgba(255, 255, 255, 0.8)" : "rgba(255, 255, 255, 0.46)",
340
- padding: 0,
341
- transition: "background 0.12s ease, color 0.12s ease"
342
- },
343
- children: /* @__PURE__ */ jsx(X, { size: 14, strokeWidth: 2 })
504
+ ),
505
+ control: /* @__PURE__ */ jsx(SmallButton, { onClick: onPickFolder, children: picking ? "..." : "Change" })
344
506
  }
345
507
  )
346
508
  ] });
@@ -350,16 +512,23 @@ function SettingsRow({
350
512
  description,
351
513
  control
352
514
  }) {
353
- return /* @__PURE__ */ jsxs("div", { style: { display: "flex", alignItems: "center", justifyContent: "space-between", gap: 16, padding: "14px 0" }, children: [
354
- /* @__PURE__ */ jsxs("div", { style: { display: "flex", flexDirection: "column", gap: 3, flex: 1, minWidth: 0 }, children: [
355
- /* @__PURE__ */ jsx("span", { style: { fontSize: 13, fontWeight: 600, color: "rgba(255, 255, 255, 0.92)" }, children: title }),
356
- description && /* @__PURE__ */ jsx("span", { style: { fontSize: 11, color: "rgba(255, 255, 255, 0.42)", lineHeight: 1.3 }, children: description })
515
+ return /* @__PURE__ */ jsxs("div", { style: { display: "flex", alignItems: "center", justifyContent: "space-between", gap: 16, padding: "10px 0" }, children: [
516
+ /* @__PURE__ */ jsxs("div", { style: { display: "flex", flexDirection: "column", gap: 2, flex: 1, minWidth: 0 }, children: [
517
+ /* @__PURE__ */ jsx("span", { style: { ...text.label.sm, color: fg.strong }, children: title }),
518
+ description && /* @__PURE__ */ jsx("span", { style: { ...text.paragraph.xs, color: fg.muted }, children: description })
357
519
  ] }),
358
520
  /* @__PURE__ */ jsx("div", { style: { flexShrink: 0 }, children: control })
359
521
  ] });
360
522
  }
361
523
  function SettingsDivider() {
362
- return /* @__PURE__ */ jsx("div", { style: { height: 1, background: "rgba(255, 255, 255, 0.08)" } });
524
+ return /* @__PURE__ */ jsx(
525
+ "div",
526
+ {
527
+ style: {
528
+ borderBottom: `1px solid ${stroke.soft}`
529
+ }
530
+ }
531
+ );
363
532
  }
364
533
  function ToggleSwitch({
365
534
  enabled,
@@ -375,7 +544,7 @@ function ToggleSwitch({
375
544
  height: 22,
376
545
  borderRadius: 999,
377
546
  border: "none",
378
- background: enabled ? "#38bdf8" : "rgba(255, 255, 255, 0.18)",
547
+ background: enabled ? accent.toggle : stroke.interactive,
379
548
  position: "relative",
380
549
  cursor: "pointer",
381
550
  padding: 0,
@@ -407,12 +576,9 @@ function FrameSizeControl({
407
576
  const [sizeOpen, setSizeOpen] = useState2(false);
408
577
  const currentPreset = FRAME_SIZE_PRESETS.find((p) => p.w === size.w && p.h === size.h);
409
578
  const isCustom = !currentPreset;
410
- return /* @__PURE__ */ jsxs("div", { style: { padding: "14px 0" }, children: [
579
+ return /* @__PURE__ */ jsxs("div", { style: { padding: "10px 0" }, children: [
411
580
  /* @__PURE__ */ jsxs("div", { style: { display: "flex", alignItems: "center", justifyContent: "space-between", gap: 16 }, children: [
412
- /* @__PURE__ */ jsxs("div", { style: { display: "flex", flexDirection: "column", gap: 3 }, children: [
413
- /* @__PURE__ */ jsx("span", { style: { fontSize: 13, fontWeight: 600, color: "rgba(255, 255, 255, 0.92)" }, children: "Size" }),
414
- /* @__PURE__ */ jsx("span", { style: { fontSize: 11, color: "rgba(255, 255, 255, 0.42)", lineHeight: 1.3 }, children: "Set the frame dimensions" })
415
- ] }),
581
+ /* @__PURE__ */ jsx("div", { children: /* @__PURE__ */ jsx("span", { style: { ...text.label.sm, color: fg.strong }, children: "Size" }) }),
416
582
  /* @__PURE__ */ jsxs("div", { style: { position: "relative", flexShrink: 0 }, children: [
417
583
  /* @__PURE__ */ jsxs(
418
584
  "button",
@@ -425,11 +591,11 @@ function FrameSizeControl({
425
591
  height: 30,
426
592
  padding: "0 10px",
427
593
  borderRadius: 7,
428
- border: "1px solid rgba(255,255,255,0.1)",
429
- background: "rgba(255,255,255,0.07)",
430
- color: "rgba(255,255,255,0.88)",
594
+ border: `1px solid ${stroke.default}`,
595
+ background: state.input,
596
+ color: fg.strong,
431
597
  cursor: "pointer",
432
- fontSize: 12,
598
+ ...text.label.xs,
433
599
  fontFamily: "inherit",
434
600
  whiteSpace: "nowrap"
435
601
  },
@@ -447,11 +613,11 @@ function FrameSizeControl({
447
613
  bottom: "calc(100% + 4px)",
448
614
  right: 0,
449
615
  minWidth: 180,
450
- background: "rgb(32, 32, 36)",
451
- border: "1px solid rgba(255, 255, 255, 0.1)",
616
+ background: bg.base,
617
+ border: `1px solid ${stroke.default}`,
452
618
  borderRadius: 8,
453
619
  padding: "4px 0",
454
- boxShadow: "0 10px 30px rgba(0, 0, 0, 0.3)",
620
+ boxShadow: shadow.dropdown,
455
621
  zIndex: 1
456
622
  },
457
623
  children: FRAME_SIZE_PRESETS.map((preset) => /* @__PURE__ */ jsxs(
@@ -464,7 +630,7 @@ function FrameSizeControl({
464
630
  },
465
631
  children: [
466
632
  /* @__PURE__ */ jsx("span", { children: preset.label }),
467
- /* @__PURE__ */ jsx("span", { style: { marginLeft: 6, fontSize: 10, color: "rgba(255,255,255,0.34)" }, children: preset.hint })
633
+ /* @__PURE__ */ jsx("span", { style: { marginLeft: 6, fontSize: 10, color: fg.faint }, children: preset.hint })
468
634
  ]
469
635
  },
470
636
  preset.label
@@ -523,12 +689,9 @@ function FrameBackgroundControl({
523
689
  reader.readAsDataURL(file);
524
690
  e.target.value = "";
525
691
  };
526
- return /* @__PURE__ */ jsxs("div", { style: { padding: "14px 0" }, children: [
692
+ return /* @__PURE__ */ jsxs("div", { style: { padding: "10px 0" }, children: [
527
693
  /* @__PURE__ */ jsxs("div", { style: { display: "flex", alignItems: "center", justifyContent: "space-between", gap: 16 }, children: [
528
- /* @__PURE__ */ jsxs("div", { style: { display: "flex", flexDirection: "column", gap: 3 }, children: [
529
- /* @__PURE__ */ jsx("span", { style: { fontSize: 13, fontWeight: 600, color: "rgba(255, 255, 255, 0.92)" }, children: "Background" }),
530
- /* @__PURE__ */ jsx("span", { style: { fontSize: 11, color: "rgba(255, 255, 255, 0.42)", lineHeight: 1.3 }, children: "Frame background color or image" })
531
- ] }),
694
+ /* @__PURE__ */ jsx("div", { children: /* @__PURE__ */ jsx("span", { style: { ...text.label.sm, color: fg.strong }, children: "Background" }) }),
532
695
  /* @__PURE__ */ jsxs("div", { style: { display: "flex", gap: 2, flexShrink: 0 }, children: [
533
696
  /* @__PURE__ */ jsxs(
534
697
  SegmentButton,
@@ -582,7 +745,7 @@ function FrameBackgroundControl({
582
745
  height: 28,
583
746
  borderRadius: 4,
584
747
  objectFit: "cover",
585
- border: "1px solid rgba(255,255,255,0.12)"
748
+ border: `1px solid ${stroke.strong}`
586
749
  }
587
750
  }
588
751
  ),
@@ -602,7 +765,7 @@ function SegmentButton({
602
765
  children,
603
766
  active,
604
767
  onClick,
605
- style
768
+ style: style2
606
769
  }) {
607
770
  return /* @__PURE__ */ jsx(
608
771
  "button",
@@ -612,16 +775,16 @@ function SegmentButton({
612
775
  display: "flex",
613
776
  alignItems: "center",
614
777
  gap: 4,
615
- height: 26,
778
+ height: 28,
616
779
  padding: "0 10px",
617
- border: "1px solid rgba(255,255,255,0.1)",
618
- background: active ? "rgba(255,255,255,0.14)" : "rgba(255,255,255,0.04)",
619
- color: active ? "rgba(255,255,255,0.92)" : "rgba(255,255,255,0.5)",
780
+ border: `1px solid ${stroke.default}`,
781
+ background: active ? state.pressed : state.subtle,
782
+ color: active ? fg.strong : fg.sub,
620
783
  cursor: "pointer",
621
- fontSize: 11,
784
+ ...text.label.xs,
622
785
  fontFamily: "inherit",
623
786
  transition: "background 0.12s ease, color 0.12s ease",
624
- ...style
787
+ ...style2
625
788
  },
626
789
  children
627
790
  }
@@ -641,7 +804,7 @@ function ColorSwatch({
641
804
  width: 24,
642
805
  height: 24,
643
806
  borderRadius: 6,
644
- border: "1px solid rgba(255,255,255,0.18)",
807
+ border: `1px solid ${stroke.interactive}`,
645
808
  background: color,
646
809
  cursor: "pointer",
647
810
  padding: 0
@@ -683,12 +846,12 @@ function SmallButton({
683
846
  display: "flex",
684
847
  alignItems: "center",
685
848
  gap: 4,
686
- padding: "3px 8px",
849
+ padding: "4px 10px",
687
850
  borderRadius: 6,
688
- border: "1px solid rgba(255,255,255,0.12)",
689
- background: hovered ? "rgba(255,255,255,0.12)" : "rgba(255,255,255,0.06)",
690
- color: "rgba(255,255,255,0.78)",
691
- fontSize: 11,
851
+ border: `1px solid ${stroke.strong}`,
852
+ background: hovered ? state.hoverStrong : state.button,
853
+ color: fg.default,
854
+ ...text.label.xs,
692
855
  cursor: "pointer",
693
856
  fontFamily: "inherit",
694
857
  transition: "background 0.12s ease"
@@ -702,7 +865,7 @@ function NumInput({
702
865
  onChange
703
866
  }) {
704
867
  const [editing, setEditing] = useState2(false);
705
- const [text, setText] = useState2(String(value));
868
+ const [text2, setText] = useState2(String(value));
706
869
  useEffect(() => {
707
870
  if (!editing) {
708
871
  setText(String(value));
@@ -712,14 +875,14 @@ function NumInput({
712
875
  "input",
713
876
  {
714
877
  type: "text",
715
- value: editing ? text : String(value),
878
+ value: editing ? text2 : String(value),
716
879
  onFocus: () => {
717
880
  setEditing(true);
718
881
  setText(String(value));
719
882
  },
720
883
  onBlur: () => {
721
884
  setEditing(false);
722
- onChange(text);
885
+ onChange(text2);
723
886
  },
724
887
  onChange: (e) => setText(e.target.value),
725
888
  onKeyDown: (e) => {
@@ -730,12 +893,12 @@ function NumInput({
730
893
  style: {
731
894
  width: 54,
732
895
  padding: "4px 6px",
733
- background: "rgba(255, 255, 255, 0.07)",
734
- border: "1px solid rgba(255, 255, 255, 0.1)",
896
+ background: state.input,
897
+ border: `1px solid ${stroke.default}`,
735
898
  borderRadius: 7,
736
- color: "rgba(255, 255, 255, 0.9)",
737
- fontSize: 12,
738
- fontFamily: "system-ui, -apple-system, sans-serif",
899
+ color: fg.strong,
900
+ ...text.label.xs,
901
+ fontFamily: "inherit",
739
902
  textAlign: "center",
740
903
  outline: "none"
741
904
  }
@@ -747,7 +910,7 @@ function HexInput({
747
910
  onChange
748
911
  }) {
749
912
  const [editing, setEditing] = useState2(false);
750
- const [text, setText] = useState2(value);
913
+ const [text2, setText] = useState2(value);
751
914
  useEffect(() => {
752
915
  if (!editing) {
753
916
  setText(value);
@@ -763,14 +926,14 @@ function HexInput({
763
926
  "input",
764
927
  {
765
928
  type: "text",
766
- value: editing ? text : value,
929
+ value: editing ? text2 : value,
767
930
  onFocus: () => {
768
931
  setEditing(true);
769
932
  setText(value);
770
933
  },
771
934
  onBlur: () => {
772
935
  setEditing(false);
773
- commit(text);
936
+ commit(text2);
774
937
  },
775
938
  onChange: (e) => setText(e.target.value),
776
939
  onKeyDown: (e) => {
@@ -781,12 +944,12 @@ function HexInput({
781
944
  style: {
782
945
  width: 72,
783
946
  padding: "4px 6px",
784
- background: "rgba(255, 255, 255, 0.07)",
785
- border: "1px solid rgba(255, 255, 255, 0.1)",
947
+ background: state.input,
948
+ border: `1px solid ${stroke.default}`,
786
949
  borderRadius: 7,
787
- color: "rgba(255, 255, 255, 0.9)",
788
- fontSize: 12,
789
- fontFamily: "system-ui, -apple-system, sans-serif",
950
+ color: fg.strong,
951
+ ...text.label.xs,
952
+ fontFamily: "inherit",
790
953
  textAlign: "left",
791
954
  outline: "none"
792
955
  }
@@ -799,7 +962,7 @@ function StaticText({ children }) {
799
962
  {
800
963
  style: {
801
964
  fontSize: 11,
802
- color: "rgba(255,255,255,0.35)",
965
+ color: fg.faint,
803
966
  minWidth: 8,
804
967
  textAlign: "center"
805
968
  },
@@ -820,12 +983,12 @@ function DropItem({
820
983
  display: "block",
821
984
  width: "100%",
822
985
  padding: "7px 12px",
823
- background: active ? "rgba(255, 255, 255, 0.08)" : "transparent",
986
+ background: active ? state.active : "transparent",
824
987
  border: "none",
825
- color: "rgba(255, 255, 255, 0.86)",
988
+ color: fg.strong,
826
989
  textAlign: "left",
827
990
  cursor: "pointer",
828
- fontSize: 13,
991
+ ...text.paragraph.sm,
829
992
  fontFamily: "inherit"
830
993
  },
831
994
  children
@@ -879,6 +1042,20 @@ function snapToCorner(x, y) {
879
1042
  }
880
1043
  return y < cy ? "top-right" : "bottom-right";
881
1044
  }
1045
+ function getCornerPosition(corner, w, h) {
1046
+ const vw = window.innerWidth;
1047
+ const vh = window.innerHeight;
1048
+ switch (corner) {
1049
+ case "bottom-right":
1050
+ return { x: vw - EDGE_MARGIN - w, y: vh - EDGE_MARGIN - h };
1051
+ case "bottom-left":
1052
+ return { x: EDGE_MARGIN, y: vh - EDGE_MARGIN - h };
1053
+ case "top-right":
1054
+ return { x: vw - EDGE_MARGIN - w, y: EDGE_MARGIN };
1055
+ case "top-left":
1056
+ return { x: EDGE_MARGIN, y: EDGE_MARGIN };
1057
+ }
1058
+ }
882
1059
  var MODES = [
883
1060
  { mode: "component", label: "Component", icon: MousePointer2 },
884
1061
  { mode: "viewport", label: "Viewport", icon: Monitor },
@@ -896,8 +1073,29 @@ function Toolbar({
896
1073
  frameSettings,
897
1074
  onFrameSettingsChange
898
1075
  }) {
899
- const [settingsOpen, setSettingsOpen] = useState3(false);
900
1076
  const [historyOpen, setHistoryOpen] = useState3(false);
1077
+ const [modesExpanded, setModesExpanded] = useState3(true);
1078
+ const [buttonsVisible, setButtonsVisible] = useState3(expanded);
1079
+ const [animIn, setAnimIn] = useState3(expanded);
1080
+ const [animDone, setAnimDone] = useState3(expanded);
1081
+ useEffect2(() => {
1082
+ if (expanded) {
1083
+ setAnimDone(false);
1084
+ setButtonsVisible(true);
1085
+ setAnimIn(false);
1086
+ requestAnimationFrame(() => {
1087
+ requestAnimationFrame(() => setAnimIn(true));
1088
+ });
1089
+ const timer = setTimeout(() => setAnimDone(true), 250);
1090
+ return () => clearTimeout(timer);
1091
+ } else if (buttonsVisible) {
1092
+ setHistoryOpen(false);
1093
+ setAnimDone(false);
1094
+ setAnimIn(false);
1095
+ const timer = setTimeout(() => setButtonsVisible(false), 150);
1096
+ return () => clearTimeout(timer);
1097
+ }
1098
+ }, [expanded]);
901
1099
  const [corner, setCorner] = useState3(() => {
902
1100
  try {
903
1101
  const stored = localStorage.getItem("ab-toolbar-corner");
@@ -910,6 +1108,7 @@ function Toolbar({
910
1108
  });
911
1109
  const [dragging, setDragging] = useState3(false);
912
1110
  const [dragPos, setDragPos] = useState3(null);
1111
+ const [snapAnim, setSnapAnim] = useState3(null);
913
1112
  const dragState = useRef2(null);
914
1113
  const toolbarRef = useRef2(null);
915
1114
  const [cameraHovered, setCameraHovered] = useState3(false);
@@ -923,8 +1122,8 @@ function Toolbar({
923
1122
  return;
924
1123
  }
925
1124
  if (e.key === "Escape") {
926
- if (settingsOpen) {
927
- setSettingsOpen(false);
1125
+ if (historyOpen) {
1126
+ setHistoryOpen(false);
928
1127
  return;
929
1128
  }
930
1129
  onCancel();
@@ -934,7 +1133,7 @@ function Toolbar({
934
1133
  };
935
1134
  document.addEventListener("keydown", onKey);
936
1135
  return () => document.removeEventListener("keydown", onKey);
937
- }, [expanded, onCancel, onCapture, selectedMode, settingsOpen]);
1136
+ }, [expanded, onCancel, onCapture, selectedMode, historyOpen]);
938
1137
  const handleMouseDown = useCallback2(
939
1138
  (e) => {
940
1139
  e.preventDefault();
@@ -971,22 +1170,35 @@ function Toolbar({
971
1170
  if (!ds) return;
972
1171
  if (ds.distance < 5) {
973
1172
  onToggle();
1173
+ setDragging(false);
1174
+ setDragPos(null);
1175
+ dragState.current = null;
974
1176
  } else {
975
1177
  const el = toolbarRef.current;
976
1178
  const w = el?.offsetWidth ?? CONTAINER_SIZE;
977
1179
  const h = el?.offsetHeight ?? CONTAINER_SIZE;
978
- const centerX = ds.origX + (e.clientX - ds.startX) + w / 2;
979
- const centerY = ds.origY + (e.clientY - ds.startY) + h / 2;
1180
+ const currentX = ds.origX + (e.clientX - ds.startX);
1181
+ const currentY = ds.origY + (e.clientY - ds.startY);
1182
+ const centerX = currentX + w / 2;
1183
+ const centerY = currentY + h / 2;
980
1184
  const newCorner = snapToCorner(centerX, centerY);
981
1185
  setCorner(newCorner);
982
1186
  try {
983
1187
  localStorage.setItem("ab-toolbar-corner", newCorner);
984
1188
  } catch {
985
1189
  }
1190
+ const targetPos = getCornerPosition(newCorner, w, h);
1191
+ setDragging(false);
1192
+ setDragPos(null);
1193
+ setSnapAnim({ x: currentX, y: currentY, animate: false });
1194
+ dragState.current = null;
1195
+ requestAnimationFrame(() => {
1196
+ requestAnimationFrame(() => {
1197
+ setSnapAnim({ ...targetPos, animate: true });
1198
+ setTimeout(() => setSnapAnim(null), 300);
1199
+ });
1200
+ });
986
1201
  }
987
- setDragging(false);
988
- setDragPos(null);
989
- dragState.current = null;
990
1202
  };
991
1203
  window.addEventListener("mousemove", handleMouseMove);
992
1204
  window.addEventListener("mouseup", handleMouseUp);
@@ -998,7 +1210,13 @@ function Toolbar({
998
1210
  const panelSide = isRightCorner(corner) ? "left" : "right";
999
1211
  const tooltipSide = panelSide;
1000
1212
  const bottom = isBottomCorner(corner);
1001
- const positionStyle = dragging && dragPos ? { left: dragPos.x, top: dragPos.y } : getCornerStyle(corner);
1213
+ const positionStyle = dragging && dragPos ? { left: dragPos.x, top: dragPos.y } : snapAnim ? {
1214
+ left: snapAnim.x,
1215
+ top: snapAnim.y,
1216
+ ...snapAnim.animate && {
1217
+ transition: "left 0.3s cubic-bezier(0.23, 1, 0.32, 1), top 0.3s cubic-bezier(0.23, 1, 0.32, 1)"
1218
+ }
1219
+ } : getCornerStyle(corner);
1002
1220
  const cameraTooltipLabel = expanded ? "Close" : void 0;
1003
1221
  const cameraTooltipStyle = cameraTooltipLabel ? tooltipSide === "left" ? { right: "calc(100% + 10px)", top: "50%", transform: "translateY(-50%)" } : { left: "calc(100% + 10px)", top: "50%", transform: "translateY(-50%)" } : void 0;
1004
1222
  const cameraButton = /* @__PURE__ */ jsxs2("div", { style: { position: "relative" }, children: [
@@ -1008,14 +1226,17 @@ function Toolbar({
1008
1226
  style: {
1009
1227
  position: "absolute",
1010
1228
  ...cameraTooltipStyle,
1011
- background: "rgb(32, 32, 36)",
1012
- border: "1px solid rgba(255, 255, 255, 0.1)",
1229
+ background: bg.base,
1230
+ border: `1px solid ${stroke.default}`,
1013
1231
  borderRadius: 6,
1014
- padding: "3px 8px",
1015
- color: "rgba(255, 255, 255, 0.88)",
1016
- fontSize: 11,
1232
+ padding: "0 8px",
1233
+ height: 24,
1234
+ display: "flex",
1235
+ alignItems: "center",
1236
+ color: fg.strong,
1237
+ ...text.label.xs,
1017
1238
  whiteSpace: "nowrap",
1018
- boxShadow: "0 8px 28px rgba(0, 0, 0, 0.28)",
1239
+ boxShadow: shadow.tooltip,
1019
1240
  pointerEvents: "none"
1020
1241
  },
1021
1242
  children: cameraTooltipLabel
@@ -1036,7 +1257,7 @@ function Toolbar({
1036
1257
  alignItems: "center",
1037
1258
  justifyContent: "center",
1038
1259
  cursor: dragging ? "grabbing" : "pointer",
1039
- background: expanded && cameraHovered ? "rgba(255, 255, 255, 0.12)" : "transparent",
1260
+ background: expanded && cameraHovered ? state.hoverStrong : "transparent",
1040
1261
  transition: "background 0.12s ease"
1041
1262
  },
1042
1263
  children: [
@@ -1048,7 +1269,12 @@ function Toolbar({
1048
1269
  @keyframes ab-spin {
1049
1270
  0% { transform: rotate(0deg); }
1050
1271
  100% { transform: rotate(360deg); }
1051
- }`
1272
+ }
1273
+ @keyframes ab-panel-in {
1274
+ from { opacity: 0; transform: translateY(4px); }
1275
+ to { opacity: 1; transform: translateY(0); }
1276
+ }
1277
+ `
1052
1278
  }
1053
1279
  }
1054
1280
  ),
@@ -1059,78 +1285,111 @@ function Toolbar({
1059
1285
  strokeWidth: 2,
1060
1286
  style: { animation: "ab-spin 0.8s linear infinite", color: "white" }
1061
1287
  }
1062
- ) : phase === "ready" ? /* @__PURE__ */ jsx2(Check, { size: 16, strokeWidth: 2.6, color: "#4ade80" }) : expanded ? /* @__PURE__ */ jsx2(
1288
+ ) : phase === "ready" ? /* @__PURE__ */ jsx2(Check, { size: 16, strokeWidth: 2.6, color: accent.check }) : expanded ? /* @__PURE__ */ jsx2(
1063
1289
  X2,
1064
1290
  {
1065
1291
  size: 16,
1066
1292
  strokeWidth: 1.7,
1067
- color: cameraHovered ? "rgba(255, 255, 255, 0.96)" : "rgba(255, 255, 255, 0.52)"
1293
+ color: cameraHovered ? fg.strong : fg.sub
1068
1294
  }
1069
1295
  ) : /* @__PURE__ */ jsx2(
1070
1296
  Camera,
1071
1297
  {
1072
1298
  size: 16,
1073
1299
  strokeWidth: 1.9,
1074
- color: "rgba(255, 255, 255, 0.52)"
1300
+ color: fg.sub
1075
1301
  }
1076
1302
  )
1077
1303
  ]
1078
1304
  }
1079
1305
  )
1080
1306
  ] });
1081
- const toolbarButtons = expanded ? /* @__PURE__ */ jsxs2(Fragment2, { children: [
1082
- /* @__PURE__ */ jsx2("div", { style: { display: "flex", flexDirection: "column", alignItems: "center", gap: 2, padding: "4px 0" }, children: MODES.map(({ mode, label, icon: ModeIcon }) => /* @__PURE__ */ jsx2(
1083
- IconButton,
1084
- {
1085
- active: selectedMode === mode,
1086
- tooltipSide,
1087
- tooltip: label,
1088
- onClick: () => {
1089
- setSettingsOpen(false);
1090
- setHistoryOpen(false);
1091
- if (mode === "viewport" || mode === "fullpage") {
1092
- onModeChange(mode);
1093
- onCapture(mode);
1094
- } else {
1095
- onModeChange(mode);
1096
- }
1097
- },
1098
- children: /* @__PURE__ */ jsx2(ModeIcon, { size: 16, strokeWidth: 1.7 })
1307
+ const toolbarButtons = buttonsVisible ? /* @__PURE__ */ jsx2(
1308
+ "div",
1309
+ {
1310
+ style: {
1311
+ overflow: animDone ? "visible" : "hidden",
1312
+ maxHeight: animIn ? 195 : 0,
1313
+ transition: animIn ? "max-height 250ms cubic-bezier(0.23, 1, 0.32, 1)" : "max-height 150ms cubic-bezier(0.23, 1, 0.32, 1)"
1099
1314
  },
1100
- mode
1101
- )) }),
1102
- /* @__PURE__ */ jsx2(Separator, { vertical: false }),
1103
- /* @__PURE__ */ jsx2(
1104
- SettingsButton,
1105
- {
1106
- open: settingsOpen,
1107
- onClick: () => {
1108
- setSettingsOpen((prev) => !prev);
1109
- setHistoryOpen(false);
1110
- },
1111
- selectedMode,
1112
- frameSettings,
1113
- onFrameSettingsChange,
1114
- panelSide,
1115
- tooltipSide,
1116
- bottom
1117
- }
1118
- ),
1119
- /* @__PURE__ */ jsx2(
1120
- HistoryButton,
1121
- {
1122
- open: historyOpen,
1123
- onClick: () => {
1124
- setHistoryOpen((prev) => !prev);
1125
- setSettingsOpen(false);
1126
- },
1127
- panelSide,
1128
- tooltipSide,
1129
- bottom
1130
- }
1131
- ),
1132
- /* @__PURE__ */ jsx2(Separator, { vertical: false })
1133
- ] }) : null;
1315
+ children: /* @__PURE__ */ jsxs2(
1316
+ "div",
1317
+ {
1318
+ style: {
1319
+ display: "flex",
1320
+ flexDirection: "column",
1321
+ alignItems: "center",
1322
+ opacity: animIn ? 1 : 0,
1323
+ transform: animIn ? "translateY(0)" : `translateY(${bottom ? 6 : -6}px)`,
1324
+ transition: animIn ? "opacity 200ms cubic-bezier(0.23, 1, 0.32, 1), transform 200ms cubic-bezier(0.23, 1, 0.32, 1)" : "opacity 150ms cubic-bezier(0.23, 1, 0.32, 1), transform 150ms cubic-bezier(0.23, 1, 0.32, 1)",
1325
+ willChange: "transform, opacity"
1326
+ },
1327
+ children: [
1328
+ /* @__PURE__ */ jsx2("div", { style: { paddingBottom: 2 }, children: /* @__PURE__ */ jsx2(
1329
+ IconButton,
1330
+ {
1331
+ active: selectedMode === "component" && !historyOpen,
1332
+ tooltipSide,
1333
+ tooltip: "Component",
1334
+ onClick: () => {
1335
+ setHistoryOpen(false);
1336
+ onModeChange("component");
1337
+ },
1338
+ children: /* @__PURE__ */ jsx2(MousePointer2, { size: 16, strokeWidth: 1.7 })
1339
+ }
1340
+ ) }),
1341
+ MODES.filter((m) => m.mode !== "component").map(({ mode, label, icon: ModeIcon }) => /* @__PURE__ */ jsx2(
1342
+ "div",
1343
+ {
1344
+ style: {
1345
+ maxHeight: modesExpanded ? 34 : 0,
1346
+ opacity: modesExpanded ? 1 : 0,
1347
+ transition: "max-height 200ms cubic-bezier(0.23, 1, 0.32, 1), opacity 150ms cubic-bezier(0.23, 1, 0.32, 1)"
1348
+ },
1349
+ children: /* @__PURE__ */ jsx2(
1350
+ IconButton,
1351
+ {
1352
+ active: selectedMode === mode && !historyOpen,
1353
+ tooltipSide,
1354
+ tooltip: label,
1355
+ onClick: () => {
1356
+ setHistoryOpen(false);
1357
+ onModeChange(mode);
1358
+ onCapture(mode);
1359
+ },
1360
+ children: /* @__PURE__ */ jsx2(ModeIcon, { size: 16, strokeWidth: 1.7 })
1361
+ }
1362
+ )
1363
+ },
1364
+ mode
1365
+ )),
1366
+ /* @__PURE__ */ jsx2(
1367
+ Separator,
1368
+ {
1369
+ vertical: false,
1370
+ onClick: () => setModesExpanded((p) => !p)
1371
+ }
1372
+ ),
1373
+ /* @__PURE__ */ jsx2(
1374
+ HistoryButton,
1375
+ {
1376
+ open: historyOpen,
1377
+ onClick: () => {
1378
+ setHistoryOpen((prev) => !prev);
1379
+ },
1380
+ selectedMode,
1381
+ frameSettings,
1382
+ onFrameSettingsChange,
1383
+ panelSide,
1384
+ tooltipSide,
1385
+ bottom
1386
+ }
1387
+ )
1388
+ ]
1389
+ }
1390
+ )
1391
+ }
1392
+ ) : null;
1134
1393
  return /* @__PURE__ */ jsx2(
1135
1394
  "div",
1136
1395
  {
@@ -1143,11 +1402,11 @@ function Toolbar({
1143
1402
  display: "flex",
1144
1403
  flexDirection: "column",
1145
1404
  alignItems: "center",
1146
- background: "rgb(32, 32, 36)",
1405
+ background: bg.base,
1147
1406
  borderRadius: 999,
1148
1407
  padding: 6,
1149
- boxShadow: "0 8px 32px rgba(0, 0, 0, 0.4)",
1150
- fontFamily: "system-ui, -apple-system, sans-serif",
1408
+ boxShadow: shadow.toolbar,
1409
+ ...fontBase,
1151
1410
  userSelect: "none"
1152
1411
  },
1153
1412
  children: bottom ? /* @__PURE__ */ jsxs2(Fragment2, { children: [
@@ -1184,14 +1443,17 @@ function IconButton({
1184
1443
  style: {
1185
1444
  position: "absolute",
1186
1445
  ...tooltipStyle,
1187
- background: "rgb(32, 32, 36)",
1188
- border: "1px solid rgba(255, 255, 255, 0.1)",
1446
+ background: bg.base,
1447
+ border: `1px solid ${stroke.default}`,
1189
1448
  borderRadius: 6,
1190
- padding: "3px 8px",
1191
- color: "rgba(255, 255, 255, 0.88)",
1192
- fontSize: 11,
1449
+ padding: "0 8px",
1450
+ height: 24,
1451
+ display: "flex",
1452
+ alignItems: "center",
1453
+ color: fg.strong,
1454
+ ...text.label.xs,
1193
1455
  whiteSpace: "nowrap",
1194
- boxShadow: "0 8px 28px rgba(0, 0, 0, 0.28)",
1456
+ boxShadow: shadow.tooltip,
1195
1457
  pointerEvents: "none"
1196
1458
  },
1197
1459
  children: tooltip
@@ -1208,13 +1470,13 @@ function IconButton({
1208
1470
  height: 32,
1209
1471
  borderRadius: "50%",
1210
1472
  border: "none",
1211
- background: active || hovered ? "rgba(255, 255, 255, 0.12)" : "transparent",
1473
+ background: active || hovered ? state.hoverStrong : "transparent",
1212
1474
  display: "flex",
1213
1475
  alignItems: "center",
1214
1476
  justifyContent: "center",
1215
1477
  cursor: "pointer",
1216
1478
  padding: 0,
1217
- color: active || hovered ? "rgba(255, 255, 255, 0.96)" : "rgba(255, 255, 255, 0.52)",
1479
+ color: active || hovered ? fg.strong : fg.sub,
1218
1480
  transition: "background 0.12s ease, color 0.12s ease"
1219
1481
  },
1220
1482
  children
@@ -1222,37 +1484,11 @@ function IconButton({
1222
1484
  )
1223
1485
  ] });
1224
1486
  }
1225
- function SettingsButton({
1226
- open,
1227
- onClick,
1228
- selectedMode,
1229
- frameSettings,
1230
- onFrameSettingsChange,
1231
- panelSide,
1232
- tooltipSide,
1233
- bottom
1234
- }) {
1235
- const verticalAlign = bottom ? { bottom: 0 } : { top: 0 };
1236
- const panelStyle = panelSide === "left" ? { right: "calc(100% + 10px)", ...verticalAlign } : { left: "calc(100% + 10px)", ...verticalAlign };
1237
- return /* @__PURE__ */ jsxs2("div", { style: { position: "relative" }, children: [
1238
- /* @__PURE__ */ jsx2(IconButton, { active: open, tooltipSide, tooltip: !open ? "Settings" : void 0, onClick, children: /* @__PURE__ */ jsx2(Settings, { size: 16, strokeWidth: 1.7 }) }),
1239
- open && /* @__PURE__ */ jsx2(
1240
- SettingsPanel,
1241
- {
1242
- style: { position: "absolute", ...panelStyle },
1243
- onClose: onClick,
1244
- selectedMode,
1245
- frameSettings,
1246
- onFrameSettingsChange
1247
- }
1248
- )
1249
- ] });
1250
- }
1251
1487
  function DropItem2({
1252
1488
  children,
1253
1489
  onClick,
1254
1490
  active,
1255
- accent
1491
+ accent: accent2
1256
1492
  }) {
1257
1493
  return /* @__PURE__ */ jsx2(
1258
1494
  "button",
@@ -1262,41 +1498,79 @@ function DropItem2({
1262
1498
  display: "block",
1263
1499
  width: "100%",
1264
1500
  padding: "7px 12px",
1265
- background: active ? "rgba(255, 255, 255, 0.08)" : "transparent",
1501
+ background: active ? state.active : "transparent",
1266
1502
  border: "none",
1267
- color: accent ? "rgba(125, 211, 252, 0.96)" : "rgba(255, 255, 255, 0.86)",
1503
+ color: accent2 ? accent.highlight : fg.strong,
1268
1504
  textAlign: "left",
1269
1505
  cursor: "pointer",
1270
- fontSize: 13,
1506
+ ...text.paragraph.sm,
1271
1507
  fontFamily: "inherit"
1272
1508
  },
1273
1509
  children
1274
1510
  }
1275
1511
  );
1276
1512
  }
1277
- function Separator({ vertical = true }) {
1513
+ function Separator({
1514
+ vertical = true,
1515
+ onClick
1516
+ }) {
1278
1517
  return /* @__PURE__ */ jsx2(
1279
1518
  "div",
1280
1519
  {
1520
+ onClick,
1281
1521
  style: {
1522
+ position: "relative",
1282
1523
  width: vertical ? 1 : 24,
1283
1524
  height: vertical ? 18 : 1,
1284
- background: "rgba(255,255,255,0.12)",
1525
+ background: stroke.strong,
1285
1526
  flexShrink: 0,
1286
- margin: vertical ? "0 6px" : "6px 0"
1287
- }
1527
+ margin: vertical ? "0 6px" : "6px 0",
1528
+ cursor: onClick ? "pointer" : void 0
1529
+ },
1530
+ children: onClick && /* @__PURE__ */ jsx2("div", { style: { position: "absolute", inset: vertical ? "0 -8px" : "-8px 0" } })
1531
+ }
1532
+ );
1533
+ }
1534
+ function PanelTab({
1535
+ children,
1536
+ active,
1537
+ onClick
1538
+ }) {
1539
+ return /* @__PURE__ */ jsx2(
1540
+ "button",
1541
+ {
1542
+ onClick,
1543
+ style: {
1544
+ flex: 1,
1545
+ padding: "6px 0",
1546
+ border: "none",
1547
+ borderBottom: `2px solid ${active ? fg.strong : "transparent"}`,
1548
+ background: "transparent",
1549
+ color: active ? fg.strong : fg.muted,
1550
+ ...text.label.xs,
1551
+ fontFamily: "inherit",
1552
+ cursor: "pointer",
1553
+ transition: "color 0.12s ease, border-color 0.12s ease"
1554
+ },
1555
+ children
1288
1556
  }
1289
1557
  );
1290
1558
  }
1291
1559
  function HistoryButton({
1292
1560
  open,
1293
1561
  onClick,
1562
+ selectedMode,
1563
+ frameSettings,
1564
+ onFrameSettingsChange,
1294
1565
  panelSide,
1295
1566
  tooltipSide,
1296
1567
  bottom
1297
1568
  }) {
1569
+ const [activeTab, setActiveTab] = useState3("screenshots");
1298
1570
  const [toast, setToast] = useState3(null);
1299
1571
  const [pushing, setPushing] = useState3(false);
1572
+ const [saveDir, setSaveDir] = useState3(null);
1573
+ const [picking, setPicking] = useState3(false);
1300
1574
  const [repos, setRepos] = useState3([]);
1301
1575
  const [branches, setBranches] = useState3([]);
1302
1576
  const [screenshots, setScreenshots] = useState3([]);
@@ -1309,6 +1583,30 @@ function HistoryButton({
1309
1583
  const [editingFile, setEditingFile] = useState3(null);
1310
1584
  const [editValue, setEditValue] = useState3("");
1311
1585
  const [hoveredThumb, setHoveredThumb] = useState3(null);
1586
+ useEffect2(() => {
1587
+ if (!open) return;
1588
+ fetch("/__afterbefore/config").then((r) => r.json()).then((data) => setSaveDir(data.saveDir)).catch(() => {
1589
+ });
1590
+ }, [open]);
1591
+ const handlePickFolder = async () => {
1592
+ setPicking(true);
1593
+ try {
1594
+ const res = await fetch("/__afterbefore/pick-folder", { method: "POST" });
1595
+ const data = await res.json();
1596
+ if (data.folder) {
1597
+ await fetch("/__afterbefore/config", {
1598
+ method: "POST",
1599
+ headers: { "Content-Type": "application/json" },
1600
+ body: JSON.stringify({ saveDir: data.folder })
1601
+ });
1602
+ setSaveDir(data.folder);
1603
+ }
1604
+ } catch {
1605
+ } finally {
1606
+ setPicking(false);
1607
+ }
1608
+ };
1609
+ const shortDir = saveDir ? saveDir.replace(/^\/Users\/[^/]+/, "~") : "~/Desktop";
1312
1610
  useEffect2(() => {
1313
1611
  if (!open) {
1314
1612
  setRepoDropOpen(false);
@@ -1425,255 +1723,259 @@ function HistoryButton({
1425
1723
  maxWidth: 360,
1426
1724
  padding: "10px 12px",
1427
1725
  borderRadius: 12,
1428
- background: "rgb(32, 32, 36)",
1429
- border: "1px solid rgba(255, 255, 255, 0.1)",
1430
- boxShadow: "0 14px 36px rgba(0, 0, 0, 0.32)"
1726
+ background: bg.base,
1727
+ border: `1px solid ${stroke.default}`,
1728
+ boxShadow: shadow.panel,
1729
+ animation: "ab-panel-in 150ms cubic-bezier(0.23, 1, 0.32, 1)"
1431
1730
  },
1432
1731
  children: [
1433
- /* @__PURE__ */ jsx2(
1434
- "div",
1732
+ /* @__PURE__ */ jsxs2("div", { style: { display: "flex", gap: 2, marginBottom: 10 }, children: [
1733
+ /* @__PURE__ */ jsx2(PanelTab, { active: activeTab === "screenshots", onClick: () => setActiveTab("screenshots"), children: "Screenshots" }),
1734
+ /* @__PURE__ */ jsx2(PanelTab, { active: activeTab === "settings", onClick: () => setActiveTab("settings"), children: "Settings" })
1735
+ ] }),
1736
+ activeTab === "settings" && /* @__PURE__ */ jsx2(
1737
+ SettingsContent,
1435
1738
  {
1436
- style: {
1437
- fontSize: 11,
1438
- color: "rgba(255, 255, 255, 0.46)",
1439
- letterSpacing: "0.03em",
1440
- textTransform: "uppercase",
1441
- marginBottom: 10
1442
- },
1443
- children: "Screenshots"
1739
+ frameSettings,
1740
+ onFrameSettingsChange,
1741
+ saveDir: shortDir,
1742
+ picking,
1743
+ onPickFolder: handlePickFolder
1444
1744
  }
1445
1745
  ),
1446
- /* @__PURE__ */ jsxs2("div", { style: { display: "flex", flexDirection: "column", gap: 6, marginBottom: 10 }, children: [
1447
- /* @__PURE__ */ jsx2(
1448
- FilterDropdown,
1449
- {
1450
- label: "Project",
1451
- value: selectedRepo,
1452
- options: repos,
1453
- isOpen: repoDropOpen,
1454
- onToggle: () => {
1455
- setRepoDropOpen((p) => !p);
1456
- setBranchDropOpen(false);
1457
- },
1458
- onSelect: (repo) => {
1459
- setSelectedRepo(repo);
1460
- setSelectedBranch(null);
1461
- setRepoDropOpen(false);
1746
+ activeTab === "screenshots" && /* @__PURE__ */ jsxs2(Fragment2, { children: [
1747
+ /* @__PURE__ */ jsxs2("div", { style: { display: "flex", flexDirection: "column", gap: 6, marginBottom: 10 }, children: [
1748
+ /* @__PURE__ */ jsx2(
1749
+ FilterDropdown,
1750
+ {
1751
+ label: "Project",
1752
+ value: selectedRepo,
1753
+ options: repos,
1754
+ isOpen: repoDropOpen,
1755
+ onToggle: () => {
1756
+ setRepoDropOpen((p) => !p);
1757
+ setBranchDropOpen(false);
1758
+ },
1759
+ onSelect: (repo) => {
1760
+ setSelectedRepo(repo);
1761
+ setSelectedBranch(null);
1762
+ setRepoDropOpen(false);
1763
+ }
1462
1764
  }
1463
- }
1464
- ),
1465
- /* @__PURE__ */ jsx2(
1466
- FilterDropdown,
1467
- {
1468
- label: "Branch",
1469
- value: selectedBranch,
1470
- options: branches,
1471
- isOpen: branchDropOpen,
1472
- onToggle: () => {
1473
- setBranchDropOpen((p) => !p);
1474
- setRepoDropOpen(false);
1475
- },
1476
- onSelect: (branch) => {
1477
- setSelectedBranch(branch);
1478
- setBranchDropOpen(false);
1765
+ ),
1766
+ /* @__PURE__ */ jsx2(
1767
+ FilterDropdown,
1768
+ {
1769
+ label: "Branch",
1770
+ value: selectedBranch,
1771
+ options: branches,
1772
+ isOpen: branchDropOpen,
1773
+ onToggle: () => {
1774
+ setBranchDropOpen((p) => !p);
1775
+ setRepoDropOpen(false);
1776
+ },
1777
+ onSelect: (branch) => {
1778
+ setSelectedBranch(branch);
1779
+ setBranchDropOpen(false);
1780
+ }
1479
1781
  }
1480
- }
1481
- )
1482
- ] }),
1483
- loading ? /* @__PURE__ */ jsx2(
1484
- "div",
1485
- {
1486
- style: {
1487
- padding: "12px 0",
1488
- textAlign: "center",
1489
- fontSize: 12,
1490
- color: "rgba(255, 255, 255, 0.35)"
1491
- },
1492
- children: "Loading..."
1493
- }
1494
- ) : screenshots.length === 0 ? /* @__PURE__ */ jsx2(
1495
- "div",
1496
- {
1497
- style: {
1498
- padding: "12px 0",
1499
- textAlign: "center",
1500
- fontSize: 12,
1501
- color: "rgba(255, 255, 255, 0.35)"
1502
- },
1503
- children: "No screenshots yet"
1504
- }
1505
- ) : /* @__PURE__ */ jsxs2(Fragment2, { children: [
1506
- /* @__PURE__ */ jsx2(
1782
+ )
1783
+ ] }),
1784
+ loading ? /* @__PURE__ */ jsx2(
1507
1785
  "div",
1508
1786
  {
1509
1787
  style: {
1510
- maxHeight: 240,
1511
- overflowY: "auto",
1512
- display: "flex",
1513
- flexDirection: "column",
1514
- gap: 8
1788
+ padding: "12px 0",
1789
+ textAlign: "center",
1790
+ ...text.paragraph.xs,
1791
+ color: fg.faint
1515
1792
  },
1516
- children: screenshots.map((shot) => {
1517
- const imgUrl = `/__afterbefore/history/image?repo=${encodeURIComponent(selectedRepo || "")}&branch=${encodeURIComponent(selectedBranch || "")}&file=${encodeURIComponent(shot.filename)}`;
1518
- const isEditing = editingFile === shot.filename;
1519
- return /* @__PURE__ */ jsxs2(
1520
- "div",
1521
- {
1522
- style: {
1523
- display: "flex",
1524
- gap: 10,
1525
- alignItems: "center"
1526
- },
1527
- children: [
1528
- /* @__PURE__ */ jsxs2(
1529
- "div",
1530
- {
1531
- style: {
1532
- position: "relative",
1533
- width: 56,
1534
- height: 36,
1535
- flexShrink: 0,
1536
- borderRadius: 4,
1537
- overflow: "hidden",
1538
- cursor: "pointer"
1539
- },
1540
- onMouseEnter: () => setHoveredThumb(shot.filename),
1541
- onMouseLeave: () => setHoveredThumb(null),
1542
- onClick: () => setLightboxSrc(imgUrl),
1543
- children: [
1544
- /* @__PURE__ */ jsx2(
1545
- "img",
1546
- {
1547
- src: imgUrl,
1548
- alt: "",
1549
- style: {
1550
- width: 56,
1551
- height: 36,
1552
- objectFit: "cover",
1553
- border: "1px solid rgba(255, 255, 255, 0.1)",
1554
- borderRadius: 4,
1555
- background: "rgba(255, 255, 255, 0.05)",
1556
- display: "block"
1557
- }
1558
- }
1559
- ),
1560
- /* @__PURE__ */ jsx2(
1561
- "div",
1562
- {
1563
- style: {
1564
- position: "absolute",
1565
- inset: 0,
1566
- background: "rgba(0, 0, 0, 0.55)",
1567
- display: "flex",
1568
- alignItems: "center",
1569
- justifyContent: "center",
1570
- borderRadius: 4,
1571
- opacity: hoveredThumb === shot.filename ? 1 : 0,
1572
- transition: "opacity 0.15s ease"
1573
- },
1574
- children: /* @__PURE__ */ jsx2(Eye, { size: 16, strokeWidth: 1.8, color: "rgba(255, 255, 255, 0.9)" })
1575
- }
1576
- )
1577
- ]
1578
- }
1579
- ),
1580
- /* @__PURE__ */ jsx2("div", { style: { flex: 1, minWidth: 0 }, children: isEditing ? /* @__PURE__ */ jsx2(
1581
- "input",
1582
- {
1583
- autoFocus: true,
1584
- value: editValue,
1585
- onChange: (e) => setEditValue(e.target.value),
1586
- onKeyDown: (e) => {
1587
- if (e.key === "Enter") handleRename(shot.filename, editValue);
1588
- if (e.key === "Escape") setEditingFile(null);
1589
- },
1590
- onBlur: () => handleRename(shot.filename, editValue),
1591
- style: {
1592
- width: "100%",
1593
- fontSize: 12,
1594
- color: "rgba(255, 255, 255, 0.88)",
1595
- background: "rgba(255, 255, 255, 0.1)",
1596
- border: "1px solid rgba(255, 255, 255, 0.2)",
1597
- borderRadius: 4,
1598
- padding: "2px 6px",
1599
- outline: "none",
1600
- fontFamily: "inherit"
1601
- }
1602
- }
1603
- ) : /* @__PURE__ */ jsx2(
1604
- "div",
1605
- {
1606
- onClick: () => {
1607
- setEditingFile(shot.filename);
1608
- setEditValue(shot.filename.replace(/\.png$/, ""));
1609
- },
1610
- style: {
1611
- fontSize: 12,
1612
- color: "rgba(255, 255, 255, 0.88)",
1613
- cursor: "pointer",
1614
- overflow: "hidden",
1615
- textOverflow: "ellipsis",
1616
- whiteSpace: "nowrap"
1617
- },
1618
- title: "Click to rename",
1619
- children: formatTimestamp(shot.filename)
1620
- }
1621
- ) }),
1622
- /* @__PURE__ */ jsx2(
1623
- "button",
1624
- {
1625
- onClick: () => handleDelete(shot.filename),
1626
- title: "Delete screenshot",
1627
- style: {
1628
- flexShrink: 0,
1629
- width: 24,
1630
- height: 24,
1631
- borderRadius: 4,
1632
- border: "none",
1633
- background: "transparent",
1634
- color: "rgba(255, 255, 255, 0.35)",
1635
- cursor: "pointer",
1636
- display: "flex",
1637
- alignItems: "center",
1638
- justifyContent: "center",
1639
- padding: 0
1640
- },
1641
- onMouseEnter: (e) => {
1642
- e.currentTarget.style.color = "rgba(239, 68, 68, 0.9)";
1643
- e.currentTarget.style.background = "rgba(239, 68, 68, 0.1)";
1644
- },
1645
- onMouseLeave: (e) => {
1646
- e.currentTarget.style.color = "rgba(255, 255, 255, 0.35)";
1647
- e.currentTarget.style.background = "transparent";
1648
- },
1649
- children: /* @__PURE__ */ jsx2(Trash22, { size: 13, strokeWidth: 1.8 })
1650
- }
1651
- )
1652
- ]
1653
- },
1654
- shot.filename
1655
- );
1656
- })
1793
+ children: "Loading..."
1657
1794
  }
1658
- ),
1659
- /* @__PURE__ */ jsx2(
1795
+ ) : screenshots.length === 0 ? /* @__PURE__ */ jsx2(
1660
1796
  "div",
1661
1797
  {
1662
1798
  style: {
1663
- height: 1,
1664
- background: "rgba(255, 255, 255, 0.08)",
1665
- margin: "8px 0"
1666
- }
1799
+ padding: "12px 0",
1800
+ textAlign: "center",
1801
+ ...text.paragraph.xs,
1802
+ color: fg.faint
1803
+ },
1804
+ children: "No screenshots yet"
1667
1805
  }
1668
- ),
1669
- /* @__PURE__ */ jsxs2("div", { style: { display: "flex", flexDirection: "column", gap: 2 }, children: [
1670
- /* @__PURE__ */ jsxs2(ActionButton, { onClick: handleOpenFolder, children: [
1671
- /* @__PURE__ */ jsx2(FolderOpen, { size: 13, strokeWidth: 1.8 }),
1672
- "Open Folder"
1673
- ] }),
1674
- /* @__PURE__ */ jsxs2(ActionButton, { onClick: handlePush, disabled: pushing, children: [
1675
- /* @__PURE__ */ jsx2(ArrowUp, { size: 13, strokeWidth: 1.8 }),
1676
- pushing ? "Pushing..." : "Push to PR"
1806
+ ) : /* @__PURE__ */ jsxs2(Fragment2, { children: [
1807
+ /* @__PURE__ */ jsx2(
1808
+ "div",
1809
+ {
1810
+ style: {
1811
+ maxHeight: 240,
1812
+ overflowY: "auto",
1813
+ display: "flex",
1814
+ flexDirection: "column",
1815
+ gap: 8
1816
+ },
1817
+ children: screenshots.map((shot) => {
1818
+ const imgUrl = `/__afterbefore/history/image?repo=${encodeURIComponent(selectedRepo || "")}&branch=${encodeURIComponent(selectedBranch || "")}&file=${encodeURIComponent(shot.filename)}`;
1819
+ const isEditing = editingFile === shot.filename;
1820
+ return /* @__PURE__ */ jsxs2(
1821
+ "div",
1822
+ {
1823
+ style: {
1824
+ display: "flex",
1825
+ gap: 10,
1826
+ alignItems: "center"
1827
+ },
1828
+ children: [
1829
+ /* @__PURE__ */ jsxs2(
1830
+ "div",
1831
+ {
1832
+ style: {
1833
+ position: "relative",
1834
+ width: 56,
1835
+ height: 36,
1836
+ flexShrink: 0,
1837
+ borderRadius: 4,
1838
+ overflow: "hidden",
1839
+ cursor: "pointer"
1840
+ },
1841
+ onMouseEnter: () => setHoveredThumb(shot.filename),
1842
+ onMouseLeave: () => setHoveredThumb(null),
1843
+ onClick: () => setLightboxSrc(imgUrl),
1844
+ children: [
1845
+ /* @__PURE__ */ jsx2(
1846
+ "img",
1847
+ {
1848
+ src: imgUrl,
1849
+ alt: "",
1850
+ style: {
1851
+ width: 56,
1852
+ height: 36,
1853
+ objectFit: "cover",
1854
+ border: `1px solid ${stroke.default}`,
1855
+ borderRadius: 4,
1856
+ background: state.subtle,
1857
+ display: "block"
1858
+ }
1859
+ }
1860
+ ),
1861
+ /* @__PURE__ */ jsx2(
1862
+ "div",
1863
+ {
1864
+ style: {
1865
+ position: "absolute",
1866
+ inset: 0,
1867
+ background: "rgba(0, 0, 0, 0.55)",
1868
+ display: "flex",
1869
+ alignItems: "center",
1870
+ justifyContent: "center",
1871
+ borderRadius: 4,
1872
+ opacity: hoveredThumb === shot.filename ? 1 : 0,
1873
+ transition: "opacity 0.15s ease"
1874
+ },
1875
+ children: /* @__PURE__ */ jsx2(Eye, { size: 16, strokeWidth: 1.8, color: fg.strong })
1876
+ }
1877
+ )
1878
+ ]
1879
+ }
1880
+ ),
1881
+ /* @__PURE__ */ jsx2("div", { style: { flex: 1, minWidth: 0 }, children: isEditing ? /* @__PURE__ */ jsx2(
1882
+ "input",
1883
+ {
1884
+ autoFocus: true,
1885
+ value: editValue,
1886
+ onChange: (e) => setEditValue(e.target.value),
1887
+ onKeyDown: (e) => {
1888
+ if (e.key === "Enter") handleRename(shot.filename, editValue);
1889
+ if (e.key === "Escape") setEditingFile(null);
1890
+ },
1891
+ onBlur: () => handleRename(shot.filename, editValue),
1892
+ style: {
1893
+ width: "100%",
1894
+ ...text.label.xs,
1895
+ color: fg.strong,
1896
+ background: state.hover,
1897
+ border: `1px solid ${stroke.interactive}`,
1898
+ borderRadius: 4,
1899
+ padding: "2px 6px",
1900
+ outline: "none",
1901
+ fontFamily: "inherit"
1902
+ }
1903
+ }
1904
+ ) : /* @__PURE__ */ jsx2(
1905
+ "div",
1906
+ {
1907
+ onClick: () => {
1908
+ setEditingFile(shot.filename);
1909
+ setEditValue(shot.filename.replace(/\.png$/, ""));
1910
+ },
1911
+ style: {
1912
+ ...text.label.xs,
1913
+ color: fg.strong,
1914
+ cursor: "pointer",
1915
+ overflow: "hidden",
1916
+ textOverflow: "ellipsis",
1917
+ whiteSpace: "nowrap"
1918
+ },
1919
+ title: "Click to rename",
1920
+ children: formatTimestamp(shot.filename)
1921
+ }
1922
+ ) }),
1923
+ /* @__PURE__ */ jsx2(
1924
+ "button",
1925
+ {
1926
+ onClick: () => handleDelete(shot.filename),
1927
+ title: "Delete screenshot",
1928
+ style: {
1929
+ flexShrink: 0,
1930
+ width: 24,
1931
+ height: 24,
1932
+ borderRadius: 4,
1933
+ border: "none",
1934
+ background: "transparent",
1935
+ color: fg.faint,
1936
+ cursor: "pointer",
1937
+ display: "flex",
1938
+ alignItems: "center",
1939
+ justifyContent: "center",
1940
+ padding: 0
1941
+ },
1942
+ onMouseEnter: (e) => {
1943
+ e.currentTarget.style.color = feedback.error;
1944
+ e.currentTarget.style.background = feedback.errorBg;
1945
+ },
1946
+ onMouseLeave: (e) => {
1947
+ e.currentTarget.style.color = fg.faint;
1948
+ e.currentTarget.style.background = "transparent";
1949
+ },
1950
+ children: /* @__PURE__ */ jsx2(Trash22, { size: 13, strokeWidth: 1.8 })
1951
+ }
1952
+ )
1953
+ ]
1954
+ },
1955
+ shot.filename
1956
+ );
1957
+ })
1958
+ }
1959
+ ),
1960
+ /* @__PURE__ */ jsx2(
1961
+ "div",
1962
+ {
1963
+ style: {
1964
+ height: 1,
1965
+ background: state.active,
1966
+ margin: "8px 0"
1967
+ }
1968
+ }
1969
+ ),
1970
+ /* @__PURE__ */ jsxs2("div", { style: { display: "flex", flexDirection: "column", gap: 2 }, children: [
1971
+ /* @__PURE__ */ jsxs2(ActionButton, { onClick: handleOpenFolder, children: [
1972
+ /* @__PURE__ */ jsx2(FolderOpen, { size: 13, strokeWidth: 1.8 }),
1973
+ "Open Folder"
1974
+ ] }),
1975
+ /* @__PURE__ */ jsxs2(ActionButton, { onClick: handlePush, disabled: pushing, children: [
1976
+ /* @__PURE__ */ jsx2(ArrowUp, { size: 13, strokeWidth: 1.8 }),
1977
+ pushing ? "Pushing..." : "Push to PR"
1978
+ ] })
1677
1979
  ] })
1678
1980
  ] })
1679
1981
  ] }),
@@ -1688,12 +1990,11 @@ function HistoryButton({
1688
1990
  marginBottom: 8,
1689
1991
  padding: "6px 12px",
1690
1992
  borderRadius: 6,
1691
- fontSize: 12,
1692
- fontWeight: 500,
1993
+ ...text.label.xs,
1693
1994
  whiteSpace: "nowrap",
1694
1995
  color: "white",
1695
- background: toast.type === "success" ? "rgba(34, 197, 94, 0.9)" : "rgba(239, 68, 68, 0.9)",
1696
- boxShadow: "0 2px 8px rgba(0,0,0,0.3)"
1996
+ background: toast.type === "success" ? feedback.success : feedback.error,
1997
+ boxShadow: shadow.toast
1697
1998
  },
1698
1999
  children: toast.message
1699
2000
  }
@@ -1701,65 +2002,68 @@ function HistoryButton({
1701
2002
  ]
1702
2003
  }
1703
2004
  ),
1704
- lightboxSrc && /* @__PURE__ */ jsxs2(
1705
- "div",
1706
- {
1707
- onClick: () => setLightboxSrc(null),
1708
- onKeyDown: (e) => {
1709
- if (e.key === "Escape") setLightboxSrc(null);
1710
- },
1711
- style: {
1712
- position: "fixed",
1713
- inset: 0,
1714
- bottom: 100,
1715
- zIndex: 2147483646,
1716
- background: "rgba(0, 0, 0, 0.85)",
1717
- display: "flex",
1718
- alignItems: "center",
1719
- justifyContent: "center",
1720
- cursor: "zoom-out"
1721
- },
1722
- children: [
1723
- /* @__PURE__ */ jsx2(
1724
- "img",
1725
- {
1726
- src: lightboxSrc,
1727
- alt: "",
1728
- onClick: (e) => e.stopPropagation(),
1729
- style: {
1730
- maxWidth: "90vw",
1731
- maxHeight: "calc(100% - 32px)",
1732
- borderRadius: 8,
1733
- boxShadow: "0 20px 60px rgba(0, 0, 0, 0.5)",
1734
- cursor: "default"
2005
+ lightboxSrc && createPortal(
2006
+ /* @__PURE__ */ jsxs2(
2007
+ "div",
2008
+ {
2009
+ "data-afterbefore": "true",
2010
+ onClick: () => setLightboxSrc(null),
2011
+ onKeyDown: (e) => {
2012
+ if (e.key === "Escape") setLightboxSrc(null);
2013
+ },
2014
+ style: {
2015
+ position: "fixed",
2016
+ inset: 0,
2017
+ zIndex: 2147483647,
2018
+ background: "rgba(0, 0, 0, 0.85)",
2019
+ display: "flex",
2020
+ alignItems: "center",
2021
+ justifyContent: "center",
2022
+ cursor: "zoom-out"
2023
+ },
2024
+ children: [
2025
+ /* @__PURE__ */ jsx2(
2026
+ "img",
2027
+ {
2028
+ src: lightboxSrc,
2029
+ alt: "",
2030
+ onClick: (e) => e.stopPropagation(),
2031
+ style: {
2032
+ maxWidth: "90vw",
2033
+ maxHeight: "calc(100vh - 64px)",
2034
+ borderRadius: 8,
2035
+ boxShadow: "0 20px 60px rgba(0, 0, 0, 0.5)",
2036
+ cursor: "default"
2037
+ }
1735
2038
  }
1736
- }
1737
- ),
1738
- /* @__PURE__ */ jsx2(
1739
- "button",
1740
- {
1741
- onClick: () => setLightboxSrc(null),
1742
- style: {
1743
- position: "absolute",
1744
- top: 16,
1745
- right: 16,
1746
- width: 32,
1747
- height: 32,
1748
- borderRadius: "50%",
1749
- border: "none",
1750
- background: "rgba(255, 255, 255, 0.15)",
1751
- color: "white",
1752
- cursor: "pointer",
1753
- display: "flex",
1754
- alignItems: "center",
1755
- justifyContent: "center",
1756
- padding: 0
1757
- },
1758
- children: /* @__PURE__ */ jsx2(X2, { size: 18, strokeWidth: 2 })
1759
- }
1760
- )
1761
- ]
1762
- }
2039
+ ),
2040
+ /* @__PURE__ */ jsx2(
2041
+ "button",
2042
+ {
2043
+ onClick: () => setLightboxSrc(null),
2044
+ style: {
2045
+ position: "absolute",
2046
+ top: 16,
2047
+ right: 16,
2048
+ width: 32,
2049
+ height: 32,
2050
+ borderRadius: "50%",
2051
+ border: "none",
2052
+ background: state.pressed,
2053
+ color: "white",
2054
+ cursor: "pointer",
2055
+ display: "flex",
2056
+ alignItems: "center",
2057
+ justifyContent: "center",
2058
+ padding: 0
2059
+ },
2060
+ children: /* @__PURE__ */ jsx2(X2, { size: 18, strokeWidth: 2 })
2061
+ }
2062
+ )
2063
+ ]
2064
+ }
2065
+ ),
2066
+ document.body
1763
2067
  )
1764
2068
  ] });
1765
2069
  }
@@ -1776,9 +2080,8 @@ function FilterDropdown({
1776
2080
  "div",
1777
2081
  {
1778
2082
  style: {
1779
- fontSize: 11,
1780
- color: "rgba(255, 255, 255, 0.42)",
1781
- letterSpacing: "0.02em",
2083
+ ...text.subheading.xxs,
2084
+ color: fg.muted,
1782
2085
  marginBottom: 3
1783
2086
  },
1784
2087
  children: label
@@ -1798,11 +2101,11 @@ function FilterDropdown({
1798
2101
  height: 30,
1799
2102
  padding: "0 8px",
1800
2103
  borderRadius: 7,
1801
- border: "1px solid rgba(255,255,255,0.1)",
1802
- background: "rgba(255,255,255,0.07)",
1803
- color: "rgba(255,255,255,0.88)",
2104
+ border: `1px solid ${stroke.default}`,
2105
+ background: state.input,
2106
+ color: fg.strong,
1804
2107
  cursor: "pointer",
1805
- fontSize: 12,
2108
+ ...text.label.xs,
1806
2109
  fontFamily: "inherit"
1807
2110
  },
1808
2111
  children: [
@@ -1831,11 +2134,11 @@ function FilterDropdown({
1831
2134
  right: 0,
1832
2135
  maxHeight: 160,
1833
2136
  overflowY: "auto",
1834
- background: "rgb(32, 32, 36)",
1835
- border: "1px solid rgba(255, 255, 255, 0.1)",
2137
+ background: bg.base,
2138
+ border: `1px solid ${stroke.default}`,
1836
2139
  borderRadius: 8,
1837
2140
  padding: "4px 0",
1838
- boxShadow: "0 10px 30px rgba(0, 0, 0, 0.3)",
2141
+ boxShadow: shadow.dropdown,
1839
2142
  zIndex: 1
1840
2143
  },
1841
2144
  children: options.map((opt) => /* @__PURE__ */ jsx2(
@@ -1872,9 +2175,9 @@ function ActionButton({
1872
2175
  width: "100%",
1873
2176
  padding: "6px 8px",
1874
2177
  border: "none",
1875
- background: hovered ? "rgba(255, 255, 255, 0.08)" : "transparent",
1876
- color: "rgba(255, 255, 255, 0.78)",
1877
- fontSize: 12,
2178
+ background: hovered ? state.active : "transparent",
2179
+ color: fg.default,
2180
+ ...text.label.xs,
1878
2181
  borderRadius: 6,
1879
2182
  cursor: disabled ? "wait" : "pointer",
1880
2183
  textAlign: "left",
@@ -1907,18 +2210,18 @@ function Inspector({ onSelect, onCancel }) {
1907
2210
  const hoveredEl = useRef3(null);
1908
2211
  const styleEl = useRef3(null);
1909
2212
  useEffect3(() => {
1910
- const style = document.createElement("style");
1911
- style.setAttribute("data-afterbefore", "true");
1912
- style.textContent = [
2213
+ const style2 = document.createElement("style");
2214
+ style2.setAttribute("data-afterbefore", "true");
2215
+ style2.textContent = [
1913
2216
  "* { cursor: crosshair !important; }",
1914
2217
  "[data-afterbefore], [data-afterbefore] * { cursor: auto !important; }",
1915
2218
  "[data-afterbefore] button, [data-afterbefore] label, [data-afterbefore] a { cursor: pointer !important; }",
1916
2219
  "[data-afterbefore] input, [data-afterbefore] textarea { cursor: text !important; }"
1917
2220
  ].join("\n");
1918
- document.head.appendChild(style);
1919
- styleEl.current = style;
2221
+ document.head.appendChild(style2);
2222
+ styleEl.current = style2;
1920
2223
  return () => {
1921
- style.remove();
2224
+ style2.remove();
1922
2225
  };
1923
2226
  }, []);
1924
2227
  const isOverlayElement = useCallback3((el) => {
@@ -2015,12 +2318,15 @@ async function saveCapture(mode, dataUrl) {
2015
2318
  }
2016
2319
  }
2017
2320
  function AfterBefore() {
2018
- const { state, captureComplete, reset } = useOverlayState();
2321
+ const { state: state2, captureComplete, reset } = useOverlayState();
2019
2322
  const [toolbarActive, setToolbarActive] = useState5(false);
2020
2323
  const [inspectorActive, setInspectorActive] = useState5(false);
2021
2324
  const [loading, setLoading] = useState5(false);
2022
2325
  const [selectedMode, setSelectedMode] = useState5("component");
2023
2326
  const [frameSettings, setFrameSettings] = useState5(DEFAULT_FRAME_SETTINGS);
2327
+ useEffect4(() => {
2328
+ injectInterFont();
2329
+ }, []);
2024
2330
  useEffect4(() => {
2025
2331
  try {
2026
2332
  const stored = localStorage.getItem("ab-frame-settings");
@@ -2037,16 +2343,16 @@ function AfterBefore() {
2037
2343
  }
2038
2344
  }, []);
2039
2345
  useEffect4(() => {
2040
- if (state.phase === "ready") {
2346
+ if (state2.phase === "ready") {
2041
2347
  const timer = setTimeout(() => {
2042
2348
  reset();
2043
2349
  }, 1500);
2044
2350
  return () => clearTimeout(timer);
2045
2351
  }
2046
- }, [state.phase, reset]);
2352
+ }, [state2.phase, reset]);
2047
2353
  const handleToggle = useCallback4(() => {
2048
2354
  if (loading) return;
2049
- if (state.phase === "ready") {
2355
+ if (state2.phase === "ready") {
2050
2356
  reset();
2051
2357
  }
2052
2358
  if (toolbarActive || inspectorActive) {
@@ -2061,7 +2367,7 @@ function AfterBefore() {
2061
2367
  setInspectorActive(false);
2062
2368
  }
2063
2369
  }
2064
- }, [state.phase, loading, toolbarActive, inspectorActive, selectedMode, reset]);
2370
+ }, [state2.phase, loading, toolbarActive, inspectorActive, selectedMode, reset]);
2065
2371
  const performCapture = useCallback4(
2066
2372
  async (mode, element) => {
2067
2373
  setLoading(true);
@@ -2128,7 +2434,7 @@ function AfterBefore() {
2128
2434
  {
2129
2435
  expanded: toolbarActive,
2130
2436
  onToggle: handleToggle,
2131
- phase: state.phase,
2437
+ phase: state2.phase,
2132
2438
  loading,
2133
2439
  selectedMode,
2134
2440
  onModeChange: handleModeChange,