afterbefore 0.2.16 → 0.2.18

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,7 +1,7 @@
1
1
  "use client";
2
2
 
3
3
  // src/overlay/index.tsx
4
- import { useState as useState5, useCallback as useCallback5, useRef as useRef4, useEffect as useEffect4 } from "react";
4
+ import { useState as useState5, useCallback as useCallback4, useEffect as useEffect4 } from "react";
5
5
 
6
6
  // src/overlay/state.ts
7
7
  import { useState, useCallback } from "react";
@@ -177,457 +177,304 @@ function loadImage(src) {
177
177
  });
178
178
  }
179
179
 
180
- // src/overlay/ui/icon.tsx
181
- import { useRef, useCallback as useCallback2, useEffect, useState as useState2 } from "react";
182
- import { Camera, Check, LoaderCircle } from "lucide-react";
183
- import { jsx, jsxs } from "react/jsx-runtime";
184
- var CONTAINER_SIZE = 38;
185
- var ICON_SIZE = CONTAINER_SIZE;
186
- var EDGE_MARGIN = 24;
187
- function Icon({ phase, onClick, loading, onPositionChange }) {
188
- const ref = useRef(null);
189
- const [pos, setPos] = useState2({ x: -1, y: -1 });
190
- const [dragging, setDragging] = useState2(false);
191
- const [hovered, setHovered] = useState2(false);
192
- const dragState = useRef(null);
193
- useEffect(() => {
194
- setPos((prev) => {
195
- if (prev.x === -1 || prev.y === -1) {
196
- return {
197
- x: window.innerWidth - ICON_SIZE - EDGE_MARGIN,
198
- y: window.innerHeight - ICON_SIZE - EDGE_MARGIN
199
- };
200
- }
201
- return prev;
202
- });
203
- }, []);
204
- useEffect(() => {
205
- const handleResize = () => {
206
- setPos((prev) => {
207
- if (prev.x === -1 || prev.y === -1) return prev;
208
- const clampedX = Math.max(0, Math.min(prev.x, window.innerWidth - ICON_SIZE));
209
- const clampedY = Math.max(0, Math.min(prev.y, window.innerHeight - ICON_SIZE));
210
- if (clampedX === prev.x && clampedY === prev.y) return prev;
211
- return { x: clampedX, y: clampedY };
212
- });
213
- };
214
- window.addEventListener("resize", handleResize);
215
- return () => window.removeEventListener("resize", handleResize);
216
- }, []);
217
- useEffect(() => {
218
- if (pos.x !== -1 && pos.y !== -1) {
219
- onPositionChange?.({ x: pos.x, y: pos.y });
220
- }
221
- }, [pos, onPositionChange]);
222
- const handleMouseDown = useCallback2(
223
- (e) => {
224
- e.preventDefault();
225
- setDragging(true);
226
- dragState.current = {
227
- dragging: true,
228
- startX: e.clientX,
229
- startY: e.clientY,
230
- origX: pos.x,
231
- origY: pos.y,
232
- distance: 0
233
- };
234
- },
235
- [pos]
236
- );
237
- useEffect(() => {
238
- const handleMouseMove = (e) => {
239
- const ds = dragState.current;
240
- if (!ds || !ds.dragging) return;
241
- const dx = e.clientX - ds.startX;
242
- const dy = e.clientY - ds.startY;
243
- ds.distance = Math.sqrt(dx * dx + dy * dy);
244
- const newX = Math.max(
245
- 0,
246
- Math.min(window.innerWidth - ICON_SIZE, ds.origX + dx)
247
- );
248
- const newY = Math.max(
249
- 0,
250
- Math.min(window.innerHeight - ICON_SIZE, ds.origY + dy)
251
- );
252
- setPos({ x: newX, y: newY });
253
- };
254
- const handleMouseUp = () => {
255
- const ds = dragState.current;
256
- if (!ds) return;
257
- if (ds.distance < 5) {
258
- onClick();
259
- }
260
- setDragging(false);
261
- dragState.current = null;
262
- };
263
- window.addEventListener("mousemove", handleMouseMove);
264
- window.addEventListener("mouseup", handleMouseUp);
265
- return () => {
266
- window.removeEventListener("mousemove", handleMouseMove);
267
- window.removeEventListener("mouseup", handleMouseUp);
268
- };
269
- }, [onClick]);
270
- if (pos.x === -1 || pos.y === -1) return null;
271
- return /* @__PURE__ */ jsxs(
272
- "div",
273
- {
274
- ref,
275
- "data-afterbefore": "true",
276
- onMouseDown: handleMouseDown,
277
- style: {
278
- position: "fixed",
279
- left: pos.x,
280
- top: pos.y,
281
- width: CONTAINER_SIZE,
282
- height: CONTAINER_SIZE,
283
- borderRadius: "50%",
284
- background: hovered ? "rgb(38, 38, 42)" : "rgb(32, 32, 36)",
285
- border: "none",
286
- display: "flex",
287
- alignItems: "center",
288
- justifyContent: "center",
289
- cursor: dragging ? "grabbing" : "pointer",
290
- zIndex: 2147483647,
291
- boxShadow: "0 8px 32px rgba(0, 0, 0, 0.4)",
292
- transition: "background 0.15s",
293
- userSelect: "none"
294
- },
295
- onMouseEnter: () => setHovered(true),
296
- onMouseLeave: () => setHovered(false),
297
- children: [
298
- /* @__PURE__ */ jsx(
299
- "style",
300
- {
301
- dangerouslySetInnerHTML: {
302
- __html: `
303
- @keyframes ab-spin {
304
- 0% { transform: rotate(0deg); }
305
- 100% { transform: rotate(360deg); }
306
- }`
307
- }
308
- }
309
- ),
310
- loading ? /* @__PURE__ */ jsx(
311
- LoaderCircle,
312
- {
313
- size: 16,
314
- strokeWidth: 2,
315
- style: { animation: "ab-spin 0.8s linear infinite", color: "white" }
316
- }
317
- ) : phase === "ready" ? /* @__PURE__ */ jsx(Check, { size: 16, strokeWidth: 2.6, color: "#4ade80" }) : /* @__PURE__ */ jsx(
318
- Camera,
319
- {
320
- size: 16,
321
- strokeWidth: 1.9,
322
- color: hovered ? "rgba(255, 255, 255, 0.96)" : "rgba(255, 255, 255, 0.52)"
323
- }
324
- )
325
- ]
326
- }
327
- );
328
- }
329
-
330
180
  // src/overlay/ui/toolbar.tsx
331
- import { useCallback as useCallback3, useEffect as useEffect2, useRef as useRef2, useState as useState3 } from "react";
181
+ import { useCallback as useCallback2, useEffect as useEffect2, useRef as useRef2, useState as useState3 } from "react";
332
182
  import {
333
183
  ArrowUp,
334
- ChevronDown,
184
+ Camera,
185
+ Check,
186
+ ChevronDown as ChevronDown2,
335
187
  Clock,
336
- Maximize,
188
+ Eye,
337
189
  FolderOpen,
338
- Frame,
339
- ImageIcon,
190
+ LoaderCircle,
191
+ Maximize,
340
192
  Monitor,
341
193
  MousePointer2,
342
- Palette,
343
194
  Settings,
195
+ Trash2 as Trash22,
196
+ X as X2
197
+ } from "lucide-react";
198
+
199
+ // src/overlay/ui/settings-panel.tsx
200
+ import { useEffect, useRef, useState as useState2 } from "react";
201
+ import {
202
+ ChevronDown,
203
+ ImageIcon,
204
+ Palette,
344
205
  Trash2,
345
206
  Upload,
346
207
  X
347
208
  } from "lucide-react";
