translime-plugin-hdr-capture 1.0.1 → 1.0.4

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.
@@ -3,24 +3,17 @@
3
3
 
4
4
  /* auto-generated by NAPI-RS */
5
5
 
6
- /** 显示器信息 */
6
+ /** 显示器描述信息 */
7
7
  export interface DisplayInfo {
8
- /** 显示器索引 */
9
8
  id: number
10
- /** 显示器名称 */
11
9
  name: string
12
- /** 左边界 */
13
10
  x: number
14
- /** 上边界 */
15
11
  y: number
16
- /** 宽度 */
17
12
  width: number
18
- /** 高度 */
19
13
  height: number
20
- /** 是否主显示器 */
21
14
  isPrimary: boolean
22
15
  }
23
- /** 窗口信息结构 */
16
+ /** 暴露给 JavaScript 的窗口信息结构 */
24
17
  export interface WindowInfo {
25
18
  /** 窗口句柄 (HWND) */
26
19
  handle: number
@@ -28,17 +21,11 @@ export interface WindowInfo {
28
21
  title: string
29
22
  /** 窗口类名 */
30
23
  className: string
31
- /** 左边界 */
32
24
  left: number
33
- /** 上边界 */
34
25
  top: number
35
- /** 右边界 */
36
26
  right: number
37
- /** 下边界 */
38
27
  bottom: number
39
- /** 窗口宽度 */
40
28
  width: number
41
- /** 窗口高度 */
42
29
  height: number
43
30
  }
44
31
  /** 矩形区域 */
@@ -48,71 +35,58 @@ export interface Rect {
48
35
  width: number
49
36
  height: number
50
37
  }
51
- /** Tone Mapping 选项 */
38
+ /** 色调映射 (Tone Mapping) 选项 */
52
39
  export interface ToneMappingOptions {
53
- /** 曝光值调整 */
40
+ /** 曝光度调整值 */
54
41
  exposure?: number
55
- /** 是否保留 HDR 元数据 */
42
+ /** 是否保留 HDR 元数据(某些格式支持) */
56
43
  preserveHdrMetadata?: boolean
57
44
  }
58
- /** HDR 映射配置选项 */
45
+ /** HDR 映射专用配置 */
59
46
  export interface HdrMappingOptions {
60
- /** 是否启用自定义 HDR 映射 */
47
+ /** 是否开启色彩映射 */
61
48
  enabled?: boolean
62
- /** SDR 白点亮度 (nits),默认 203 */
49
+ /** SDR 白点参考亮度 (nits),默认为 203 */
63
50
  sdrWhiteNits?: number
64
- /** HDR 峰值亮度 (nits),默认 1000 */
51
+ /** HDR 峰值亮度 (nits),默认为 1000 */
65
52
  hdrMaxNits?: number
66
- /** 是否同时保留原始 HDR 数据(用于后续保存 HDR 原始文件) */
53
+ /** 是否导出原始 HDR 字节流(供后续保存为 EXR 等格式) */
67
54
  preserveRaw?: boolean
68
55
  }
69
- /** 屏幕捕获结果 */
56
+ /** 屏幕捕获返回的综合结果 */
70
57
  export interface CaptureResult {
71
- /** 图像数据 (RGBA,经过 Tone Mapping) */
58
+ /** 处理后的图像数据 (RGBA8 缓冲区) */
72
59
  buffer: Buffer
73
- /** 实际图像宽度 (物理像素) */
60
+ /** 图像物理宽度 (像素) */
74
61
  width: number
75
- /** 实际图像高度 (物理像素) */
62
+ /** 图像物理高度 (像素) */
76
63
  height: number
77
- /** 是否为 HDR 源数据(经过 Tonemap) */
64
+ /** 是否来源于 HDR 屏幕 */
78
65
  isHdr: boolean
79
- /**
80
- * 原始 HDR 数据 (可选,仅当 preserve_raw 为 true 且为 HDR 屏幕时存在)
81
- * 保存为 RGBA Float16 或 10bit 原始格式的字节流
82
- */
66
+ /** 原始 HDR 数据 (F16 格式,仅在 preserve_raw 为开启时存在) */
83
67
  rawHdrBuffer?: Buffer
84
68
  }
85
- /** 获取所有顶层窗口 */
69
+ /** 获取所有的顶层可见窗口 */
86
70
  export declare function getTopLevelWindows(): Array<WindowInfo>
87
- /** 获取指定坐标处的窗口 */
71
+ /** 获取指定屏幕坐标下的最前端窗口 */
88
72
  export declare function getWindowAtPoint(x: number, y: number, ignoreHandle?: number | undefined | null): WindowInfo | null
89
73
  /**
90
- * 捕获指定显示器的屏幕 (返回 RGBA 结果对象)
74
+ * 捕获指定显示器的当前画面
91
75
  *
92
- * `hdr_options` - 可选的 HDR 映射配置,用于自定义色调映射参数
76
+ * 返回包含图像数据及元数据的结果对象。
93
77
  */
94
- export declare function captureDisplay(displayId: number, hdrOptions?: HdrMappingOptions | undefined | null): Promise<CaptureResult>
95
- /** 获取显示器列表 */
78
+ export declare function captureDisplay(displayId: number, hdrOptions?: HdrMappingOptions | undefined | null, captureCursor?: boolean | undefined | null): Promise<CaptureResult>
79
+ /** 获取当前连接的所有显示器列表 */
96
80
  export declare function getDisplays(): Array<DisplayInfo>
97
- /** 裁剪图像 */
81
+ /** 根据给定矩形裁剪图像缓冲区 */
98
82
  export declare function cropImage(buffer: Buffer, width: number, height: number, rect: Rect): Promise<Buffer>
99
- /** HDR SDR 的 Tone Mapping */
83
+ /** 对捕获的 HDR 数据执行后处理色调映射 */
100
84
  export declare function toneMap(hdrBuffer: Buffer, width: number, height: number, options?: ToneMappingOptions | undefined | null): Promise<Buffer>
101
- /** 编码图像为指定格式 */
85
+ /** 将像素数据编码为特定格式(png, jpg, webp) */
102
86
  export declare function encodeImage(buffer: Buffer, width: number, height: number, format: string): Promise<Buffer>
103
- /** 调整图像大小 */
87
+ /** 调整图像大小到指定的分辨率 */
104
88
  export declare function resizeImage(buffer: Buffer, width: number, height: number, newWidth: number, newHeight: number): Promise<Buffer>
105
- /**
106
- * 将原始 HDR F16 数据编码为 EXR 格式
107
- *
108
- * 输入: RGBA F16 格式的原始 HDR 数据 (每像素 8 字节)
109
- * 输出: OpenEXR 文件字节流
110
- */
89
+ /** 将 F16 格式的原始 HDR 数据编码为高性能的 OpenEXR 文件 */
111
90
  export declare function encodeHdrToExr(rawBuffer: Buffer, width: number, height: number): Promise<Buffer>
112
- /**
113
- * 裁剪 HDR F16 格式的原始数据
114
- *
115
- * 输入: RGBA F16 格式的原始 HDR 数据 (每像素 8 字节)
116
- * 输出: 裁剪后的 RGBA F16 数据
117
- */
91
+ /** 在 F16 原始数据层级执行裁剪 */
118
92
  export declare function cropHdrF16(rawBuffer: Buffer, width: number, height: number, rect: Rect): Promise<Buffer>
Binary file
package/dist/main.cjs.js CHANGED
@@ -654,7 +654,7 @@ try {
654
654
  const getTopLevelWindows = () => nativeAddon.getTopLevelWindows();
655
655
  const getWindowAtPoint = (x, y, ignoreHandle = null) => nativeAddon.getWindowAtPoint(x, y, ignoreHandle);
656
656
  const getDisplays = () => nativeAddon.getDisplays();
657
- const captureDisplay = async (displayId = 0, hdrOptions = null) => {
657
+ const captureDisplay = async (displayId = 0, hdrOptions = null, captureCursor = false) => {
658
658
  try {
659
659
  const nativeHdrOptions = hdrOptions ? {
660
660
  enabled: hdrOptions.enabled,
@@ -662,12 +662,13 @@ const captureDisplay = async (displayId = 0, hdrOptions = null) => {
662
662
  hdrMaxNits: hdrOptions.hdrMaxNits,
663
663
  preserveRaw: hdrOptions.preserveRaw
664
664
  } : null;
665
- return await nativeAddon.captureDisplay(displayId, nativeHdrOptions);
665
+ return await nativeAddon.captureDisplay(displayId, nativeHdrOptions, captureCursor);
666
666
  } catch (e) {
667
667
  logger$1.error(`captureDisplay 失败 (ID=${displayId}):`, e);
668
668
  throw e;
669
669
  }
670
670
  };
671
+ const toneMap = async (hdrBuffer, width, height, options = {}) => nativeAddon.toneMap(hdrBuffer, width, height, options);
671
672
  const encodeImage = async (buffer, width, height, format = "png") => nativeAddon.encodeImage(buffer, width, height, format);
672
673
  const encodeHdrToExr = async (rawBuffer, width, height) => {
673
674
  try {
@@ -876,6 +877,11 @@ const cropAndSaveScaledFromBuffer = async (sessionData, rect, options = {}) => {
876
877
  if (savePath) {
877
878
  const fs = await import("node:fs/promises");
878
879
  const path2 = await import("node:path");
880
+ try {
881
+ await fs.mkdir(savePath, { recursive: true });
882
+ } catch (err) {
883
+ logger$1.error(`无法创建保存目录: ${savePath}`, err);
884
+ }
879
885
  const timestamp = dayjs().format("HDR_Capture_YYYY-MM-DD_HH-mm-ss");
880
886
  let fileName;
881
887
  if (saveFilenameTemplate) {
@@ -972,6 +978,7 @@ const getPreserveHdr = () => pluginConfig.get("preserveHdr", false);
972
978
  const getEnableHdrMapping = () => pluginConfig.get("enableHdrMapping", true);
973
979
  const getSdrWhiteNits = () => pluginConfig.get("sdrWhiteNits", 203);
974
980
  const getHdrMaxNits = () => pluginConfig.get("hdrMaxNits", 1e3);
981
+ const getCaptureCursor = () => pluginConfig.get("captureCursor", false);
975
982
  const getAllDisplaysBounds = () => {
976
983
  const displays = electron.screen.getAllDisplays();
977
984
  let minX = Infinity;
@@ -994,6 +1001,8 @@ const getAllDisplaysBounds = () => {
994
1001
  displays,
995
1002
  minX,
996
1003
  minY,
1004
+ maxX,
1005
+ maxY,
997
1006
  width: maxX - minX,
998
1007
  height: maxY - minY
999
1008
  };
@@ -1005,16 +1014,28 @@ const updateOverlayBounds = () => {
1005
1014
  const {
1006
1015
  minX,
1007
1016
  minY,
1017
+ maxY,
1008
1018
  width,
1009
1019
  height
1010
1020
  } = getAllDisplaysBounds();
1011
- overlayWindow.setBounds({
1012
- x: minX,
1013
- y: minY,
1014
- width,
1015
- height
1016
- });
1017
- logger.info(`屏幕变动,已更新 Overlay 边界: ${width}x${height} at (${minX}, ${minY})`);
1021
+ const { y } = overlayWindow.getBounds();
1022
+ if (y >= maxY) {
1023
+ overlayWindow.setBounds({
1024
+ width,
1025
+ height,
1026
+ x: 0,
1027
+ y: maxY + 100
1028
+ });
1029
+ logger.info(`屏幕变动,更新离屏位置: (0, ${maxY + 100})`);
1030
+ } else {
1031
+ overlayWindow.setBounds({
1032
+ x: minX,
1033
+ y: minY,
1034
+ width,
1035
+ height
1036
+ });
1037
+ logger.info(`屏幕变动,更新捕获边界: ${width}x${height} at (${minX}, ${minY})`);
1038
+ }
1018
1039
  };
1019
1040
  const unregisterShortcut = () => {
1020
1041
  if (registeredShortcut) {
@@ -1042,7 +1063,7 @@ const createOverlayWindow = (isDebug = false) => {
1042
1063
  skipTaskbar: !isDebug,
1043
1064
  resizable: isDebug,
1044
1065
  movable: isDebug,
1045
- maximizable: true,
1066
+ maximizable: false,
1046
1067
  fullscreen: false,
1047
1068
  thickFrame: false,
1048
1069
  hasShadow: false,
@@ -1073,49 +1094,64 @@ const createOverlayWindow = (isDebug = false) => {
1073
1094
  });
1074
1095
  return overlayWindow;
1075
1096
  };
1076
- const preCaptureAllScreens = async () => {
1097
+ const preCaptureAllScreens = async (startTime = Date.now(), isDebug = false) => {
1077
1098
  const electronDisplays = electron.screen.getAllDisplays();
1078
1099
  const nativeDisplays = getDisplays();
1079
- logger.info("开始预捕获。检测到原生显示器数量:", nativeDisplays.length);
1100
+ logger.info(`[Perf] 开始预捕获 (T+${Date.now() - startTime}ms). 检测到原生显示器数量:`, nativeDisplays.length);
1080
1101
  const enableHdrMapping = getEnableHdrMapping();
1081
1102
  const sdrWhiteNits = getSdrWhiteNits();
1082
1103
  const hdrMaxNits = getHdrMaxNits();
1083
1104
  const preserveHdr = getPreserveHdr();
1105
+ const captureCursor = getCaptureCursor();
1084
1106
  const hdrOptions = enableHdrMapping ? {
1085
1107
  enabled: true,
1086
1108
  sdrWhiteNits,
1087
1109
  hdrMaxNits,
1088
- // 当用户启用了 HDR 映射且勾选了保存 HDR 原始文件时,请求原始数据
1110
+ preserveHdr,
1111
+ // 注意:传递给 native 的参数名和 config 可能略有不同,这里复用 preserveHdr
1089
1112
  preserveRaw: preserveHdr
1090
1113
  } : null;
1091
1114
  logger.info("HDR 映射配置:", {
1092
1115
  enableHdrMapping,
1093
1116
  sdrWhiteNits,
1094
1117
  hdrMaxNits,
1095
- preserveHdr
1118
+ preserveHdr,
1119
+ captureCursor
1096
1120
  });
1097
1121
  const capturePromises = nativeDisplays.map(async (nd) => {
1098
1122
  try {
1099
- logger.info(`正在捕获显示器 ID=${nd.id} (预期 ${nd.width}x${nd.height})`);
1123
+ const t0 = Date.now();
1124
+ logger.info(`[Perf] 正在捕获显示器 ID=${nd.id} (预期 ${nd.width}x${nd.height}) ...`);
1100
1125
  const {
1101
1126
  buffer,
1102
1127
  width,
1103
1128
  height,
1104
1129
  isHdr,
1105
1130
  rawHdrBuffer
1106
- } = await captureDisplay(nd.id, hdrOptions);
1131
+ } = await captureDisplay(nd.id, hdrOptions, captureCursor);
1132
+ const t1 = Date.now();
1133
+ logger.info(`[Perf] 显示器 ID=${nd.id} 捕获完成, 耗时: ${t1 - t0}ms(T+${t1 - startTime}ms). 实际尺寸 ${width}x${height}, Buffer: ${buffer ? buffer.length : 0}, IS_HDR: ${isHdr}`);
1134
+ if (isDebug && !isHdr && buffer && buffer.length > 0) {
1135
+ try {
1136
+ logger.info("[Perf] (Debug模式) 强制对 SDR 数据执行 Tone Mapping 测试...");
1137
+ const tMap0 = Date.now();
1138
+ await toneMap(buffer, width, height, { exposure: 1 });
1139
+ const tMap1 = Date.now();
1140
+ logger.info(`[Perf] (Debug模式) 强制 Tone Mapping 耗时: ${tMap1 - tMap0}ms(T+${tMap1 - startTime}ms)`);
1141
+ } catch (tmErr) {
1142
+ logger.error("强制 Tone Mapping 测试失败:", tmErr);
1143
+ }
1144
+ }
1107
1145
  if (!buffer || buffer.length === 0) {
1108
1146
  logger.warn(`显示器 ID=${nd.id} 返回的 Buffer 为空`);
1109
1147
  return null;
1110
1148
  }
1111
- logger.info(`显示器 ID=${nd.id} 捕获成功: 实际尺寸 ${width}x${height}, Buffer 长度 ${buffer.length}, IS_HDR: ${isHdr}, 原始 HDR 数据: ${rawHdrBuffer ? rawHdrBuffer.length : "N/A"}`);
1112
1149
  const centerX = nd.x + nd.width / 2;
1113
1150
  const centerY = nd.y + nd.height / 2;
1114
1151
  const ed = electronDisplays.find((d) => {
1115
1152
  const b = d.bounds;
1116
1153
  return centerX >= b.x && centerX <= b.x + b.width && centerY >= b.y && centerY <= b.y + b.height;
1117
1154
  }) || electronDisplays[0];
1118
- logger.info(`显示器 ID=${nd.id} 匹配到 Electron 显示器: scale=${ed.scaleFactor}`);
1119
1155
  return {
1120
1156
  displayId: nd.id,
1121
1157
  buffer,
@@ -1134,37 +1170,63 @@ const preCaptureAllScreens = async () => {
1134
1170
  });
1135
1171
  const results = await Promise.allSettled(capturePromises);
1136
1172
  const finalResults = results.filter((result) => result.status === "fulfilled" && result.value !== null).map((result) => result.value);
1137
- logger.info(`预捕获完成,成功获取到 ${finalResults.length} 张屏幕画面`);
1173
+ logger.info(`[Perf] 预捕获全部完成 (T+${Date.now() - startTime}ms), 成功获取到 ${finalResults.length} 张屏幕画面`);
1138
1174
  return finalResults;
1139
1175
  };
1140
- const startCapture = async (isDebug = false) => {
1176
+ const startCapture = async (isDebug = false, startTime = Date.now()) => {
1177
+ logger.info(`[Perf] startCapture 开始 (T+${Date.now() - startTime}ms)`);
1178
+ const {
1179
+ displays: allDisplays,
1180
+ minX,
1181
+ minY,
1182
+ maxY,
1183
+ width: totalWidth,
1184
+ height: totalHeight
1185
+ } = getAllDisplaysBounds();
1186
+ if (!overlayWindow || overlayWindow.isDestroyed()) {
1187
+ logger.info(`[Perf] 预创建 Overlay 窗口 (T+${Date.now() - startTime}ms)`);
1188
+ createOverlayWindow(isDebug);
1189
+ }
1141
1190
  if (overlayWindow && !overlayWindow.isDestroyed() && overlayWindow.isVisible()) {
1142
- overlayWindow.focus();
1143
- return;
1191
+ const { y } = overlayWindow.getBounds();
1192
+ if (y < maxY) {
1193
+ overlayWindow.focus();
1194
+ return;
1195
+ }
1144
1196
  }
1145
1197
  await new Promise((resolve) => {
1146
- setTimeout(resolve, 100);
1198
+ setTimeout(resolve, 10);
1147
1199
  });
1200
+ logger.info(`[Perf] DWM 稳定等待结束 (T+${Date.now() - startTime}ms)`);
1148
1201
  let sessionData = [];
1149
1202
  let capturedScreens = [];
1150
- if (!isDebug) {
1151
- sessionData = await preCaptureAllScreens();
1152
- if (!sessionData || sessionData.length === 0) {
1153
- logger.error("启动失败: 未能捕获到任何屏幕画面。请检查原生模块加载状态及录屏权限。");
1154
- return;
1155
- }
1203
+ sessionData = await preCaptureAllScreens(startTime, isDebug);
1204
+ if (sessionData && sessionData.length > 0) {
1156
1205
  currentCaptureSession = sessionData;
1157
- capturedScreens = await Promise.all(sessionData.map(async (s) => ({
1158
- displayId: s.displayId,
1159
- bounds: s.bounds,
1160
- data: await encodeImage(s.buffer, s.width, s.height, "webp")
1161
- })));
1206
+ logger.info(`[Perf] 开始编码预览图 (T+${Date.now() - startTime}ms)...`);
1207
+ capturedScreens = await Promise.all(sessionData.map(async (s) => {
1208
+ const tEncode0 = Date.now();
1209
+ const data = await encodeImage(s.buffer, s.width, s.height, "webp");
1210
+ const tEncode1 = Date.now();
1211
+ logger.info(`[Perf] 编码显示器 ${s.displayId} 为 WebP 耗时: ${tEncode1 - tEncode0}ms`);
1212
+ return {
1213
+ displayId: s.displayId,
1214
+ bounds: s.bounds,
1215
+ data
1216
+ };
1217
+ }));
1218
+ logger.info(`[Perf] 所有预览图编码完成 (T+${Date.now() - startTime}ms)`);
1219
+ } else if (!isDebug) {
1220
+ logger.error("启动失败: 未能捕获到任何屏幕画面。请检查原生模块加载状态及录屏权限。");
1221
+ return;
1162
1222
  } else {
1163
- logger.info("Debug 模式:跳过实际截屏,提供空数据以启动 UI");
1223
+ logger.info("Debug 模式:真实截图未返回数据,提供空/Mock数据以启动 UI");
1224
+ capturedScreens = [];
1164
1225
  }
1165
1226
  const cursorPos = electron.screen.getCursorScreenPoint();
1227
+ const tWin0 = Date.now();
1166
1228
  const nativeWindows = getTopLevelWindows();
1167
- logger.info(`原生模块返回 ${nativeWindows.length} 个窗口`);
1229
+ logger.info(`[Perf] 获取顶层窗口耗时: ${Date.now() - tWin0}ms, 数量: ${nativeWindows.length}`);
1168
1230
  const windows = nativeWindows.map((win) => {
1169
1231
  try {
1170
1232
  const topLeft = electron.screen.screenToDipPoint({ x: win.left, y: win.top });
@@ -1183,13 +1245,6 @@ const startCapture = async (isDebug = false) => {
1183
1245
  return null;
1184
1246
  }
1185
1247
  }).filter(Boolean);
1186
- const {
1187
- displays: allDisplays,
1188
- minX,
1189
- minY,
1190
- width: totalWidth,
1191
- height: totalHeight
1192
- } = getAllDisplaysBounds();
1193
1248
  allDisplays.forEach((d, idx) => {
1194
1249
  windows.push({
1195
1250
  handle: 0,
@@ -1208,11 +1263,6 @@ const startCapture = async (isDebug = false) => {
1208
1263
  } else {
1209
1264
  logger.warn("未能成功转换任何窗口坐标或搜索结果为空");
1210
1265
  }
1211
- if (!overlayWindow || overlayWindow.isDestroyed()) {
1212
- createOverlayWindow(isDebug);
1213
- } else {
1214
- updateOverlayBounds();
1215
- }
1216
1266
  const initData = {
1217
1267
  isDebug,
1218
1268
  minX,
@@ -1226,14 +1276,25 @@ const startCapture = async (isDebug = false) => {
1226
1276
  displays: allDisplays.map((d) => ({
1227
1277
  id: d.id,
1228
1278
  bounds: d.bounds
1229
- }))
1279
+ })),
1280
+ // 传递触发时间戳,供 UI 计算总耗时
1281
+ startTime
1230
1282
  };
1231
1283
  const sendDataAndShow = () => {
1232
- logger.info(`发送初始化数据, 截图数量: ${capturedScreens.length}, 窗口数量: ${windows.length}, isDebug: ${isDebug}`);
1284
+ logger.info(`[Perf] 发送初始化数据 (T+${Date.now() - startTime}ms), 截图数量: ${capturedScreens.length}, 窗口数量: ${windows.length}, isDebug: ${isDebug}`);
1233
1285
  overlayWindow.webContents.send(`overlay-init@${PLUGIN_ID}`, initData);
1234
- overlayWindow.show();
1286
+ overlayWindow.setBounds({
1287
+ x: minX,
1288
+ y: minY,
1289
+ width: totalWidth,
1290
+ height: totalHeight
1291
+ });
1292
+ if (!overlayWindow.isVisible()) {
1293
+ overlayWindow.showInactive();
1294
+ }
1235
1295
  overlayWindow.focus();
1236
1296
  overlayWindow.setIgnoreMouseEvents(false);
1297
+ logger.info(`[Perf] 窗口已显示并聚焦 (T+${Date.now() - startTime}ms)`);
1237
1298
  };
1238
1299
  if (overlayWindow.webContents.isLoading()) {
1239
1300
  overlayWindow.webContents.once("did-finish-load", sendDataAndShow);
@@ -1251,11 +1312,15 @@ const registerShortcut = (accelerator) => {
1251
1312
  if (!lastPart || modifiers.includes(lastPart)) {
1252
1313
  return false;
1253
1314
  }
1315
+ if (registeredShortcut === finalAccelerator) {
1316
+ return true;
1317
+ }
1254
1318
  unregisterShortcut();
1255
1319
  try {
1256
1320
  const success = electron.globalShortcut.register(finalAccelerator, () => {
1257
- logger.info(`触发快捷键: ${finalAccelerator}`);
1258
- startCapture().catch((err) => {
1321
+ const now = Date.now();
1322
+ logger.info(`[Perf] 快捷键触发: ${finalAccelerator} (T+0ms)`);
1323
+ startCapture(false, now).catch((err) => {
1259
1324
  logger.error("快捷键触发 startCapture 失败:", err);
1260
1325
  });
1261
1326
  });
@@ -1273,10 +1338,15 @@ const registerShortcut = (accelerator) => {
1273
1338
  };
1274
1339
  const closeOverlay = () => {
1275
1340
  if (overlayWindow && !overlayWindow.isDestroyed()) {
1341
+ try {
1342
+ overlayWindow.webContents.send(`overlay-reset@${PLUGIN_ID}`);
1343
+ } catch (e) {
1344
+ }
1276
1345
  if (getFastResponse()) {
1277
- overlayWindow.hide();
1346
+ const { maxY } = getAllDisplaysBounds();
1278
1347
  overlayWindow.setIgnoreMouseEvents(true);
1279
- logger.info("快速响应模式: Overlay 已隐藏");
1348
+ overlayWindow.setPosition(0, maxY + 100);
1349
+ logger.info(`快速响应模式: Overlay 已移至离屏常驻 (0, ${maxY + 100})`);
1280
1350
  } else {
1281
1351
  overlayWindow.close();
1282
1352
  overlayWindow = null;
@@ -1325,7 +1395,7 @@ const ipcHandlers = [
1325
1395
  {
1326
1396
  type: "start-capture",
1327
1397
  handler: () => async ({ isDebug = false }) => {
1328
- await startCapture(isDebug);
1398
+ await startCapture(isDebug, Date.now());
1329
1399
  }
1330
1400
  },
1331
1401
  {
@@ -49,6 +49,15 @@ electron.contextBridge.exposeInMainWorld("hdrCapture", {
49
49
  * @param {function} callback
50
50
  */
51
51
  onInit: (callback) => {
52
+ electron.ipcRenderer.removeAllListeners(`overlay-init@${PLUGIN_ID}`);
52
53
  electron.ipcRenderer.on(`overlay-init@${PLUGIN_ID}`, (event, data) => callback(data));
54
+ },
55
+ /**
56
+ * 监听重置消息
57
+ * @param {function} callback
58
+ */
59
+ onReset: (callback) => {
60
+ electron.ipcRenderer.removeAllListeners(`overlay-reset@${PLUGIN_ID}`);
61
+ electron.ipcRenderer.on(`overlay-reset@${PLUGIN_ID}`, () => callback());
53
62
  }
54
63
  });
package/dist/overlay.css CHANGED
@@ -1 +1 @@
1
- .frozen-screens-layer[data-v-0b001d45]{position:absolute;top:0;left:0;width:100%;height:100%;z-index:1;pointer-events:none}.frozen-screen[data-v-0b001d45]{position:absolute}.frozen-screen img[data-v-0b001d45]{width:100%;height:100%;object-fit:fill;display:block}.action-toolbar-container[data-v-2b54cb73]{position:absolute;display:flex;flex-direction:column;align-items:flex-end;z-index:100;pointer-events:none}.action-toolbar-main[data-v-2b54cb73]{display:flex;padding:4px;background:#1e1e1ef2;border-radius:8px;box-shadow:0 4px 20px #0006;backdrop-filter:blur(12px);border:1px solid rgb(255 255 255 / 10%);pointer-events:auto}.btn-group[data-v-2b54cb73]{display:flex;align-items:center}.btn[data-v-2b54cb73]{width:28px;height:28px;border:none;cursor:pointer;display:flex;align-items:center;justify-content:center;color:#fff;transition:all .15s ease;background:transparent;border-radius:4px;margin:0 1px}.btn[data-v-2b54cb73]:hover{background:#ffffff26}.btn[data-v-2b54cb73]:active{transform:scale(.95)}.btn.active[data-v-2b54cb73]{background:#ffffff40;color:#adf}.divider[data-v-2b54cb73]{width:1px;height:16px;background:#fff3;margin:0 4px}.btn-save[data-v-2b54cb73]:hover{background:#4caf5099}.btn-copy[data-v-2b54cb73]:hover{background:#2196f399}.btn-cancel[data-v-2b54cb73]:hover{background:#f4433699}.size-settings-bar[data-v-2b54cb73]{margin-top:4px;padding:4px 8px;background:#1e1e1ef2;border-radius:8px;box-shadow:0 4px 20px #0006;backdrop-filter:blur(12px);border:1px solid rgb(255 255 255 / 10%);pointer-events:auto;display:flex;align-items:center;gap:8px;height:36px;box-sizing:border-box}.size-inputs[data-v-2b54cb73]{display:flex;align-items:center;gap:4px;font-family:monospace;font-size:13px;color:#eee}.size-input[data-v-2b54cb73]{background:#0000004d;border:1px solid rgb(255 255 255 / 20%);border-radius:4px;color:#fff;padding:2px 4px;text-align:center;outline:none;font-family:inherit;font-size:inherit;min-width:4ch;height:22px;box-sizing:border-box}.size-input[data-v-2b54cb73]:focus{border-color:#2196f3;background:#0000007f}.size-input[data-v-2b54cb73]::-webkit-outer-spin-button,.size-input[data-v-2b54cb73]::-webkit-inner-spin-button{appearance:none;margin:0}.size-separator[data-v-2b54cb73]{color:#ffffff7f;font-size:12px}.size-unit[data-v-2b54cb73]{color:#ffffff7f;font-size:12px;margin-left:2px}.btn-confirm[data-v-2b54cb73]{border:none;background:#2196f3;color:#fff;border-radius:4px;padding:2px 8px;font-size:12px;cursor:pointer;height:22px;line-height:18px;transition:background .15s;white-space:nowrap}.btn-confirm[data-v-2b54cb73]:hover{background:#42a5f5}.btn-confirm[data-v-2b54cb73]:active{background:#1976d2}.radius-settings[data-v-2b54cb73]{display:flex;align-items:center;gap:8px;width:100%;color:#eee;font-size:13px}.radius-label[data-v-2b54cb73]{font-size:12px;color:#ffffffb3;white-space:nowrap}.radius-slider[data-v-2b54cb73]{flex:1;height:4px;border-radius:2px;background:#ffffff4d;outline:none;cursor:pointer;accent-color:#2196f3;width:100px}.magnifier[data-v-cf910dc4]{position:absolute;border-radius:4px;overflow:hidden;box-shadow:0 4px 12px #0000007f,0 0 0 1px #fff3;pointer-events:none;z-index:9999;background:#000;border:1px solid rgb(255 255 255 / 50%)}@layer properties{@supports (((-webkit-hyphens:none)) and (not (margin-trim:inline))) or ((-moz-orient:inline) and (not (color:rgb(from red r g b)))){*,:before,:after,::backdrop{--tw-translate-x:0;--tw-translate-y:0;--tw-translate-z:0;--tw-rotate-x:initial;--tw-rotate-y:initial;--tw-rotate-z:initial;--tw-skew-x:initial;--tw-skew-y:initial;--tw-border-style:solid;--tw-shadow:0 0 #0000;--tw-shadow-color:initial;--tw-shadow-alpha:100%;--tw-inset-shadow:0 0 #0000;--tw-inset-shadow-color:initial;--tw-inset-shadow-alpha:100%;--tw-ring-color:initial;--tw-ring-shadow:0 0 #0000;--tw-inset-ring-color:initial;--tw-inset-ring-shadow:0 0 #0000;--tw-ring-inset:initial;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-offset-shadow:0 0 #0000;--tw-outline-style:solid;--tw-backdrop-blur:initial;--tw-backdrop-brightness:initial;--tw-backdrop-contrast:initial;--tw-backdrop-grayscale:initial;--tw-backdrop-hue-rotate:initial;--tw-backdrop-invert:initial;--tw-backdrop-opacity:initial;--tw-backdrop-saturate:initial;--tw-backdrop-sepia:initial;--tw-duration:initial;--tw-ease:initial}}}@layer theme{:root,:host{--font-sans:ui-sans-serif,system-ui,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji";--font-mono:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace;--color-black:#000;--color-white:#fff;--spacing:.25rem;--text-xs:.75rem;--text-xs--line-height:calc(1/.75);--radius-xl:.75rem;--ease-out:cubic-bezier(0,0,.2,1);--blur-xs:4px;--default-transition-duration:.15s;--default-transition-timing-function:cubic-bezier(.4,0,.2,1);--default-font-family:var(--font-sans);--default-mono-font-family:var(--font-mono)}}@layer base{*,:after,:before,::backdrop{box-sizing:border-box;border:0 solid;margin:0;padding:0}::file-selector-button{box-sizing:border-box;border:0 solid;margin:0;padding:0}html,:host{-webkit-text-size-adjust:100%;tab-size:4;line-height:1.5;font-family:var(--default-font-family,ui-sans-serif,system-ui,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji");font-feature-settings:var(--default-font-feature-settings,normal);font-variation-settings:var(--default-font-variation-settings,normal);-webkit-tap-highlight-color:transparent}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;-webkit-text-decoration:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,samp,pre{font-family:var(--default-mono-font-family,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace);font-feature-settings:var(--default-mono-font-feature-settings,normal);font-variation-settings:var(--default-mono-font-variation-settings,normal);font-size:1em}small{font-size:80%}sub,sup{vertical-align:baseline;font-size:75%;line-height:0;position:relative}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}:-moz-focusring{outline:auto}progress{vertical-align:baseline}summary{display:list-item}ol,ul,menu{list-style:none}img,svg,video,canvas,audio,iframe,embed,object{vertical-align:middle;display:block}img,video{max-width:100%;height:auto}button,input,select,optgroup,textarea{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}::file-selector-button{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}:where(select:is([multiple],[size])) optgroup{font-weight:bolder}:where(select:is([multiple],[size])) optgroup option{padding-inline-start:20px}::file-selector-button{margin-inline-end:4px}::placeholder{opacity:1}@supports (not ((-webkit-appearance:-apple-pay-button))) or (contain-intrinsic-size:1px){::placeholder{color:currentColor}@supports (color:color-mix(in lab,red,red)){::placeholder{color:color-mix(in oklab,currentcolor 50%,transparent)}}}textarea{resize:vertical}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-date-and-time-value{min-height:1lh;text-align:inherit}::-webkit-datetime-edit{display:inline-flex}::-webkit-datetime-edit-fields-wrapper{padding:0}::-webkit-datetime-edit{padding-block:0}::-webkit-datetime-edit-year-field{padding-block:0}::-webkit-datetime-edit-month-field{padding-block:0}::-webkit-datetime-edit-day-field{padding-block:0}::-webkit-datetime-edit-hour-field{padding-block:0}::-webkit-datetime-edit-minute-field{padding-block:0}::-webkit-datetime-edit-second-field{padding-block:0}::-webkit-datetime-edit-millisecond-field{padding-block:0}::-webkit-datetime-edit-meridiem-field{padding-block:0}::-webkit-calendar-picker-indicator{line-height:1}:-moz-ui-invalid{box-shadow:none}button,input:where([type=button],[type=reset],[type=submit]){appearance:button}::file-selector-button{appearance:button}::-webkit-inner-spin-button{height:auto}::-webkit-outer-spin-button{height:auto}[hidden]:where(:not([hidden=until-found])){display:none!important}}@layer components;@layer utilities{.pointer-events-auto{pointer-events:auto}.pointer-events-none{pointer-events:none}.visible{visibility:visible}.absolute{position:absolute}.relative{position:relative}.inset-0{inset:calc(var(--spacing)*0)}.-top-1\.5{top:calc(var(--spacing)*-1.5)}.-top-6{top:calc(var(--spacing)*-6)}.top-1{top:calc(var(--spacing)*1)}.top-1\/2{top:50%}.-right-1\.5{right:calc(var(--spacing)*-1.5)}.-bottom-1\.5{bottom:calc(var(--spacing)*-1.5)}.-left-1\.5{left:calc(var(--spacing)*-1.5)}.left-0{left:calc(var(--spacing)*0)}.left-1{left:calc(var(--spacing)*1)}.left-1\/2{left:50%}.z-10{z-index:10}.z-20{z-index:20}.z-50{z-index:50}.z-99{z-index:99}.box-border{box-sizing:border-box}.flex{display:flex}.h-3{height:calc(var(--spacing)*3)}.h-full{height:100%}.h-screen{height:100vh}.w-3{width:calc(var(--spacing)*3)}.w-full{width:100%}.w-screen{width:100vw}.-translate-x-1\/2{--tw-translate-x: -50% ;translate:var(--tw-translate-x)var(--tw-translate-y)}.-translate-y-1\/2{--tw-translate-y: -50% ;translate:var(--tw-translate-x)var(--tw-translate-y)}.transform{transform:var(--tw-rotate-x,)var(--tw-rotate-y,)var(--tw-rotate-z,)var(--tw-skew-x,)var(--tw-skew-y,)}.cursor-e-resize{cursor:e-resize}.cursor-move{cursor:move}.cursor-n-resize{cursor:n-resize}.cursor-ne-resize{cursor:ne-resize}.cursor-nw-resize{cursor:nw-resize}.cursor-s-resize{cursor:s-resize}.cursor-se-resize{cursor:se-resize}.cursor-sw-resize{cursor:sw-resize}.cursor-w-resize{cursor:w-resize}.overflow-hidden{overflow:hidden}.rounded{border-radius:.25rem}.rounded-full{border-radius:3.40282e38px}.rounded-xl{border-radius:var(--radius-xl)}.border{border-style:var(--tw-border-style);border-width:1px}.border-dashed{--tw-border-style:dashed;border-style:dashed}.border-\[\#2196F3\]{border-color:#2196f3}.border-white\/10{border-color:#ffffff1a}@supports (color:color-mix(in lab,red,red)){.border-white\/10{border-color:color-mix(in oklab,var(--color-white)10%,transparent)}}.bg-\[\#2196F3\]{background-color:#2196f3}.bg-\[\#2196F3\]\/10{background-color:#2196f31a}.bg-black\/75{background-color:#000000bf}@supports (color:color-mix(in lab,red,red)){.bg-black\/75{background-color:color-mix(in oklab,var(--color-black)75%,transparent)}}.bg-white{background-color:var(--color-white)}.px-2{padding-inline:calc(var(--spacing)*2)}.px-4{padding-inline:calc(var(--spacing)*4)}.py-0\.5{padding-block:calc(var(--spacing)*.5)}.py-2{padding-block:calc(var(--spacing)*2)}.font-mono{font-family:var(--font-mono)}.text-xs{font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height))}.text-\[10px\]{font-size:10px}.whitespace-nowrap{white-space:nowrap}.text-\[\#FF5252\]{color:#ff5252}.text-white{color:var(--color-white)}.shadow-lg{--tw-shadow:0 10px 15px -3px var(--tw-shadow-color,#0000001a),0 4px 6px -4px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.shadow-md{--tw-shadow:0 4px 6px -1px var(--tw-shadow-color,#0000001a),0 2px 4px -2px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.outline{outline-style:var(--tw-outline-style);outline-width:1px}.backdrop-blur-xs{--tw-backdrop-blur:blur(var(--blur-xs));-webkit-backdrop-filter:var(--tw-backdrop-blur,)var(--tw-backdrop-brightness,)var(--tw-backdrop-contrast,)var(--tw-backdrop-grayscale,)var(--tw-backdrop-hue-rotate,)var(--tw-backdrop-invert,)var(--tw-backdrop-opacity,)var(--tw-backdrop-saturate,)var(--tw-backdrop-sepia,);backdrop-filter:var(--tw-backdrop-blur,)var(--tw-backdrop-brightness,)var(--tw-backdrop-contrast,)var(--tw-backdrop-grayscale,)var(--tw-backdrop-hue-rotate,)var(--tw-backdrop-invert,)var(--tw-backdrop-opacity,)var(--tw-backdrop-saturate,)var(--tw-backdrop-sepia,)}.backdrop-filter{-webkit-backdrop-filter:var(--tw-backdrop-blur,)var(--tw-backdrop-brightness,)var(--tw-backdrop-contrast,)var(--tw-backdrop-grayscale,)var(--tw-backdrop-hue-rotate,)var(--tw-backdrop-invert,)var(--tw-backdrop-opacity,)var(--tw-backdrop-saturate,)var(--tw-backdrop-sepia,);backdrop-filter:var(--tw-backdrop-blur,)var(--tw-backdrop-brightness,)var(--tw-backdrop-contrast,)var(--tw-backdrop-grayscale,)var(--tw-backdrop-hue-rotate,)var(--tw-backdrop-invert,)var(--tw-backdrop-opacity,)var(--tw-backdrop-saturate,)var(--tw-backdrop-sepia,)}.transition{transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to,opacity,box-shadow,transform,translate,scale,rotate,filter,-webkit-backdrop-filter,backdrop-filter,display,content-visibility,overlay,pointer-events;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.transition-all{transition-property:all;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.duration-100{--tw-duration:.1s;transition-duration:.1s}.duration-300{--tw-duration:.3s;transition-duration:.3s}.ease-\[cubic-bezier\(0\.23\,1\,0\.32\,1\)\]{--tw-ease:cubic-bezier(.23,1,.32,1);transition-timing-function:cubic-bezier(.23,1,.32,1)}.ease-out{--tw-ease:var(--ease-out);transition-timing-function:var(--ease-out)}.select-none{-webkit-user-select:none;user-select:none}}html,body{background:0 0;width:100vw;height:100vh;margin:0;padding:0;overflow:hidden}@property --tw-translate-x{syntax:"*";inherits:false;initial-value:0}@property --tw-translate-y{syntax:"*";inherits:false;initial-value:0}@property --tw-translate-z{syntax:"*";inherits:false;initial-value:0}@property --tw-rotate-x{syntax:"*";inherits:false}@property --tw-rotate-y{syntax:"*";inherits:false}@property --tw-rotate-z{syntax:"*";inherits:false}@property --tw-skew-x{syntax:"*";inherits:false}@property --tw-skew-y{syntax:"*";inherits:false}@property --tw-border-style{syntax:"*";inherits:false;initial-value:solid}@property --tw-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-shadow-color{syntax:"*";inherits:false}@property --tw-shadow-alpha{syntax:"<percentage>";inherits:false;initial-value:100%}@property --tw-inset-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-inset-shadow-color{syntax:"*";inherits:false}@property --tw-inset-shadow-alpha{syntax:"<percentage>";inherits:false;initial-value:100%}@property --tw-ring-color{syntax:"*";inherits:false}@property --tw-ring-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-inset-ring-color{syntax:"*";inherits:false}@property --tw-inset-ring-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-ring-inset{syntax:"*";inherits:false}@property --tw-ring-offset-width{syntax:"<length>";inherits:false;initial-value:0}@property --tw-ring-offset-color{syntax:"*";inherits:false;initial-value:#fff}@property --tw-ring-offset-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-outline-style{syntax:"*";inherits:false;initial-value:solid}@property --tw-backdrop-blur{syntax:"*";inherits:false}@property --tw-backdrop-brightness{syntax:"*";inherits:false}@property --tw-backdrop-contrast{syntax:"*";inherits:false}@property --tw-backdrop-grayscale{syntax:"*";inherits:false}@property --tw-backdrop-hue-rotate{syntax:"*";inherits:false}@property --tw-backdrop-invert{syntax:"*";inherits:false}@property --tw-backdrop-opacity{syntax:"*";inherits:false}@property --tw-backdrop-saturate{syntax:"*";inherits:false}@property --tw-backdrop-sepia{syntax:"*";inherits:false}@property --tw-duration{syntax:"*";inherits:false}@property --tw-ease{syntax:"*";inherits:false}
1
+ .frozen-screens-layer[data-v-0b001d45]{position:absolute;top:0;left:0;width:100%;height:100%;z-index:1;pointer-events:none}.frozen-screen[data-v-0b001d45]{position:absolute}.frozen-screen img[data-v-0b001d45]{width:100%;height:100%;object-fit:fill;display:block}.action-toolbar-container[data-v-ebf36e2e]{position:absolute;display:flex;flex-direction:column;align-items:flex-end;z-index:100;pointer-events:none}.action-toolbar-main[data-v-ebf36e2e]{display:flex;padding:4px;background:#1e1e1ef2;border-radius:8px;box-shadow:0 4px 20px #0006;backdrop-filter:blur(12px);border:1px solid rgb(255 255 255 / 10%);pointer-events:auto}.btn-group[data-v-ebf36e2e]{display:flex;align-items:center}.btn[data-v-ebf36e2e]{width:28px;height:28px;border:none;cursor:pointer;display:flex;align-items:center;justify-content:center;color:#fff;transition:all .15s ease;background:transparent;border-radius:4px;margin:0 1px}.btn[data-v-ebf36e2e]:hover{background:#ffffff26}.btn[data-v-ebf36e2e]:active{transform:scale(.95)}.btn.active[data-v-ebf36e2e]{background:#ffffff40;color:#adf}.divider[data-v-ebf36e2e]{width:1px;height:16px;background:#fff3;margin:0 4px}.btn-save[data-v-ebf36e2e]:hover{background:#4caf5099}.btn-copy[data-v-ebf36e2e]:hover{background:#2196f399}.btn-cancel[data-v-ebf36e2e]:hover{background:#f4433699}.size-settings-bar[data-v-ebf36e2e]{margin-top:4px;padding:4px 8px;background:#1e1e1ef2;border-radius:8px;box-shadow:0 4px 20px #0006;backdrop-filter:blur(12px);border:1px solid rgb(255 255 255 / 10%);pointer-events:auto;display:flex;align-items:center;gap:8px;height:36px;box-sizing:border-box}.size-inputs[data-v-ebf36e2e]{display:flex;align-items:center;gap:4px;font-family:monospace;font-size:13px;color:#eee}.size-input[data-v-ebf36e2e]{background:#0000004d;border:1px solid rgb(255 255 255 / 20%);border-radius:4px;color:#fff;padding:2px 4px;text-align:center;outline:none;font-family:inherit;font-size:inherit;min-width:4ch;height:22px;box-sizing:border-box}.size-input[data-v-ebf36e2e]:focus{border-color:#2196f3;background:#0000007f}.size-input[data-v-ebf36e2e]::-webkit-outer-spin-button,.size-input[data-v-ebf36e2e]::-webkit-inner-spin-button{appearance:none;margin:0}.size-separator[data-v-ebf36e2e]{color:#ffffff7f;font-size:12px}.size-unit[data-v-ebf36e2e]{color:#ffffff7f;font-size:12px;margin-left:2px}.btn-confirm[data-v-ebf36e2e]{border:none;background:#2196f3;color:#fff;border-radius:4px;padding:2px 8px;font-size:12px;cursor:pointer;height:22px;line-height:18px;transition:background .15s;white-space:nowrap}.btn-confirm[data-v-ebf36e2e]:hover{background:#42a5f5}.btn-confirm[data-v-ebf36e2e]:active{background:#1976d2}.radius-settings[data-v-ebf36e2e]{display:flex;align-items:center;gap:8px;width:100%;color:#eee;font-size:13px}.radius-label[data-v-ebf36e2e]{font-size:12px;color:#ffffffb3;white-space:nowrap}.radius-slider[data-v-ebf36e2e]{flex:1;height:4px;border-radius:2px;background:#ffffff4d;outline:none;cursor:pointer;accent-color:#2196f3;width:100px}.magnifier[data-v-37869409]{position:absolute;border-radius:4px;overflow:hidden;box-shadow:0 4px 12px #0000007f,0 0 0 1px #fff3;pointer-events:none;z-index:9999;background:#000;border:1px solid rgb(255 255 255 / 50%)}@layer properties{@supports (((-webkit-hyphens:none)) and (not (margin-trim:inline))) or ((-moz-orient:inline) and (not (color:rgb(from red r g b)))){*,:before,:after,::backdrop{--tw-translate-x:0;--tw-translate-y:0;--tw-translate-z:0;--tw-rotate-x:initial;--tw-rotate-y:initial;--tw-rotate-z:initial;--tw-skew-x:initial;--tw-skew-y:initial;--tw-border-style:solid;--tw-shadow:0 0 #0000;--tw-shadow-color:initial;--tw-shadow-alpha:100%;--tw-inset-shadow:0 0 #0000;--tw-inset-shadow-color:initial;--tw-inset-shadow-alpha:100%;--tw-ring-color:initial;--tw-ring-shadow:0 0 #0000;--tw-inset-ring-color:initial;--tw-inset-ring-shadow:0 0 #0000;--tw-ring-inset:initial;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-offset-shadow:0 0 #0000;--tw-outline-style:solid;--tw-backdrop-blur:initial;--tw-backdrop-brightness:initial;--tw-backdrop-contrast:initial;--tw-backdrop-grayscale:initial;--tw-backdrop-hue-rotate:initial;--tw-backdrop-invert:initial;--tw-backdrop-opacity:initial;--tw-backdrop-saturate:initial;--tw-backdrop-sepia:initial;--tw-duration:initial;--tw-ease:initial}}}@layer theme{:root,:host{--font-sans:ui-sans-serif,system-ui,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji";--font-mono:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace;--color-black:#000;--color-white:#fff;--spacing:.25rem;--text-xs:.75rem;--text-xs--line-height:calc(1/.75);--radius-xl:.75rem;--ease-out:cubic-bezier(0,0,.2,1);--blur-xs:4px;--default-transition-duration:.15s;--default-transition-timing-function:cubic-bezier(.4,0,.2,1);--default-font-family:var(--font-sans);--default-mono-font-family:var(--font-mono)}}@layer base{*,:after,:before,::backdrop{box-sizing:border-box;border:0 solid;margin:0;padding:0}::file-selector-button{box-sizing:border-box;border:0 solid;margin:0;padding:0}html,:host{-webkit-text-size-adjust:100%;tab-size:4;line-height:1.5;font-family:var(--default-font-family,ui-sans-serif,system-ui,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji");font-feature-settings:var(--default-font-feature-settings,normal);font-variation-settings:var(--default-font-variation-settings,normal);-webkit-tap-highlight-color:transparent}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;-webkit-text-decoration:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,samp,pre{font-family:var(--default-mono-font-family,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace);font-feature-settings:var(--default-mono-font-feature-settings,normal);font-variation-settings:var(--default-mono-font-variation-settings,normal);font-size:1em}small{font-size:80%}sub,sup{vertical-align:baseline;font-size:75%;line-height:0;position:relative}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}:-moz-focusring{outline:auto}progress{vertical-align:baseline}summary{display:list-item}ol,ul,menu{list-style:none}img,svg,video,canvas,audio,iframe,embed,object{vertical-align:middle;display:block}img,video{max-width:100%;height:auto}button,input,select,optgroup,textarea{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}::file-selector-button{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}:where(select:is([multiple],[size])) optgroup{font-weight:bolder}:where(select:is([multiple],[size])) optgroup option{padding-inline-start:20px}::file-selector-button{margin-inline-end:4px}::placeholder{opacity:1}@supports (not ((-webkit-appearance:-apple-pay-button))) or (contain-intrinsic-size:1px){::placeholder{color:currentColor}@supports (color:color-mix(in lab,red,red)){::placeholder{color:color-mix(in oklab,currentcolor 50%,transparent)}}}textarea{resize:vertical}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-date-and-time-value{min-height:1lh;text-align:inherit}::-webkit-datetime-edit{display:inline-flex}::-webkit-datetime-edit-fields-wrapper{padding:0}::-webkit-datetime-edit{padding-block:0}::-webkit-datetime-edit-year-field{padding-block:0}::-webkit-datetime-edit-month-field{padding-block:0}::-webkit-datetime-edit-day-field{padding-block:0}::-webkit-datetime-edit-hour-field{padding-block:0}::-webkit-datetime-edit-minute-field{padding-block:0}::-webkit-datetime-edit-second-field{padding-block:0}::-webkit-datetime-edit-millisecond-field{padding-block:0}::-webkit-datetime-edit-meridiem-field{padding-block:0}::-webkit-calendar-picker-indicator{line-height:1}:-moz-ui-invalid{box-shadow:none}button,input:where([type=button],[type=reset],[type=submit]){appearance:button}::file-selector-button{appearance:button}::-webkit-inner-spin-button{height:auto}::-webkit-outer-spin-button{height:auto}[hidden]:where(:not([hidden=until-found])){display:none!important}}@layer components;@layer utilities{.pointer-events-auto{pointer-events:auto}.pointer-events-none{pointer-events:none}.visible{visibility:visible}.absolute{position:absolute}.relative{position:relative}.inset-0{inset:calc(var(--spacing)*0)}.-top-1\.5{top:calc(var(--spacing)*-1.5)}.-top-6{top:calc(var(--spacing)*-6)}.top-1{top:calc(var(--spacing)*1)}.top-1\/2{top:50%}.-right-1\.5{right:calc(var(--spacing)*-1.5)}.-bottom-1\.5{bottom:calc(var(--spacing)*-1.5)}.-left-1\.5{left:calc(var(--spacing)*-1.5)}.left-0{left:calc(var(--spacing)*0)}.left-1{left:calc(var(--spacing)*1)}.left-1\/2{left:50%}.z-10{z-index:10}.z-20{z-index:20}.z-50{z-index:50}.z-99{z-index:99}.box-border{box-sizing:border-box}.flex{display:flex}.h-3{height:calc(var(--spacing)*3)}.h-full{height:100%}.h-screen{height:100vh}.w-3{width:calc(var(--spacing)*3)}.w-full{width:100%}.w-screen{width:100vw}.-translate-x-1\/2{--tw-translate-x: -50% ;translate:var(--tw-translate-x)var(--tw-translate-y)}.-translate-y-1\/2{--tw-translate-y: -50% ;translate:var(--tw-translate-x)var(--tw-translate-y)}.transform{transform:var(--tw-rotate-x,)var(--tw-rotate-y,)var(--tw-rotate-z,)var(--tw-skew-x,)var(--tw-skew-y,)}.cursor-e-resize{cursor:e-resize}.cursor-move{cursor:move}.cursor-n-resize{cursor:n-resize}.cursor-ne-resize{cursor:ne-resize}.cursor-nw-resize{cursor:nw-resize}.cursor-s-resize{cursor:s-resize}.cursor-se-resize{cursor:se-resize}.cursor-sw-resize{cursor:sw-resize}.cursor-w-resize{cursor:w-resize}.overflow-hidden{overflow:hidden}.rounded{border-radius:.25rem}.rounded-full{border-radius:3.40282e38px}.rounded-xl{border-radius:var(--radius-xl)}.border{border-style:var(--tw-border-style);border-width:1px}.border-dashed{--tw-border-style:dashed;border-style:dashed}.border-\[\#2196F3\]{border-color:#2196f3}.border-white\/10{border-color:#ffffff1a}@supports (color:color-mix(in lab,red,red)){.border-white\/10{border-color:color-mix(in oklab,var(--color-white)10%,transparent)}}.bg-\[\#2196F3\]{background-color:#2196f3}.bg-\[\#2196F3\]\/10{background-color:#2196f31a}.bg-black\/75{background-color:#000000bf}@supports (color:color-mix(in lab,red,red)){.bg-black\/75{background-color:color-mix(in oklab,var(--color-black)75%,transparent)}}.bg-white{background-color:var(--color-white)}.px-2{padding-inline:calc(var(--spacing)*2)}.px-4{padding-inline:calc(var(--spacing)*4)}.py-0\.5{padding-block:calc(var(--spacing)*.5)}.py-2{padding-block:calc(var(--spacing)*2)}.font-mono{font-family:var(--font-mono)}.text-xs{font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height))}.text-\[10px\]{font-size:10px}.whitespace-nowrap{white-space:nowrap}.text-\[\#FF5252\]{color:#ff5252}.text-white{color:var(--color-white)}.shadow-lg{--tw-shadow:0 10px 15px -3px var(--tw-shadow-color,#0000001a),0 4px 6px -4px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.shadow-md{--tw-shadow:0 4px 6px -1px var(--tw-shadow-color,#0000001a),0 2px 4px -2px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.outline{outline-style:var(--tw-outline-style);outline-width:1px}.backdrop-blur-xs{--tw-backdrop-blur:blur(var(--blur-xs));-webkit-backdrop-filter:var(--tw-backdrop-blur,)var(--tw-backdrop-brightness,)var(--tw-backdrop-contrast,)var(--tw-backdrop-grayscale,)var(--tw-backdrop-hue-rotate,)var(--tw-backdrop-invert,)var(--tw-backdrop-opacity,)var(--tw-backdrop-saturate,)var(--tw-backdrop-sepia,);backdrop-filter:var(--tw-backdrop-blur,)var(--tw-backdrop-brightness,)var(--tw-backdrop-contrast,)var(--tw-backdrop-grayscale,)var(--tw-backdrop-hue-rotate,)var(--tw-backdrop-invert,)var(--tw-backdrop-opacity,)var(--tw-backdrop-saturate,)var(--tw-backdrop-sepia,)}.backdrop-filter{-webkit-backdrop-filter:var(--tw-backdrop-blur,)var(--tw-backdrop-brightness,)var(--tw-backdrop-contrast,)var(--tw-backdrop-grayscale,)var(--tw-backdrop-hue-rotate,)var(--tw-backdrop-invert,)var(--tw-backdrop-opacity,)var(--tw-backdrop-saturate,)var(--tw-backdrop-sepia,);backdrop-filter:var(--tw-backdrop-blur,)var(--tw-backdrop-brightness,)var(--tw-backdrop-contrast,)var(--tw-backdrop-grayscale,)var(--tw-backdrop-hue-rotate,)var(--tw-backdrop-invert,)var(--tw-backdrop-opacity,)var(--tw-backdrop-saturate,)var(--tw-backdrop-sepia,)}.transition{transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to,opacity,box-shadow,transform,translate,scale,rotate,filter,-webkit-backdrop-filter,backdrop-filter,display,content-visibility,overlay,pointer-events;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.transition-all{transition-property:all;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.duration-100{--tw-duration:.1s;transition-duration:.1s}.duration-300{--tw-duration:.3s;transition-duration:.3s}.ease-\[cubic-bezier\(0\.23\,1\,0\.32\,1\)\]{--tw-ease:cubic-bezier(.23,1,.32,1);transition-timing-function:cubic-bezier(.23,1,.32,1)}.ease-out{--tw-ease:var(--ease-out);transition-timing-function:var(--ease-out)}.select-none{-webkit-user-select:none;user-select:none}}html,body{background:0 0;width:100vw;height:100vh;margin:0;padding:0;overflow:hidden}@property --tw-translate-x{syntax:"*";inherits:false;initial-value:0}@property --tw-translate-y{syntax:"*";inherits:false;initial-value:0}@property --tw-translate-z{syntax:"*";inherits:false;initial-value:0}@property --tw-rotate-x{syntax:"*";inherits:false}@property --tw-rotate-y{syntax:"*";inherits:false}@property --tw-rotate-z{syntax:"*";inherits:false}@property --tw-skew-x{syntax:"*";inherits:false}@property --tw-skew-y{syntax:"*";inherits:false}@property --tw-border-style{syntax:"*";inherits:false;initial-value:solid}@property --tw-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-shadow-color{syntax:"*";inherits:false}@property --tw-shadow-alpha{syntax:"<percentage>";inherits:false;initial-value:100%}@property --tw-inset-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-inset-shadow-color{syntax:"*";inherits:false}@property --tw-inset-shadow-alpha{syntax:"<percentage>";inherits:false;initial-value:100%}@property --tw-ring-color{syntax:"*";inherits:false}@property --tw-ring-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-inset-ring-color{syntax:"*";inherits:false}@property --tw-inset-ring-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-ring-inset{syntax:"*";inherits:false}@property --tw-ring-offset-width{syntax:"<length>";inherits:false;initial-value:0}@property --tw-ring-offset-color{syntax:"*";inherits:false;initial-value:#fff}@property --tw-ring-offset-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-outline-style{syntax:"*";inherits:false;initial-value:solid}@property --tw-backdrop-blur{syntax:"*";inherits:false}@property --tw-backdrop-brightness{syntax:"*";inherits:false}@property --tw-backdrop-contrast{syntax:"*";inherits:false}@property --tw-backdrop-grayscale{syntax:"*";inherits:false}@property --tw-backdrop-hue-rotate{syntax:"*";inherits:false}@property --tw-backdrop-invert{syntax:"*";inherits:false}@property --tw-backdrop-opacity{syntax:"*";inherits:false}@property --tw-backdrop-saturate{syntax:"*";inherits:false}@property --tw-backdrop-sepia{syntax:"*";inherits:false}@property --tw-duration{syntax:"*";inherits:false}@property --tw-ease{syntax:"*";inherits:false}