From 9a5ff5c0a21c57c2c2c9b224b3925587d7c4dd3c Mon Sep 17 00:00:00 2001
From: webadderall <131426131+webadderall@users.noreply.github.com>
Date: Thu, 16 Apr 2026 17:50:09 +1000
Subject: [PATCH 1/4] =?UTF-8?q?feat:=20ui=20overhaul=20=E2=80=94=20Phospho?=
=?UTF-8?q?r=20icons,=20redesigned=20launcher,=20editor=20layout,=20and=20?=
=?UTF-8?q?component=20refresh?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
- Replace all lucide-react imports with @phosphor-icons/react across every
component (LaunchWindow, AnnotationSettingsPanel, ExtensionManager,
ExtensionIcon, VideoPlayback, AnnotationOverlay, CropControl, TutorialHelp,
PlaybackControls, all shadcn ui/ primitives, etc.)
- ExtensionIcon: new Phosphor-based resolver with Lucide name alias map for
backwards-compatible extension manifests
- LaunchWindow / SourceSelector: full redesign — new icon rail layout,
updated backgrounds, improved source selection UX, audio/video controls,
countdown overlay
- VideoPlayback: overhaul playback controls and preview panel layout
- Timeline items: border-radius 16px→8px, cyan→blue unification, pill height
85% for breathing room, resize handle vertical centering
- shadcn ui/: refresh accordion, dialog, dropdown, select, slider, switch,
tabs, toggle, popover, button, card, input, label, sonner, content-clamp,
audio-level-meter
- App.tsx / index.css / App.css / tailwind.config: global style updates
- types.ts, captionLayout.ts, captionStyle.ts, webcamOverlay.ts: data model
updates to support new timeline and export features
- package.json: add @phosphor-icons/react, bump dependencies
- scripts/, .eslintrc, biome, tsconfig, vite.config: tooling alignment
- extension-sources: update built-in extension bundles
---
.eslintrc.cjs | 34 +-
README.md | 20 +-
README.zh-CN.md | 14 +-
biome.json | 30 +-
package-lock.json | 19 +-
package.json | 1 +
postcss.config.cjs | 10 +-
scripts/benchmark-export-queues.mjs | 94 +-
scripts/build-native-helpers.mjs | 12 +-
scripts/build-whisper-runtime.mjs | 11 +-
scripts/build-windows-capture.mjs | 218 +-
scripts/i18n-check.mjs | 134 +-
scripts/native-helper-manifest.mjs | 21 +-
scripts/postinstall.mjs | 8 +-
src/App.css | 83 +-
src/App.tsx | 184 +-
src/components/launch/LaunchWindow.tsx | 521 +-
.../launch/SourceSelector.module.css | 73 +-
src/components/launch/SourceSelector.tsx | 483 +-
src/components/launch/UpdateToastWindow.tsx | 72 +-
src/components/ui/accordion.tsx | 87 +-
src/components/ui/audio-level-meter.tsx | 56 +-
src/components/ui/button.tsx | 95 +-
src/components/ui/card.tsx | 132 +-
src/components/ui/content-clamp.tsx | 166 +-
src/components/ui/dialog.tsx | 184 +-
src/components/ui/dropdown-menu.tsx | 320 +-
src/components/ui/input.tsx | 46 +-
src/components/ui/label.tsx | 42 +-
src/components/ui/popover.tsx | 91 +-
src/components/ui/select.tsx | 256 +-
src/components/ui/slider.tsx | 40 +-
src/components/ui/sonner.tsx | 37 +-
src/components/ui/switch.tsx | 51 +-
src/components/ui/tabs.tsx | 83 +-
src/components/ui/toggle-group.tsx | 104 +-
src/components/ui/toggle.tsx | 73 +-
.../video-editor/AddCustomFontDialog.tsx | 363 +-
.../video-editor/AnnotationOverlay.tsx | 436 +-
.../video-editor/AnnotationSettingsPanel.tsx | 1388 ++---
src/components/video-editor/ArrowSvgs.tsx | 325 +-
src/components/video-editor/CropControl.tsx | 465 +-
.../video-editor/ExportSettingsMenu.tsx | 221 +-
src/components/video-editor/ExtensionIcon.tsx | 199 +-
.../video-editor/ExtensionManager.tsx | 1943 +++----
.../video-editor/FormatSelector.tsx | 6 +-
.../video-editor/KeyboardShortcutsHelp.tsx | 14 +-
.../video-editor/PlaybackControls.tsx | 16 +-
.../video-editor/ProjectBrowserDialog.tsx | 26 +-
.../video-editor/ShortcutsConfigDialog.tsx | 473 +-
src/components/video-editor/SliderControl.tsx | 100 +-
src/components/video-editor/TutorialHelp.tsx | 126 +-
src/components/video-editor/VideoPlayback.tsx | 4772 +++++++++--------
src/components/video-editor/audio.test.ts | 77 +-
src/components/video-editor/captionLayout.ts | 820 +--
src/components/video-editor/captionStyle.ts | 20 +-
src/components/video-editor/index.ts | 11 +-
src/components/video-editor/types.ts | 28 +-
src/components/video-editor/webcamOverlay.ts | 13 +-
src/index.css | 424 +-
src/lib/assetPath.ts | 279 +-
src/lib/customFonts.ts | 365 +-
src/lib/geometry/squircle.ts | 51 +-
src/lib/mediaTiming.test.ts | 194 +-
src/lib/mediaTiming.ts | 163 +-
src/lib/shortcuts.ts | 181 +-
src/lib/utils.ts | 7 +-
src/lib/wallpapers.ts | 295 +-
src/main.tsx | 31 +-
src/utils/aspectRatioUtils.ts | 130 +-
src/utils/platformUtils.ts | 56 +-
tailwind.config.cjs | 142 +-
tsconfig.json | 1 +
tsconfig.node.json | 23 +-
vite.config.ts | 145 +-
75 files changed, 9478 insertions(+), 8756 deletions(-)
diff --git a/.eslintrc.cjs b/.eslintrc.cjs
index dc6ce092b..f63fe7dcb 100644
--- a/.eslintrc.cjs
+++ b/.eslintrc.cjs
@@ -1,19 +1,15 @@
-module.exports = {
- root: true,
- env: { browser: true, es2020: true },
- extends: [
- 'eslint:recommended',
- 'plugin:@typescript-eslint/recommended',
- 'plugin:react-hooks/recommended',
- ],
- ignorePatterns: ['dist', '.eslintrc.cjs'],
- parser: '@typescript-eslint/parser',
- plugins: ['react-refresh'],
- rules: {
- 'react-refresh/only-export-components': [
- 'warn',
- { allowConstantExport: true },
- ],
- },
-}
-
+module.exports = {
+ root: true,
+ env: { browser: true, es2020: true },
+ extends: [
+ "eslint:recommended",
+ "plugin:@typescript-eslint/recommended",
+ "plugin:react-hooks/recommended",
+ ],
+ ignorePatterns: ["dist", ".eslintrc.cjs"],
+ parser: "@typescript-eslint/parser",
+ plugins: ["react-refresh"],
+ rules: {
+ "react-refresh/only-export-components": ["warn", { allowConstantExport: true }],
+ },
+};
diff --git a/README.md b/README.md
index 1c4ec2048..f9ae83d9f 100644
--- a/README.md
+++ b/README.md
@@ -26,7 +26,7 @@ Recordly is a desktop app for recording and editing screen captures with motion-
Recordly runs on:
-- **macOS** 13.0+
+- **macOS** 14.0+
- **Windows** 10 Build 19041+
- **Linux** on modern distros
@@ -61,6 +61,12 @@ Use drag-and-drop timeline tools for zooms, trims, speed regions, annotations, e
+## 扩展与市场
+
+Recordly 拥有一个社区驱动的扩展系统。任何人都可以构建和发布扩展来为 Recordly 添加新功能——光标点击音效、设备边框、浏览器模拟外壳、壁纸、渲染钩子、设置面板等等。
+
+浏览并安装社区扩展:[Recordly 扩展市场](https://marketplace.recordly.dev/extensions)。
+
---
## 全部功能
@@ -234,8 +240,8 @@ xattr -rd com.apple.quarantine /Applications/Recordly.app
| 平台 | 最低版本 | 说明 |
|---|---|---|
-| **macOS** | macOS 13.0 (Ventura) | 使用 ScreenCaptureKit 音频捕获所必需。 |
-| **Windows** | Windows 10 20H1(Build 19041,2020 年 5 月) | 原生 Windows Graphics Capture(WGC)辅助程序以及最佳光标隐藏行为所必需。 |
+| **macOS** | macOS 14.0 (Sonoma) | 使用 ScreenCaptureKit 捕获音频和麦克风所必需。 |
+| **Windows** | Windows 10 20H1(Build 19041,2020 年 5 月) | 原生 Windows Graphics Capture(WGC)辅助程序及最佳光标隐藏行为所必需。 |
| **Linux** | 任意现代发行版 | 通过 Electron 捕获录制。系统音频通常需要 PipeWire。 |
> [!IMPORTANT]
@@ -305,7 +311,7 @@ Recordly 会在录制画面上渲染一个经过美化的光标叠加层,但
- 通常需要 PipeWire
**macOS**
-- 需要 macOS 13.0+ 以及基于 ScreenCaptureKit 的工作流
+- 需要 macOS 14.0+ 和基于 ScreenCaptureKit 的工作流
---
diff --git a/biome.json b/biome.json
index 98c88e9ac..798cd2720 100644
--- a/biome.json
+++ b/biome.json
@@ -44,7 +44,7 @@
"noUnsafeOptionalChaining": "error",
"noUnusedLabels": "error",
"noUnusedVariables": "error",
- "useExhaustiveDependencies": "warn",
+ "useExhaustiveDependencies": "off",
"useHookAtTopLevel": "error",
"useIsNan": "error",
"useValidForDirection": "error",
@@ -55,7 +55,7 @@
"noNamespace": "error",
"useArrayLiterals": "error",
"useAsConstAssertion": "error",
- "useComponentExportOnlyModules": "warn"
+ "useComponentExportOnlyModules": "off"
},
"suspicious": {
"noAssignInExpressions": "error",
@@ -96,6 +96,7 @@
"includes": ["**", "**/dist", "**/.eslintrc.cjs", "**", "**/dist", "**/.eslintrc.cjs"]
},
"javascript": { "formatter": { "quoteStyle": "double" } },
+ "css": { "parser": { "tailwindDirectives": true } },
"overrides": [
{
"includes": ["*.ts", "*.tsx", "*.mts", "*.cts"],
@@ -156,6 +157,31 @@
}
}
}
+ },
+ {
+ "includes": ["src/components/ui/**/*.tsx"],
+ "linter": {
+ "rules": {
+ "style": {
+ "useComponentExportOnlyModules": "off"
+ }
+ }
+ }
+ },
+ {
+ "includes": ["**/*.test.ts", "**/*.test.tsx"],
+ "linter": {
+ "rules": {
+ "correctness": {
+ "noEmptyPattern": "off",
+ "noUnusedVariables": "off"
+ },
+ "suspicious": {
+ "noEmptyBlockStatements": "off",
+ "noExplicitAny": "off"
+ }
+ }
+ }
}
],
"assist": {
diff --git a/package-lock.json b/package-lock.json
index 46d2bfe70..8367a4c0f 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -9,6 +9,7 @@
"version": "1.1.23",
"hasInstallScript": true,
"dependencies": {
+ "@phosphor-icons/react": "^2.1.10",
"capturekit": "^1.0.13",
"electron-updater": "^6.8.3",
"ffmpeg-static": "^5.3.0",
@@ -2843,6 +2844,19 @@
"node": "^12.13.0 || ^14.15.0 || >=16.0.0"
}
},
+ "node_modules/@phosphor-icons/react": {
+ "version": "2.1.10",
+ "resolved": "https://registry.npmjs.org/@phosphor-icons/react/-/react-2.1.10.tgz",
+ "integrity": "sha512-vt8Tvq8GLjheAZZYa+YG/pW7HDbov8El/MANW8pOAz4eGxrwhnbfrQZq0Cp4q8zBEu8NIhHdnr+r8thnfRSNYA==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=10"
+ },
+ "peerDependencies": {
+ "react": ">= 16.8",
+ "react-dom": ">= 16.8"
+ }
+ },
"node_modules/@pixi/color": {
"version": "7.4.3",
"resolved": "https://registry.npmjs.org/@pixi/color/-/color-7.4.3.tgz",
@@ -9046,7 +9060,6 @@
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
"integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==",
- "dev": true,
"license": "MIT"
},
"node_modules/js-yaml": {
@@ -9361,7 +9374,6 @@
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz",
"integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==",
- "dev": true,
"license": "MIT",
"dependencies": {
"js-tokens": "^3.0.0 || ^4.0.0"
@@ -11227,7 +11239,6 @@
"version": "18.3.1",
"resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz",
"integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==",
- "dev": true,
"license": "MIT",
"dependencies": {
"loose-envify": "^1.1.0"
@@ -11240,7 +11251,6 @@
"version": "18.3.1",
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz",
"integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==",
- "dev": true,
"license": "MIT",
"dependencies": {
"loose-envify": "^1.1.0",
@@ -11948,7 +11958,6 @@
"version": "0.23.2",
"resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.2.tgz",
"integrity": "sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==",
- "dev": true,
"license": "MIT",
"dependencies": {
"loose-envify": "^1.1.0"
diff --git a/package.json b/package.json
index 68af12b76..d9e99cb95 100644
--- a/package.json
+++ b/package.json
@@ -37,6 +37,7 @@
"test:watch": "vitest"
},
"dependencies": {
+ "@phosphor-icons/react": "^2.1.10",
"capturekit": "^1.0.13",
"electron-updater": "^6.8.3",
"ffmpeg-static": "^5.3.0",
diff --git a/postcss.config.cjs b/postcss.config.cjs
index 33ad091d2..e873f1a4f 100644
--- a/postcss.config.cjs
+++ b/postcss.config.cjs
@@ -1,6 +1,6 @@
module.exports = {
- plugins: {
- tailwindcss: {},
- autoprefixer: {},
- },
-}
+ plugins: {
+ tailwindcss: {},
+ autoprefixer: {},
+ },
+};
diff --git a/scripts/benchmark-export-queues.mjs b/scripts/benchmark-export-queues.mjs
index db6902c0f..3028ab12d 100644
--- a/scripts/benchmark-export-queues.mjs
+++ b/scripts/benchmark-export-queues.mjs
@@ -17,8 +17,14 @@ const rendererEntry = path.join(repoRoot, "dist", "index.html");
const width = parseEvenInteger(process.env.RECORDLY_BENCH_EXPORT_WIDTH ?? "1280", "Width");
const height = parseEvenInteger(process.env.RECORDLY_BENCH_EXPORT_HEIGHT ?? "720", "Height");
const frameRate = parsePositiveInteger(process.env.RECORDLY_BENCH_EXPORT_FPS ?? "60", "Frame rate");
-const durationSeconds = parsePositiveInteger(process.env.RECORDLY_BENCH_EXPORT_DURATION ?? "15", "Duration");
-const timeoutMs = parsePositiveInteger(process.env.RECORDLY_BENCH_EXPORT_TIMEOUT_MS ?? "180000", "Timeout");
+const durationSeconds = parsePositiveInteger(
+ process.env.RECORDLY_BENCH_EXPORT_DURATION ?? "15",
+ "Duration",
+);
+const timeoutMs = parsePositiveInteger(
+ process.env.RECORDLY_BENCH_EXPORT_TIMEOUT_MS ?? "180000",
+ "Timeout",
+);
const runsPerVariant = parsePositiveInteger(process.env.RECORDLY_BENCH_EXPORT_RUNS ?? "2", "Runs");
const useNativeExport = process.env.RECORDLY_BENCH_EXPORT_USE_NATIVE === "1";
const useWebcamOverlay = process.env.RECORDLY_BENCH_EXPORT_ENABLE_WEBCAM === "1";
@@ -39,9 +45,7 @@ const webcamHeight = parseEvenInteger(
const webcamShadowIntensity = parseExportShadowIntensity(
process.env.RECORDLY_BENCH_EXPORT_WEBCAM_SHADOW ?? null,
);
-const webcamSize = parseExportWebcamSize(
- process.env.RECORDLY_BENCH_EXPORT_WEBCAM_SIZE ?? null,
-);
+const webcamSize = parseExportWebcamSize(process.env.RECORDLY_BENCH_EXPORT_WEBCAM_SIZE ?? null);
const MODERN_BACKEND_SWEEP = ["auto", "webcodecs", "breeze"];
const exportPipeline = parseExportPipeline(process.env.RECORDLY_BENCH_EXPORT_PIPELINE ?? null);
const exportBackend = parseExportBackend(process.env.RECORDLY_BENCH_EXPORT_BACKEND ?? null);
@@ -106,9 +110,7 @@ function parseExportBackend(rawValue) {
return rawValue;
}
- throw new Error(
- "RECORDLY_BENCH_EXPORT_BACKEND must be 'auto', 'webcodecs', or 'breeze'",
- );
+ throw new Error("RECORDLY_BENCH_EXPORT_BACKEND must be 'auto', 'webcodecs', or 'breeze'");
}
function parseExportBackendList(rawValue) {
@@ -172,9 +174,7 @@ function parseExportEncodingMode(rawValue) {
return rawValue;
}
- throw new Error(
- "RECORDLY_BENCH_EXPORT_ENCODING_MODE must be 'fast', 'balanced', or 'quality'",
- );
+ throw new Error("RECORDLY_BENCH_EXPORT_ENCODING_MODE must be 'fast', 'balanced', or 'quality'");
}
function parseExportShadowIntensity(rawValue) {
@@ -209,7 +209,10 @@ function summarizeSmokeProgress(progressSamples) {
}
const extractingSamples = progressSamples.filter(
- (sample) => sample?.phase === "extracting" && typeof sample?.currentFrame === "number" && sample.currentFrame > 1,
+ (sample) =>
+ sample?.phase === "extracting" &&
+ typeof sample?.currentFrame === "number" &&
+ sample.currentFrame > 1,
);
const fpsSource = extractingSamples.length > 0 ? extractingSamples : progressSamples;
const renderFpsSamples = fpsSource
@@ -245,7 +248,6 @@ async function ensureBuildArtifacts() {
await fs.access(rendererEntry);
}
-
async function createFixtureVideo(
ffmpegPath,
targetPath,
@@ -256,16 +258,7 @@ async function createFixtureVideo(
videoFilter = `testsrc2=size=${fixtureWidth}x${fixtureHeight}:rate=${frameRate}`,
} = {},
) {
- const args = [
- "-y",
- "-hide_banner",
- "-loglevel",
- "error",
- "-f",
- "lavfi",
- "-i",
- videoFilter,
- ];
+ const args = ["-y", "-hide_banner", "-loglevel", "error", "-f", "lavfi", "-i", videoFilter];
if (includeAudio) {
args.push(
@@ -421,9 +414,7 @@ function printTable(title, columns, rows) {
console.log(divider);
for (const row of formattedRows) {
console.log(
- `| ${row
- .map((value, columnIndex) => value.padEnd(widths[columnIndex]))
- .join(" | ")} |`,
+ `| ${row.map((value, columnIndex) => value.padEnd(widths[columnIndex])).join(" | ")} |`,
);
}
}
@@ -471,7 +462,8 @@ function calculateDelta(referenceValue, nextValue) {
return {
deltaMs: nextValue - referenceValue,
- deltaPercent: referenceValue > 0 ? ((nextValue - referenceValue) / referenceValue) * 100 : 0,
+ deltaPercent:
+ referenceValue > 0 ? ((nextValue - referenceValue) / referenceValue) * 100 : 0,
};
}
@@ -545,7 +537,10 @@ function printTimingSummaryTable(benchmarkResults) {
{ header: "Avg export", getValue: (row) => formatMs(row.averageSmokeElapsedMs) },
{ header: "Min", getValue: (row) => formatMs(row.minElapsedMs) },
{ header: "Max", getValue: (row) => formatMs(row.maxElapsedMs) },
- { header: "Avg output", getValue: (row) => formatSeconds(row.averageOutputDurationSeconds) },
+ {
+ header: "Avg output",
+ getValue: (row) => formatSeconds(row.averageOutputDurationSeconds),
+ },
{ header: "Avg size", getValue: (row) => formatMegabytes(row.averageSizeBytes) },
{ header: "Webcam", getValue: (row) => formatBoolean(row.webcamEnabled) },
],
@@ -590,7 +585,9 @@ function printBackendDetailTable(benchmarkResults) {
function buildDeltaTableRows(benchmarkResults) {
return benchmarkResults
.map((result) => {
- const baseline = result.summaries.find((summary) => summary.variant.name === "baseline");
+ const baseline = result.summaries.find(
+ (summary) => summary.variant.name === "baseline",
+ );
const tuned = result.summaries.find((summary) => summary.variant.name === "tuned");
if (!baseline || !tuned) {
return null;
@@ -623,9 +620,21 @@ function printDeltaTable(benchmarkResults) {
[
{ header: "Pipeline", getValue: (row) => row.pipeline },
{ header: "Backend", getValue: (row) => row.backend },
- { header: "Avg delta", getValue: (row) => `${formatDeltaMs(row.averageDeltaMs)} (${formatPercent(row.averageDeltaPercent)})` },
- { header: "Median delta", getValue: (row) => `${formatDeltaMs(row.medianDeltaMs)} (${formatPercent(row.medianDeltaPercent)})` },
- { header: "Export delta", getValue: (row) => `${formatDeltaMs(row.exportDeltaMs)} (${formatPercent(row.exportDeltaPercent)})` },
+ {
+ header: "Avg delta",
+ getValue: (row) =>
+ `${formatDeltaMs(row.averageDeltaMs)} (${formatPercent(row.averageDeltaPercent)})`,
+ },
+ {
+ header: "Median delta",
+ getValue: (row) =>
+ `${formatDeltaMs(row.medianDeltaMs)} (${formatPercent(row.medianDeltaPercent)})`,
+ },
+ {
+ header: "Export delta",
+ getValue: (row) =>
+ `${formatDeltaMs(row.exportDeltaMs)} (${formatPercent(row.exportDeltaPercent)})`,
+ },
],
buildDeltaTableRows(benchmarkResults),
);
@@ -659,9 +668,7 @@ async function runVariant(
...(exportShadowIntensity !== null
? { RECORDLY_SMOKE_EXPORT_SHADOW_INTENSITY: String(exportShadowIntensity) }
: {}),
- ...(webcamInputPath
- ? { RECORDLY_SMOKE_EXPORT_WEBCAM_INPUT: webcamInputPath }
- : {}),
+ ...(webcamInputPath ? { RECORDLY_SMOKE_EXPORT_WEBCAM_INPUT: webcamInputPath } : {}),
...(webcamShadowIntensity !== null
? { RECORDLY_SMOKE_EXPORT_WEBCAM_SHADOW: String(webcamShadowIntensity) }
: {}),
@@ -744,9 +751,7 @@ async function runVariant(
outputDuration,
webcamEnabled: !!webcamInputPath,
smokeExportReport: smokeExportReport?.report ?? null,
- smokeProgressSummary: summarizeSmokeProgress(
- smokeExportReport?.report?.progressSamples,
- ),
+ smokeProgressSummary: summarizeSmokeProgress(smokeExportReport?.report?.progressSamples),
};
}
@@ -877,7 +882,9 @@ async function main() {
console.log(`[benchmark-export-queues] Generating fixture video: ${inputPath}`);
await createFixtureVideo(ffmpegStatic, inputPath);
if (webcamInputPath) {
- console.log(`[benchmark-export-queues] Generating webcam fixture video: ${webcamInputPath}`);
+ console.log(
+ `[benchmark-export-queues] Generating webcam fixture video: ${webcamInputPath}`,
+ );
await createFixtureVideo(ffmpegStatic, webcamInputPath, {
fixtureWidth: webcamWidth,
fixtureHeight: webcamHeight,
@@ -945,12 +952,11 @@ async function main() {
const baseline = result.summaries[0];
const tuned = result.summaries[1];
const deltaMs = tuned.averageElapsedMs - baseline.averageElapsedMs;
- const percent = baseline.averageElapsedMs > 0 ? (deltaMs / baseline.averageElapsedMs) * 100 : 0;
+ const percent =
+ baseline.averageElapsedMs > 0 ? (deltaMs / baseline.averageElapsedMs) * 100 : 0;
const medianDeltaMs = tuned.medianElapsedMs - baseline.medianElapsedMs;
const medianPercent =
- baseline.medianElapsedMs > 0
- ? (medianDeltaMs / baseline.medianElapsedMs) * 100
- : 0;
+ baseline.medianElapsedMs > 0 ? (medianDeltaMs / baseline.medianElapsedMs) * 100 : 0;
const backendLabel = result.request.backend ?? "default";
console.log(
`[benchmark-export-queues] ${backendLabel} tuned vs baseline: ${deltaMs}ms (${percent.toFixed(1)}%)`,
@@ -969,4 +975,4 @@ main().catch((error) => {
`[benchmark-export-queues] ${error instanceof Error ? error.message : String(error)}`,
);
process.exitCode = 1;
-});
\ No newline at end of file
+});
diff --git a/scripts/build-native-helpers.mjs b/scripts/build-native-helpers.mjs
index 8cf67c4ad..778b19b87 100644
--- a/scripts/build-native-helpers.mjs
+++ b/scripts/build-native-helpers.mjs
@@ -14,11 +14,11 @@ function getTargetConfigs() {
return [
{
archTag: "darwin-arm64",
- swiftTarget: "arm64-apple-macos13.0",
+ swiftTarget: "arm64-apple-macos14.0",
},
{
archTag: "darwin-x64",
- swiftTarget: "x86_64-apple-macos13.0",
+ swiftTarget: "x86_64-apple-macos14.0",
},
];
}
@@ -26,19 +26,19 @@ function getTargetConfigs() {
const helpers = [
{
source: "ScreenCaptureKitRecorder.swift",
- output: "openscreen-screencapturekit-helper",
+ output: "recordly-screencapturekit-helper",
},
{
source: "ScreenCaptureKitWindowList.swift",
- output: "openscreen-window-list",
+ output: "recordly-window-list",
},
{
source: "SystemCursorAssets.swift",
- output: "openscreen-system-cursors",
+ output: "recordly-system-cursors",
},
{
source: "NativeCursorMonitor.swift",
- output: "openscreen-native-cursor-monitor",
+ output: "recordly-native-cursor-monitor",
},
];
diff --git a/scripts/build-whisper-runtime.mjs b/scripts/build-whisper-runtime.mjs
index 0bdf9dd84..13c944e65 100644
--- a/scripts/build-whisper-runtime.mjs
+++ b/scripts/build-whisper-runtime.mjs
@@ -101,7 +101,12 @@ function getTargetConfigs() {
archTag,
buildRoot: path.join(cacheRoot, `build-${archTag}`),
outputDir: path.join(nativeRoot, "bin", archTag),
- configureArgs: ["-G", "Visual Studio 17 2022", "-A", arch === "arm64" ? "ARM64" : "x64"],
+ configureArgs: [
+ "-G",
+ "Visual Studio 17 2022",
+ "-A",
+ arch === "arm64" ? "ARM64" : "x64",
+ ],
},
];
}
@@ -258,7 +263,9 @@ async function shouldSkipBuild(target) {
const binaryName = target.platform === "win32" ? "whisper-cli.exe" : "whisper-cli";
const binaryPath = path.join(target.outputDir, binaryName);
return (
- manifest.version === whisperVersion && manifest.arch === target.arch && existsSync(binaryPath)
+ manifest.version === whisperVersion &&
+ manifest.arch === target.arch &&
+ existsSync(binaryPath)
);
} catch {
return false;
diff --git a/scripts/build-windows-capture.mjs b/scripts/build-windows-capture.mjs
index 18f5ca36b..3ae981b28 100644
--- a/scripts/build-windows-capture.mjs
+++ b/scripts/build-windows-capture.mjs
@@ -1,11 +1,11 @@
import { execSync } from "node:child_process";
-import { copyFileSync, mkdirSync, existsSync, rmSync } from "node:fs";
+import { copyFileSync, existsSync, mkdirSync, rmSync } from "node:fs";
import path from "node:path";
import {
- formatNativeHelperManifestWarning,
- updateNativeHelperManifest,
- verifyNativeHelperManifest,
+ formatNativeHelperManifestWarning,
+ updateNativeHelperManifest,
+ verifyNativeHelperManifest,
} from "./native-helper-manifest.mjs";
const projectRoot = process.cwd();
@@ -21,81 +21,83 @@ const bundledDir = path.join(
const bundledExePath = path.join(bundledDir, "wgc-capture.exe");
const helperId = "wgc-capture";
-if (process.platform !== 'win32') {
- console.log('[build-windows-capture] Skipping native Windows capture build: host platform is not Windows.');
- process.exit(0);
+if (process.platform !== "win32") {
+ console.log(
+ "[build-windows-capture] Skipping native Windows capture build: host platform is not Windows.",
+ );
+ process.exit(0);
}
-if (!existsSync(path.join(sourceDir, 'CMakeLists.txt'))) {
- console.error('[build-windows-capture] CMakeLists.txt not found at', sourceDir);
- process.exit(1);
+if (!existsSync(path.join(sourceDir, "CMakeLists.txt"))) {
+ console.error("[build-windows-capture] CMakeLists.txt not found at", sourceDir);
+ process.exit(1);
}
function findCmake() {
- // Check PATH first
- try {
- execSync('cmake --version', { stdio: 'pipe' });
- return 'cmake';
- } catch {
- // not on PATH
- }
+ // Check PATH first
+ try {
+ execSync("cmake --version", { stdio: "pipe" });
+ return "cmake";
+ } catch {
+ // not on PATH
+ }
- const standaloneCmakePaths = [
- path.join('C:', 'Program Files', 'CMake', 'bin', 'cmake.exe'),
- path.join('C:', 'Program Files (x86)', 'CMake', 'bin', 'cmake.exe'),
- ];
- for (const cmakePath of standaloneCmakePaths) {
- if (existsSync(cmakePath)) {
- return `"${cmakePath}"`;
- }
- }
+ const standaloneCmakePaths = [
+ path.join("C:", "Program Files", "CMake", "bin", "cmake.exe"),
+ path.join("C:", "Program Files (x86)", "CMake", "bin", "cmake.exe"),
+ ];
+ for (const cmakePath of standaloneCmakePaths) {
+ if (existsSync(cmakePath)) {
+ return `"${cmakePath}"`;
+ }
+ }
- // VS 2022 bundled CMake
- const vsRoots = [
- path.join('C:', 'Program Files', 'Microsoft Visual Studio'),
- path.join('C:', 'Program Files (x86)', 'Microsoft Visual Studio'),
- ];
- const vsEditions = ['Community', 'Professional', 'Enterprise', 'BuildTools'];
- const vsVersions = ['2022', '2019'];
- for (const root of vsRoots) {
- for (const version of vsVersions) {
- for (const edition of vsEditions) {
- const cmakePath = path.join(
- root,
- version,
- edition,
- 'Common7',
- 'IDE',
- 'CommonExtensions',
- 'Microsoft',
- 'CMake',
- 'CMake',
- 'bin',
- 'cmake.exe'
- );
- if (existsSync(cmakePath)) {
- return `"${cmakePath}"`;
- }
- }
- }
- }
+ // VS 2022 bundled CMake
+ const vsRoots = [
+ path.join("C:", "Program Files", "Microsoft Visual Studio"),
+ path.join("C:", "Program Files (x86)", "Microsoft Visual Studio"),
+ ];
+ const vsEditions = ["Community", "Professional", "Enterprise", "BuildTools"];
+ const vsVersions = ["2022", "2019"];
+ for (const root of vsRoots) {
+ for (const version of vsVersions) {
+ for (const edition of vsEditions) {
+ const cmakePath = path.join(
+ root,
+ version,
+ edition,
+ "Common7",
+ "IDE",
+ "CommonExtensions",
+ "Microsoft",
+ "CMake",
+ "CMake",
+ "bin",
+ "cmake.exe",
+ );
+ if (existsSync(cmakePath)) {
+ return `"${cmakePath}"`;
+ }
+ }
+ }
+ }
- return null;
+ return null;
}
const cmake = findCmake();
if (!cmake) {
if (existsSync(bundledExePath)) {
- const verification = verifyNativeHelperManifest({
- projectRoot,
- helperId,
- sourceDir,
- binaryPath: bundledExePath,
- binaryName: "wgc-capture.exe",
- });
- if (!verification.ok) {
- console.warn(formatNativeHelperManifestWarning("build-windows-capture", verification));
- }
+ const verification = verifyNativeHelperManifest({
+ projectRoot,
+ helperId,
+ sourceDir,
+ binaryPath: bundledExePath,
+ binaryName: "wgc-capture.exe",
+ });
+ if (!verification.ok) {
+ console.warn(formatNativeHelperManifestWarning("build-windows-capture", verification));
+ }
console.log(`[build-windows-capture] Using bundled helper: ${bundledExePath}`);
process.exit(0);
}
@@ -107,64 +109,64 @@ if (!cmake) {
}
mkdirSync(buildDir, { recursive: true });
-const cacheFile = path.join(buildDir, 'CMakeCache.txt');
-const cacheDir = path.join(buildDir, 'CMakeFiles');
+const cacheFile = path.join(buildDir, "CMakeCache.txt");
+const cacheDir = path.join(buildDir, "CMakeFiles");
function clearCmakeCache() {
- rmSync(cacheFile, { force: true });
- rmSync(cacheDir, { recursive: true, force: true });
+ rmSync(cacheFile, { force: true });
+ rmSync(cacheDir, { recursive: true, force: true });
}
-console.log('[build-windows-capture] Configuring CMake...');
+console.log("[build-windows-capture] Configuring CMake...");
try {
- clearCmakeCache();
- execSync(`${cmake} .. -G "Visual Studio 17 2022" -A x64`, {
- cwd: buildDir,
- stdio: 'inherit',
- timeout: 120000,
- });
+ clearCmakeCache();
+ execSync(`${cmake} .. -G "Visual Studio 17 2022" -A x64`, {
+ cwd: buildDir,
+ stdio: "inherit",
+ timeout: 120000,
+ });
} catch {
- console.log('[build-windows-capture] VS 2022 generator not found, trying VS 2019...');
- try {
- clearCmakeCache();
- execSync(`${cmake} .. -G "Visual Studio 16 2019" -A x64`, {
- cwd: buildDir,
- stdio: 'inherit',
- timeout: 120000,
- });
- } catch (innerError) {
- console.error('[build-windows-capture] CMake configure failed:', innerError.message);
- process.exit(1);
- }
+ console.log("[build-windows-capture] VS 2022 generator not found, trying VS 2019...");
+ try {
+ clearCmakeCache();
+ execSync(`${cmake} .. -G "Visual Studio 16 2019" -A x64`, {
+ cwd: buildDir,
+ stdio: "inherit",
+ timeout: 120000,
+ });
+ } catch (innerError) {
+ console.error("[build-windows-capture] CMake configure failed:", innerError.message);
+ process.exit(1);
+ }
}
-console.log('[build-windows-capture] Building native Windows capture helper...');
+console.log("[build-windows-capture] Building native Windows capture helper...");
try {
- execSync(`${cmake} --build . --config Release`, {
- cwd: buildDir,
- stdio: 'inherit',
- timeout: 300000,
- });
+ execSync(`${cmake} --build . --config Release`, {
+ cwd: buildDir,
+ stdio: "inherit",
+ timeout: 300000,
+ });
} catch (error) {
- console.error('[build-windows-capture] Build failed:', error.message);
- process.exit(1);
+ console.error("[build-windows-capture] Build failed:", error.message);
+ process.exit(1);
}
-const exePath = path.join(buildDir, 'Release', 'wgc-capture.exe');
+const exePath = path.join(buildDir, "Release", "wgc-capture.exe");
if (existsSync(exePath)) {
console.log(`[build-windows-capture] Built successfully: ${exePath}`);
mkdirSync(bundledDir, { recursive: true });
copyFileSync(exePath, bundledExePath);
console.log(`[build-windows-capture] Staged bundled helper: ${bundledExePath}`);
- const manifestPath = updateNativeHelperManifest({
- projectRoot,
- helperId,
- sourceDir,
- binaryPath: bundledExePath,
- binaryName: "wgc-capture.exe",
- });
- console.log(`[build-windows-capture] Updated helper manifest: ${manifestPath}`);
+ const manifestPath = updateNativeHelperManifest({
+ projectRoot,
+ helperId,
+ sourceDir,
+ binaryPath: bundledExePath,
+ binaryName: "wgc-capture.exe",
+ });
+ console.log(`[build-windows-capture] Updated helper manifest: ${manifestPath}`);
} else {
- console.error('[build-windows-capture] Expected exe not found at', exePath);
- process.exit(1);
+ console.error("[build-windows-capture] Expected exe not found at", exePath);
+ process.exit(1);
}
diff --git a/scripts/i18n-check.mjs b/scripts/i18n-check.mjs
index 19535bda9..cff56c298 100644
--- a/scripts/i18n-check.mjs
+++ b/scripts/i18n-check.mjs
@@ -1,86 +1,86 @@
-import fs from 'node:fs'
-import path from 'node:path'
+import fs from "node:fs";
+import path from "node:path";
-const root = process.cwd()
-const localesDir = path.join(root, 'src', 'i18n', 'locales')
+const root = process.cwd();
+const localesDir = path.join(root, "src", "i18n", "locales");
const locales = fs.readdirSync(localesDir).filter((entry) => {
- const fullPath = path.join(localesDir, entry)
- return fs.statSync(fullPath).isDirectory()
-})
+ const fullPath = path.join(localesDir, entry);
+ return fs.statSync(fullPath).isDirectory();
+});
-if (!locales.includes('en')) {
- console.error('i18n-check: expected base locale directory "en"')
- process.exit(1)
+if (!locales.includes("en")) {
+ console.error('i18n-check: expected base locale directory "en"');
+ process.exit(1);
}
function loadJson(filePath) {
- return JSON.parse(fs.readFileSync(filePath, 'utf8'))
+ return JSON.parse(fs.readFileSync(filePath, "utf8"));
}
-function collectKeyPaths(obj, prefix = '') {
- if (!obj || typeof obj !== 'object' || Array.isArray(obj)) {
- return prefix ? [prefix] : []
- }
-
- const keys = Object.keys(obj)
- if (keys.length === 0) {
- return prefix ? [prefix] : []
- }
-
- const paths = []
- for (const key of keys) {
- const nextPrefix = prefix ? `${prefix}.${key}` : key
- const value = obj[key]
- if (value && typeof value === 'object' && !Array.isArray(value)) {
- paths.push(...collectKeyPaths(value, nextPrefix))
- } else {
- paths.push(nextPrefix)
- }
- }
- return paths
+function collectKeyPaths(obj, prefix = "") {
+ if (!obj || typeof obj !== "object" || Array.isArray(obj)) {
+ return prefix ? [prefix] : [];
+ }
+
+ const keys = Object.keys(obj);
+ if (keys.length === 0) {
+ return prefix ? [prefix] : [];
+ }
+
+ const paths = [];
+ for (const key of keys) {
+ const nextPrefix = prefix ? `${prefix}.${key}` : key;
+ const value = obj[key];
+ if (value && typeof value === "object" && !Array.isArray(value)) {
+ paths.push(...collectKeyPaths(value, nextPrefix));
+ } else {
+ paths.push(nextPrefix);
+ }
+ }
+ return paths;
}
-const baseLocaleDir = path.join(localesDir, 'en')
-const namespaceFiles = fs.readdirSync(baseLocaleDir).filter((file) => file.endsWith('.json'))
+const baseLocaleDir = path.join(localesDir, "en");
+const namespaceFiles = fs.readdirSync(baseLocaleDir).filter((file) => file.endsWith(".json"));
-let hasErrors = false
+let hasErrors = false;
for (const namespaceFile of namespaceFiles) {
- const baseData = loadJson(path.join(baseLocaleDir, namespaceFile))
- const baseKeys = new Set(collectKeyPaths(baseData))
-
- for (const locale of locales) {
- if (locale === 'en') continue
-
- const localeFile = path.join(localesDir, locale, namespaceFile)
- if (!fs.existsSync(localeFile)) {
- console.error(`i18n-check: missing namespace file ${locale}/${namespaceFile}`)
- hasErrors = true
- continue
- }
-
- const localeData = loadJson(localeFile)
- const localeKeys = new Set(collectKeyPaths(localeData))
-
- for (const key of baseKeys) {
- if (!localeKeys.has(key)) {
- console.error(`i18n-check: missing key ${locale}/${namespaceFile}:${key}`)
- hasErrors = true
- }
- }
-
- for (const key of localeKeys) {
- if (!baseKeys.has(key)) {
- console.error(`i18n-check: extra key ${locale}/${namespaceFile}:${key}`)
- hasErrors = true
- }
- }
- }
+ const baseData = loadJson(path.join(baseLocaleDir, namespaceFile));
+ const baseKeys = new Set(collectKeyPaths(baseData));
+
+ for (const locale of locales) {
+ if (locale === "en") continue;
+
+ const localeFile = path.join(localesDir, locale, namespaceFile);
+ if (!fs.existsSync(localeFile)) {
+ console.error(`i18n-check: missing namespace file ${locale}/${namespaceFile}`);
+ hasErrors = true;
+ continue;
+ }
+
+ const localeData = loadJson(localeFile);
+ const localeKeys = new Set(collectKeyPaths(localeData));
+
+ for (const key of baseKeys) {
+ if (!localeKeys.has(key)) {
+ console.error(`i18n-check: missing key ${locale}/${namespaceFile}:${key}`);
+ hasErrors = true;
+ }
+ }
+
+ for (const key of localeKeys) {
+ if (!baseKeys.has(key)) {
+ console.error(`i18n-check: extra key ${locale}/${namespaceFile}:${key}`);
+ hasErrors = true;
+ }
+ }
+ }
}
if (hasErrors) {
- process.exit(1)
+ process.exit(1);
}
-console.log('i18n-check: locale files are structurally consistent')
\ No newline at end of file
+console.log("i18n-check: locale files are structurally consistent");
diff --git a/scripts/native-helper-manifest.mjs b/scripts/native-helper-manifest.mjs
index 5c13996e1..0cf96e0f3 100644
--- a/scripts/native-helper-manifest.mjs
+++ b/scripts/native-helper-manifest.mjs
@@ -1,12 +1,5 @@
import { createHash } from "node:crypto";
-import {
- existsSync,
- mkdirSync,
- readFileSync,
- readdirSync,
- statSync,
- writeFileSync,
-} from "node:fs";
+import { existsSync, mkdirSync, readdirSync, readFileSync, statSync, writeFileSync } from "node:fs";
import path from "node:path";
const MANIFEST_FILE_NAME = "helpers-manifest.json";
@@ -66,7 +59,11 @@ function hashFile(filePath) {
return hashBuffer(readFileSync(filePath));
}
-export function getNativeHelperManifestPath({ projectRoot, platform = process.platform, arch = process.arch }) {
+export function getNativeHelperManifestPath({
+ projectRoot,
+ platform = process.platform,
+ arch = process.arch,
+}) {
return path.join(
projectRoot,
"electron",
@@ -161,7 +158,9 @@ export function verifyNativeHelperManifest({
const reasons = [];
if (helperManifest.binaryName !== binaryName) {
- reasons.push(`expected binary ${binaryName}, found ${helperManifest.binaryName ?? "unknown"}`);
+ reasons.push(
+ `expected binary ${binaryName}, found ${helperManifest.binaryName ?? "unknown"}`,
+ );
}
const expectedBinaryHash = helperManifest.binarySha256;
@@ -186,4 +185,4 @@ export function verifyNativeHelperManifest({
export function formatNativeHelperManifestWarning(helperLabel, verificationResult) {
const reasonText = verificationResult.reasons.join(", ");
return `[${helperLabel}] Bundled helper provenance check failed (${reasonText}). Rebuild the helper to refresh ${path.basename(verificationResult.manifestPath)}.`;
-}
\ No newline at end of file
+}
diff --git a/scripts/postinstall.mjs b/scripts/postinstall.mjs
index 3a35d3db5..b0715a831 100644
--- a/scripts/postinstall.mjs
+++ b/scripts/postinstall.mjs
@@ -23,9 +23,7 @@ function runScript(scriptName) {
});
if (result.error) {
- console.error(
- `[postinstall] Failed to start "${scriptName}" (${result.error.message}).`,
- );
+ console.error(`[postinstall] Failed to start "${scriptName}" (${result.error.message}).`);
return false;
}
@@ -35,9 +33,7 @@ function runScript(scriptName) {
}
if (result.status !== 0) {
- console.error(
- `[postinstall] "${scriptName}" exited with code ${result.status}.`,
- );
+ console.error(`[postinstall] "${scriptName}" exited with code ${result.status}.`);
return false;
}
diff --git a/src/App.css b/src/App.css
index d936c2c56..df674c0d8 100644
--- a/src/App.css
+++ b/src/App.css
@@ -1,43 +1,42 @@
-#root {
- max-width: 1280px;
- margin: 0 auto;
- padding: 2rem;
- text-align: center;
-}
-
-.logo {
- height: 6em;
- padding: 1.5em;
- will-change: filter;
- transition: filter 300ms;
-}
-.logo:hover {
- filter: drop-shadow(0 0 2em #646cffaa);
-}
-.logo.react:hover {
- filter: drop-shadow(0 0 2em #61dafbaa);
-}
-
-@keyframes logo-spin {
- from {
- transform: rotate(0deg);
- }
- to {
- transform: rotate(360deg);
- }
-}
-
-@media (prefers-reduced-motion: no-preference) {
- a:nth-of-type(2) .logo {
- animation: logo-spin infinite 20s linear;
- }
-}
-
-.card {
- padding: 2em;
-}
-
-.read-the-docs {
- color: #888;
-}
+#root {
+ max-width: 1280px;
+ margin: 0 auto;
+ padding: 2rem;
+ text-align: center;
+}
+.logo {
+ height: 6em;
+ padding: 1.5em;
+ will-change: filter;
+ transition: filter 300ms;
+}
+.logo:hover {
+ filter: drop-shadow(0 0 2em #646cffaa);
+}
+.logo.react:hover {
+ filter: drop-shadow(0 0 2em #61dafbaa);
+}
+
+@keyframes logo-spin {
+ from {
+ transform: rotate(0deg);
+ }
+ to {
+ transform: rotate(360deg);
+ }
+}
+
+@media (prefers-reduced-motion: no-preference) {
+ a:nth-of-type(2) .logo {
+ animation: logo-spin infinite 20s linear;
+ }
+}
+
+.card {
+ padding: 2em;
+}
+
+.read-the-docs {
+ color: #888;
+}
diff --git a/src/App.tsx b/src/App.tsx
index 4622bddac..3ba05874c 100644
--- a/src/App.tsx
+++ b/src/App.tsx
@@ -1,90 +1,94 @@
-import { useEffect, useState } from "react";
-import { CountdownOverlay } from "./components/countdown/CountdownOverlay";
-import { LaunchWindow } from "./components/launch/LaunchWindow";
-import { SourceSelector } from "./components/launch/SourceSelector";
-import { UpdateToastWindow } from "./components/launch/UpdateToastWindow";
-import { Toaster } from "./components/ui/sonner";
-import { ShortcutsConfigDialog } from "./components/video-editor/ShortcutsConfigDialog";
-import VideoEditor from "./components/video-editor/VideoEditor";
-import { useI18n } from "./contexts/I18nContext";
-import { ShortcutsProvider } from "./contexts/ShortcutsContext";
-import { loadAllCustomFonts } from "./lib/customFonts";
-
-export default function App() {
- const [windowType, setWindowType] = useState("");
- const { locale, t } = useI18n();
-
- useEffect(() => {
- const params = new URLSearchParams(window.location.search);
- const type = params.get("windowType") || "";
- const isMacOS = /mac/i.test(navigator.platform);
- setWindowType(type);
-
- if (
- type === "hud-overlay" ||
- type === "source-selector" ||
- type === "countdown" ||
- (type === "update-toast" && isMacOS)
- ) {
- document.body.style.background = "transparent";
- document.documentElement.style.background = "transparent";
- document.getElementById("root")?.style.setProperty("background", "transparent");
- }
-
- if (type === "hud-overlay" || type === "update-toast") {
- document.documentElement.style.overflow = "visible";
- document.body.style.overflow = "visible";
- document.getElementById("root")?.style.setProperty("overflow", "visible");
- }
-
- loadAllCustomFonts().catch((error) => {
- console.error("Failed to load custom fonts:", error);
- });
- }, []);
-
- useEffect(() => {
- document.title =
- windowType === "editor" ? t("app.editorTitle", "Recordly Editor") : t("app.name", "Recordly");
- }, [windowType, locale, t]);
-
- switch (windowType) {
- case "hud-overlay":
- return (
- <>
- - {t("app.subtitle", "Screen recording and editing")} -
-+ {t("app.subtitle", "Screen recording and editing")} +
+