348
- import { Fragment, jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
349
- var MODES = [
350
- { mode: "component", label: "Component", icon: MousePointer2 },
351
- { mode: "viewport", label: "Viewport", icon: Monitor },
352
- { mode: "fullpage", label: "Full Page", icon: Maximize }
353
- ];
354
- function Toolbar({
209
+ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
210
+ function SettingsPanel({
211
+ style,
212
+ onClose,
355
213
  selectedMode,
356
- onModeChange,
357
- onCapture,
358
- onCancel,
359
214
  frameSettings,
360
215
  onFrameSettingsChange
361
216
  }) {
362
- const [settingsOpen, setSettingsOpen] = useState3(false);
363
- const [historyOpen, setHistoryOpen] = useState3(false);
364
- useEffect2(() => {
365
- const onKey = (e) => {
366
- if (e.target?.tagName === "INPUT") {
367
- if (e.key === "Escape") {
368
- e.target.blur();
369
- }
370
- return;
371
- }
372
- if (e.key === "Escape") {
373
- if (settingsOpen) {
374
- setSettingsOpen(false);
375
- return;
376
- }
377
- onCancel();
378
- } else if (e.key === "Enter") {
379
- onCapture(selectedMode);
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);
380
235
  }
381
- };
382
- document.addEventListener("keydown", onKey);
383
- return () => document.removeEventListener("keydown", onKey);
384
- }, [onCancel, onCapture, selectedMode, settingsOpen]);
385
- return /* @__PURE__ */ jsx2(
236
+ } catch {
237
+ } finally {
238
+ setPicking(false);
239
+ }
240
+ };
241
+ const shortDir = saveDir ? saveDir.replace(/^\/Users\/[^/]+/, "~") : "~/Desktop";
242
+ return /* @__PURE__ */ jsxs(
386
243
  "div",
387
244
  {
388
- "data-afterbefore": "true",
389
245
  style: {
390
- position: "fixed",
391
- bottom: 48,
392
- left: "50%",
393
- transform: "translateX(-50%)",
394
- zIndex: 2147483647,
395
- display: "flex",
396
- alignItems: "center",
397
- gap: 10,
398
- flexWrap: "wrap",
399
- justifyContent: "center",
400
- maxWidth: "min(calc(100vw - 32px), 1120px)",
246
+ minWidth: 300,
247
+ padding: "12px 16px",
248
+ borderRadius: 12,
401
249
  background: "rgb(32, 32, 36)",
402
- border: "none",
403
- borderRadius: 999,
404
- padding: 6,
405
- boxShadow: "0 8px 32px rgba(0, 0, 0, 0.4)",
406
- fontFamily: "system-ui, -apple-system, sans-serif"
250
+ border: "1px solid rgba(255, 255, 255, 0.1)",
251
+ boxShadow: "0 14px 36px rgba(0, 0, 0, 0.32)",
252
+ ...style
407
253
  },
408
- children: /* @__PURE__ */ jsxs2("div", { style: { display: "flex", alignItems: "center", gap: 0 }, children: [
409
- /* @__PURE__ */ jsx2(IconButton, { tooltip: "Close", onClick: onCancel, children: /* @__PURE__ */ jsx2(X, { size: 16, strokeWidth: 1.7 }) }),
410
- /* @__PURE__ */ jsx2("div", { style: { display: "flex", alignItems: "center", gap: 2, padding: "0 4px" }, children: MODES.map(({ mode, label, icon: ModeIcon }) => /* @__PURE__ */ jsx2(
411
- IconButton,
412
- {
413
- active: selectedMode === mode,
414
- tooltip: label,
415
- onClick: () => {
416
- setSettingsOpen(false);
417
- setHistoryOpen(false);
418
- if (mode === "viewport" || mode === "fullpage") {
419
- onModeChange(mode);
420
- onCapture(mode);
421
- } else {
422
- onModeChange(mode);
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, {}),
274
+ /* @__PURE__ */ jsx(
275
+ FrameSizeControl,
276
+ {
277
+ size: frameSettings.size,
278
+ onChange: (size) => onFrameSettingsChange({ ...frameSettings, size })
423
279
  }
424
- },
425
- children: /* @__PURE__ */ jsx2(ModeIcon, { size: 16, strokeWidth: 1.7 })
426
- },
427
- mode
428
- )) }),
429
- /* @__PURE__ */ jsx2(Separator, {}),
430
- /* @__PURE__ */ jsx2(
431
- SettingsButton,
432
- {
433
- open: settingsOpen,
434
- onClick: () => {
435
- setSettingsOpen((prev) => !prev);
436
- setHistoryOpen(false);
437
- },
438
- selectedMode,
439
- frameSettings,
440
- onFrameSettingsChange
441
- }
442
- ),
443
- /* @__PURE__ */ jsx2(
444
- HistoryButton,
280
+ ),
281
+ /* @__PURE__ */ jsx(SettingsDivider, {}),
282
+ /* @__PURE__ */ jsx(
283
+ FrameBackgroundControl,
284
+ {
285
+ bgType: frameSettings.bgType,
286
+ bgColor: frameSettings.bgColor,
287
+ bgImage: frameSettings.bgImage,
288
+ frameSize: frameSettings.size,
289
+ onChange: (updates) => onFrameSettingsChange({ ...frameSettings, ...updates })
290
+ }
291
+ )
292
+ ] }),
293
+ /* @__PURE__ */ jsx(SettingsDivider, {})
294
+ ] }),
295
+ /* @__PURE__ */ jsx(
296
+ SettingsRow,
445
297
  {
446
- open: historyOpen,
447
- onClick: () => {
448
- setHistoryOpen((prev) => !prev);
449
- setSettingsOpen(false);
450
- }
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" })
451
313
  }
452
314
  )
453
- ] })
315
+ ]
454
316
  }
455
317
  );
456
318
  }
457
- function IconButton({
458
- children,
459
- active,
460
- tooltip,
461
- onClick
462
- }) {
463
- const [hovered, setHovered] = useState3(false);
464
- return /* @__PURE__ */ jsxs2("div", { style: { position: "relative" }, children: [
465
- tooltip && hovered && /* @__PURE__ */ jsx2(
466
- "div",
467
- {
468
- style: {
469
- position: "absolute",
470
- left: "50%",
471
- bottom: "calc(100% + 16px)",
472
- transform: "translateX(-50%)",
473
- background: "rgb(32, 32, 36)",
474
- border: "1px solid rgba(255, 255, 255, 0.1)",
475
- borderRadius: 6,
476
- padding: "3px 8px",
477
- color: "rgba(255, 255, 255, 0.88)",
478
- fontSize: 11,
479
- whiteSpace: "nowrap",
480
- boxShadow: "0 8px 28px rgba(0, 0, 0, 0.28)",
481
- pointerEvents: "none"
482
- },
483
- children: tooltip
484
- }
485
- ),
486
- /* @__PURE__ */ jsx2(
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(
487
324
  "button",
488
325
  {
489
- onClick,
326
+ onClick: onClose,
490
327
  onMouseEnter: () => setHovered(true),
491
328
  onMouseLeave: () => setHovered(false),
492
329
  style: {
493
- width: 32,
494
- height: 32,
495
- borderRadius: "50%",
330
+ width: 24,
331
+ height: 24,
332
+ borderRadius: 6,
496
333
  border: "none",
497
- background: active || hovered ? "rgba(255, 255, 255, 0.12)" : "transparent",
334
+ background: hovered ? "rgba(255, 255, 255, 0.1)" : "transparent",
498
335
  display: "flex",
499
336
  alignItems: "center",
500
337
  justifyContent: "center",
501
338
  cursor: "pointer",
339
+ color: hovered ? "rgba(255, 255, 255, 0.8)" : "rgba(255, 255, 255, 0.46)",
502
340
  padding: 0,
503
- color: active || hovered ? "rgba(255, 255, 255, 0.96)" : "rgba(255, 255, 255, 0.52)",
504
341
  transition: "background 0.12s ease, color 0.12s ease"
505
342
  },
506
- children
343
+ children: /* @__PURE__ */ jsx(X, { size: 14, strokeWidth: 2 })
507
344
  }
508
345
  )
509
346
  ] });
510
347
  }
511
- function SettingsButton({
512
- open,
513
- onClick,
514
- selectedMode,
515
- frameSettings,
516
- onFrameSettingsChange
348
+ function SettingsRow({
349
+ title,
350
+ description,
351
+ control
517
352
  }) {
518
- const [saveDir, setSaveDir] = useState3(null);
519
- const [picking, setPicking] = useState3(false);
520
- useEffect2(() => {
521
- if (!open) return;
522
- fetch("/__afterbefore/config").then((r) => r.json()).then((data) => setSaveDir(data.saveDir)).catch(() => {
523
- });
524
- }, [open]);
525
- const handlePickFolder = async () => {
526
- setPicking(true);
527
- try {
528
- const res = await fetch("/__afterbefore/pick-folder", { method: "POST" });
529
- const data = await res.json();
530
- if (data.folder) {
531
- await fetch("/__afterbefore/config", {
532
- method: "POST",
533
- headers: { "Content-Type": "application/json" },
534
- body: JSON.stringify({ saveDir: data.folder })
535
- });
536
- setSaveDir(data.folder);
537
- }
538
- } catch {
539
- } finally {
540
- setPicking(false);
541
- }
542
- };
543
- const shortDir = saveDir ? saveDir.replace(/^\/Users\/[^/]+/, "~") : "~/Desktop";
544
- return /* @__PURE__ */ jsxs2("div", { style: { position: "relative" }, children: [
545
- /* @__PURE__ */ jsx2(IconButton, { active: open, tooltip: !open ? "Settings" : void 0, onClick, children: /* @__PURE__ */ jsx2(Settings, { size: 16, strokeWidth: 1.7 }) }),
546
- open && /* @__PURE__ */ jsxs2(
547
- "div",
548
- {
549
- style: {
550
- position: "absolute",
551
- left: "50%",
552
- bottom: "calc(100% + 12px)",
553
- transform: "translateX(-50%)",
554
- minWidth: 260,
555
- padding: "10px 12px",
556
- borderRadius: 12,
557
- background: "rgb(32, 32, 36)",
558
- border: "1px solid rgba(255, 255, 255, 0.1)",
559
- boxShadow: "0 14px 36px rgba(0, 0, 0, 0.32)"
560
- },
561
- children: [
562
- /* @__PURE__ */ jsx2(
563
- "div",
564
- {
565
- style: {
566
- fontSize: 11,
567
- color: "rgba(255, 255, 255, 0.46)",
568
- letterSpacing: "0.03em",
569
- textTransform: "uppercase",
570
- marginBottom: 10
571
- },
572
- children: "Settings"
573
- }
574
- ),
575
- selectedMode === "component" && /* @__PURE__ */ jsxs2(Fragment, { children: [
576
- /* @__PURE__ */ jsx2(
577
- ToggleRow,
578
- {
579
- icon: /* @__PURE__ */ jsx2(Frame, { size: 15, strokeWidth: 1.8 }),
580
- label: "Frame",
581
- hint: "Wrap component in a sized canvas with background",
582
- enabled: frameSettings.enabled,
583
- onChange: () => onFrameSettingsChange({ ...frameSettings, enabled: !frameSettings.enabled })
584
- }
585
- ),
586
- frameSettings.enabled && /* @__PURE__ */ jsxs2("div", { style: { marginTop: 8, display: "flex", flexDirection: "column", gap: 10 }, children: [
587
- /* @__PURE__ */ jsx2(
588
- FrameSizeControl,
589
- {
590
- size: frameSettings.size,
591
- onChange: (size) => onFrameSettingsChange({ ...frameSettings, size })
592
- }
593
- ),
594
- /* @__PURE__ */ jsx2(
595
- FrameBackgroundControl,
596
- {
597
- bgType: frameSettings.bgType,
598
- bgColor: frameSettings.bgColor,
599
- bgImage: frameSettings.bgImage,
600
- frameSize: frameSettings.size,
601
- onChange: (updates) => onFrameSettingsChange({ ...frameSettings, ...updates })
602
- }
603
- )
604
- ] })
605
- ] }),
606
- selectedMode === "component" && /* @__PURE__ */ jsx2("div", { style: { height: 1, background: "rgba(255,255,255,0.08)", margin: "8px 0" } }),
607
- /* @__PURE__ */ jsx2(
608
- SaveLocationRow,
609
- {
610
- dir: shortDir,
611
- picking,
612
- onPick: handlePickFolder
613
- }
614
- )
615
- ]
616
- }
617
- )
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 })
357
+ ] }),
358
+ /* @__PURE__ */ jsx("div", { style: { flexShrink: 0 }, children: control })
618
359
  ] });
619
360
  }
361
+ function SettingsDivider() {
362
+ return /* @__PURE__ */ jsx("div", { style: { height: 1, background: "rgba(255, 255, 255, 0.08)" } });
363
+ }
364
+ function ToggleSwitch({
365
+ enabled,
366
+ onChange
367
+ }) {
368
+ return /* @__PURE__ */ jsx(
369
+ "button",
370
+ {
371
+ type: "button",
372
+ onClick: onChange,
373
+ style: {
374
+ width: 38,
375
+ height: 22,
376
+ borderRadius: 999,
377
+ border: "none",
378
+ background: enabled ? "#38bdf8" : "rgba(255, 255, 255, 0.18)",
379
+ position: "relative",
380
+ cursor: "pointer",
381
+ padding: 0,
382
+ flexShrink: 0,
383
+ transition: "background 0.12s ease"
384
+ },
385
+ children: /* @__PURE__ */ jsx(
386
+ "span",
387
+ {
388
+ style: {
389
+ position: "absolute",
390
+ top: 2,
391
+ left: enabled ? 18 : 2,
392
+ width: 18,
393
+ height: 18,
394
+ borderRadius: "50%",
395
+ background: "#fff",
396
+ transition: "left 0.12s ease"
397
+ }
398
+ }
399
+ )
400
+ }
401
+ );
402
+ }
620
403
  function FrameSizeControl({
621
404
  size,
622
405
  onChange
623
406
  }) {
624
- const [sizeOpen, setSizeOpen] = useState3(false);
407
+ const [sizeOpen, setSizeOpen] = useState2(false);
625
408
  const currentPreset = FRAME_SIZE_PRESETS.find((p) => p.w === size.w && p.h === size.h);
626
409
  const isCustom = !currentPreset;
627
- return /* @__PURE__ */ jsxs2("div", { children: [
628
- /* @__PURE__ */ jsx2(SettingsLabel, { children: "Size" }),
629
- /* @__PURE__ */ jsxs2("div", { style: { display: "flex", alignItems: "center", gap: 4, marginTop: 4 }, children: [
630
- /* @__PURE__ */ jsx2(
410
+ return /* @__PURE__ */ jsxs("div", { style: { padding: "14px 0" }, children: [
411
+ /* @__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
+ ] }),
416
+ /* @__PURE__ */ jsxs("div", { style: { position: "relative", flexShrink: 0 }, children: [
417
+ /* @__PURE__ */ jsxs(
418
+ "button",
419
+ {
420
+ onClick: () => setSizeOpen((prev) => !prev),
421
+ style: {
422
+ display: "flex",
423
+ alignItems: "center",
424
+ gap: 6,
425
+ height: 30,
426
+ padding: "0 10px",
427
+ 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)",
431
+ cursor: "pointer",
432
+ fontSize: 12,
433
+ fontFamily: "inherit",
434
+ whiteSpace: "nowrap"
435
+ },
436
+ children: [
437
+ currentPreset ? currentPreset.label : "Custom",
438
+ /* @__PURE__ */ jsx(ChevronDown, { size: 12, strokeWidth: 2 })
439
+ ]
440
+ }
441
+ ),
442
+ sizeOpen && /* @__PURE__ */ jsx(
443
+ "div",
444
+ {
445
+ style: {
446
+ position: "absolute",
447
+ bottom: "calc(100% + 4px)",
448
+ right: 0,
449
+ minWidth: 180,
450
+ background: "rgb(32, 32, 36)",
451
+ border: "1px solid rgba(255, 255, 255, 0.1)",
452
+ borderRadius: 8,
453
+ padding: "4px 0",
454
+ boxShadow: "0 10px 30px rgba(0, 0, 0, 0.3)",
455
+ zIndex: 1
456
+ },
457
+ children: FRAME_SIZE_PRESETS.map((preset) => /* @__PURE__ */ jsxs(
458
+ DropItem,
459
+ {
460
+ active: !isCustom && preset.w === size.w && preset.h === size.h,
461
+ onClick: () => {
462
+ onChange({ w: preset.w, h: preset.h });
463
+ setSizeOpen(false);
464
+ },
465
+ children: [
466
+ /* @__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 })
468
+ ]
469
+ },
470
+ preset.label
471
+ ))
472
+ }
473
+ )
474
+ ] })
475
+ ] }),
476
+ /* @__PURE__ */ jsxs("div", { style: { display: "flex", alignItems: "center", gap: 4, marginTop: 10 }, children: [
477
+ /* @__PURE__ */ jsx(
631
478
  NumInput,
632
479
  {
633
480
  value: size.w,
@@ -637,8 +484,8 @@ function FrameSizeControl({
637
484
  }
638
485
  }
639
486
  ),
640
- /* @__PURE__ */ jsx2(StaticText, { children: "x" }),
641
- /* @__PURE__ */ jsx2(
487
+ /* @__PURE__ */ jsx(StaticText, { children: "x" }),
488
+ /* @__PURE__ */ jsx(
642
489
  NumInput,
643
490
  {
644
491
  value: size.h,
@@ -648,66 +495,6 @@ function FrameSizeControl({
648
495
  }
649
496
  }
650
497
  )
651
- ] }),
652
- /* @__PURE__ */ jsxs2("div", { style: { position: "relative", marginTop: 6 }, children: [
653
- /* @__PURE__ */ jsxs2(
654
- "button",
655
- {
656
- onClick: () => setSizeOpen((prev) => !prev),
657
- style: {
658
- display: "flex",
659
- alignItems: "center",
660
- justifyContent: "space-between",
661
- gap: 6,
662
- width: "100%",
663
- height: 30,
664
- padding: "0 8px",
665
- borderRadius: 7,
666
- border: "1px solid rgba(255,255,255,0.1)",
667
- background: "rgba(255,255,255,0.07)",
668
- color: "rgba(255,255,255,0.88)",
669
- cursor: "pointer",
670
- fontSize: 12,
671
- fontFamily: "inherit"
672
- },
673
- children: [
674
- currentPreset ? currentPreset.label : "Custom",
675
- /* @__PURE__ */ jsx2(ChevronDown, { size: 12, strokeWidth: 2 })
676
- ]
677
- }
678
- ),
679
- sizeOpen && /* @__PURE__ */ jsx2(
680
- "div",
681
- {
682
- style: {
683
- position: "absolute",
684
- bottom: "calc(100% + 4px)",
685
- left: 0,
686
- right: 0,
687
- background: "rgb(32, 32, 36)",
688
- border: "1px solid rgba(255, 255, 255, 0.1)",
689
- borderRadius: 8,
690
- padding: "4px 0",
691
- boxShadow: "0 10px 30px rgba(0, 0, 0, 0.3)",
692
- zIndex: 1
693
- },
694
- children: FRAME_SIZE_PRESETS.map((preset) => /* @__PURE__ */ jsxs2(
695
- DropItem,
696
- {
697
- active: !isCustom && preset.w === size.w && preset.h === size.h,
698
- onClick: () => {
699
- onChange({ w: preset.w, h: preset.h });
700
- setSizeOpen(false);
701
- },
702
- children: [
703
- /* @__PURE__ */ jsx2("span", { children: preset.label }),
704
- /* @__PURE__ */ jsx2("span", { style: { marginLeft: 6, fontSize: 10, color: "rgba(255,255,255,0.34)" }, children: preset.hint })
705
- ]
706
- },
707
- preset.label
708
- ))
709
- }
710
- )
711
498
  ] })
712
499
  ] });
713
500
  }
@@ -718,7 +505,7 @@ function FrameBackgroundControl({
718
505
  frameSize,
719
506
  onChange
720
507
  }) {
721
- const fileInputRef = useRef2(null);
508
+ const fileInputRef = useRef(null);
722
509
  const handleFileSelect = (e) => {
723
510
  const file = e.target.files?.[0];
724
511
  if (!file) return;
@@ -736,40 +523,45 @@ function FrameBackgroundControl({
736
523
  reader.readAsDataURL(file);
737
524
  e.target.value = "";
738
525
  };
739
- return /* @__PURE__ */ jsxs2("div", { children: [
740
- /* @__PURE__ */ jsx2(SettingsLabel, { children: "Background" }),
741
- /* @__PURE__ */ jsxs2("div", { style: { display: "flex", gap: 2, marginTop: 4 }, children: [
742
- /* @__PURE__ */ jsxs2(
743
- SegmentButton,
744
- {
745
- active: bgType === "color",
746
- onClick: () => onChange({ bgType: "color" }),
747
- style: { borderRadius: "6px 0 0 6px" },
748
- children: [
749
- /* @__PURE__ */ jsx2(Palette, { size: 12, strokeWidth: 2 }),
750
- "Color"
751
- ]
752
- }
753
- ),
754
- /* @__PURE__ */ jsxs2(
755
- SegmentButton,
756
- {
757
- active: bgType === "image",
758
- onClick: () => onChange({ bgType: "image" }),
759
- style: { borderRadius: "0 6px 6px 0" },
760
- children: [
761
- /* @__PURE__ */ jsx2(ImageIcon, { size: 12, strokeWidth: 2 }),
762
- "Image"
763
- ]
764
- }
765
- )
526
+ return /* @__PURE__ */ jsxs("div", { style: { padding: "14px 0" }, children: [
527
+ /* @__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
+ ] }),
532
+ /* @__PURE__ */ jsxs("div", { style: { display: "flex", gap: 2, flexShrink: 0 }, children: [
533
+ /* @__PURE__ */ jsxs(
534
+ SegmentButton,
535
+ {
536
+ active: bgType === "color",
537
+ onClick: () => onChange({ bgType: "color" }),
538
+ style: { borderRadius: "6px 0 0 6px" },
539
+ children: [
540
+ /* @__PURE__ */ jsx(Palette, { size: 12, strokeWidth: 2 }),
541
+ "Color"
542
+ ]
543
+ }
544
+ ),
545
+ /* @__PURE__ */ jsxs(
546
+ SegmentButton,
547
+ {
548
+ active: bgType === "image",
549
+ onClick: () => onChange({ bgType: "image" }),
550
+ style: { borderRadius: "0 6px 6px 0" },
551
+ children: [
552
+ /* @__PURE__ */ jsx(ImageIcon, { size: 12, strokeWidth: 2 }),
553
+ "Image"
554
+ ]
555
+ }
556
+ )
557
+ ] })
766
558
  ] }),
767
- bgType === "color" && /* @__PURE__ */ jsxs2("div", { style: { display: "flex", alignItems: "center", gap: 8, marginTop: 6 }, children: [
768
- /* @__PURE__ */ jsx2(ColorSwatch, { color: bgColor, onChange: (c) => onChange({ bgColor: c }) }),
769
- /* @__PURE__ */ jsx2(HexInput, { value: bgColor, onChange: (c) => onChange({ bgColor: c }) })
559
+ bgType === "color" && /* @__PURE__ */ jsxs("div", { style: { display: "flex", alignItems: "center", gap: 8, marginTop: 10 }, children: [
560
+ /* @__PURE__ */ jsx(ColorSwatch, { color: bgColor, onChange: (c) => onChange({ bgColor: c }) }),
561
+ /* @__PURE__ */ jsx(HexInput, { value: bgColor, onChange: (c) => onChange({ bgColor: c }) })
770
562
  ] }),
771
- bgType === "image" && /* @__PURE__ */ jsxs2("div", { style: { marginTop: 6 }, children: [
772
- /* @__PURE__ */ jsx2(
563
+ bgType === "image" && /* @__PURE__ */ jsxs("div", { style: { marginTop: 10 }, children: [
564
+ /* @__PURE__ */ jsx(
773
565
  "input",
774
566
  {
775
567
  ref: fileInputRef,
@@ -779,8 +571,8 @@ function FrameBackgroundControl({
779
571
  style: { display: "none" }
780
572
  }
781
573
  ),
782
- bgImage ? /* @__PURE__ */ jsxs2("div", { style: { display: "flex", alignItems: "center", gap: 8 }, children: [
783
- /* @__PURE__ */ jsx2(
574
+ bgImage ? /* @__PURE__ */ jsxs("div", { style: { display: "flex", alignItems: "center", gap: 8 }, children: [
575
+ /* @__PURE__ */ jsx(
784
576
  "img",
785
577
  {
786
578
  src: bgImage,
@@ -794,38 +586,25 @@ function FrameBackgroundControl({
794
586
  }
795
587
  }
796
588
  ),
797
- /* @__PURE__ */ jsxs2(SmallButton, { onClick: () => fileInputRef.current?.click(), children: [
798
- /* @__PURE__ */ jsx2(Upload, { size: 11, strokeWidth: 2 }),
589
+ /* @__PURE__ */ jsxs(SmallButton, { onClick: () => fileInputRef.current?.click(), children: [
590
+ /* @__PURE__ */ jsx(Upload, { size: 11, strokeWidth: 2 }),
799
591
  "Replace"
800
592
  ] }),
801
- /* @__PURE__ */ jsx2(SmallButton, { onClick: () => onChange({ bgImage: null }), children: /* @__PURE__ */ jsx2(Trash2, { size: 11, strokeWidth: 2 }) })
802
- ] }) : /* @__PURE__ */ jsxs2(SmallButton, { onClick: () => fileInputRef.current?.click(), children: [
803
- /* @__PURE__ */ jsx2(Upload, { size: 11, strokeWidth: 2 }),
593
+ /* @__PURE__ */ jsx(SmallButton, { onClick: () => onChange({ bgImage: null }), children: /* @__PURE__ */ jsx(Trash2, { size: 11, strokeWidth: 2 }) })
594
+ ] }) : /* @__PURE__ */ jsxs(SmallButton, { onClick: () => fileInputRef.current?.click(), children: [
595
+ /* @__PURE__ */ jsx(Upload, { size: 11, strokeWidth: 2 }),
804
596
  "Upload image"
805
597
  ] })
806
598
  ] })
807
599
  ] });
808
600
  }
809
- function SettingsLabel({ children }) {
810
- return /* @__PURE__ */ jsx2(
811
- "div",
812
- {
813
- style: {
814
- fontSize: 11,
815
- color: "rgba(255, 255, 255, 0.42)",
816
- letterSpacing: "0.02em"
817
- },
818
- children
819
- }
820
- );
821
- }
822
601
  function SegmentButton({
823
602
  children,
824
603
  active,
825
604
  onClick,
826
605
  style
827
606
  }) {
828
- return /* @__PURE__ */ jsx2(
607
+ return /* @__PURE__ */ jsx(
829
608
  "button",
830
609
  {
831
610
  onClick,
@@ -852,9 +631,9 @@ function ColorSwatch({
852
631
  color,
853
632
  onChange
854
633
  }) {
855
- const inputRef = useRef2(null);
856
- return /* @__PURE__ */ jsxs2("div", { style: { position: "relative" }, children: [
857
- /* @__PURE__ */ jsx2(
634
+ const inputRef = useRef(null);
635
+ return /* @__PURE__ */ jsxs("div", { style: { position: "relative" }, children: [
636
+ /* @__PURE__ */ jsx(
858
637
  "button",
859
638
  {
860
639
  onClick: () => inputRef.current?.click(),
@@ -869,7 +648,7 @@ function ColorSwatch({
869
648
  }
870
649
  }
871
650
  ),
872
- /* @__PURE__ */ jsx2(
651
+ /* @__PURE__ */ jsx(
873
652
  "input",
874
653
  {
875
654
  ref: inputRef,
@@ -893,8 +672,8 @@ function SmallButton({
893
672
  children,
894
673
  onClick
895
674
  }) {
896
- const [hovered, setHovered] = useState3(false);
897
- return /* @__PURE__ */ jsx2(
675
+ const [hovered, setHovered] = useState2(false);
676
+ return /* @__PURE__ */ jsx(
898
677
  "button",
899
678
  {
900
679
  onClick,
@@ -918,6 +697,141 @@ function SmallButton({
918
697
  }
919
698
  );
920
699
  }
700
+ function NumInput({
701
+ value,
702
+ onChange
703
+ }) {
704
+ const [editing, setEditing] = useState2(false);
705
+ const [text, setText] = useState2(String(value));
706
+ useEffect(() => {
707
+ if (!editing) {
708
+ setText(String(value));
709
+ }
710
+ }, [editing, value]);
711
+ return /* @__PURE__ */ jsx(
712
+ "input",
713
+ {
714
+ type: "text",
715
+ value: editing ? text : String(value),
716
+ onFocus: () => {
717
+ setEditing(true);
718
+ setText(String(value));
719
+ },
720
+ onBlur: () => {
721
+ setEditing(false);
722
+ onChange(text);
723
+ },
724
+ onChange: (e) => setText(e.target.value),
725
+ onKeyDown: (e) => {
726
+ if (e.key === "Enter") {
727
+ e.target.blur();
728
+ }
729
+ },
730
+ style: {
731
+ width: 54,
732
+ padding: "4px 6px",
733
+ background: "rgba(255, 255, 255, 0.07)",
734
+ border: "1px solid rgba(255, 255, 255, 0.1)",
735
+ borderRadius: 7,
736
+ color: "rgba(255, 255, 255, 0.9)",
737
+ fontSize: 12,
738
+ fontFamily: "system-ui, -apple-system, sans-serif",
739
+ textAlign: "center",
740
+ outline: "none"
741
+ }
742
+ }
743
+ );
744
+ }
745
+ function HexInput({
746
+ value,
747
+ onChange
748
+ }) {
749
+ const [editing, setEditing] = useState2(false);
750
+ const [text, setText] = useState2(value);
751
+ useEffect(() => {
752
+ if (!editing) {
753
+ setText(value);
754
+ }
755
+ }, [editing, value]);
756
+ const commit = (raw) => {
757
+ const hex = raw.startsWith("#") ? raw : `#${raw}`;
758
+ if (/^#[0-9a-fA-F]{6}$/.test(hex)) {
759
+ onChange(hex);
760
+ }
761
+ };
762
+ return /* @__PURE__ */ jsx(
763
+ "input",
764
+ {
765
+ type: "text",
766
+ value: editing ? text : value,
767
+ onFocus: () => {
768
+ setEditing(true);
769
+ setText(value);
770
+ },
771
+ onBlur: () => {
772
+ setEditing(false);
773
+ commit(text);
774
+ },
775
+ onChange: (e) => setText(e.target.value),
776
+ onKeyDown: (e) => {
777
+ if (e.key === "Enter") {
778
+ e.target.blur();
779
+ }
780
+ },
781
+ style: {
782
+ width: 72,
783
+ padding: "4px 6px",
784
+ background: "rgba(255, 255, 255, 0.07)",
785
+ border: "1px solid rgba(255, 255, 255, 0.1)",
786
+ borderRadius: 7,
787
+ color: "rgba(255, 255, 255, 0.9)",
788
+ fontSize: 12,
789
+ fontFamily: "system-ui, -apple-system, sans-serif",
790
+ textAlign: "left",
791
+ outline: "none"
792
+ }
793
+ }
794
+ );
795
+ }
796
+ function StaticText({ children }) {
797
+ return /* @__PURE__ */ jsx(
798
+ "span",
799
+ {
800
+ style: {
801
+ fontSize: 11,
802
+ color: "rgba(255,255,255,0.35)",
803
+ minWidth: 8,
804
+ textAlign: "center"
805
+ },
806
+ children
807
+ }
808
+ );
809
+ }
810
+ function DropItem({
811
+ children,
812
+ onClick,
813
+ active
814
+ }) {
815
+ return /* @__PURE__ */ jsx(
816
+ "button",
817
+ {
818
+ onClick,
819
+ style: {
820
+ display: "block",
821
+ width: "100%",
822
+ padding: "7px 12px",
823
+ background: active ? "rgba(255, 255, 255, 0.08)" : "transparent",
824
+ border: "none",
825
+ color: "rgba(255, 255, 255, 0.86)",
826
+ textAlign: "left",
827
+ cursor: "pointer",
828
+ fontSize: 13,
829
+ fontFamily: "inherit"
830
+ },
831
+ children
832
+ }
833
+ );
834
+ }
921
835
  async function downscaleImage(dataUrl, maxW, maxH) {
922
836
  return new Promise((resolve) => {
923
837
  const img = new Image();
@@ -934,261 +848,407 @@ async function downscaleImage(dataUrl, maxW, maxH) {
934
848
  img.src = dataUrl;
935
849
  });
936
850
  }
937
- function SaveLocationRow({
938
- dir,
939
- picking,
940
- onPick
941
- }) {
942
- const [btnHovered, setBtnHovered] = useState3(false);
943
- return /* @__PURE__ */ jsx2("div", { style: { display: "flex", flexDirection: "column", gap: 6 }, children: /* @__PURE__ */ jsxs2(
944
- "div",
945
- {
946
- style: {
947
- display: "flex",
948
- alignItems: "center",
949
- gap: 8,
950
- color: "rgba(255, 255, 255, 0.88)",
951
- fontSize: 13
952
- },
953
- children: [
954
- /* @__PURE__ */ jsx2(FolderOpen, { size: 15, strokeWidth: 1.8, style: { flexShrink: 0 } }),
955
- /* @__PURE__ */ jsx2(
956
- "span",
957
- {
958
- style: {
959
- overflow: "hidden",
960
- textOverflow: "ellipsis",
961
- whiteSpace: "nowrap",
962
- flex: 1,
963
- minWidth: 0
964
- },
965
- title: dir,
966
- children: dir
967
- }
968
- ),
969
- /* @__PURE__ */ jsx2(
970
- "button",
971
- {
972
- onClick: onPick,
973
- disabled: picking,
974
- onMouseEnter: () => setBtnHovered(true),
975
- onMouseLeave: () => setBtnHovered(false),
976
- style: {
977
- padding: "3px 8px",
978
- borderRadius: 6,
979
- border: "1px solid rgba(255,255,255,0.12)",
980
- background: btnHovered ? "rgba(255,255,255,0.12)" : "rgba(255,255,255,0.06)",
981
- color: "rgba(255,255,255,0.78)",
982
- fontSize: 11,
983
- cursor: picking ? "wait" : "pointer",
984
- flexShrink: 0,
985
- fontFamily: "inherit",
986
- transition: "background 0.12s ease"
987
- },
988
- children: picking ? "..." : "Change"
989
- }
990
- )
991
- ]
992
- }
993
- ) });
851
+
852
+ // src/overlay/ui/toolbar.tsx
853
+ import { Fragment as Fragment2, jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
854
+ var EDGE_MARGIN = 24;
855
+ var CONTAINER_SIZE = 38;
856
+ function getCornerStyle(corner) {
857
+ switch (corner) {
858
+ case "bottom-right":
859
+ return { bottom: EDGE_MARGIN, right: EDGE_MARGIN };
860
+ case "bottom-left":
861
+ return { bottom: EDGE_MARGIN, left: EDGE_MARGIN };
862
+ case "top-right":
863
+ return { top: EDGE_MARGIN, right: EDGE_MARGIN };
864
+ case "top-left":
865
+ return { top: EDGE_MARGIN, left: EDGE_MARGIN };
866
+ }
994
867
  }
995
- function ToggleRow({
996
- icon,
997
- label,
998
- hint,
999
- enabled,
1000
- onChange
868
+ function isBottomCorner(corner) {
869
+ return corner === "bottom-right" || corner === "bottom-left";
870
+ }
871
+ function isRightCorner(corner) {
872
+ return corner === "bottom-right" || corner === "top-right";
873
+ }
874
+ function snapToCorner(x, y) {
875
+ const cx = window.innerWidth / 2;
876
+ const cy = window.innerHeight / 2;
877
+ if (x < cx) {
878
+ return y < cy ? "top-left" : "bottom-left";
879
+ }
880
+ return y < cy ? "top-right" : "bottom-right";
881
+ }
882
+ var MODES = [
883
+ { mode: "component", label: "Component", icon: MousePointer2 },
884
+ { mode: "viewport", label: "Viewport", icon: Monitor },
885
+ { mode: "fullpage", label: "Full Page", icon: Maximize }
886
+ ];
887
+ function Toolbar({
888
+ expanded,
889
+ onToggle,
890
+ phase,
891
+ loading,
892
+ selectedMode,
893
+ onModeChange,
894
+ onCapture,
895
+ onCancel,
896
+ frameSettings,
897
+ onFrameSettingsChange
1001
898
  }) {
1002
- return /* @__PURE__ */ jsxs2(
1003
- "label",
1004
- {
1005
- style: {
1006
- display: "flex",
1007
- alignItems: "center",
1008
- justifyContent: "space-between",
1009
- gap: 12,
1010
- cursor: "pointer"
1011
- },
1012
- children: [
1013
- /* @__PURE__ */ jsxs2("div", { style: { display: "flex", flexDirection: "column", gap: 2 }, children: [
1014
- /* @__PURE__ */ jsxs2(
1015
- "span",
899
+ const [settingsOpen, setSettingsOpen] = useState3(false);
900
+ const [historyOpen, setHistoryOpen] = useState3(false);
901
+ const [corner, setCorner] = useState3(() => {
902
+ try {
903
+ const stored = localStorage.getItem("ab-toolbar-corner");
904
+ if (stored && ["bottom-right", "bottom-left", "top-right", "top-left"].includes(stored)) {
905
+ return stored;
906
+ }
907
+ } catch {
908
+ }
909
+ return "bottom-right";
910
+ });
911
+ const [dragging, setDragging] = useState3(false);
912
+ const [dragPos, setDragPos] = useState3(null);
913
+ const dragState = useRef2(null);
914
+ const toolbarRef = useRef2(null);
915
+ const [cameraHovered, setCameraHovered] = useState3(false);
916
+ useEffect2(() => {
917
+ if (!expanded) return;
918
+ const onKey = (e) => {
919
+ if (e.target?.tagName === "INPUT") {
920
+ if (e.key === "Escape") {
921
+ e.target.blur();
922
+ }
923
+ return;
924
+ }
925
+ if (e.key === "Escape") {
926
+ if (settingsOpen) {
927
+ setSettingsOpen(false);
928
+ return;
929
+ }
930
+ onCancel();
931
+ } else if (e.key === "Enter") {
932
+ onCapture(selectedMode);
933
+ }
934
+ };
935
+ document.addEventListener("keydown", onKey);
936
+ return () => document.removeEventListener("keydown", onKey);
937
+ }, [expanded, onCancel, onCapture, selectedMode, settingsOpen]);
938
+ const handleMouseDown = useCallback2(
939
+ (e) => {
940
+ e.preventDefault();
941
+ const el = toolbarRef.current;
942
+ if (!el) return;
943
+ const rect = el.getBoundingClientRect();
944
+ setDragging(true);
945
+ setDragPos({ x: rect.left, y: rect.top });
946
+ dragState.current = {
947
+ dragging: true,
948
+ startX: e.clientX,
949
+ startY: e.clientY,
950
+ origX: rect.left,
951
+ origY: rect.top,
952
+ distance: 0
953
+ };
954
+ },
955
+ []
956
+ );
957
+ useEffect2(() => {
958
+ const handleMouseMove = (e) => {
959
+ const ds = dragState.current;
960
+ if (!ds || !ds.dragging) return;
961
+ const dx = e.clientX - ds.startX;
962
+ const dy = e.clientY - ds.startY;
963
+ ds.distance = Math.sqrt(dx * dx + dy * dy);
964
+ setDragPos({
965
+ x: ds.origX + dx,
966
+ y: ds.origY + dy
967
+ });
968
+ };
969
+ const handleMouseUp = (e) => {
970
+ const ds = dragState.current;
971
+ if (!ds) return;
972
+ if (ds.distance < 5) {
973
+ onToggle();
974
+ } else {
975
+ const el = toolbarRef.current;
976
+ const w = el?.offsetWidth ?? CONTAINER_SIZE;
977
+ 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;
980
+ const newCorner = snapToCorner(centerX, centerY);
981
+ setCorner(newCorner);
982
+ try {
983
+ localStorage.setItem("ab-toolbar-corner", newCorner);
984
+ } catch {
985
+ }
986
+ }
987
+ setDragging(false);
988
+ setDragPos(null);
989
+ dragState.current = null;
990
+ };
991
+ window.addEventListener("mousemove", handleMouseMove);
992
+ window.addEventListener("mouseup", handleMouseUp);
993
+ return () => {
994
+ window.removeEventListener("mousemove", handleMouseMove);
995
+ window.removeEventListener("mouseup", handleMouseUp);
996
+ };
997
+ }, [onToggle]);
998
+ const panelSide = isRightCorner(corner) ? "left" : "right";
999
+ const tooltipSide = panelSide;
1000
+ const bottom = isBottomCorner(corner);
1001
+ const positionStyle = dragging && dragPos ? { left: dragPos.x, top: dragPos.y } : getCornerStyle(corner);
1002
+ const cameraTooltipLabel = expanded ? "Close" : void 0;
1003
+ 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
+ const cameraButton = /* @__PURE__ */ jsxs2("div", { style: { position: "relative" }, children: [
1005
+ cameraTooltipLabel && cameraHovered && !dragging && /* @__PURE__ */ jsx2(
1006
+ "div",
1007
+ {
1008
+ style: {
1009
+ position: "absolute",
1010
+ ...cameraTooltipStyle,
1011
+ background: "rgb(32, 32, 36)",
1012
+ border: "1px solid rgba(255, 255, 255, 0.1)",
1013
+ borderRadius: 6,
1014
+ padding: "3px 8px",
1015
+ color: "rgba(255, 255, 255, 0.88)",
1016
+ fontSize: 11,
1017
+ whiteSpace: "nowrap",
1018
+ boxShadow: "0 8px 28px rgba(0, 0, 0, 0.28)",
1019
+ pointerEvents: "none"
1020
+ },
1021
+ children: cameraTooltipLabel
1022
+ }
1023
+ ),
1024
+ /* @__PURE__ */ jsxs2(
1025
+ "div",
1026
+ {
1027
+ onMouseDown: handleMouseDown,
1028
+ onMouseEnter: () => setCameraHovered(true),
1029
+ onMouseLeave: () => setCameraHovered(false),
1030
+ style: {
1031
+ width: 36,
1032
+ height: 36,
1033
+ padding: 2,
1034
+ borderRadius: "50%",
1035
+ display: "flex",
1036
+ alignItems: "center",
1037
+ justifyContent: "center",
1038
+ cursor: dragging ? "grabbing" : "pointer",
1039
+ background: expanded && cameraHovered ? "rgba(255, 255, 255, 0.12)" : "transparent",
1040
+ transition: "background 0.12s ease"
1041
+ },
1042
+ children: [
1043
+ /* @__PURE__ */ jsx2(
1044
+ "style",
1016
1045
  {
1017
- style: {
1018
- display: "flex",
1019
- alignItems: "center",
1020
- gap: 8,
1021
- color: "rgba(255, 255, 255, 0.88)",
1022
- fontSize: 13,
1023
- whiteSpace: "nowrap"
1024
- },
1025
- children: [
1026
- icon,
1027
- label
1028
- ]
1046
+ dangerouslySetInnerHTML: {
1047
+ __html: `
1048
+ @keyframes ab-spin {
1049
+ 0% { transform: rotate(0deg); }
1050
+ 100% { transform: rotate(360deg); }
1051
+ }`
1052
+ }
1029
1053
  }
1030
1054
  ),
1031
- hint && /* @__PURE__ */ jsx2(
1032
- "span",
1055
+ loading ? /* @__PURE__ */ jsx2(
1056
+ LoaderCircle,
1033
1057
  {
1034
- style: {
1035
- fontSize: 10,
1036
- color: "rgba(255, 255, 255, 0.36)",
1037
- paddingLeft: 23
1038
- },
1039
- children: hint
1058
+ size: 16,
1059
+ strokeWidth: 2,
1060
+ style: { animation: "ab-spin 0.8s linear infinite", color: "white" }
1061
+ }
1062
+ ) : phase === "ready" ? /* @__PURE__ */ jsx2(Check, { size: 16, strokeWidth: 2.6, color: "#4ade80" }) : expanded ? /* @__PURE__ */ jsx2(
1063
+ X2,
1064
+ {
1065
+ size: 16,
1066
+ strokeWidth: 1.7,
1067
+ color: cameraHovered ? "rgba(255, 255, 255, 0.96)" : "rgba(255, 255, 255, 0.52)"
1068
+ }
1069
+ ) : /* @__PURE__ */ jsx2(
1070
+ Camera,
1071
+ {
1072
+ size: 16,
1073
+ strokeWidth: 1.9,
1074
+ color: "rgba(255, 255, 255, 0.52)"
1040
1075
  }
1041
1076
  )
1042
- ] }),
1043
- /* @__PURE__ */ jsx2(
1044
- "button",
1045
- {
1046
- type: "button",
1047
- onClick: onChange,
1048
- style: {
1049
- width: 38,
1050
- height: 22,
1051
- borderRadius: 999,
1052
- border: "none",
1053
- background: enabled ? "#38bdf8" : "rgba(255, 255, 255, 0.18)",
1054
- position: "relative",
1055
- cursor: "pointer",
1056
- padding: 0,
1057
- flexShrink: 0,
1058
- transition: "background 0.12s ease"
1059
- },
1060
- children: /* @__PURE__ */ jsx2(
1061
- "span",
1062
- {
1063
- style: {
1064
- position: "absolute",
1065
- top: 2,
1066
- left: enabled ? 18 : 2,
1067
- width: 18,
1068
- height: 18,
1069
- borderRadius: "50%",
1070
- background: "#fff",
1071
- transition: "left 0.12s ease"
1072
- }
1073
- }
1074
- )
1077
+ ]
1078
+ }
1079
+ )
1080
+ ] });
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);
1075
1096
  }
1076
- )
1077
- ]
1078
- }
1079
- );
1080
- }
1081
- function NumInput({
1082
- value,
1083
- onChange
1084
- }) {
1085
- const [editing, setEditing] = useState3(false);
1086
- const [text, setText] = useState3(String(value));
1087
- useEffect2(() => {
1088
- if (!editing) {
1089
- setText(String(value));
1090
- }
1091
- }, [editing, value]);
1097
+ },
1098
+ children: /* @__PURE__ */ jsx2(ModeIcon, { size: 16, strokeWidth: 1.7 })
1099
+ },
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;
1092
1134
  return /* @__PURE__ */ jsx2(
1093
- "input",
1135
+ "div",
1094
1136
  {
1095
- type: "text",
1096
- value: editing ? text : String(value),
1097
- onFocus: () => {
1098
- setEditing(true);
1099
- setText(String(value));
1100
- },
1101
- onBlur: () => {
1102
- setEditing(false);
1103
- onChange(text);
1104
- },
1105
- onChange: (e) => setText(e.target.value),
1106
- onKeyDown: (e) => {
1107
- if (e.key === "Enter") {
1108
- e.target.blur();
1109
- }
1110
- },
1137
+ ref: toolbarRef,
1138
+ "data-afterbefore": "true",
1111
1139
  style: {
1112
- width: 54,
1113
- padding: "4px 6px",
1114
- background: "rgba(255, 255, 255, 0.07)",
1115
- border: "1px solid rgba(255, 255, 255, 0.1)",
1116
- borderRadius: 7,
1117
- color: "rgba(255, 255, 255, 0.9)",
1118
- fontSize: 12,
1140
+ position: "fixed",
1141
+ ...positionStyle,
1142
+ zIndex: 2147483647,
1143
+ display: "flex",
1144
+ flexDirection: "column",
1145
+ alignItems: "center",
1146
+ background: "rgb(32, 32, 36)",
1147
+ borderRadius: 999,
1148
+ padding: 6,
1149
+ boxShadow: "0 8px 32px rgba(0, 0, 0, 0.4)",
1119
1150
  fontFamily: "system-ui, -apple-system, sans-serif",
1120
- textAlign: "center",
1121
- outline: "none"
1122
- }
1151
+ userSelect: "none"
1152
+ },
1153
+ children: bottom ? /* @__PURE__ */ jsxs2(Fragment2, { children: [
1154
+ toolbarButtons,
1155
+ cameraButton
1156
+ ] }) : /* @__PURE__ */ jsxs2(Fragment2, { children: [
1157
+ cameraButton,
1158
+ toolbarButtons
1159
+ ] })
1123
1160
  }
1124
1161
  );
1125
1162
  }
1126
- function HexInput({
1127
- value,
1128
- onChange
1163
+ function IconButton({
1164
+ children,
1165
+ active,
1166
+ tooltip,
1167
+ tooltipSide = "left",
1168
+ onClick
1129
1169
  }) {
1130
- const [editing, setEditing] = useState3(false);
1131
- const [text, setText] = useState3(value);
1132
- useEffect2(() => {
1133
- if (!editing) {
1134
- setText(value);
1135
- }
1136
- }, [editing, value]);
1137
- const commit = (raw) => {
1138
- const hex = raw.startsWith("#") ? raw : `#${raw}`;
1139
- if (/^#[0-9a-fA-F]{6}$/.test(hex)) {
1140
- onChange(hex);
1141
- }
1170
+ const [hovered, setHovered] = useState3(false);
1171
+ const tooltipStyle = tooltipSide === "left" ? {
1172
+ right: "calc(100% + 10px)",
1173
+ top: "50%",
1174
+ transform: "translateY(-50%)"
1175
+ } : {
1176
+ left: "calc(100% + 10px)",
1177
+ top: "50%",
1178
+ transform: "translateY(-50%)"
1142
1179
  };
1143
- return /* @__PURE__ */ jsx2(
1144
- "input",
1145
- {
1146
- type: "text",
1147
- value: editing ? text : value,
1148
- onFocus: () => {
1149
- setEditing(true);
1150
- setText(value);
1151
- },
1152
- onBlur: () => {
1153
- setEditing(false);
1154
- commit(text);
1155
- },
1156
- onChange: (e) => setText(e.target.value),
1157
- onKeyDown: (e) => {
1158
- if (e.key === "Enter") {
1159
- e.target.blur();
1160
- }
1161
- },
1162
- style: {
1163
- width: 72,
1164
- padding: "4px 6px",
1165
- background: "rgba(255, 255, 255, 0.07)",
1166
- border: "1px solid rgba(255, 255, 255, 0.1)",
1167
- borderRadius: 7,
1168
- color: "rgba(255, 255, 255, 0.9)",
1169
- fontSize: 12,
1170
- fontFamily: "system-ui, -apple-system, sans-serif",
1171
- textAlign: "left",
1172
- outline: "none"
1180
+ return /* @__PURE__ */ jsxs2("div", { style: { position: "relative" }, children: [
1181
+ tooltip && hovered && /* @__PURE__ */ jsx2(
1182
+ "div",
1183
+ {
1184
+ style: {
1185
+ position: "absolute",
1186
+ ...tooltipStyle,
1187
+ background: "rgb(32, 32, 36)",
1188
+ border: "1px solid rgba(255, 255, 255, 0.1)",
1189
+ borderRadius: 6,
1190
+ padding: "3px 8px",
1191
+ color: "rgba(255, 255, 255, 0.88)",
1192
+ fontSize: 11,
1193
+ whiteSpace: "nowrap",
1194
+ boxShadow: "0 8px 28px rgba(0, 0, 0, 0.28)",
1195
+ pointerEvents: "none"
1196
+ },
1197
+ children: tooltip
1173
1198
  }
1174
- }
1175
- );
1199
+ ),
1200
+ /* @__PURE__ */ jsx2(
1201
+ "button",
1202
+ {
1203
+ onClick,
1204
+ onMouseEnter: () => setHovered(true),
1205
+ onMouseLeave: () => setHovered(false),
1206
+ style: {
1207
+ width: 32,
1208
+ height: 32,
1209
+ borderRadius: "50%",
1210
+ border: "none",
1211
+ background: active || hovered ? "rgba(255, 255, 255, 0.12)" : "transparent",
1212
+ display: "flex",
1213
+ alignItems: "center",
1214
+ justifyContent: "center",
1215
+ cursor: "pointer",
1216
+ padding: 0,
1217
+ color: active || hovered ? "rgba(255, 255, 255, 0.96)" : "rgba(255, 255, 255, 0.52)",
1218
+ transition: "background 0.12s ease, color 0.12s ease"
1219
+ },
1220
+ children
1221
+ }
1222
+ )
1223
+ ] });
1176
1224
  }
1177
- function StaticText({ children }) {
1178
- return /* @__PURE__ */ jsx2(
1179
- "span",
1180
- {
1181
- style: {
1182
- fontSize: 11,
1183
- color: "rgba(255,255,255,0.35)",
1184
- minWidth: 8,
1185
- textAlign: "center"
1186
- },
1187
- children
1188
- }
1189
- );
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
+ ] });
1190
1250
  }
1191
- function DropItem({
1251
+ function DropItem2({
1192
1252
  children,
1193
1253
  onClick,
1194
1254
  active,
@@ -1230,7 +1290,10 @@ function Separator({ vertical = true }) {
1230
1290
  }
1231
1291
  function HistoryButton({
1232
1292
  open,
1233
- onClick
1293
+ onClick,
1294
+ panelSide,
1295
+ tooltipSide,
1296
+ bottom
1234
1297
  }) {
1235
1298
  const [toast, setToast] = useState3(null);
1236
1299
  const [pushing, setPushing] = useState3(false);
@@ -1245,6 +1308,7 @@ function HistoryButton({
1245
1308
  const [lightboxSrc, setLightboxSrc] = useState3(null);
1246
1309
  const [editingFile, setEditingFile] = useState3(null);
1247
1310
  const [editValue, setEditValue] = useState3("");
1311
+ const [hoveredThumb, setHoveredThumb] = useState3(null);
1248
1312
  useEffect2(() => {
1249
1313
  if (!open) {
1250
1314
  setRepoDropOpen(false);
@@ -1264,7 +1328,7 @@ function HistoryButton({
1264
1328
  }).catch(() => {
1265
1329
  }).finally(() => setLoading(false));
1266
1330
  }, [open, selectedRepo, selectedBranch]);
1267
- const showToast = useCallback3((message, type) => {
1331
+ const showToast = useCallback2((message, type) => {
1268
1332
  setToast({ message, type });
1269
1333
  setTimeout(() => setToast(null), 3e3);
1270
1334
  }, []);
@@ -1347,16 +1411,16 @@ function HistoryButton({
1347
1411
  setPushing(false);
1348
1412
  }
1349
1413
  };
1414
+ const verticalAlign = bottom ? { bottom: 0 } : { top: 0 };
1415
+ const panelStyle = panelSide === "left" ? { right: "calc(100% + 10px)", ...verticalAlign } : { left: "calc(100% + 10px)", ...verticalAlign };
1350
1416
  return /* @__PURE__ */ jsxs2("div", { style: { position: "relative" }, children: [
1351
- /* @__PURE__ */ jsx2(IconButton, { active: open, tooltip: !open ? "Screenshots" : void 0, onClick, children: /* @__PURE__ */ jsx2(Clock, { size: 16, strokeWidth: 1.7 }) }),
1417
+ /* @__PURE__ */ jsx2(IconButton, { active: open, tooltipSide, tooltip: !open ? "Screenshots" : void 0, onClick, children: /* @__PURE__ */ jsx2(Clock, { size: 16, strokeWidth: 1.7 }) }),
1352
1418
  open && /* @__PURE__ */ jsxs2(
1353
1419
  "div",
1354
1420
  {
1355
1421
  style: {
1356
1422
  position: "absolute",
1357
- left: "50%",
1358
- bottom: "calc(100% + 12px)",
1359
- transform: "translateX(-50%)",
1423
+ ...panelStyle,
1360
1424
  minWidth: 300,
1361
1425
  maxWidth: 360,
1362
1426
  padding: "10px 12px",
@@ -1438,7 +1502,7 @@ function HistoryButton({
1438
1502
  },
1439
1503
  children: "No screenshots yet"
1440
1504
  }
1441
- ) : /* @__PURE__ */ jsxs2(Fragment, { children: [
1505
+ ) : /* @__PURE__ */ jsxs2(Fragment2, { children: [
1442
1506
  /* @__PURE__ */ jsx2(
1443
1507
  "div",
1444
1508
  {
@@ -1461,22 +1525,56 @@ function HistoryButton({
1461
1525
  alignItems: "center"
1462
1526
  },
1463
1527
  children: [
1464
- /* @__PURE__ */ jsx2(
1465
- "img",
1528
+ /* @__PURE__ */ jsxs2(
1529
+ "div",
1466
1530
  {
1467
- src: imgUrl,
1468
- alt: "",
1469
- onClick: () => setLightboxSrc(imgUrl),
1470
1531
  style: {
1532
+ position: "relative",
1471
1533
  width: 56,
1472
1534
  height: 36,
1473
- borderRadius: 4,
1474
- objectFit: "cover",
1475
- border: "1px solid rgba(255, 255, 255, 0.1)",
1476
1535
  flexShrink: 0,
1477
- background: "rgba(255, 255, 255, 0.05)",
1536
+ borderRadius: 4,
1537
+ overflow: "hidden",
1478
1538
  cursor: "pointer"
1479
- }
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
+ ]
1480
1578
  }
1481
1579
  ),
1482
1580
  /* @__PURE__ */ jsx2("div", { style: { flex: 1, minWidth: 0 }, children: isEditing ? /* @__PURE__ */ jsx2(
@@ -1548,7 +1646,7 @@ function HistoryButton({
1548
1646
  e.currentTarget.style.color = "rgba(255, 255, 255, 0.35)";
1549
1647
  e.currentTarget.style.background = "transparent";
1550
1648
  },
1551
- children: /* @__PURE__ */ jsx2(Trash2, { size: 13, strokeWidth: 1.8 })
1649
+ children: /* @__PURE__ */ jsx2(Trash22, { size: 13, strokeWidth: 1.8 })
1552
1650
  }
1553
1651
  )
1554
1652
  ]
@@ -1613,7 +1711,8 @@ function HistoryButton({
1613
1711
  style: {
1614
1712
  position: "fixed",
1615
1713
  inset: 0,
1616
- zIndex: 2147483647,
1714
+ bottom: 100,
1715
+ zIndex: 2147483646,
1617
1716
  background: "rgba(0, 0, 0, 0.85)",
1618
1717
  display: "flex",
1619
1718
  alignItems: "center",
@@ -1629,7 +1728,7 @@ function HistoryButton({
1629
1728
  onClick: (e) => e.stopPropagation(),
1630
1729
  style: {
1631
1730
  maxWidth: "90vw",
1632
- maxHeight: "90vh",
1731
+ maxHeight: "calc(100% - 32px)",
1633
1732
  borderRadius: 8,
1634
1733
  boxShadow: "0 20px 60px rgba(0, 0, 0, 0.5)",
1635
1734
  cursor: "default"
@@ -1656,7 +1755,7 @@ function HistoryButton({
1656
1755
  justifyContent: "center",
1657
1756
  padding: 0
1658
1757
  },
1659
- children: /* @__PURE__ */ jsx2(X, { size: 18, strokeWidth: 2 })
1758
+ children: /* @__PURE__ */ jsx2(X2, { size: 18, strokeWidth: 2 })
1660
1759
  }
1661
1760
  )
1662
1761
  ]
@@ -1718,7 +1817,7 @@ function FilterDropdown({
1718
1817
  children: value || "\u2014"
1719
1818
  }
1720
1819
  ),
1721
- /* @__PURE__ */ jsx2(ChevronDown, { size: 12, strokeWidth: 2 })
1820
+ /* @__PURE__ */ jsx2(ChevronDown2, { size: 12, strokeWidth: 2 })
1722
1821
  ]
1723
1822
  }
1724
1823
  ),
@@ -1740,7 +1839,7 @@ function FilterDropdown({
1740
1839
  zIndex: 1
1741
1840
  },
1742
1841
  children: options.map((opt) => /* @__PURE__ */ jsx2(
1743
- DropItem,
1842
+ DropItem2,
1744
1843
  {
1745
1844
  active: opt === value,
1746
1845
  onClick: () => onSelect(opt),
@@ -1801,8 +1900,8 @@ function formatTimestamp(filename) {
1801
1900
  }
1802
1901
 
1803
1902
  // src/overlay/ui/inspector.tsx
1804
- import { useEffect as useEffect3, useRef as useRef3, useCallback as useCallback4, useState as useState4 } from "react";
1805
- import { Fragment as Fragment2, jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
1903
+ import { useEffect as useEffect3, useRef as useRef3, useCallback as useCallback3, useState as useState4 } from "react";
1904
+ import { jsx as jsx3 } from "react/jsx-runtime";
1806
1905
  function Inspector({ onSelect, onCancel }) {
1807
1906
  const [highlight, setHighlight] = useState4(null);
1808
1907
  const hoveredEl = useRef3(null);
@@ -1822,7 +1921,7 @@ function Inspector({ onSelect, onCancel }) {
1822
1921
  style.remove();
1823
1922
  };
1824
1923
  }, []);
1825
- const isOverlayElement = useCallback4((el) => {
1924
+ const isOverlayElement = useCallback3((el) => {
1826
1925
  let node = el;
1827
1926
  while (node) {
1828
1927
  if (node instanceof HTMLElement && node.dataset.afterbefore) return true;
@@ -1830,7 +1929,7 @@ function Inspector({ onSelect, onCancel }) {
1830
1929
  }
1831
1930
  return false;
1832
1931
  }, []);
1833
- const handleMouseMove = useCallback4(
1932
+ const handleMouseMove = useCallback3(
1834
1933
  (e) => {
1835
1934
  const el = document.elementFromPoint(e.clientX, e.clientY);
1836
1935
  if (!el || !(el instanceof HTMLElement) || isOverlayElement(el)) {
@@ -1844,13 +1943,12 @@ function Inspector({ onSelect, onCancel }) {
1844
1943
  x: rect.x,
1845
1944
  y: rect.y,
1846
1945
  width: rect.width,
1847
- height: rect.height,
1848
- tag: el.tagName.toLowerCase() + (el.className && typeof el.className === "string" ? `.${el.className.split(" ")[0]}` : "")
1946
+ height: rect.height
1849
1947
  });
1850
1948
  },
1851
1949
  [isOverlayElement]
1852
1950
  );
1853
- const handleClick = useCallback4(
1951
+ const handleClick = useCallback3(
1854
1952
  (e) => {
1855
1953
  if (isOverlayElement(e.target)) return;
1856
1954
  e.preventDefault();
@@ -1862,7 +1960,7 @@ function Inspector({ onSelect, onCancel }) {
1862
1960
  },
1863
1961
  [onSelect, isOverlayElement]
1864
1962
  );
1865
- const handleKeyDown = useCallback4(
1963
+ const handleKeyDown = useCallback3(
1866
1964
  (e) => {
1867
1965
  if (e.key === "Escape") {
1868
1966
  onCancel();
@@ -1880,49 +1978,27 @@ function Inspector({ onSelect, onCancel }) {
1880
1978
  document.removeEventListener("keydown", handleKeyDown);
1881
1979
  };
1882
1980
  }, [handleMouseMove, handleClick, handleKeyDown]);
1883
- return /* @__PURE__ */ jsx3("div", { "data-afterbefore": "true", style: { position: "fixed", inset: 0, zIndex: 2147483646, pointerEvents: "none" }, children: highlight && /* @__PURE__ */ jsxs3(Fragment2, { children: [
1884
- /* @__PURE__ */ jsx3(
1885
- "div",
1886
- {
1887
- style: {
1888
- position: "fixed",
1889
- left: highlight.x,
1890
- top: highlight.y,
1891
- width: highlight.width,
1892
- height: highlight.height,
1893
- background: "transparent",
1894
- border: "2px solid rgba(255, 255, 255, 0.5)",
1895
- borderRadius: 2,
1896
- boxShadow: "0 0 0 9999px rgba(0, 0, 0, 0.5)",
1897
- pointerEvents: "none"
1898
- }
1899
- }
1900
- ),
1901
- /* @__PURE__ */ jsx3(
1902
- "div",
1903
- {
1904
- style: {
1905
- position: "fixed",
1906
- left: highlight.x,
1907
- top: Math.max(0, highlight.y - 24),
1908
- background: "rgba(0, 0, 0, 0.8)",
1909
- color: "#fff",
1910
- fontSize: 11,
1911
- fontFamily: "system-ui, -apple-system, monospace",
1912
- padding: "2px 6px",
1913
- borderRadius: 3,
1914
- pointerEvents: "none",
1915
- whiteSpace: "nowrap",
1916
- lineHeight: "18px"
1917
- },
1918
- children: highlight.tag
1981
+ return /* @__PURE__ */ jsx3("div", { "data-afterbefore": "true", style: { position: "fixed", inset: 0, zIndex: 2147483646, pointerEvents: "none" }, children: highlight && /* @__PURE__ */ jsx3(
1982
+ "div",
1983
+ {
1984
+ style: {
1985
+ position: "fixed",
1986
+ left: highlight.x,
1987
+ top: highlight.y,
1988
+ width: highlight.width,
1989
+ height: highlight.height,
1990
+ background: "transparent",
1991
+ border: "1px solid #fff",
1992
+ borderRadius: 2,
1993
+ boxShadow: "0 0 0 9999px rgba(0, 0, 0, 0.5)",
1994
+ pointerEvents: "none"
1919
1995
  }
1920
- )
1921
- ] }) });
1996
+ }
1997
+ ) });
1922
1998
  }
1923
1999
 
1924
2000
  // src/overlay/index.tsx
1925
- import { jsx as jsx4, jsxs as jsxs4 } from "react/jsx-runtime";
2001
+ import { jsx as jsx4, jsxs as jsxs3 } from "react/jsx-runtime";
1926
2002
  async function saveCapture(mode, dataUrl) {
1927
2003
  try {
1928
2004
  const res = await fetch("/__afterbefore/save", {
@@ -1945,7 +2021,6 @@ function AfterBefore() {
1945
2021
  const [loading, setLoading] = useState5(false);
1946
2022
  const [selectedMode, setSelectedMode] = useState5("component");
1947
2023
  const [frameSettings, setFrameSettings] = useState5(DEFAULT_FRAME_SETTINGS);
1948
- const iconPos = useRef4({ x: 24, y: 0 });
1949
2024
  useEffect4(() => {
1950
2025
  try {
1951
2026
  const stored = localStorage.getItem("ab-frame-settings");
@@ -1969,13 +2044,7 @@ function AfterBefore() {
1969
2044
  return () => clearTimeout(timer);
1970
2045
  }
1971
2046
  }, [state.phase, reset]);
1972
- const handlePositionChange = useCallback5(
1973
- (pos) => {
1974
- iconPos.current = pos;
1975
- },
1976
- []
1977
- );
1978
- const handleIconClick = useCallback5(() => {
2047
+ const handleToggle = useCallback4(() => {
1979
2048
  if (loading) return;
1980
2049
  if (state.phase === "ready") {
1981
2050
  reset();
@@ -1993,7 +2062,7 @@ function AfterBefore() {
1993
2062
  }
1994
2063
  }
1995
2064
  }, [state.phase, loading, toolbarActive, inspectorActive, selectedMode, reset]);
1996
- const performCapture = useCallback5(
2065
+ const performCapture = useCallback4(
1997
2066
  async (mode, element) => {
1998
2067
  setLoading(true);
1999
2068
  try {
@@ -2012,7 +2081,7 @@ function AfterBefore() {
2012
2081
  },
2013
2082
  [captureComplete, frameSettings]
2014
2083
  );
2015
- const handleToolbarCapture = useCallback5(
2084
+ const handleToolbarCapture = useCallback4(
2016
2085
  (mode) => {
2017
2086
  if (mode === "viewport") {
2018
2087
  setToolbarActive(false);
@@ -2026,10 +2095,11 @@ function AfterBefore() {
2026
2095
  },
2027
2096
  [performCapture]
2028
2097
  );
2029
- const handleToolbarCancel = useCallback5(() => {
2098
+ const handleToolbarCancel = useCallback4(() => {
2030
2099
  setToolbarActive(false);
2100
+ setInspectorActive(false);
2031
2101
  }, []);
2032
- const handleComponentSelect = useCallback5(
2102
+ const handleComponentSelect = useCallback4(
2033
2103
  (element) => {
2034
2104
  setInspectorActive(false);
2035
2105
  setToolbarActive(false);
@@ -2037,34 +2107,29 @@ function AfterBefore() {
2037
2107
  },
2038
2108
  [performCapture]
2039
2109
  );
2040
- const handleComponentCancel = useCallback5(() => {
2110
+ const handleComponentCancel = useCallback4(() => {
2041
2111
  setInspectorActive(false);
2042
2112
  setToolbarActive(true);
2043
2113
  }, []);
2044
- const handleFrameSettingsChange = useCallback5((next) => {
2114
+ const handleFrameSettingsChange = useCallback4((next) => {
2045
2115
  setFrameSettings(next);
2046
2116
  try {
2047
2117
  localStorage.setItem("ab-frame-settings", JSON.stringify(next));
2048
2118
  } catch {
2049
2119
  }
2050
2120
  }, []);
2051
- const handleModeChange = useCallback5((mode) => {
2121
+ const handleModeChange = useCallback4((mode) => {
2052
2122
  setSelectedMode(mode);
2053
2123
  setInspectorActive(mode === "component");
2054
2124
  }, []);
2055
- return /* @__PURE__ */ jsxs4("div", { "data-afterbefore": "true", children: [
2125
+ return /* @__PURE__ */ jsxs3("div", { "data-afterbefore": "true", children: [
2056
2126
  /* @__PURE__ */ jsx4(
2057
- Icon,
2127
+ Toolbar,
2058
2128
  {
2129
+ expanded: toolbarActive,
2130
+ onToggle: handleToggle,
2059
2131
  phase: state.phase,
2060
- onClick: handleIconClick,
2061
2132
  loading,
2062
- onPositionChange: handlePositionChange
2063
- }
2064
- ),
2065
- toolbarActive && /* @__PURE__ */ jsx4(
2066
- Toolbar,
2067
- {
2068
2133
  selectedMode,
2069
2134
  onModeChange: handleModeChange,
2070
2135
  onCapture: handleToolbarCapture,