diff --git a/.gitignore b/.gitignore
index 71417d2..294e3cb 100644
--- a/.gitignore
+++ b/.gitignore
@@ -41,6 +41,8 @@ build/Release
*.tgz
src/renderer/modules/*.js
src/renderer/modules/*.js.map
+src/renderer/rosiEngine.js
+src/renderer/rosiEngine.js.map
# macOS provisioning
*.provisionprofile
diff --git a/CHANGELOG.md b/CHANGELOG.md
index b9300e1..ffa5e1e 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -14,11 +14,11 @@
[ ](https://apps.microsoft.com/detail/9p4q134b2jw3?referrer=appbadge&mode=direct)
-| Windows | MacOS | Linux |
-| :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :-------------------------------------------------------------------------------------------------------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
-| **[Universal EXE](https://github.com/BurntToasters/ROSI/releases/download/v4.0.13/ROSI-Windows.exe)** _(Both x64 and arm64)_ | **[Universal DMG](https://github.com/BurntToasters/ROSI/releases/download/v4.0.13/ROSI-MacOS-universal.dmg)** | **AppImage:** [x64](https://github.com/BurntToasters/ROSI/releases/download/v4.0.13/ROSI-Linux-x86_64.AppImage) / [arm64](https://github.com/BurntToasters/ROSI/releases/download/v4.0.13/ROSI-Linux-arm64.AppImage) |
-| **Other:** [x64](https://github.com/BurntToasters/ROSI/releases/download/v4.0.13/ROSI-Windows-x64.exe) / [arm64](https://github.com/BurntToasters/ROSI/releases/download/v4.0.13/ROSI-Windows-arm64.exe) | | **DEB:** [x64](https://github.com/BurntToasters/ROSI/releases/download/v4.0.13/ROSI-linux_amd64.deb) / [arm64](https://github.com/BurntToasters/ROSI/releases/download/v4.0.13/ROSI-linux_arm64.deb) |
-| | | **RPM:** [x64](https://github.com/BurntToasters/ROSI/releases/download/v4.0.13/ROSI-linux.x86_64.rpm) / [arm64](https://github.com/BurntToasters/ROSI/releases/download/v4.0.13/ROSI-linux.aarch64.rpm) |
+| Windows | macOS | Linux |
+| :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :------------------------------------------------------------------------------------------------------------------ | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
+| **[Universal EXE](https://github.com/BurntToasters/ROSI/releases/download/v4.1.0-beta.1/ROSI-Windows.exe)** _(Both x64 and arm64)_ | **[Universal DMG](https://github.com/BurntToasters/ROSI/releases/download/v4.1.0-beta.1/ROSI-MacOS-universal.dmg)** | **AppImage:** [x64](https://github.com/BurntToasters/ROSI/releases/download/v4.1.0-beta.1/ROSI-Linux-x86_64.AppImage) / [arm64](https://github.com/BurntToasters/ROSI/releases/download/v4.1.0-beta.1/ROSI-Linux-arm64.AppImage) |
+| **Other:** [x64](https://github.com/BurntToasters/ROSI/releases/download/v4.1.0-beta.1/ROSI-Windows-x64.exe) / [arm64](https://github.com/BurntToasters/ROSI/releases/download/v4.1.0-beta.1/ROSI-Windows-arm64.exe) | | **DEB:** [x64](https://github.com/BurntToasters/ROSI/releases/download/v4.1.0-beta.1/ROSI-linux_amd64.deb) / [arm64](https://github.com/BurntToasters/ROSI/releases/download/v4.1.0-beta.1/ROSI-linux_arm64.deb) |
+| | | **RPM:** [x64](https://github.com/BurntToasters/ROSI/releases/download/v4.1.0-beta.1/ROSI-linux.x86_64.rpm) / [arm64](https://github.com/BurntToasters/ROSI/releases/download/v4.1.0-beta.1/ROSI-linux.aarch64.rpm) |
@@ -35,6 +35,18 @@
---
+## Changes in `v4.1.0-beta.1:`
+
+- **NEW - Preview:** Added video preview before downloading so ROSI can show the title, uploader, duration, thumbnail, playlist info, and other basic metadata before saving.
+- **NEW - Enhancements:** Added download options for embedded metadata, embedded thumbnails / cover art, subtitles with custom language codes, and SponsorBlock segment removal.
+- **Conversion:** Updated FFmpeg conversion to probe source codecs first, then copy compatible video/audio streams instead of re-encoding when possible.
+- **GPU detection:** Updated hardware acceleration detection to probe actual FFmpeg encoders, cache the result, and only claim a GPU path when that encoder can run.
+- **Typescript:** Migrated the main renderer engine from JavaScript to TypeScript and widened renderer type coverage.
+- **UI:** Split the renderer CSS into focused files, bundled local Manrope / IBM Plex Mono fonts, tightened CSP by removing remote Google Fonts, and added the preview/enhancement UI.
+- **FFMPEG:** Updated FFmpeg compliance docs, notices, source offer, and binary placeholders for FFmpeg `8.1` builds on Windows, Linux, and macOS arm64. macOS x64 stays on FFmpeg `8.0.1`.
+- **Testing:** Added jsdom DOM coverage for the renderer plus tests for video info parsing, codec-aware FFmpeg args, GPU probing, settings migration, IPC validation, and queue wiring.
+- **PKG:** Updated packages.
+
## Changes in `v4.0.13:`
- **Electron:** Updated electron to `42.2.0`
@@ -50,7 +62,7 @@
- **Electron:** Updated electron to `41.5.0`.
- **PKG:** Updated packages.
-## Changes in `v4.0.0`:
+## Changes in `v4.0.0:`
### Welcome to ROSI v4!
@@ -63,7 +75,7 @@ Version 4 is the biggest change to ROSI of all time! I have been working hard on
- **GPU detection:** Improved the `auto` mode for GPU detection if a user chooses to convert a download.
- **NEW - UI:** The UI has been revamped again with a much more space efficient design with better UX/UI.
- **Themes:** Say hello to theming in ROSI! Currently Dark, Light, and Purple (the old theme) are available!
-- **MISC:** Much much more improvements to the code! Linux support has been improved and other aspects of the code now runs better!
+- **Misc:** Much much more improvements to the code! Linux support has been improved and other aspects of the code now runs better!
### FULL CHANGELOG:
@@ -84,7 +96,7 @@ ROSI Binaries (`v2.1.2+`) are GPG signed. You can verify the authenticity of you
> **Windows Users:** If you want a fully codesigned experience for Windows, check out the [Microsoft Store](https://apps.microsoft.com/detail/9p4q134b2jw3?referrer=appbadge&mode=direct) version (Stable releases only).
-_ROSI's MacOS release is the only GitHub release that is fully codesigned by a developer cert from apple. If you are looking for a version of ROSI that is codesigned for windows, check out the [Microsoft Store](https://apps.microsoft.com/detail/9p4q134b2jw3?referrer=appbadge&mode=direct) version!_
+_ROSI's macOS release is the only GitHub release that is fully codesigned by a developer cert from apple. If you are looking for a version of ROSI that is codesigned for windows, check out the [Microsoft Store](https://apps.microsoft.com/detail/9p4q134b2jw3?referrer=appbadge&mode=direct) version!_
# LTS Version
diff --git a/README.md b/README.md
index 66094cf..ed1d9bc 100644
--- a/README.md
+++ b/README.md
@@ -45,7 +45,7 @@ Download ROSI source code from source (main)
There is an LTS version of the previous stable full release of ROSI (which is now `v3.x.x`) which can be found at [β‘οΈROSI-LTS's Repo](https://github.com/BurntToasters/ROSI-LTS)
-This is mainly for people who perfered the previous look of ROSI, or has a current issue with a newly released major version.
+This is mainly for people who prefer the previous look of ROSI, or has an issue with a newly released major version.
The LTS version only provides yt-dlp updates and minor bug fixes. No feature additions will happen with LTS versions. Whatever features were added to that version before it became LTS are the last features it will receive.
diff --git "a/THIRD\342\200\221PARTY\342\200\221NOTICES.md" "b/THIRD\342\200\221PARTY\342\200\221NOTICES.md"
index cfe482d..30cd738 100644
--- "a/THIRD\342\200\221PARTY\342\200\221NOTICES.md"
+++ "b/THIRD\342\200\221PARTY\342\200\221NOTICES.md"
@@ -26,10 +26,11 @@
License: GPLβ2.0βorβlater
Link: https://www.gnu.org/licenses/old-licenses/gpl-2.0.html
Source: https://github.com/BurntToasters/ffmpeg-static-builds
- ROSI bundles static GPL builds of FFmpeg 8.0 for all platforms. FFmpeg is invoked as an
- external process and is not linked into ROSI. The complete corresponding source code,
- build scripts, and configuration are available at the source URL above. A written source
- offer is included with the distributed binaries at `ffmpeg/SOURCE_OFFER.txt`.
+ ROSI bundles static GPL builds of FFmpeg 8.1 for Windows, Linux, and macOS arm64;
+ macOS x64 remains on FFmpeg 8.0.1. FFmpeg is invoked as an external process and is
+ not linked into ROSI. The complete corresponding source code, build scripts, and
+ configuration are available at the source URL above. A written source offer is
+ included with the distributed binaries at `ffmpeg/SOURCE_OFFER.txt`.
# Python intepreter full license
@@ -557,7 +558,7 @@ Jean-Philippe Aumasson (https://131002.net/siphash/siphash24.c)
The file Python/dtoa.c, which supplies C functions dtoa and strtod for conversion of C doubles to and from strings, is derived from the file of the same name by David M. Gay, currently available from https://web.archive.org/web/20220517033456/http://www.netlib.org/fp/dtoa.c. The original file, as retrieved on March 16, 2009, contains the following copyright and licensing notice:
-/******************************\*\*\*\*******************************
+/**************\*\***************\*\*\*\***************\*\***************
-
- The author of this software is David M. Gay.
@@ -574,7 +575,7 @@ The file Python/dtoa.c, which supplies C functions dtoa and strtod for conversio
- WARRANTY. IN PARTICULAR, NEITHER THE AUTHOR NOR LUCENT MAKES ANY
- REPRESENTATION OR WARRANTY OF ANY KIND CONCERNING THE MERCHANTABILITY
- OF THIS SOFTWARE OR ITS FITNESS FOR ANY PARTICULAR PURPOSE.
-- ******************************\*\*\*******************************/
+- **************\*\***************\*\*\***************\*\***************/
---
diff --git a/build-scripts/check-ffmpeg.js b/build-scripts/check-ffmpeg.js
index e2bae83..4a2093f 100644
--- a/build-scripts/check-ffmpeg.js
+++ b/build-scripts/check-ffmpeg.js
@@ -7,16 +7,34 @@ const checksumsPath = path.join(projectRoot, 'resources', 'ffmpeg', 'checksums.j
const REQUIRED_BINARIES = {
win: {
- x64: 'resources/ffmpeg/win/x64/ffmpeg.exe',
- arm64: 'resources/ffmpeg/win/arm64/ffmpeg.exe',
+ x64: {
+ ffmpeg: 'resources/ffmpeg/win/x64/ffmpeg.exe',
+ ffprobe: 'resources/ffmpeg/win/x64/ffprobe.exe',
+ },
+ arm64: {
+ ffmpeg: 'resources/ffmpeg/win/arm64/ffmpeg.exe',
+ ffprobe: 'resources/ffmpeg/win/arm64/ffprobe.exe',
+ },
},
mac: {
- x64: 'resources/ffmpeg/mac/x64/ffmpeg',
- arm64: 'resources/ffmpeg/mac/arm64/ffmpeg',
+ x64: {
+ ffmpeg: 'resources/ffmpeg/mac/x64/ffmpeg',
+ ffprobe: 'resources/ffmpeg/mac/x64/ffprobe',
+ },
+ arm64: {
+ ffmpeg: 'resources/ffmpeg/mac/arm64/ffmpeg',
+ ffprobe: 'resources/ffmpeg/mac/arm64/ffprobe',
+ },
},
linux: {
- x64: 'resources/ffmpeg/linux/x64/ffmpeg',
- arm64: 'resources/ffmpeg/linux/arm64/ffmpeg',
+ x64: {
+ ffmpeg: 'resources/ffmpeg/linux/x64/ffmpeg',
+ ffprobe: 'resources/ffmpeg/linux/x64/ffprobe',
+ },
+ arm64: {
+ ffmpeg: 'resources/ffmpeg/linux/arm64/ffmpeg',
+ ffprobe: 'resources/ffmpeg/linux/arm64/ffprobe',
+ },
},
};
@@ -44,7 +62,8 @@ function normalizeArch(value) {
function usage() {
console.error(
- 'Usage: node build-scripts/check-ffmpeg.js [--all] [--current] [--target ]... [--generate-checksums]'
+ 'Usage: node build-scripts/check-ffmpeg.js [--all] [--current] [--target ]... [--generate-checksums] [--require-checksums]\n' +
+ ' Checksum mismatches are warnings by default; add --require-checksums to make them fatal.'
);
process.exit(1);
}
@@ -92,6 +111,7 @@ function parseArgs(args) {
let explicitAll = false;
let includeCurrent = false;
let generateChecksums = false;
+ let requireChecksums = false;
const targets = [];
for (let i = 0; i < args.length; i += 1) {
@@ -108,6 +128,10 @@ function parseArgs(args) {
generateChecksums = true;
continue;
}
+ if (arg === '--require-checksums') {
+ requireChecksums = true;
+ continue;
+ }
if (arg === '--target') {
const value = args[i + 1];
if (!value) usage();
@@ -143,7 +167,7 @@ function parseArgs(args) {
resolvedTargets = dedupeTargets(targets);
}
- return { targets: resolvedTargets, generateChecksums };
+ return { targets: resolvedTargets, generateChecksums, requireChecksums };
}
function computeSha256(filePath) {
@@ -159,51 +183,100 @@ function loadChecksums() {
function generateChecksumManifest(targets) {
const manifest = loadChecksums() || {};
for (const target of targets) {
- const relativePath = REQUIRED_BINARIES[target.platform][target.arch];
- const absolutePath = path.join(projectRoot, relativePath);
- if (!fs.existsSync(absolutePath)) {
- console.error(`Cannot generate checksum β binary missing: ${relativePath}`);
- process.exit(1);
- }
+ const required = REQUIRED_BINARIES[target.platform][target.arch];
const key = `${target.platform}:${target.arch}`;
+ const binaries = {};
+
+ for (const [binaryName, relativePath] of Object.entries(required)) {
+ const absolutePath = path.join(projectRoot, relativePath);
+ if (!fs.existsSync(absolutePath)) {
+ console.error(`Cannot generate checksum - binary missing: ${relativePath}`);
+ process.exit(1);
+ }
+ binaries[binaryName] = {
+ path: relativePath,
+ sha256: computeSha256(absolutePath),
+ };
+ }
+
manifest[key] = {
- path: relativePath,
- sha256: computeSha256(absolutePath),
+ binaries,
};
}
fs.writeFileSync(checksumsPath, JSON.stringify(manifest, null, 2) + '\n', 'utf8');
console.log(`Checksums written to resources/ffmpeg/checksums.json`);
}
-function validateTargets(targets) {
+function validateTargets(targets, requireChecksums = false) {
const missing = [];
const checksumErrors = [];
const manifest = loadChecksums();
+ if (requireChecksums && !manifest) {
+ console.error('\nFFmpeg/ffprobe checksum verification failed.');
+ console.error('- resources/ffmpeg/checksums.json is missing');
+ console.error('\nGenerate with: node build-scripts/check-ffmpeg.js --all --generate-checksums');
+ process.exit(1);
+ }
+
for (const target of targets) {
- const relativePath = REQUIRED_BINARIES[target.platform][target.arch];
- const absolutePath = path.join(projectRoot, relativePath);
- if (!fs.existsSync(absolutePath)) {
- missing.push(`${target.platform}:${target.arch} -> ${relativePath}`);
- continue;
- }
+ const key = `${target.platform}:${target.arch}`;
+ const required = REQUIRED_BINARIES[target.platform][target.arch];
+ const entry = manifest?.[key];
- const stats = fs.statSync(absolutePath);
- if (!stats.isFile()) {
- missing.push(`${target.platform}:${target.arch} -> ${relativePath} (not a file)`);
- continue;
- }
+ for (const [binaryName, relativePath] of Object.entries(required)) {
+ const absolutePath = path.join(projectRoot, relativePath);
+ if (!fs.existsSync(absolutePath)) {
+ missing.push(`${target.platform}:${target.arch} -> ${relativePath}`);
+ continue;
+ }
+
+ const stats = fs.statSync(absolutePath);
+ if (!stats.isFile()) {
+ missing.push(`${target.platform}:${target.arch} -> ${relativePath} (not a file)`);
+ continue;
+ }
+
+ if (!manifest) {
+ continue;
+ }
- if (manifest) {
- const key = `${target.platform}:${target.arch}`;
- const entry = manifest[key];
if (!entry) {
checksumErrors.push(`${key} -> no checksum entry in manifest`);
- } else {
- const actual = computeSha256(absolutePath);
- if (actual !== entry.sha256) {
+ continue;
+ }
+
+ const binaryEntry =
+ entry.binaries?.[binaryName] ??
+ (binaryName === 'ffmpeg' && entry.path && entry.sha256
+ ? { path: entry.path, sha256: entry.sha256 }
+ : null);
+
+ if (!binaryEntry) {
+ checksumErrors.push(`${key} -> no checksum entry for ${binaryName}`);
+ continue;
+ }
+
+ if (binaryEntry.path !== relativePath) {
+ checksumErrors.push(
+ `${key} -> checksum path mismatch for ${binaryName}\n expected path: ${relativePath}\n manifest path: ${binaryEntry.path}`
+ );
+ continue;
+ }
+
+ const actual = computeSha256(absolutePath);
+ if (actual !== binaryEntry.sha256) {
+ checksumErrors.push(
+ `${key} -> SHA-256 mismatch for ${binaryName}\n expected: ${binaryEntry.sha256}\n actual: ${actual}`
+ );
+ }
+ }
+
+ if (manifest && entry && entry.binaries) {
+ for (const binaryName of Object.keys(entry.binaries)) {
+ if (!Object.prototype.hasOwnProperty.call(required, binaryName)) {
checksumErrors.push(
- `${key} -> SHA-256 mismatch\n expected: ${entry.sha256}\n actual: ${actual}`
+ `${key} -> unexpected checksum entry for ${binaryName} (remove stale manifest entry)`
);
}
}
@@ -211,7 +284,7 @@ function validateTargets(targets) {
}
if (missing.length > 0) {
- console.error('\nFFmpeg prebuild check failed.');
+ console.error('\nFFmpeg/ffprobe prebuild check failed.');
console.error('Missing required binaries for target architectures:');
for (const item of missing) {
console.error(`- ${item}`);
@@ -221,23 +294,36 @@ function validateTargets(targets) {
}
if (checksumErrors.length > 0) {
- console.error('\nFFmpeg checksum verification failed.');
- for (const item of checksumErrors) {
- console.error(`- ${item}`);
+ if (requireChecksums) {
+ console.error('\nFFmpeg/ffprobe checksum verification failed.');
+ for (const item of checksumErrors) {
+ console.error(`- ${item}`);
+ }
+ console.error('\nRegenerate with: node build-scripts/check-ffmpeg.js --generate-checksums');
+ process.exit(1);
+ } else {
+ console.warn(
+ '\nFFmpeg/ffprobe checksum warnings (non-fatal; pass --require-checksums to enforce):'
+ );
+ for (const item of checksumErrors) {
+ console.warn(`- ${item}`);
+ }
}
- console.error('\nRegenerate with: node build-scripts/check-ffmpeg.js --generate-checksums');
- process.exit(1);
}
}
-const { targets, generateChecksums: shouldGenerate } = parseArgs(process.argv.slice(2));
+const {
+ targets,
+ generateChecksums: shouldGenerate,
+ requireChecksums,
+} = parseArgs(process.argv.slice(2));
if (shouldGenerate) {
- validateTargets(targets);
+ validateTargets(targets, requireChecksums);
generateChecksumManifest(targets);
} else {
- validateTargets(targets);
+ validateTargets(targets, requireChecksums);
}
const checksumNote = loadChecksums() ? ' (checksums verified)' : ' (no checksum manifest found)';
console.log(
- `FFmpeg prebuild check passed for: ${targets.map((t) => `${t.platform}:${t.arch}`).join(', ')}${checksumNote}`
+ `FFmpeg/ffprobe prebuild check passed for: ${targets.map((t) => `${t.platform}:${t.arch}`).join(', ')}${checksumNote}`
);
diff --git a/build-scripts/dist-tools.js b/build-scripts/dist-tools.js
index bc4d8ce..7d70fa7 100644
--- a/build-scripts/dist-tools.js
+++ b/build-scripts/dist-tools.js
@@ -3,6 +3,8 @@ const path = require('path');
const FLATPAK_BUILD_DIR_PREFIX = 'build-dir';
const RENDERER_MODULES_DIR = path.join('src', 'renderer', 'modules');
+const RENDERER_DIR = path.join('src', 'renderer');
+const RENDERER_ROOT_TS = ['rosiEngine.ts'];
function listFlatpakBuildDirs() {
try {
@@ -31,6 +33,7 @@ function cleanBuildArtifacts() {
}
cleanRendererModuleArtifacts();
+ cleanRendererRootArtifacts();
}
function cleanReleaseArtifacts() {
@@ -69,6 +72,20 @@ function cleanRendererModuleArtifacts() {
}
}
+function cleanRendererRootArtifacts() {
+ for (const tsName of RENDERER_ROOT_TS) {
+ const stem = tsName.slice(0, -3);
+ for (const target of [`${stem}.js`, `${stem}.js.map`]) {
+ const artifactPath = path.join(RENDERER_DIR, target);
+ try {
+ fs.rmSync(artifactPath, { force: true, maxRetries: 8, retryDelay: 100 });
+ } catch (error) {
+ if (error && error.code === 'ENOENT') continue;
+ }
+ }
+ }
+}
+
function copyFileEnsuringDir(src, dest) {
fs.mkdirSync(path.dirname(dest), { recursive: true });
fs.copyFileSync(src, dest);
diff --git a/com.burnttoasters.rosi.metainfo.xml b/com.burnttoasters.rosi.metainfo.xml
index fe8e411..4541893 100644
--- a/com.burnttoasters.rosi.metainfo.xml
+++ b/com.burnttoasters.rosi.metainfo.xml
@@ -33,7 +33,7 @@
-
+
diff --git a/eslint.config.mjs b/eslint.config.mjs
index 404a492..192e432 100644
--- a/eslint.config.mjs
+++ b/eslint.config.mjs
@@ -5,7 +5,17 @@ export default tseslint.config(
eslint.configs.recommended,
...tseslint.configs.recommended,
{
- ignores: ['dist/', 'release/', 'node_modules/', 'src/renderer/', '._temp_*/', '*.js', '!eslint.config.mjs', '!build-scripts/*.js'],
+ ignores: [
+ 'dist/',
+ 'release/',
+ 'node_modules/',
+ '._temp_*/',
+ '*.js',
+ 'src/renderer/**/*.js',
+ 'src/renderer/**/*.js.map',
+ '!eslint.config.mjs',
+ '!build-scripts/*.js',
+ ],
},
{
files: ['src/**/*.ts'],
@@ -37,6 +47,41 @@ export default tseslint.config(
'@typescript-eslint/no-misused-promises': ['error', { checksVoidReturn: false }],
},
},
+ {
+ files: ['src/renderer/**/*.ts'],
+ languageOptions: {
+ globals: {
+ window: 'readonly',
+ document: 'readonly',
+ navigator: 'readonly',
+ localStorage: 'readonly',
+ URL: 'readonly',
+ Event: 'readonly',
+ KeyboardEvent: 'readonly',
+ FocusEvent: 'readonly',
+ MouseEvent: 'readonly',
+ DragEvent: 'readonly',
+ PointerEvent: 'readonly',
+ DOMException: 'readonly',
+ Node: 'readonly',
+ HTMLElement: 'readonly',
+ HTMLInputElement: 'readonly',
+ HTMLButtonElement: 'readonly',
+ HTMLSelectElement: 'readonly',
+ HTMLTextAreaElement: 'readonly',
+ HTMLImageElement: 'readonly',
+ HTMLIFrameElement: 'readonly',
+ MediaQueryList: 'readonly',
+ NodeListOf: 'readonly',
+ GlobalEventHandlers: 'readonly',
+ requestAnimationFrame: 'readonly',
+ setTimeout: 'readonly',
+ clearTimeout: 'readonly',
+ console: 'readonly',
+ getComputedStyle: 'readonly',
+ },
+ },
+ },
{
files: ['src/tests/**/*.ts'],
rules: {
diff --git a/package-lock.json b/package-lock.json
index ea00c3c..a22b4c8 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,12 +1,12 @@
{
"name": "rosi",
- "version": "4.0.13",
+ "version": "4.1.0-beta.1",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "rosi",
- "version": "4.0.13",
+ "version": "4.1.0-beta.1",
"license": "MPL-2.0",
"dependencies": {
"electron-log": "^5.3.4",
@@ -24,6 +24,7 @@
"eslint": "^10.3.0",
"husky": "^9.1.7",
"js-yaml": "^4.1.0",
+ "jsdom": "^29.1.1",
"lint-staged": "^17.0.2",
"prettier": "^3.8.1",
"typescript": "^6.0.2",
@@ -35,10 +36,61 @@
"npm": ">=10.x"
}
},
+ "node_modules/@asamuzakjp/css-color": {
+ "version": "5.1.11",
+ "resolved": "https://registry.npmjs.org/@asamuzakjp/css-color/-/css-color-5.1.11.tgz",
+ "integrity": "sha512-KVw6qIiCTUQhByfTd78h2yD1/00waTmm9uy/R7Ck/ctUyAPj+AEDLkQIdJW0T8+qGgj3j5bpNKK7Q3G+LedJWg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@asamuzakjp/generational-cache": "^1.0.1",
+ "@csstools/css-calc": "^3.2.0",
+ "@csstools/css-color-parser": "^4.1.0",
+ "@csstools/css-parser-algorithms": "^4.0.0",
+ "@csstools/css-tokenizer": "^4.0.0"
+ },
+ "engines": {
+ "node": "^20.19.0 || ^22.12.0 || >=24.0.0"
+ }
+ },
+ "node_modules/@asamuzakjp/dom-selector": {
+ "version": "7.1.1",
+ "resolved": "https://registry.npmjs.org/@asamuzakjp/dom-selector/-/dom-selector-7.1.1.tgz",
+ "integrity": "sha512-67RZDnYRc8H/8MLDgQCDE//zoqVFwajkepHZgmXrbwybzXOEwOWGPYGmALYl9J2DOLfFPPs6kKCqmbzV895hTQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@asamuzakjp/generational-cache": "^1.0.1",
+ "@asamuzakjp/nwsapi": "^2.3.9",
+ "bidi-js": "^1.0.3",
+ "css-tree": "^3.2.1",
+ "is-potential-custom-element-name": "^1.0.1"
+ },
+ "engines": {
+ "node": "^20.19.0 || ^22.12.0 || >=24.0.0"
+ }
+ },
+ "node_modules/@asamuzakjp/generational-cache": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/@asamuzakjp/generational-cache/-/generational-cache-1.0.1.tgz",
+ "integrity": "sha512-wajfB8KqzMCN2KGNFdLkReeHncd0AslUSrvHVvvYWuU8ghncRJoA50kT3zP9MVL0+9g4/67H+cdvBskj9THPzg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": "^20.19.0 || ^22.12.0 || >=24.0.0"
+ }
+ },
+ "node_modules/@asamuzakjp/nwsapi": {
+ "version": "2.3.9",
+ "resolved": "https://registry.npmjs.org/@asamuzakjp/nwsapi/-/nwsapi-2.3.9.tgz",
+ "integrity": "sha512-n8GuYSrI9bF7FFZ/SjhwevlHc8xaVlb/7HmHelnc/PZXBD2ZR49NnN9sMMuDdEGPeeRQ5d0hqlSlEpgCX3Wl0Q==",
+ "dev": true,
+ "license": "MIT"
+ },
"node_modules/@babel/helper-string-parser": {
- "version": "7.27.1",
- "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz",
- "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==",
+ "version": "7.29.7",
+ "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.29.7.tgz",
+ "integrity": "sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw==",
"dev": true,
"license": "MIT",
"engines": {
@@ -46,9 +98,9 @@
}
},
"node_modules/@babel/helper-validator-identifier": {
- "version": "7.28.5",
- "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz",
- "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==",
+ "version": "7.29.7",
+ "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.29.7.tgz",
+ "integrity": "sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg==",
"dev": true,
"license": "MIT",
"engines": {
@@ -56,13 +108,13 @@
}
},
"node_modules/@babel/parser": {
- "version": "7.29.3",
- "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.3.tgz",
- "integrity": "sha512-b3ctpQwp+PROvU/cttc4OYl4MzfJUWy6FZg+PMXfzmt/+39iHVF0sDfqay8TQM3JA2EUOyKcFZt75jWriQijsA==",
+ "version": "7.29.7",
+ "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.7.tgz",
+ "integrity": "sha512-hnORnjP/1P/zFEndoeX+n+t1RwWRJiJpM/jO7FW32Kn9r5+sJB2JWOdYo4L6k78j15eCwY3Gm/7364B1EMwtNg==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@babel/types": "^7.29.0"
+ "@babel/types": "^7.29.7"
},
"bin": {
"parser": "bin/babel-parser.js"
@@ -72,14 +124,14 @@
}
},
"node_modules/@babel/types": {
- "version": "7.29.0",
- "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.0.tgz",
- "integrity": "sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==",
+ "version": "7.29.7",
+ "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.7.tgz",
+ "integrity": "sha512-4zBIxpPzowiZpusoFkyGVwakdRJUyuH5PxQ/PrqghfdFWWasvnCdPfQXHrenDai+gyLARulZjZowCOj6fjT4pA==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@babel/helper-string-parser": "^7.27.1",
- "@babel/helper-validator-identifier": "^7.28.5"
+ "@babel/helper-string-parser": "^7.29.7",
+ "@babel/helper-validator-identifier": "^7.29.7"
},
"engines": {
"node": ">=6.9.0"
@@ -95,22 +147,157 @@
"node": ">=18"
}
},
- "node_modules/@develar/schema-utils": {
- "version": "2.6.5",
- "resolved": "https://registry.npmjs.org/@develar/schema-utils/-/schema-utils-2.6.5.tgz",
- "integrity": "sha512-0cp4PsWQ/9avqTVMCtZ+GirikIA36ikvjtHweU4/j8yLtgObI0+JUPhYFScgwlteveGB1rt3Cm8UhN04XayDig==",
+ "node_modules/@bramus/specificity": {
+ "version": "2.4.2",
+ "resolved": "https://registry.npmjs.org/@bramus/specificity/-/specificity-2.4.2.tgz",
+ "integrity": "sha512-ctxtJ/eA+t+6q2++vj5j7FYX3nRu311q1wfYH3xjlLOsczhlhxAg2FWNUXhpGvAw3BWo1xBcvOV6/YLc2r5FJw==",
"dev": true,
"license": "MIT",
"dependencies": {
- "ajv": "^6.12.0",
- "ajv-keywords": "^3.4.1"
+ "css-tree": "^3.0.0"
},
+ "bin": {
+ "specificity": "bin/cli.js"
+ }
+ },
+ "node_modules/@csstools/color-helpers": {
+ "version": "6.0.2",
+ "resolved": "https://registry.npmjs.org/@csstools/color-helpers/-/color-helpers-6.0.2.tgz",
+ "integrity": "sha512-LMGQLS9EuADloEFkcTBR3BwV/CGHV7zyDxVRtVDTwdI2Ca4it0CCVTT9wCkxSgokjE5Ho41hEPgb8OEUwoXr6Q==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/csstools"
+ },
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/csstools"
+ }
+ ],
+ "license": "MIT-0",
+ "engines": {
+ "node": ">=20.19.0"
+ }
+ },
+ "node_modules/@csstools/css-calc": {
+ "version": "3.2.1",
+ "resolved": "https://registry.npmjs.org/@csstools/css-calc/-/css-calc-3.2.1.tgz",
+ "integrity": "sha512-DtdHlgXh5ZkA43cwBcAm+huzgJiwx3ZTWVjBs94kwz2xKqSimDA3lBgCjphYgwgVUMWatSM0pDd8TILB1yrVVg==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/csstools"
+ },
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/csstools"
+ }
+ ],
+ "license": "MIT",
"engines": {
- "node": ">= 8.9.0"
+ "node": ">=20.19.0"
},
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/webpack"
+ "peerDependencies": {
+ "@csstools/css-parser-algorithms": "^4.0.0",
+ "@csstools/css-tokenizer": "^4.0.0"
+ }
+ },
+ "node_modules/@csstools/css-color-parser": {
+ "version": "4.1.1",
+ "resolved": "https://registry.npmjs.org/@csstools/css-color-parser/-/css-color-parser-4.1.1.tgz",
+ "integrity": "sha512-eZ5XOtyhK+mggRafYUWzA0tvaYOFgdY8AkgQiCJF9qNAePnUo/zmsqqYubBBb3sQ8uNUaSKTY9s9klfRaAXL0g==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/csstools"
+ },
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/csstools"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "@csstools/color-helpers": "^6.0.2",
+ "@csstools/css-calc": "^3.2.1"
+ },
+ "engines": {
+ "node": ">=20.19.0"
+ },
+ "peerDependencies": {
+ "@csstools/css-parser-algorithms": "^4.0.0",
+ "@csstools/css-tokenizer": "^4.0.0"
+ }
+ },
+ "node_modules/@csstools/css-parser-algorithms": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/@csstools/css-parser-algorithms/-/css-parser-algorithms-4.0.0.tgz",
+ "integrity": "sha512-+B87qS7fIG3L5h3qwJ/IFbjoVoOe/bpOdh9hAjXbvx0o8ImEmUsGXN0inFOnk2ChCFgqkkGFQ+TpM5rbhkKe4w==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/csstools"
+ },
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/csstools"
+ }
+ ],
+ "license": "MIT",
+ "engines": {
+ "node": ">=20.19.0"
+ },
+ "peerDependencies": {
+ "@csstools/css-tokenizer": "^4.0.0"
+ }
+ },
+ "node_modules/@csstools/css-syntax-patches-for-csstree": {
+ "version": "1.1.5",
+ "resolved": "https://registry.npmjs.org/@csstools/css-syntax-patches-for-csstree/-/css-syntax-patches-for-csstree-1.1.5.tgz",
+ "integrity": "sha512-oNjBvzLq2GPZtJphCjLqXow/cHySHSgtxvKZb7OqSZ/xHgw6NWNhfad+6AB9cLeVm6eA9d/qMll3JdEHjy6M+A==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/csstools"
+ },
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/csstools"
+ }
+ ],
+ "license": "MIT-0",
+ "peerDependencies": {
+ "css-tree": "^3.2.1"
+ },
+ "peerDependenciesMeta": {
+ "css-tree": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@csstools/css-tokenizer": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/@csstools/css-tokenizer/-/css-tokenizer-4.0.0.tgz",
+ "integrity": "sha512-QxULHAm7cNu72w97JUNCBFODFaXpbDg+dP8b/oWFAZ2MTRppA3U00Y2L1HqaS4J6yBqxwa/Y3nMBaxVKbB/NsA==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/csstools"
+ },
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/csstools"
+ }
+ ],
+ "license": "MIT",
+ "engines": {
+ "node": ">=20.19.0"
}
},
"node_modules/@electron/asar": {
@@ -139,9 +326,9 @@
"license": "MIT"
},
"node_modules/@electron/asar/node_modules/brace-expansion": {
- "version": "1.1.14",
- "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.14.tgz",
- "integrity": "sha512-MWPGfDxnyzKU7rNOW9SP/c50vi3xrmrua/+6hfPbCS2ABNWfx24vPidzvC7krjU/RTo235sV776ymlsMtGKj8g==",
+ "version": "1.1.15",
+ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.15.tgz",
+ "integrity": "sha512-EwOCDEex4quD37XhqM3omwtMoJjr//isUZz1JopUNWms+4Z2ViyM/k1YIRePpoVNnQhENnxtFjLaxNHrT7xIUg==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -281,18 +468,25 @@
}
},
"node_modules/@electron/rebuild": {
- "version": "4.0.4",
- "resolved": "https://registry.npmjs.org/@electron/rebuild/-/rebuild-4.0.4.tgz",
- "integrity": "sha512-Rzc39XPdk/+/wBG8MfwAHohXflep0ITUfulb6Rgz3R0NeSB1noE+E9/M/cb8ftCAiyDD9PPhLuuWgE1GaInbKg==",
+ "version": "4.0.3",
+ "resolved": "https://registry.npmjs.org/@electron/rebuild/-/rebuild-4.0.3.tgz",
+ "integrity": "sha512-u9vpTHRMkOYCs/1FLiSVAFZ7FbjsXK+bQuzviJZa+lG7BHZl1nz52/IcGvwa3sk80/fc3llutBkbCq10Vh8WQA==",
"dev": true,
"license": "MIT",
"dependencies": {
"@malept/cross-spawn-promise": "^2.0.0",
"debug": "^4.1.1",
+ "detect-libc": "^2.0.1",
+ "got": "^11.7.0",
+ "graceful-fs": "^4.2.11",
"node-abi": "^4.2.0",
"node-api-version": "^0.2.1",
- "node-gyp": "^12.2.0",
- "read-binary-file-arch": "^1.0.6"
+ "node-gyp": "^11.2.0",
+ "ora": "^5.1.0",
+ "read-binary-file-arch": "^1.0.6",
+ "semver": "^7.3.5",
+ "tar": "^7.5.6",
+ "yargs": "^17.0.1"
},
"bin": {
"electron-rebuild": "lib/cli.js"
@@ -328,9 +522,9 @@
"license": "MIT"
},
"node_modules/@electron/universal/node_modules/brace-expansion": {
- "version": "2.1.0",
- "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.0.tgz",
- "integrity": "sha512-TN1kCZAgdgweJhWWpgKYrQaMNHcDULHkWwQIspdtjV4Y5aurRdZpjAqn6yX3FPqTA9ngHCc4hJxMAMgGfve85w==",
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.1.tgz",
+ "integrity": "sha512-WR1cURNjuvBLMZBMbqM0UoE+WAfdUcEV1ccD8PVBVOI+Z3ND4+SZbN8RsfT2bMuG1qwz5RFvPukSZm5fF2D5eA==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -563,9 +757,9 @@
}
},
"node_modules/@eslint/plugin-kit": {
- "version": "0.7.1",
- "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.7.1.tgz",
- "integrity": "sha512-rZAP3aVgB9ds9KOeUSL+zZ21hPmo8dh6fnIFwRQj5EAZl9gzR7wxYbYXYysAM8CTqGmUGyp2S4kUdV17MnGuWQ==",
+ "version": "0.7.2",
+ "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.7.2.tgz",
+ "integrity": "sha512-+CNAzxglkrpNf/kKywqQfk74QjtceuOE7Qm+AF8miRvPF/wmmK5+OJOgVh3AVTT3RP2mH3+FOaxlE5v72owk0A==",
"dev": true,
"license": "Apache-2.0",
"dependencies": {
@@ -576,6 +770,24 @@
"node": "^20.19.0 || ^22.13.0 || >=24"
}
},
+ "node_modules/@exodus/bytes": {
+ "version": "1.15.1",
+ "resolved": "https://registry.npmjs.org/@exodus/bytes/-/bytes-1.15.1.tgz",
+ "integrity": "sha512-S6mL0yNB/Abt9Ei4tq8gDhcczc4S3+vQ4ra7vxnAf+YHC02srtqxKKZghx2Dq6p0e66THKwR6r8N6P95wEty7Q==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": "^20.19.0 || ^22.12.0 || >=24.0.0"
+ },
+ "peerDependencies": {
+ "@noble/hashes": "^1.8.0 || ^2.0.0"
+ },
+ "peerDependenciesMeta": {
+ "@noble/hashes": {
+ "optional": true
+ }
+ }
+ },
"node_modules/@humanfs/core": {
"version": "0.19.2",
"resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.2.tgz",
@@ -642,6 +854,80 @@
"url": "https://github.com/sponsors/nzakas"
}
},
+ "node_modules/@isaacs/cliui": {
+ "version": "8.0.2",
+ "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz",
+ "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "string-width": "^5.1.2",
+ "string-width-cjs": "npm:string-width@^4.2.0",
+ "strip-ansi": "^7.0.1",
+ "strip-ansi-cjs": "npm:strip-ansi@^6.0.1",
+ "wrap-ansi": "^8.1.0",
+ "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0"
+ },
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@isaacs/cliui/node_modules/ansi-styles": {
+ "version": "6.2.3",
+ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz",
+ "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/ansi-styles?sponsor=1"
+ }
+ },
+ "node_modules/@isaacs/cliui/node_modules/emoji-regex": {
+ "version": "9.2.2",
+ "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz",
+ "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@isaacs/cliui/node_modules/string-width": {
+ "version": "5.1.2",
+ "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz",
+ "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "eastasianwidth": "^0.2.0",
+ "emoji-regex": "^9.2.2",
+ "strip-ansi": "^7.0.1"
+ },
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/@isaacs/cliui/node_modules/wrap-ansi": {
+ "version": "8.1.0",
+ "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz",
+ "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "ansi-styles": "^6.1.0",
+ "string-width": "^5.0.1",
+ "strip-ansi": "^7.0.1"
+ },
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/wrap-ansi?sponsor=1"
+ }
+ },
"node_modules/@isaacs/fs-minipass": {
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/@isaacs/fs-minipass/-/fs-minipass-4.0.1.tgz",
@@ -757,20 +1043,133 @@
"@emnapi/runtime": "^1.7.1"
}
},
+ "node_modules/@noble/hashes": {
+ "version": "2.2.0",
+ "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-2.2.0.tgz",
+ "integrity": "sha512-IYqDGiTXab6FniAgnSdZwgWbomxpy9FtYvLKs7wCUs2a8RkITG+DFGO1DM9cr+E3/RgADRpFjrKVaJ1z6sjtEg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 20.19.0"
+ },
+ "funding": {
+ "url": "https://paulmillr.com/funding/"
+ }
+ },
+ "node_modules/@npmcli/agent": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/@npmcli/agent/-/agent-3.0.0.tgz",
+ "integrity": "sha512-S79NdEgDQd/NGCay6TCoVzXSj74skRZIKJcpJjC5lOq34SZzyI6MqtiiWoiVWoVrTcGjNeC4ipbh1VIHlpfF5Q==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "agent-base": "^7.1.0",
+ "http-proxy-agent": "^7.0.0",
+ "https-proxy-agent": "^7.0.1",
+ "lru-cache": "^10.0.1",
+ "socks-proxy-agent": "^8.0.3"
+ },
+ "engines": {
+ "node": "^18.17.0 || >=20.5.0"
+ }
+ },
+ "node_modules/@npmcli/agent/node_modules/lru-cache": {
+ "version": "10.4.3",
+ "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz",
+ "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==",
+ "dev": true,
+ "license": "ISC"
+ },
+ "node_modules/@npmcli/fs": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/@npmcli/fs/-/fs-4.0.0.tgz",
+ "integrity": "sha512-/xGlezI6xfGO9NwuJlnwz/K14qD1kCSAGtacBHnGzeAIuJGazcp45KP5NuyARXoKb7cwulAGWVsbeSxdG/cb0Q==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "semver": "^7.3.5"
+ },
+ "engines": {
+ "node": "^18.17.0 || >=20.5.0"
+ }
+ },
"node_modules/@oxc-project/types": {
- "version": "0.132.0",
- "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.132.0.tgz",
- "integrity": "sha512-FESMOxil5Se014ui/Eq8fT5uHJo6nIRwH0PfJrZJXs6Gek3ZVFOrpUv3YIZT20m+extU98Hg1Ym72U58rlsxUQ==",
+ "version": "0.133.0",
+ "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.133.0.tgz",
+ "integrity": "sha512-KzkdCd6Uxqnf6l3HOw1xfatAlUURA0g14cvBYFyJ5SaNOQbOUvBr9PKArcPcrNIeRsBdgcUzOGrhKveVpvOIGA==",
"dev": true,
"license": "MIT",
"funding": {
"url": "https://github.com/sponsors/Boshen"
}
},
+ "node_modules/@peculiar/asn1-schema": {
+ "version": "2.7.0",
+ "resolved": "https://registry.npmjs.org/@peculiar/asn1-schema/-/asn1-schema-2.7.0.tgz",
+ "integrity": "sha512-W8ZfWzLmQnrcky+eh3tni4IozMdqBDiHWU0N+vve/UGjMaUs8c0L7A2oEdkBXS8rTpWDpK/aoI3DG/L/hxmxPg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@peculiar/utils": "^2.0.2",
+ "asn1js": "^3.0.6",
+ "tslib": "^2.8.1"
+ }
+ },
+ "node_modules/@peculiar/json-schema": {
+ "version": "1.1.12",
+ "resolved": "https://registry.npmjs.org/@peculiar/json-schema/-/json-schema-1.1.12.tgz",
+ "integrity": "sha512-coUfuoMeIB7B8/NMekxaDzLhaYmp0HZNPEjYRm9goRou8UZIC3z21s0sL9AWoCw4EG876QyO3kYrc61WNF9B/w==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "tslib": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=8.0.0"
+ }
+ },
+ "node_modules/@peculiar/utils": {
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/@peculiar/utils/-/utils-2.0.3.tgz",
+ "integrity": "sha512-+oL3HPFRIZ1St2K50lWCXiioIgSoxzz7R1J3uF6neO2yl1sgmpgY6XXJH4BdpoDkMWznQTeYF6oWNDZLCdQ4eQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "tslib": "^2.8.1"
+ }
+ },
+ "node_modules/@peculiar/webcrypto": {
+ "version": "1.7.1",
+ "resolved": "https://registry.npmjs.org/@peculiar/webcrypto/-/webcrypto-1.7.1.tgz",
+ "integrity": "sha512-ODOov0sGMJMf3jPonOkgGqPknTsu+DdQ7kD++gz8aI+aFMOMHFbWAA2taqXXVTdP+OTOQR/znGvSpmkeI0WTYQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@peculiar/asn1-schema": "^2.7.0",
+ "@peculiar/json-schema": "^1.1.12",
+ "@peculiar/utils": "^2.0.2",
+ "tslib": "^2.8.1",
+ "webcrypto-core": "^1.9.2"
+ },
+ "engines": {
+ "node": ">=14.18.0"
+ }
+ },
+ "node_modules/@pkgjs/parseargs": {
+ "version": "0.11.0",
+ "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz",
+ "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==",
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "engines": {
+ "node": ">=14"
+ }
+ },
"node_modules/@rolldown/binding-android-arm64": {
- "version": "1.0.2",
- "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.0.2.tgz",
- "integrity": "sha512-ZS4D1JPGn/MYQN/SYDWftIE/nVsM8j/AFOYEzAoOE2O3NktQOZru+/vYXGbR/qtdLdIfGCP0lcoJiYVzsEz+iQ==",
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.0.3.tgz",
+ "integrity": "sha512-454rs7jHngixp/NMxd5srYD57OnzSlZ/eFTETjORQHLwJG1lRtmNOJcBerZlfu4GjKqeq8aCCIQrMdHyhI51Hw==",
"cpu": [
"arm64"
],
@@ -785,9 +1184,9 @@
}
},
"node_modules/@rolldown/binding-darwin-arm64": {
- "version": "1.0.2",
- "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.0.2.tgz",
- "integrity": "sha512-vdFA9+C/rekyGce7WqHs/xoT0ioZEWaOFyZLIV1mEeNFaFDUQrPIo8Vs2GvJ6eetb3rzDUtUBgzto3ExpXJB3w==",
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.0.3.tgz",
+ "integrity": "sha512-PcAhP+ynjURNyy8SKGl5DQP94aGuB/7JrXJb/t7P+hanXvQVMWzUvRRhBAcg/lNRadBhoUPqSoP4xw5tR/KBEA==",
"cpu": [
"arm64"
],
@@ -802,9 +1201,9 @@
}
},
"node_modules/@rolldown/binding-darwin-x64": {
- "version": "1.0.2",
- "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.0.2.tgz",
- "integrity": "sha512-BewSOwTHazv77DTYiAZXSqqKZ4KP/KonFisDMVU7PImxoWfB2aepnPhd2E4SWz3zDzYgDNbs6jBmTdgNnF02GA==",
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.0.3.tgz",
+ "integrity": "sha512-9YpfeUvSE2RS7wysJ81uOZkXJz7f7Q55H2Gvp3VEw/EsahqDtrphrZ0EwDLK5vvKOzaCrBsjF8JmnMLcUt78Gg==",
"cpu": [
"x64"
],
@@ -819,9 +1218,9 @@
}
},
"node_modules/@rolldown/binding-freebsd-x64": {
- "version": "1.0.2",
- "resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.0.2.tgz",
- "integrity": "sha512-m41o7M0YWtUdqk61Tb+jnKb2rN++iRdIASlExkUoKfIAH30DOHCB8fVLzSUpbWHHU8esmEioY62PxzexE8MBuA==",
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.0.3.tgz",
+ "integrity": "sha512-yB1IlAsSNHncV6SCTL27/MVGR5htvQsoGxIv5KMGXALp+Ll1wYsn+x98M9MW7qa+NdSbvrrY7ANI4wLJ0n1e6g==",
"cpu": [
"x64"
],
@@ -836,9 +1235,9 @@
}
},
"node_modules/@rolldown/binding-linux-arm-gnueabihf": {
- "version": "1.0.2",
- "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.0.2.tgz",
- "integrity": "sha512-jcojB9H7W/jS29pMKWAK1N+fU99vXodHDTatS3b3y/XSOCiHo0kkA74pL3jJmkoQtYpOCxDvaKs1fo2Ij/1X5w==",
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.0.3.tgz",
+ "integrity": "sha512-Yi30IVAAfLUCy2MseFjbB1jAMDl1VMCAas5StnYp8da9+CKvMd2H2cbEjWcw5NPaPqzvYkVIaF1nNUG+b7u/sw==",
"cpu": [
"arm"
],
@@ -853,9 +1252,9 @@
}
},
"node_modules/@rolldown/binding-linux-arm64-gnu": {
- "version": "1.0.2",
- "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.0.2.tgz",
- "integrity": "sha512-1jn6qDU5iiOgFgygDzKUuKP0maTi0/f1+sBLgvij/76C77Nm3ts6ufz9Bjg5q5dduxiUIxtq86JIoBvo1xQ4Ig==",
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.0.3.tgz",
+ "integrity": "sha512-jsO7R8To+AdlYgUmN5sHSCZbfhtMBkO0WUx8iORQnPcMMdgr7qM2DQmMwgabs3GhNztdmoKkMKQFHD6DTMCIQw==",
"cpu": [
"arm64"
],
@@ -873,9 +1272,9 @@
}
},
"node_modules/@rolldown/binding-linux-arm64-musl": {
- "version": "1.0.2",
- "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.0.2.tgz",
- "integrity": "sha512-QVLO/czFMdoMFSqlX3bcswcJNm/23r+qoa/jgtmFc/qEp6/jXmIkDjF/XIo8dPfGaiwy1xfQn8o77L79GeXFgw==",
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.0.3.tgz",
+ "integrity": "sha512-VWkUHwWriDciit80wleYwKILoR/KMvxh/IdwS/paX+ZgpuRpCrKLUdadJbc0NpBEiyhpYawsJ73j9aCvOH+f7Q==",
"cpu": [
"arm64"
],
@@ -893,9 +1292,9 @@
}
},
"node_modules/@rolldown/binding-linux-ppc64-gnu": {
- "version": "1.0.2",
- "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.0.2.tgz",
- "integrity": "sha512-hgO5Abm0w5UL6FEa2iFnZqo2KlK7TQ5QhV5x09hujBf7t5KzHQ1VmfPuTpqRy/rNlSxua3eWH374xxiVrP+lcA==",
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.0.3.tgz",
+ "integrity": "sha512-5f1laC0SlIR0yDbFCd8acUhvJIag6N3zC5P7oUPN6wX0aOma+uKJ0wBDH5aq7I1PVI2ttTlhJwzwRIBnLiSGEg==",
"cpu": [
"ppc64"
],
@@ -913,9 +1312,9 @@
}
},
"node_modules/@rolldown/binding-linux-s390x-gnu": {
- "version": "1.0.2",
- "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.0.2.tgz",
- "integrity": "sha512-fy8rXxuYEu602abC8MUNaPjYLIFzReOaEIEMKMUa0rFEUxNpVXhs15KSSQ4qlqSaM7B6rcj9rDZgADh/IGDzLQ==",
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.0.3.tgz",
+ "integrity": "sha512-Iq4ko0r4XsgbrF/LunNgHtAGLRRVE2kXonAXQ/MV0mC6jQpMOhW1SvtZja2EhC/kd05++bP78dsqBeIQyYJ6Yg==",
"cpu": [
"s390x"
],
@@ -933,9 +1332,9 @@
}
},
"node_modules/@rolldown/binding-linux-x64-gnu": {
- "version": "1.0.2",
- "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.0.2.tgz",
- "integrity": "sha512-0+bOkiQ779+r1WpoHOWHqncvyySci0vKph+myNDYb+im6meJAzHQXay6oEgnkHuUGouM1LKTZwqKpBow6Kj7CQ==",
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.0.3.tgz",
+ "integrity": "sha512-B8m6tD5+/N5FeNQFbKlLA/2yVq9ycQP1SeedyEYYKWBNR3ZQbkvIUcNnDNM03lO1l5F2roiiFJGgvoLLyZXtSg==",
"cpu": [
"x64"
],
@@ -953,9 +1352,9 @@
}
},
"node_modules/@rolldown/binding-linux-x64-musl": {
- "version": "1.0.2",
- "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.0.2.tgz",
- "integrity": "sha512-mjSkrzZK5Qsl0a9d1JgILOiuZOSDTVdKENcSXBoqbzSrspLR/4/IRVDo5wd2GgZjNss/viBFJdeq+j7qH2nypw==",
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.0.3.tgz",
+ "integrity": "sha512-pSdpdUJHkuCxun9LE7jvgUB9qsRgaiyNNCX7m/AvHTcq67AiT/Yhoxvw5zPfhrM8k/BfP8ce/hMOpthKDpEUow==",
"cpu": [
"x64"
],
@@ -973,9 +1372,9 @@
}
},
"node_modules/@rolldown/binding-openharmony-arm64": {
- "version": "1.0.2",
- "resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.0.2.tgz",
- "integrity": "sha512-1v5vHasdfQAZoEHakBV72LIFAC9JjnymsiKxp+GEr/ma3+NJCPSaYK+qavInOovJkgwFrs7GccX2d6IgDA3Z5w==",
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.0.3.tgz",
+ "integrity": "sha512-OXXS3RKJgX2uLwM+gYyuH5omcH8fL1LJs96pZGgtetVCahON57+d4SJHzTgZiOjxgGkSnpXpOsWuPDGAKAigEg==",
"cpu": [
"arm64"
],
@@ -990,9 +1389,9 @@
}
},
"node_modules/@rolldown/binding-wasm32-wasi": {
- "version": "1.0.2",
- "resolved": "https://registry.npmjs.org/@rolldown/binding-wasm32-wasi/-/binding-wasm32-wasi-1.0.2.tgz",
- "integrity": "sha512-mb1VobWn6NheziTk5/WEaR6AKVbrwT5sOi6C7zk3gy/pD1qtJfU1j4PgTo2NJnOtbL9Dl3Aeei8w9jJ7qC2jZQ==",
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-wasm32-wasi/-/binding-wasm32-wasi-1.0.3.tgz",
+ "integrity": "sha512-JTtb8BWFynicNSoPrehsCzBtOKjZ6jhMiPFEmOiuXg1Fl8dn2KHQob+GuPSGR0dryQa1PQJbzjF3dqO/whhjLg==",
"cpu": [
"wasm32"
],
@@ -1009,9 +1408,9 @@
}
},
"node_modules/@rolldown/binding-win32-arm64-msvc": {
- "version": "1.0.2",
- "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.0.2.tgz",
- "integrity": "sha512-SqKonF56vA/L2yHwHYcEp2P34URpOZ7d1fS635cTkpDnUtEGdUbhI6NzsPdqeSWvAAeGDrxjWjNmibDIdFf9/A==",
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.0.3.tgz",
+ "integrity": "sha512-gEdFFEN70A/jxb2svrWsN3aDL7OUtmvlOy+6fa2jxG8K0wQ1ZbdeLGnidov6Yu5/733dI5ySfzFlQ/cb0bSz1g==",
"cpu": [
"arm64"
],
@@ -1026,9 +1425,9 @@
}
},
"node_modules/@rolldown/binding-win32-x64-msvc": {
- "version": "1.0.2",
- "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.0.2.tgz",
- "integrity": "sha512-v7qRI7gXLRINcOGXt+7YmAZ6iFuyZVMIoXAxhd8oP+DR9dLfL9GfNIx7PLMxmhZdvq8waUJBQiWN9EKNy+TRBQ==",
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.0.3.tgz",
+ "integrity": "sha512-eXB7CHuaQdqmJcc3koCNtNPmT/bj2gc999kUFgBxG8Ac0NdgXc4rkCHhqrgrhN3zddvvvrgzj1e90SuSfmyIXA==",
"cpu": [
"x64"
],
@@ -1190,27 +1589,15 @@
"license": "MIT"
},
"node_modules/@types/node": {
- "version": "25.9.1",
- "resolved": "https://registry.npmjs.org/@types/node/-/node-25.9.1.tgz",
- "integrity": "sha512-xfrlY7UD5rMJk3ZVJP8BNzS28J36YJg+xp+LPXV1TdWxr8uMH5A860QNxYDGQe/ylDSgjxE52Q9VnO7p75tJxg==",
+ "version": "25.9.2",
+ "resolved": "https://registry.npmjs.org/@types/node/-/node-25.9.2.tgz",
+ "integrity": "sha512-G05zqtJhcDLb8uslf5EjCxXg9G1KQxiV8OS0R26IC//Eoyitzqe8z37I7cqvnZlrlSfgocQRfSn/AHBZJJFyGw==",
"dev": true,
"license": "MIT",
"dependencies": {
"undici-types": ">=7.24.0 <7.24.7"
}
},
- "node_modules/@types/plist": {
- "version": "3.0.5",
- "resolved": "https://registry.npmjs.org/@types/plist/-/plist-3.0.5.tgz",
- "integrity": "sha512-E6OCaRmAe4WDmWNsL/9RMqdkkzDCY1etutkflWk4c+AcjDU07Pcz1fQwTX0TQz+Pxqn9i4L1TU3UFpjnrcDgxA==",
- "dev": true,
- "license": "MIT",
- "optional": true,
- "dependencies": {
- "@types/node": "*",
- "xmlbuilder": ">=11.0.1"
- }
- },
"node_modules/@types/responselike": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/@types/responselike/-/responselike-1.0.3.tgz",
@@ -1221,14 +1608,6 @@
"@types/node": "*"
}
},
- "node_modules/@types/verror": {
- "version": "1.10.11",
- "resolved": "https://registry.npmjs.org/@types/verror/-/verror-1.10.11.tgz",
- "integrity": "sha512-RlDm9K7+o5stv0Co8i8ZRGxDbrTxhJtgjqjFyVh/tXQyl/rYtTKlnTvZ88oSTeYREWurwx20Js4kTuKCsFkUtg==",
- "dev": true,
- "license": "MIT",
- "optional": true
- },
"node_modules/@types/yauzl": {
"version": "2.10.3",
"resolved": "https://registry.npmjs.org/@types/yauzl/-/yauzl-2.10.3.tgz",
@@ -1241,17 +1620,17 @@
}
},
"node_modules/@typescript-eslint/eslint-plugin": {
- "version": "8.59.4",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.59.4.tgz",
- "integrity": "sha512-PegsU+XfyJJNjd4+u/k6f9yTyp0lEXXiPopUNobZcIAUJFGICFLN+sP0Rb3JehVmiij1Ph0dFGYqODoRo/2+6A==",
+ "version": "8.60.1",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.60.1.tgz",
+ "integrity": "sha512-JQ4S5GB0tfjO8BuJ4fcX+HodkzJjYBV+7OJ+wLygaX7OGQ7FudyHL4NSCA6ob+w3Yn+5MkKIozOwQhXeM7opVg==",
"dev": true,
"license": "MIT",
"dependencies": {
"@eslint-community/regexpp": "^4.12.2",
- "@typescript-eslint/scope-manager": "8.59.4",
- "@typescript-eslint/type-utils": "8.59.4",
- "@typescript-eslint/utils": "8.59.4",
- "@typescript-eslint/visitor-keys": "8.59.4",
+ "@typescript-eslint/scope-manager": "8.60.1",
+ "@typescript-eslint/type-utils": "8.60.1",
+ "@typescript-eslint/utils": "8.60.1",
+ "@typescript-eslint/visitor-keys": "8.60.1",
"ignore": "^7.0.5",
"natural-compare": "^1.4.0",
"ts-api-utils": "^2.5.0"
@@ -1264,7 +1643,7 @@
"url": "https://opencollective.com/typescript-eslint"
},
"peerDependencies": {
- "@typescript-eslint/parser": "^8.59.4",
+ "@typescript-eslint/parser": "^8.60.1",
"eslint": "^8.57.0 || ^9.0.0 || ^10.0.0",
"typescript": ">=4.8.4 <6.1.0"
}
@@ -1280,16 +1659,16 @@
}
},
"node_modules/@typescript-eslint/parser": {
- "version": "8.59.4",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.59.4.tgz",
- "integrity": "sha512-zORHqO/tuhxY1zWuTvMUqddRxpiFJ72xVfcNoWpqdLjs6lfPbuQBJuW4pk+49/uBMy7Ssr4bzgjiKmmDB1UbZQ==",
+ "version": "8.60.1",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.60.1.tgz",
+ "integrity": "sha512-A0M6ua6H252bVjPvvtSgl2QA4+ET9S5Mtkb2GDyTxIhH/C4qDItT7RQNO5PhMC6NXGYXOR9dIalcDDgBKT7oFA==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@typescript-eslint/scope-manager": "8.59.4",
- "@typescript-eslint/types": "8.59.4",
- "@typescript-eslint/typescript-estree": "8.59.4",
- "@typescript-eslint/visitor-keys": "8.59.4",
+ "@typescript-eslint/scope-manager": "8.60.1",
+ "@typescript-eslint/types": "8.60.1",
+ "@typescript-eslint/typescript-estree": "8.60.1",
+ "@typescript-eslint/visitor-keys": "8.60.1",
"debug": "^4.4.3"
},
"engines": {
@@ -1305,14 +1684,14 @@
}
},
"node_modules/@typescript-eslint/project-service": {
- "version": "8.59.4",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.59.4.tgz",
- "integrity": "sha512-Ly00Vu4oAacfDeHp2Zg85ioNG6l8HG+tN1D7J+xTHSxu9y0awYKJ2zH1rFBn8ZSfuGK+7FxK3Cgl3uAz0aZZLg==",
+ "version": "8.60.1",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.60.1.tgz",
+ "integrity": "sha512-eXkTH2bxmXlqD1RnOPmLZ9ZM9D3VwSx04JOwBnP9RQ+yUA5a2Mu7SfW8uaV2Aon53NJzZlZYuX7tn91Izf+xaw==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@typescript-eslint/tsconfig-utils": "^8.59.4",
- "@typescript-eslint/types": "^8.59.4",
+ "@typescript-eslint/tsconfig-utils": "^8.60.1",
+ "@typescript-eslint/types": "^8.60.1",
"debug": "^4.4.3"
},
"engines": {
@@ -1327,14 +1706,14 @@
}
},
"node_modules/@typescript-eslint/scope-manager": {
- "version": "8.59.4",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.59.4.tgz",
- "integrity": "sha512-mUeR/3H1WrTAddJrwut8OoPjfauaztMQmRwV5fQTUyNVJCLiUXXe4lGEyYIL2oFDpP7UtgbGJXCt72wT0z2S3Q==",
+ "version": "8.60.1",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.60.1.tgz",
+ "integrity": "sha512-gvI5OQoptnxQnchOirukCuQ55svJSTuD/4k5+pC267xyBtYry748R9/c3tYUzb/iE6RZfllRz2lVulLCHkTm4w==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@typescript-eslint/types": "8.59.4",
- "@typescript-eslint/visitor-keys": "8.59.4"
+ "@typescript-eslint/types": "8.60.1",
+ "@typescript-eslint/visitor-keys": "8.60.1"
},
"engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
@@ -1345,9 +1724,9 @@
}
},
"node_modules/@typescript-eslint/tsconfig-utils": {
- "version": "8.59.4",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.59.4.tgz",
- "integrity": "sha512-DLCpnKgD4alVxTBSKulK+gU1KCqOgUXfDRDXh2mZgzokQKa/70ax93I2uVO3m/LLvIAtWZIFoiifudmIqAxpMA==",
+ "version": "8.60.1",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.60.1.tgz",
+ "integrity": "sha512-nh8w4qAteiKuZu3pSSzG/yGKpw0OlkrKnzFmbVRenKaD4qc+7i1GrmZaLVkr8rk4uipiPGMOW4YsM6WmKZ5CvA==",
"dev": true,
"license": "MIT",
"engines": {
@@ -1362,15 +1741,15 @@
}
},
"node_modules/@typescript-eslint/type-utils": {
- "version": "8.59.4",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.59.4.tgz",
- "integrity": "sha512-uonTuPAAKr9XaBGqJ3LjYTh72zy5DyGesljO9gtmk/eFW0W1fRHjnwVYKB35Lm8d5Q5CluEW3gPHjTvZTmgrfA==",
+ "version": "8.60.1",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.60.1.tgz",
+ "integrity": "sha512-sdwTrpjosW7ANQYJ39ZBF1ZyEMEGVB2UsikrserVM/30a/F1dTLnu9bGxEdosugyu5caigjLrR2qiD11asjI1A==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@typescript-eslint/types": "8.59.4",
- "@typescript-eslint/typescript-estree": "8.59.4",
- "@typescript-eslint/utils": "8.59.4",
+ "@typescript-eslint/types": "8.60.1",
+ "@typescript-eslint/typescript-estree": "8.60.1",
+ "@typescript-eslint/utils": "8.60.1",
"debug": "^4.4.3",
"ts-api-utils": "^2.5.0"
},
@@ -1387,9 +1766,9 @@
}
},
"node_modules/@typescript-eslint/types": {
- "version": "8.59.4",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.59.4.tgz",
- "integrity": "sha512-F1o7WJcCq+bc8dwcO/YsSEOudAH8RDtaOhM6wcAQhcUsFhnWQl81JKy48q1hoxAU0qrzM89+31GYh1515Zde3Q==",
+ "version": "8.60.1",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.60.1.tgz",
+ "integrity": "sha512-4h0tY8ppCkdCzcrl2YM5M3my0xsE1Tf8om3owEu5oPWmXwkKRmk0j0LGDzYBGUcAlesEbxBhazqu/K4cu3Ug7w==",
"dev": true,
"license": "MIT",
"engines": {
@@ -1401,16 +1780,16 @@
}
},
"node_modules/@typescript-eslint/typescript-estree": {
- "version": "8.59.4",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.59.4.tgz",
- "integrity": "sha512-F+RuOmcDXo4+TPdfd/TCLS3m2nw8gE9XXyZLrA3JBfaA5tz9TtdkyD3YJFmPxulyc2cKbEok/CvFE3MgSLWnag==",
+ "version": "8.60.1",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.60.1.tgz",
+ "integrity": "sha512-alpRkfG8hlVE5kdJW2GkfgDgXxold3e8e4l6EnmhRmRLbekgAPCCGDVD++sABy9FcgPFroq+uFcCSM1vR57Cew==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@typescript-eslint/project-service": "8.59.4",
- "@typescript-eslint/tsconfig-utils": "8.59.4",
- "@typescript-eslint/types": "8.59.4",
- "@typescript-eslint/visitor-keys": "8.59.4",
+ "@typescript-eslint/project-service": "8.60.1",
+ "@typescript-eslint/tsconfig-utils": "8.60.1",
+ "@typescript-eslint/types": "8.60.1",
+ "@typescript-eslint/visitor-keys": "8.60.1",
"debug": "^4.4.3",
"minimatch": "^10.2.2",
"semver": "^7.7.3",
@@ -1429,16 +1808,16 @@
}
},
"node_modules/@typescript-eslint/utils": {
- "version": "8.59.4",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.59.4.tgz",
- "integrity": "sha512-cYXeNAUsG4lJo5dbc1FcKm+JwIWrj1/UpTORsC6tGMjEZ81DYcvIr9/ueikhMa/Y/gDQYGp+YX9/xQrXje5BJw==",
+ "version": "8.60.1",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.60.1.tgz",
+ "integrity": "sha512-h2MPBLoNtjc3qZWfY3Tl51yPorQ2McHn8pJfcMNTcIvrrZrr90Ykffit0yjrPFWQcRcUxzH20+6OcVdW4yHtUg==",
"dev": true,
"license": "MIT",
"dependencies": {
"@eslint-community/eslint-utils": "^4.9.1",
- "@typescript-eslint/scope-manager": "8.59.4",
- "@typescript-eslint/types": "8.59.4",
- "@typescript-eslint/typescript-estree": "8.59.4"
+ "@typescript-eslint/scope-manager": "8.60.1",
+ "@typescript-eslint/types": "8.60.1",
+ "@typescript-eslint/typescript-estree": "8.60.1"
},
"engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
@@ -1453,13 +1832,13 @@
}
},
"node_modules/@typescript-eslint/visitor-keys": {
- "version": "8.59.4",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.59.4.tgz",
- "integrity": "sha512-U3gxVaDVnuZKhSspW/MzMxE1kq7zOdc072FcSNoqA1I9p8HyKbBFfEHoWckBAMgNMph4MamwS5iTVzFmrnt8TQ==",
+ "version": "8.60.1",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.60.1.tgz",
+ "integrity": "sha512-EbGRQg4FhrmwLodl+t3JNAnXHWVr9Vp+Zl1QBZVPY4ByfkzIT8cX3K6QWODHtkIZqqJVEWvhHSx3v5PDHsaQag==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@typescript-eslint/types": "8.59.4",
+ "@typescript-eslint/types": "8.60.1",
"eslint-visitor-keys": "^5.0.0"
},
"engines": {
@@ -1471,14 +1850,14 @@
}
},
"node_modules/@vitest/coverage-v8": {
- "version": "4.1.7",
- "resolved": "https://registry.npmjs.org/@vitest/coverage-v8/-/coverage-v8-4.1.7.tgz",
- "integrity": "sha512-qsYPeXc5Q9dFLd1i8Ap+Bx8sQgcp+rFVQo4R0dDsWNBzl26ldVF1qOO+RL24K7FDrR6pA+50XedRLSoSG24bVQ==",
+ "version": "4.1.8",
+ "resolved": "https://registry.npmjs.org/@vitest/coverage-v8/-/coverage-v8-4.1.8.tgz",
+ "integrity": "sha512-lt3kovsyHwYe00wq4D1ti0Z974fWj4NLp6siqiyEufUpyFwK9Yhi7rBhac9JL5aA0zoMrJqc4vYPZRUnI7l7nw==",
"dev": true,
"license": "MIT",
"dependencies": {
"@bcoe/v8-coverage": "^1.0.2",
- "@vitest/utils": "4.1.7",
+ "@vitest/utils": "4.1.8",
"ast-v8-to-istanbul": "^1.0.0",
"istanbul-lib-coverage": "^3.2.2",
"istanbul-lib-report": "^3.0.1",
@@ -1492,8 +1871,8 @@
"url": "https://opencollective.com/vitest"
},
"peerDependencies": {
- "@vitest/browser": "4.1.7",
- "vitest": "4.1.7"
+ "@vitest/browser": "4.1.8",
+ "vitest": "4.1.8"
},
"peerDependenciesMeta": {
"@vitest/browser": {
@@ -1502,16 +1881,16 @@
}
},
"node_modules/@vitest/expect": {
- "version": "4.1.7",
- "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-4.1.7.tgz",
- "integrity": "sha512-1R+tw0ortHEbZDGMymm+pN7/AFQ/RkFFdtd7EN+VBpynKmLbP8A3rpEXdshBJ7+8hQ9zBJh/i1s0yKNtxAnU7w==",
+ "version": "4.1.8",
+ "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-4.1.8.tgz",
+ "integrity": "sha512-h3nDO677RDLEGlBxyQ5CW8RlMThSKSRLUePLOx09gNIWRL40edgA1GCZSZgf1W55MFAG6/Sw14KeaAnqv0NKdQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"@standard-schema/spec": "^1.1.0",
"@types/chai": "^5.2.2",
- "@vitest/spy": "4.1.7",
- "@vitest/utils": "4.1.7",
+ "@vitest/spy": "4.1.8",
+ "@vitest/utils": "4.1.8",
"chai": "^6.2.2",
"tinyrainbow": "^3.1.0"
},
@@ -1520,13 +1899,13 @@
}
},
"node_modules/@vitest/mocker": {
- "version": "4.1.7",
- "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-4.1.7.tgz",
- "integrity": "sha512-vY7nuamKgfvpA1Koa3oYIw/k7D6kZnpGyNMZW8loow2bsBYla1TFdqTaXncWdRn4pgwNs+90RhnXhJScDwQeJA==",
+ "version": "4.1.8",
+ "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-4.1.8.tgz",
+ "integrity": "sha512-LEiN/xe4OSIbKe9HQIp5OC24agGD9J5CnmMgsLohVVoOPWL9a2sBoR6VBx43jQZb7Kr1l4RCuyCJzcAa0+dojw==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@vitest/spy": "4.1.7",
+ "@vitest/spy": "4.1.8",
"estree-walker": "^3.0.3",
"magic-string": "^0.30.21"
},
@@ -1547,9 +1926,9 @@
}
},
"node_modules/@vitest/pretty-format": {
- "version": "4.1.7",
- "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-4.1.7.tgz",
- "integrity": "sha512-umgCarTOYQWIaDMvGDRZij+6b9oVeLIyJzfN+AS88e0ZOU3QTgNNSTtjQOpcvWr3np1N0j4WgZj+sb3oYBDscw==",
+ "version": "4.1.8",
+ "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-4.1.8.tgz",
+ "integrity": "sha512-9GasEBxpZ1VYIpqHf/0+YGg121uSNwCKOJqIrTwWP/TB7DmFCiaBpNl3aPZzoLWfWkuqhbH8vJIVobZkvdo2cA==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -1560,13 +1939,13 @@
}
},
"node_modules/@vitest/runner": {
- "version": "4.1.7",
- "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-4.1.7.tgz",
- "integrity": "sha512-BapjmAQ2aI78WdMEfeUWivnfVzB+VPGwWRQcJE0OUq7qEeEcBsCSf+0T5iREBNE5nBb4wA5Ya0W6IA+sghdEFw==",
+ "version": "4.1.8",
+ "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-4.1.8.tgz",
+ "integrity": "sha512-EmVxeBAfMJvycdjd6Hm+RbFBbA9fKvo0Kx37hNpBYoYeavH3RNsBXWDooR1mgD52dCrxIIuP7UotpfiwOikvcg==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@vitest/utils": "4.1.7",
+ "@vitest/utils": "4.1.8",
"pathe": "^2.0.3"
},
"funding": {
@@ -1574,14 +1953,14 @@
}
},
"node_modules/@vitest/snapshot": {
- "version": "4.1.7",
- "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-4.1.7.tgz",
- "integrity": "sha512-ZacLzja+TmJeZ1h14xW2FB/WpeimUD3haBXQPyJqxvo8jQTmfeA8zv58mtjN2C7EHXZDYVcVYdYmAxjkWVvKCw==",
+ "version": "4.1.8",
+ "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-4.1.8.tgz",
+ "integrity": "sha512-acfZboRmAIf05DEKcBQy33VXojFJjtUdLyo7oOmV9kebb2xdU01UknNiPuPZoJZQyO7DF0gZdTGTpeAzET9QPQ==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@vitest/pretty-format": "4.1.7",
- "@vitest/utils": "4.1.7",
+ "@vitest/pretty-format": "4.1.8",
+ "@vitest/utils": "4.1.8",
"magic-string": "^0.30.21",
"pathe": "^2.0.3"
},
@@ -1590,9 +1969,9 @@
}
},
"node_modules/@vitest/spy": {
- "version": "4.1.7",
- "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-4.1.7.tgz",
- "integrity": "sha512-kbkI5LMWakyuTIvs6fUJ5qdIVb1XVKsYJAT4OJ938cHMROYMSfmoQdZy0aaAnjbbc8F61vkoTqz/Az+/HiIu5Q==",
+ "version": "4.1.8",
+ "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-4.1.8.tgz",
+ "integrity": "sha512-6EevtBp6OZOPF7bmz36HrGMeP3txgVSrgebWxHOafDXGkhIzfXK14f8KF6MuFfgXXUeHxmpD3BQxkV00/3s5mA==",
"dev": true,
"license": "MIT",
"funding": {
@@ -1600,13 +1979,13 @@
}
},
"node_modules/@vitest/utils": {
- "version": "4.1.7",
- "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-4.1.7.tgz",
- "integrity": "sha512-T532WBu791cBxJlCl6SO+J14l81DQx6uQHm1bQbmCDY7nqlEIgkza/UFnSBNaUtSf41unldDFjdOBYEQC4b5Hw==",
+ "version": "4.1.8",
+ "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-4.1.8.tgz",
+ "integrity": "sha512-uOJamYALNhfJ6iolExyQM40yIQwDqYnkKtQ5VCiSe17E33H0aQ/u+1GlRuz4LZBk6Mm3sg90G9hEbmEt37C1Zg==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@vitest/pretty-format": "4.1.7",
+ "@vitest/pretty-format": "4.1.8",
"convert-source-map": "^2.0.0",
"tinyrainbow": "^3.1.0"
},
@@ -1624,21 +2003,14 @@
"node": ">=10.0.0"
}
},
- "node_modules/7zip-bin": {
- "version": "5.2.0",
- "resolved": "https://registry.npmjs.org/7zip-bin/-/7zip-bin-5.2.0.tgz",
- "integrity": "sha512-ukTPVhqG4jNzMro2qA9HSCSSVJN3aN7tlb+hfqYCt3ER0yWroeA2VR38MNrOHLQ/cVj+DaIMad0kFCtWWowh/A==",
- "dev": true,
- "license": "MIT"
- },
"node_modules/abbrev": {
- "version": "4.0.0",
- "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-4.0.0.tgz",
- "integrity": "sha512-a1wflyaL0tHtJSmLSOVybYhy22vRih4eduhhrkcjgrWGnRfrZtovJ2FRjxuTtkkj47O/baf0R86QU5OuYpz8fA==",
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-3.0.1.tgz",
+ "integrity": "sha512-AO2ac6pjRB3SJmGJo+v5/aK6Omggp6fsLrs6wN9bd35ulu4cCwaAU9+7ZhXjeqHVkaHThLuzH0nZr0YpCDhygg==",
"dev": true,
"license": "ISC",
"engines": {
- "node": "^20.17.0 || >=22.9.0"
+ "node": "^18.17.0 || >=20.5.0"
}
},
"node_modules/acorn": {
@@ -1675,32 +2047,22 @@
}
},
"node_modules/ajv": {
- "version": "6.15.0",
- "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.15.0.tgz",
- "integrity": "sha512-fgFx7Hfoq60ytK2c7DhnF8jIvzYgOMxfugjLOSMHjLIPgenqa7S7oaagATUq99mV6IYvN2tRmC0wnTYX6iPbMw==",
+ "version": "8.20.0",
+ "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.20.0.tgz",
+ "integrity": "sha512-Thbli+OlOj+iMPYFBVBfJ3OmCAnaSyNn4M1vz9T6Gka5Jt9ba/HIR56joy65tY6kx/FCF5VXNB819Y7/GUrBGA==",
"dev": true,
"license": "MIT",
"dependencies": {
- "fast-deep-equal": "^3.1.1",
- "fast-json-stable-stringify": "^2.0.0",
- "json-schema-traverse": "^0.4.1",
- "uri-js": "^4.2.2"
+ "fast-deep-equal": "^3.1.3",
+ "fast-uri": "^3.0.1",
+ "json-schema-traverse": "^1.0.0",
+ "require-from-string": "^2.0.2"
},
"funding": {
"type": "github",
"url": "https://github.com/sponsors/epoberezkin"
}
},
- "node_modules/ajv-keywords": {
- "version": "3.5.2",
- "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz",
- "integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==",
- "dev": true,
- "license": "MIT",
- "peerDependencies": {
- "ajv": "^6.9.1"
- }
- },
"node_modules/ansi-escapes": {
"version": "7.3.0",
"resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-7.3.0.tgz",
@@ -1746,40 +2108,36 @@
"url": "https://github.com/chalk/ansi-styles?sponsor=1"
}
},
- "node_modules/app-builder-bin": {
- "version": "5.0.0-alpha.12",
- "resolved": "https://registry.npmjs.org/app-builder-bin/-/app-builder-bin-5.0.0-alpha.12.tgz",
- "integrity": "sha512-j87o0j6LqPL3QRr8yid6c+Tt5gC7xNfYo6uQIQkorAC6MpeayVMZrEDzKmJJ/Hlv7EnOQpaRm53k6ktDYZyB6w==",
- "dev": true,
- "license": "MIT"
- },
"node_modules/app-builder-lib": {
- "version": "26.8.1",
- "resolved": "https://registry.npmjs.org/app-builder-lib/-/app-builder-lib-26.8.1.tgz",
- "integrity": "sha512-p0Im/Dx5C4tmz8QEE1Yn4MkuPC8PrnlRneMhWJj7BBXQfNTJUshM/bp3lusdEsDbvvfJZpXWnYesgSLvwtM2Zw==",
+ "version": "26.15.0",
+ "resolved": "https://registry.npmjs.org/app-builder-lib/-/app-builder-lib-26.15.0.tgz",
+ "integrity": "sha512-j2+P6Lh+l/VuWfXZWSs7u+OAPqYJQGnZZO30M833XQQaRuyohm4RZk7Gw4nQXfeyQH9GqXaTwR16Y0LaVTlS+g==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@develar/schema-utils": "~2.6.5",
"@electron/asar": "3.4.1",
"@electron/fuses": "^1.8.0",
"@electron/get": "^3.0.0",
"@electron/notarize": "2.5.0",
"@electron/osx-sign": "1.3.3",
- "@electron/rebuild": "^4.0.3",
+ "@electron/rebuild": "4.0.3",
"@electron/universal": "2.0.3",
"@malept/flatpak-bundler": "^0.4.0",
+ "@noble/hashes": "^2.2.0",
+ "@peculiar/webcrypto": "^1.7.1",
"@types/fs-extra": "9.0.13",
+ "ajv": "^8.18.0",
+ "asn1js": "^3.0.10",
"async-exit-hook": "^2.0.1",
- "builder-util": "26.8.1",
- "builder-util-runtime": "9.5.1",
+ "builder-util": "26.15.0",
+ "builder-util-runtime": "9.7.0",
"chromium-pickle-js": "^0.2.0",
"ci-info": "4.3.1",
"debug": "^4.3.4",
"dotenv": "^16.4.5",
"dotenv-expand": "^11.0.6",
"ejs": "^3.1.8",
- "electron-publish": "26.8.1",
+ "electron-publish": "26.15.0",
"fs-extra": "^10.1.0",
"hosted-git-info": "^4.1.0",
"isbinaryfile": "^5.0.0",
@@ -1787,7 +2145,8 @@
"js-yaml": "^4.1.0",
"json5": "^2.2.3",
"lazy-val": "^1.0.5",
- "minimatch": "^10.0.3",
+ "minimatch": "^10.2.5",
+ "pkijs": "^3.4.0",
"plist": "3.1.0",
"proper-lockfile": "^4.1.2",
"resedit": "^1.7.0",
@@ -1795,14 +2154,15 @@
"tar": "^7.5.7",
"temp-file": "^3.4.0",
"tiny-async-pool": "1.3.0",
+ "unzipper": "^0.12.3",
"which": "^5.0.0"
},
"engines": {
"node": ">=14.0.0"
},
"peerDependencies": {
- "dmg-builder": "26.8.1",
- "electron-builder-squirrel-windows": "26.8.1"
+ "dmg-builder": "26.15.0",
+ "electron-builder-squirrel-windows": "26.15.0"
}
},
"node_modules/app-builder-lib/node_modules/@electron/get": {
@@ -1972,15 +2332,19 @@
"integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==",
"license": "Python-2.0"
},
- "node_modules/assert-plus": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz",
- "integrity": "sha512-NfJ4UzBCcQGLDlQq7nHxH+tv3kyZ0hHQqF5BO6J7tNJeP5do1llPr8dZ8zHonfhAu0PHAdMkSo+8o0wxg9lZWw==",
+ "node_modules/asn1js": {
+ "version": "3.0.10",
+ "resolved": "https://registry.npmjs.org/asn1js/-/asn1js-3.0.10.tgz",
+ "integrity": "sha512-S2s3aOytiKdFRdulw2qPE51MzjzVOisppcVv7jVFR+Kw0kxwvFrDcYA0h7Ndqbmj0HkMIXYWaoj7fli8kgx1eg==",
"dev": true,
- "license": "MIT",
- "optional": true,
+ "license": "BSD-3-Clause",
+ "dependencies": {
+ "pvtsutils": "^1.3.6",
+ "pvutils": "^1.1.5",
+ "tslib": "^2.8.1"
+ },
"engines": {
- "node": ">=0.8"
+ "node": ">=12.0.0"
}
},
"node_modules/assertion-error": {
@@ -1994,9 +2358,9 @@
}
},
"node_modules/ast-v8-to-istanbul": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/ast-v8-to-istanbul/-/ast-v8-to-istanbul-1.0.0.tgz",
- "integrity": "sha512-1fSfIwuDICFA4LKkCzRPO7F0hzFf0B7+Xqrl27ynQaa+Rh0e1Es0v6kWHPott3lU10AyAr7oKHa65OppjLn3Rg==",
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/ast-v8-to-istanbul/-/ast-v8-to-istanbul-1.0.3.tgz",
+ "integrity": "sha512-jCMQ6ZylLPudp0CDfBmQBZUsrh1/8psbmu9ibeVWKuHWD0YrH9YABwlKu5kVEFoT0GCQQW9Z/SxfuEbbkGQCRg==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -2005,17 +2369,6 @@
"js-tokens": "^10.0.0"
}
},
- "node_modules/astral-regex": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-2.0.0.tgz",
- "integrity": "sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ==",
- "dev": true,
- "license": "MIT",
- "optional": true,
- "engines": {
- "node": ">=8"
- }
- },
"node_modules/async": {
"version": "3.2.6",
"resolved": "https://registry.npmjs.org/async/-/async-3.2.6.tgz",
@@ -2050,6 +2403,13 @@
"node": ">= 4.0.0"
}
},
+ "node_modules/aws4": {
+ "version": "1.13.2",
+ "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.13.2.tgz",
+ "integrity": "sha512-lHe62zvbTB5eEABUVi/AwVh0ZKY9rMMDhmm+eeyuuUQbQ3+J+fONVQOZyj+DdrvD4BY33uYniyRJ4UJIaSKAfw==",
+ "dev": true,
+ "license": "MIT"
+ },
"node_modules/balanced-match": {
"version": "4.0.4",
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz",
@@ -2081,6 +2441,35 @@
],
"license": "MIT"
},
+ "node_modules/bidi-js": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/bidi-js/-/bidi-js-1.0.3.tgz",
+ "integrity": "sha512-RKshQI1R3YQ+n9YJz2QQ147P66ELpa1FQEg20Dk8oW9t2KgLbpDLLp9aGZ7y8WHSshDknG0bknqGw5/tyCs5tw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "require-from-string": "^2.0.2"
+ }
+ },
+ "node_modules/bl": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz",
+ "integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "buffer": "^5.5.0",
+ "inherits": "^2.0.4",
+ "readable-stream": "^3.4.0"
+ }
+ },
+ "node_modules/bluebird": {
+ "version": "3.7.2",
+ "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.7.2.tgz",
+ "integrity": "sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==",
+ "dev": true,
+ "license": "MIT"
+ },
"node_modules/boolean": {
"version": "3.2.0",
"resolved": "https://registry.npmjs.org/boolean/-/boolean-3.2.0.tgz",
@@ -2123,7 +2512,6 @@
}
],
"license": "MIT",
- "optional": true,
"dependencies": {
"base64-js": "^1.3.1",
"ieee754": "^1.1.13"
@@ -2147,16 +2535,14 @@
"license": "MIT"
},
"node_modules/builder-util": {
- "version": "26.8.1",
- "resolved": "https://registry.npmjs.org/builder-util/-/builder-util-26.8.1.tgz",
- "integrity": "sha512-pm1lTYbGyc90DHgCDO7eo8Rl4EqKLciayNbZqGziqnH9jrlKe8ZANGdityLZU+pJh16dfzjAx2xQq9McuIPEtw==",
+ "version": "26.15.0",
+ "resolved": "https://registry.npmjs.org/builder-util/-/builder-util-26.15.0.tgz",
+ "integrity": "sha512-dUx+HxVbiNsNQ4mGe1PyoC/tBmsHwBNDLdBuqWCj+rhHFE9lHgrXiGYKAM1uNlznhAaUSyMlms84VeSSr3gOBA==",
"dev": true,
"license": "MIT",
"dependencies": {
"@types/debug": "^4.1.6",
- "7zip-bin": "~5.2.0",
- "app-builder-bin": "5.0.0-alpha.12",
- "builder-util-runtime": "9.5.1",
+ "builder-util-runtime": "9.7.0",
"chalk": "^4.1.2",
"cross-spawn": "^7.0.6",
"debug": "^4.3.4",
@@ -2169,12 +2555,15 @@
"stat-mode": "^1.0.0",
"temp-file": "^3.4.0",
"tiny-async-pool": "1.3.0"
+ },
+ "engines": {
+ "node": ">=14.0.0"
}
},
"node_modules/builder-util-runtime": {
- "version": "9.5.1",
- "resolved": "https://registry.npmjs.org/builder-util-runtime/-/builder-util-runtime-9.5.1.tgz",
- "integrity": "sha512-qt41tMfgHTllhResqM5DcnHyDIWNgzHvuY2jDcYP9iaGpkWxTUzV6GQjDeLnlR1/DtdlcsWQbA7sByMpmJFTLQ==",
+ "version": "9.7.0",
+ "resolved": "https://registry.npmjs.org/builder-util-runtime/-/builder-util-runtime-9.7.0.tgz",
+ "integrity": "sha512-g/kR520giAFYkSXTzcmF3kqQq7wi8F6N6SzeDgZrqTBN+VHdmgWOyTdD1yD7AATDId/yXLvuP34CxW46/BwCdw==",
"license": "MIT",
"dependencies": {
"debug": "^4.3.4",
@@ -2184,6 +2573,102 @@
"node": ">=12.0.0"
}
},
+ "node_modules/bytestreamjs": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/bytestreamjs/-/bytestreamjs-2.0.1.tgz",
+ "integrity": "sha512-U1Z/ob71V/bXfVABvNr/Kumf5VyeQRBEm6Txb0PQ6S7V5GpBM3w4Cbqz/xPDicR5tN0uvDifng8C+5qECeGwyQ==",
+ "dev": true,
+ "license": "BSD-3-Clause",
+ "engines": {
+ "node": ">=6.0.0"
+ }
+ },
+ "node_modules/cacache": {
+ "version": "19.0.1",
+ "resolved": "https://registry.npmjs.org/cacache/-/cacache-19.0.1.tgz",
+ "integrity": "sha512-hdsUxulXCi5STId78vRVYEtDAjq99ICAUktLTeTYsLoTE6Z8dS0c8pWNCxwdrk9YfJeobDZc2Y186hD/5ZQgFQ==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "@npmcli/fs": "^4.0.0",
+ "fs-minipass": "^3.0.0",
+ "glob": "^10.2.2",
+ "lru-cache": "^10.0.1",
+ "minipass": "^7.0.3",
+ "minipass-collect": "^2.0.1",
+ "minipass-flush": "^1.0.5",
+ "minipass-pipeline": "^1.2.4",
+ "p-map": "^7.0.2",
+ "ssri": "^12.0.0",
+ "tar": "^7.4.3",
+ "unique-filename": "^4.0.0"
+ },
+ "engines": {
+ "node": "^18.17.0 || >=20.5.0"
+ }
+ },
+ "node_modules/cacache/node_modules/balanced-match": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
+ "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/cacache/node_modules/brace-expansion": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.1.tgz",
+ "integrity": "sha512-WR1cURNjuvBLMZBMbqM0UoE+WAfdUcEV1ccD8PVBVOI+Z3ND4+SZbN8RsfT2bMuG1qwz5RFvPukSZm5fF2D5eA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "balanced-match": "^1.0.0"
+ }
+ },
+ "node_modules/cacache/node_modules/glob": {
+ "version": "10.5.0",
+ "resolved": "https://registry.npmjs.org/glob/-/glob-10.5.0.tgz",
+ "integrity": "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==",
+ "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "foreground-child": "^3.1.0",
+ "jackspeak": "^3.1.2",
+ "minimatch": "^9.0.4",
+ "minipass": "^7.1.2",
+ "package-json-from-dist": "^1.0.0",
+ "path-scurry": "^1.11.1"
+ },
+ "bin": {
+ "glob": "dist/esm/bin.mjs"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/isaacs"
+ }
+ },
+ "node_modules/cacache/node_modules/lru-cache": {
+ "version": "10.4.3",
+ "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz",
+ "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==",
+ "dev": true,
+ "license": "ISC"
+ },
+ "node_modules/cacache/node_modules/minimatch": {
+ "version": "9.0.9",
+ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.9.tgz",
+ "integrity": "sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "brace-expansion": "^2.0.2"
+ },
+ "engines": {
+ "node": ">=16 || 14 >=14.17"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/isaacs"
+ }
+ },
"node_modules/cacheable-lookup": {
"version": "5.0.4",
"resolved": "https://registry.npmjs.org/cacheable-lookup/-/cacheable-lookup-5.0.4.tgz",
@@ -2303,19 +2788,31 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
+ "node_modules/cli-spinners": {
+ "version": "2.9.2",
+ "resolved": "https://registry.npmjs.org/cli-spinners/-/cli-spinners-2.9.2.tgz",
+ "integrity": "sha512-ywqV+5MmyL4E7ybXgKys4DugZbX0FC6LnwrhjuykIjnK9k8OQacQ7axGKnjDXWNhns0xot3bZI5h55H8yo9cJg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
"node_modules/cli-truncate": {
- "version": "2.1.0",
- "resolved": "https://registry.npmjs.org/cli-truncate/-/cli-truncate-2.1.0.tgz",
- "integrity": "sha512-n8fOixwDD6b/ObinzTrp1ZKFzbgvKZvuz/TvejnLn1aQfC6r52XEx85FmuC+3HI+JM7coBRXUvNqEU2PHVrHpg==",
+ "version": "5.2.0",
+ "resolved": "https://registry.npmjs.org/cli-truncate/-/cli-truncate-5.2.0.tgz",
+ "integrity": "sha512-xRwvIOMGrfOAnM1JYtqQImuaNtDEv9v6oIYAs4LIHwTiKee8uwvIi363igssOC0O5U04i4AlENs79LQLu9tEMw==",
"dev": true,
"license": "MIT",
- "optional": true,
"dependencies": {
- "slice-ansi": "^3.0.0",
- "string-width": "^4.2.0"
+ "slice-ansi": "^8.0.0",
+ "string-width": "^8.2.0"
},
"engines": {
- "node": ">=8"
+ "node": ">=20"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
@@ -2346,6 +2843,31 @@
"node": ">=8"
}
},
+ "node_modules/cliui/node_modules/is-fullwidth-code-point": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz",
+ "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/cliui/node_modules/string-width": {
+ "version": "4.2.3",
+ "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
+ "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "emoji-regex": "^8.0.0",
+ "is-fullwidth-code-point": "^3.0.0",
+ "strip-ansi": "^6.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
"node_modules/cliui/node_modules/strip-ansi": {
"version": "6.0.1",
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
@@ -2377,6 +2899,16 @@
"url": "https://github.com/chalk/wrap-ansi?sponsor=1"
}
},
+ "node_modules/clone": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/clone/-/clone-1.0.4.tgz",
+ "integrity": "sha512-JQHZ2QMW6l3aH/j6xCqQThY/9OH4D/9ls34cgkUBiEeocRTU04tHfKPBsUK1PqZCUQM7GiA0IIXJSuXHI64Kbg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.8"
+ }
+ },
"node_modules/clone-response": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/clone-response/-/clone-response-1.0.3.tgz",
@@ -2458,23 +2990,11 @@
"license": "MIT"
},
"node_modules/core-util-is": {
- "version": "1.0.2",
- "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz",
- "integrity": "sha512-3lqz5YjWTYnW6dlDa5TLaTCcShfar1e40rmcJVwCBJC6mWlFuj0eCHIElmG1g5kyuJ/GD+8Wn4FFCcz4gJPfaQ==",
- "dev": true,
- "license": "MIT",
- "optional": true
- },
- "node_modules/crc": {
- "version": "3.8.0",
- "resolved": "https://registry.npmjs.org/crc/-/crc-3.8.0.tgz",
- "integrity": "sha512-iX3mfgcTMIq3ZKLIsVFAbv7+Mc10kxabAGQb8HvjA1o3T1PIYprbakQ65d3I+2HGHt6nSKkM9PYjgoJO2KcFBQ==",
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz",
+ "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==",
"dev": true,
- "license": "MIT",
- "optional": true,
- "dependencies": {
- "buffer": "^5.1.0"
- }
+ "license": "MIT"
},
"node_modules/cross-dirname": {
"version": "0.1.0",
@@ -2518,6 +3038,34 @@
"node": ">= 8"
}
},
+ "node_modules/css-tree": {
+ "version": "3.2.1",
+ "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-3.2.1.tgz",
+ "integrity": "sha512-X7sjQzceUhu1u7Y/ylrRZFU2FS6LRiFVp6rKLPg23y3x3c3DOKAwuXGDp+PAGjh6CSnCjYeAul8pcT8bAl+lSA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "mdn-data": "2.27.1",
+ "source-map-js": "^1.2.1"
+ },
+ "engines": {
+ "node": "^10 || ^12.20.0 || ^14.13.0 || >=15.0.0"
+ }
+ },
+ "node_modules/data-urls": {
+ "version": "7.0.0",
+ "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-7.0.0.tgz",
+ "integrity": "sha512-23XHcCF+coGYevirZceTVD7NdJOqVn+49IHyxgszm+JIiHLoB2TkmPtsYkNWT1pvRSGkc35L6NHs0yHkN2SumA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "whatwg-mimetype": "^5.0.0",
+ "whatwg-url": "^16.0.0"
+ },
+ "engines": {
+ "node": "^20.19.0 || ^22.12.0 || >=24.0.0"
+ }
+ },
"node_modules/debug": {
"version": "4.4.3",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz",
@@ -2535,6 +3083,13 @@
}
}
},
+ "node_modules/decimal.js": {
+ "version": "10.6.0",
+ "resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.6.0.tgz",
+ "integrity": "sha512-YpgQiITW3JXGntzdUmyUR1V812Hn8T1YVXhCu+wO3OpS4eU9l4YdD3qjyiKdV6mvV29zapkMeD390UVEf2lkUg==",
+ "dev": true,
+ "license": "MIT"
+ },
"node_modules/decompress-response": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-6.0.0.tgz",
@@ -2571,6 +3126,19 @@
"dev": true,
"license": "MIT"
},
+ "node_modules/defaults": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/defaults/-/defaults-1.0.4.tgz",
+ "integrity": "sha512-eFuaLoy/Rxalv2kr+lqMlUnrDWV+3j4pljOIJgLIhI058IQfWJ7vXhyEIHu+HtC738klGALYxOKDO0bQP3tg8A==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "clone": "^1.0.2"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
"node_modules/defer-to-connect": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/defer-to-connect/-/defer-to-connect-2.0.1.tgz",
@@ -2666,9 +3234,9 @@
"license": "MIT"
},
"node_modules/dir-compare/node_modules/brace-expansion": {
- "version": "1.1.14",
- "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.14.tgz",
- "integrity": "sha512-MWPGfDxnyzKU7rNOW9SP/c50vi3xrmrua/+6hfPbCS2ABNWfx24vPidzvC7krjU/RTo235sV776ymlsMtGKj8g==",
+ "version": "1.1.15",
+ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.15.tgz",
+ "integrity": "sha512-EwOCDEex4quD37XhqM3omwtMoJjr//isUZz1JopUNWms+4Z2ViyM/k1YIRePpoVNnQhENnxtFjLaxNHrT7xIUg==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -2690,47 +3258,16 @@
}
},
"node_modules/dmg-builder": {
- "version": "26.8.1",
- "resolved": "https://registry.npmjs.org/dmg-builder/-/dmg-builder-26.8.1.tgz",
- "integrity": "sha512-glMJgnTreo8CFINujtAhCgN96QAqApDMZ8Vl1r8f0QT8QprvC1UCltV4CcWj20YoIyLZx6IUskaJZ0NV8fokcg==",
+ "version": "26.15.0",
+ "resolved": "https://registry.npmjs.org/dmg-builder/-/dmg-builder-26.15.0.tgz",
+ "integrity": "sha512-oS8MWttbpIUF/2v8LOEY+f4ayL84ipMOarZvdRMl/pxlhLxAYjYMklTXHEXIl37Ig+qJv/bVF7HgyIoOoZyMWA==",
"dev": true,
"license": "MIT",
"dependencies": {
- "app-builder-lib": "26.8.1",
- "builder-util": "26.8.1",
+ "app-builder-lib": "26.15.0",
+ "builder-util": "26.15.0",
"fs-extra": "^10.1.0",
- "iconv-lite": "^0.6.2",
"js-yaml": "^4.1.0"
- },
- "optionalDependencies": {
- "dmg-license": "^1.0.11"
- }
- },
- "node_modules/dmg-license": {
- "version": "1.0.11",
- "resolved": "https://registry.npmjs.org/dmg-license/-/dmg-license-1.0.11.tgz",
- "integrity": "sha512-ZdzmqwKmECOWJpqefloC5OJy1+WZBBse5+MR88z9g9Zn4VY+WYUkAyojmhzJckH5YbbZGcYIuGAkY5/Ys5OM2Q==",
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "darwin"
- ],
- "dependencies": {
- "@types/plist": "^3.0.1",
- "@types/verror": "^1.10.3",
- "ajv": "^6.10.0",
- "crc": "^3.8.0",
- "iconv-corefoundation": "^1.1.7",
- "plist": "^3.0.4",
- "smart-buffer": "^4.0.2",
- "verror": "^1.10.0"
- },
- "bin": {
- "dmg-license": "bin/dmg-license.js"
- },
- "engines": {
- "node": ">=8"
}
},
"node_modules/dotenv": {
@@ -2806,6 +3343,56 @@
"node": ">= 0.4"
}
},
+ "node_modules/duplexer2": {
+ "version": "0.1.4",
+ "resolved": "https://registry.npmjs.org/duplexer2/-/duplexer2-0.1.4.tgz",
+ "integrity": "sha512-asLFVfWWtJ90ZyOUHMqk7/S2w2guQKxUI2itj3d92ADHhxUSbCMGi1f1cBcJ7xM1To+pE/Khbwo1yuNbMEPKeA==",
+ "dev": true,
+ "license": "BSD-3-Clause",
+ "dependencies": {
+ "readable-stream": "^2.0.2"
+ }
+ },
+ "node_modules/duplexer2/node_modules/readable-stream": {
+ "version": "2.3.8",
+ "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz",
+ "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "core-util-is": "~1.0.0",
+ "inherits": "~2.0.3",
+ "isarray": "~1.0.0",
+ "process-nextick-args": "~2.0.0",
+ "safe-buffer": "~5.1.1",
+ "string_decoder": "~1.1.1",
+ "util-deprecate": "~1.0.1"
+ }
+ },
+ "node_modules/duplexer2/node_modules/safe-buffer": {
+ "version": "5.1.2",
+ "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
+ "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/duplexer2/node_modules/string_decoder": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz",
+ "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "safe-buffer": "~5.1.0"
+ }
+ },
+ "node_modules/eastasianwidth": {
+ "version": "0.2.0",
+ "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz",
+ "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==",
+ "dev": true,
+ "license": "MIT"
+ },
"node_modules/ejs": {
"version": "3.1.10",
"resolved": "https://registry.npmjs.org/ejs/-/ejs-3.1.10.tgz",
@@ -2823,9 +3410,9 @@
}
},
"node_modules/electron": {
- "version": "42.2.0",
- "resolved": "https://registry.npmjs.org/electron/-/electron-42.2.0.tgz",
- "integrity": "sha512-b2Tc7sIKiZEl0tBVwFM5GJ+FT5KYhmy9QJHjx8BGVZPVW2SctXWEvrE959ElB56qw7H05dBkhlikDA1DmpaAMw==",
+ "version": "42.3.3",
+ "resolved": "https://registry.npmjs.org/electron/-/electron-42.3.3.tgz",
+ "integrity": "sha512-0MwYp9wTb7TrtTalOYqeW+suqd9T/Znstr/nDLKqFGIjHdBZX339guo3mQqTPURRZ/UQmYM4uMpzKpI5wLptfQ==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -2842,18 +3429,18 @@
}
},
"node_modules/electron-builder": {
- "version": "26.8.1",
- "resolved": "https://registry.npmjs.org/electron-builder/-/electron-builder-26.8.1.tgz",
- "integrity": "sha512-uWhx1r74NGpCagG0ULs/P9Nqv2nsoo+7eo4fLUOB8L8MdWltq9odW/uuLXMFCDGnPafknYLZgjNX0ZIFRzOQAw==",
+ "version": "26.15.0",
+ "resolved": "https://registry.npmjs.org/electron-builder/-/electron-builder-26.15.0.tgz",
+ "integrity": "sha512-zd4cfvjHmtyGqMaDudg5rAjNUkwIJDz8ICaCsz77hFKcjMQHcZNNNCs/C4phwN9+gEVwmhvpKMzNFum6fs/n6A==",
"dev": true,
"license": "MIT",
"dependencies": {
- "app-builder-lib": "26.8.1",
- "builder-util": "26.8.1",
- "builder-util-runtime": "9.5.1",
+ "app-builder-lib": "26.15.0",
+ "builder-util": "26.15.0",
+ "builder-util-runtime": "9.7.0",
"chalk": "^4.1.2",
"ci-info": "^4.2.0",
- "dmg-builder": "26.8.1",
+ "dmg-builder": "26.15.0",
"fs-extra": "^10.1.0",
"lazy-val": "^1.0.5",
"simple-update-notifier": "2.0.0",
@@ -2868,15 +3455,15 @@
}
},
"node_modules/electron-builder-squirrel-windows": {
- "version": "26.8.1",
- "resolved": "https://registry.npmjs.org/electron-builder-squirrel-windows/-/electron-builder-squirrel-windows-26.8.1.tgz",
- "integrity": "sha512-o288fIdgPLHA76eDrFADHPoo7VyGkDCYbLV1GzndaMSAVBoZrGvM9m2IehdcVMzdAZJ2eV9bgyissQXHv5tGzA==",
+ "version": "26.15.0",
+ "resolved": "https://registry.npmjs.org/electron-builder-squirrel-windows/-/electron-builder-squirrel-windows-26.15.0.tgz",
+ "integrity": "sha512-WyiGTdAHOk8oncyg/Ik0+NsrPI6y7ftG/eB7uxr9iGjmF9RNuIdeW/BRpI7iLEe718WkBQCbspUAq/4Vi31wXg==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
- "app-builder-lib": "26.8.1",
- "builder-util": "26.8.1",
+ "app-builder-lib": "26.15.0",
+ "builder-util": "26.15.0",
"electron-winstaller": "5.4.0"
}
},
@@ -2890,15 +3477,16 @@
}
},
"node_modules/electron-publish": {
- "version": "26.8.1",
- "resolved": "https://registry.npmjs.org/electron-publish/-/electron-publish-26.8.1.tgz",
- "integrity": "sha512-q+jrSTIh/Cv4eGZa7oVR+grEJo/FoLMYBAnSL5GCtqwUpr1T+VgKB/dn1pnzxIxqD8S/jP1yilT9VrwCqINR4w==",
+ "version": "26.15.0",
+ "resolved": "https://registry.npmjs.org/electron-publish/-/electron-publish-26.15.0.tgz",
+ "integrity": "sha512-pt6K3ol/a+o3HbqmYkL2NYlVH5pd34tL4FPRcgX8E88xQAqQyIsseXe4vWy7Pq2BaYy+iFGJrtInZe11FFAQwQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"@types/fs-extra": "^9.0.11",
- "builder-util": "26.8.1",
- "builder-util-runtime": "9.5.1",
+ "aws4": "^1.13.2",
+ "builder-util": "26.15.0",
+ "builder-util-runtime": "9.7.0",
"chalk": "^4.1.2",
"form-data": "^4.0.5",
"fs-extra": "^10.1.0",
@@ -2907,12 +3495,12 @@
}
},
"node_modules/electron-updater": {
- "version": "6.8.3",
- "resolved": "https://registry.npmjs.org/electron-updater/-/electron-updater-6.8.3.tgz",
- "integrity": "sha512-Z6sgw3jgbikWKXei1ENdqFOxBP0WlXg3TtKfz0rgw2vIZFJUyI4pD7ZN7jrkm7EoMK+tcm/qTnPUdqfZukBlBQ==",
+ "version": "6.8.9",
+ "resolved": "https://registry.npmjs.org/electron-updater/-/electron-updater-6.8.9.tgz",
+ "integrity": "sha512-ZhVxM9iGONUpZGI1FxdMRgJjUFXi7AYGVa5PwKlO1tV1/4zDxQmfKpXOHVztKrd6L9rLcFjERvi1Mf2vxyTkig==",
"license": "MIT",
"dependencies": {
- "builder-util-runtime": "9.5.1",
+ "builder-util-runtime": "9.7.0",
"fs-extra": "^10.1.0",
"js-yaml": "^4.1.0",
"lazy-val": "^1.0.5",
@@ -2995,19 +3583,19 @@
}
},
"node_modules/electron/node_modules/@types/node": {
- "version": "24.12.4",
- "resolved": "https://registry.npmjs.org/@types/node/-/node-24.12.4.tgz",
- "integrity": "sha512-GUUEShf+PBCGW2KaXwcIt3Yk+e3pkKwWKb9GSyM9WQVE+ep2jzmHdGsHzu4wgcZy5fN9FBdVzjpBQsYlpfpgLA==",
+ "version": "24.13.1",
+ "resolved": "https://registry.npmjs.org/@types/node/-/node-24.13.1.tgz",
+ "integrity": "sha512-RSpUJGmvsJ1ZeBehQZFhIdpsz+bIpES0nIQXko4Ybq+N+kX6XvOq3Jo+iJ82FWLdblFq85AsMikd3m35jgezYg==",
"dev": true,
"license": "MIT",
"dependencies": {
- "undici-types": "~7.16.0"
+ "undici-types": "~7.18.0"
}
},
"node_modules/electron/node_modules/undici-types": {
- "version": "7.16.0",
- "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.16.0.tgz",
- "integrity": "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==",
+ "version": "7.18.2",
+ "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.18.2.tgz",
+ "integrity": "sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w==",
"dev": true,
"license": "MIT"
},
@@ -3018,6 +3606,17 @@
"dev": true,
"license": "MIT"
},
+ "node_modules/encoding": {
+ "version": "0.1.13",
+ "resolved": "https://registry.npmjs.org/encoding/-/encoding-0.1.13.tgz",
+ "integrity": "sha512-ETBauow1T35Y/WZMkio9jiM0Z5xjHHmJ4XmjZOq1l/dXz3lr2sRn87nJy20RupqSh1F2m3HHPSp8ShIPQJrJ3A==",
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "dependencies": {
+ "iconv-lite": "^0.6.2"
+ }
+ },
"node_modules/end-of-stream": {
"version": "1.4.5",
"resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.5.tgz",
@@ -3028,6 +3627,19 @@
"once": "^1.4.0"
}
},
+ "node_modules/entities": {
+ "version": "8.0.0",
+ "resolved": "https://registry.npmjs.org/entities/-/entities-8.0.0.tgz",
+ "integrity": "sha512-zwfzJecQ/Uej6tusMqwAqU/6KL2XaB2VZ2Jg54Je6ahNBGNH6Ek6g3jjNCF0fG9EWQKGZNddNjU5F1ZQn/sBnA==",
+ "dev": true,
+ "license": "BSD-2-Clause",
+ "engines": {
+ "node": ">=20.19.0"
+ },
+ "funding": {
+ "url": "https://github.com/fb55/entities?sponsor=1"
+ }
+ },
"node_modules/env-paths": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/env-paths/-/env-paths-3.0.0.tgz",
@@ -3149,9 +3761,9 @@
}
},
"node_modules/eslint": {
- "version": "10.4.0",
- "resolved": "https://registry.npmjs.org/eslint/-/eslint-10.4.0.tgz",
- "integrity": "sha512-loXy6bWOoP3EP6JA7jo6p5jMpBJmHmsNZM5SFRHLdh1MGOPurMnNBj4ZlAbaqUAaQWbCr7jHV4P7gzAyryZWkQ==",
+ "version": "10.4.1",
+ "resolved": "https://registry.npmjs.org/eslint/-/eslint-10.4.1.tgz",
+ "integrity": "sha512-AyIKhnOBuOAdueD7RB3xB+YeAWScb9jHsJBgH2Hcde8InP5JYhqrRR6iTMHyTEwgENK54Cp44e4v8BwNhsuHuw==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -3160,7 +3772,7 @@
"@eslint/config-array": "^0.23.5",
"@eslint/config-helpers": "^0.6.0",
"@eslint/core": "^1.2.1",
- "@eslint/plugin-kit": "^0.7.1",
+ "@eslint/plugin-kit": "^0.7.2",
"@humanfs/node": "^0.16.6",
"@humanwhocodes/module-importer": "^1.0.1",
"@humanwhocodes/retry": "^0.4.2",
@@ -3236,6 +3848,30 @@
"url": "https://opencollective.com/eslint"
}
},
+ "node_modules/eslint/node_modules/ajv": {
+ "version": "6.15.0",
+ "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.15.0.tgz",
+ "integrity": "sha512-fgFx7Hfoq60ytK2c7DhnF8jIvzYgOMxfugjLOSMHjLIPgenqa7S7oaagATUq99mV6IYvN2tRmC0wnTYX6iPbMw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "fast-deep-equal": "^3.1.1",
+ "fast-json-stable-stringify": "^2.0.0",
+ "json-schema-traverse": "^0.4.1",
+ "uri-js": "^4.2.2"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/epoberezkin"
+ }
+ },
+ "node_modules/eslint/node_modules/json-schema-traverse": {
+ "version": "0.4.1",
+ "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz",
+ "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==",
+ "dev": true,
+ "license": "MIT"
+ },
"node_modules/espree": {
"version": "11.2.0",
"resolved": "https://registry.npmjs.org/espree/-/espree-11.2.0.tgz",
@@ -3355,17 +3991,6 @@
"@types/yauzl": "^2.9.1"
}
},
- "node_modules/extsprintf": {
- "version": "1.4.1",
- "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.4.1.tgz",
- "integrity": "sha512-Wrk35e8ydCKDj/ArClo1VrPVmN8zph5V4AtHwIuHhvMXsKf73UT3BOD+azBIW+3wOJ4FhEH7zyaJCFvChjYvMA==",
- "dev": true,
- "engines": [
- "node >=0.6.0"
- ],
- "license": "MIT",
- "optional": true
- },
"node_modules/fast-deep-equal": {
"version": "3.1.3",
"resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
@@ -3387,6 +4012,23 @@
"dev": true,
"license": "MIT"
},
+ "node_modules/fast-uri": {
+ "version": "3.1.2",
+ "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.2.tgz",
+ "integrity": "sha512-rVjf7ArG3LTk+FS6Yw81V1DLuZl1bRbNrev6Tmd/9RaroeeRRJhAt7jg/6YFxbvAQXUCavSoZhPPj6oOx+5KjQ==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/fastify"
+ },
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/fastify"
+ }
+ ],
+ "license": "BSD-3-Clause"
+ },
"node_modules/fd-slicer": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/fd-slicer/-/fd-slicer-1.1.0.tgz",
@@ -3446,9 +4088,9 @@
"license": "MIT"
},
"node_modules/filelist/node_modules/brace-expansion": {
- "version": "2.1.0",
- "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.0.tgz",
- "integrity": "sha512-TN1kCZAgdgweJhWWpgKYrQaMNHcDULHkWwQIspdtjV4Y5aurRdZpjAqn6yX3FPqTA9ngHCc4hJxMAMgGfve85w==",
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.1.tgz",
+ "integrity": "sha512-WR1cURNjuvBLMZBMbqM0UoE+WAfdUcEV1ccD8PVBVOI+Z3ND4+SZbN8RsfT2bMuG1qwz5RFvPukSZm5fF2D5eA==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -3506,6 +4148,36 @@
"dev": true,
"license": "ISC"
},
+ "node_modules/foreground-child": {
+ "version": "3.3.1",
+ "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.1.tgz",
+ "integrity": "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "cross-spawn": "^7.0.6",
+ "signal-exit": "^4.0.1"
+ },
+ "engines": {
+ "node": ">=14"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/isaacs"
+ }
+ },
+ "node_modules/foreground-child/node_modules/signal-exit": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz",
+ "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==",
+ "dev": true,
+ "license": "ISC",
+ "engines": {
+ "node": ">=14"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/isaacs"
+ }
+ },
"node_modules/form-data": {
"version": "4.0.5",
"resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.5.tgz",
@@ -3537,6 +4209,19 @@
"node": ">=12"
}
},
+ "node_modules/fs-minipass": {
+ "version": "3.0.3",
+ "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-3.0.3.tgz",
+ "integrity": "sha512-XUBA9XClHbnJWSfBzjkm6RvPsyg3sryZt06BEQoXcF7EK/xpGaQYJgQKDJSUH5SGZ76Y7pFx1QBnXz09rU5Fbw==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "minipass": "^7.0.3"
+ },
+ "engines": {
+ "node": "^14.17.0 || ^16.13.0 || >=18.0.0"
+ }
+ },
"node_modules/fs.realpath": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
@@ -3690,9 +4375,9 @@
"license": "MIT"
},
"node_modules/glob/node_modules/brace-expansion": {
- "version": "1.1.14",
- "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.14.tgz",
- "integrity": "sha512-MWPGfDxnyzKU7rNOW9SP/c50vi3xrmrua/+6hfPbCS2ABNWfx24vPidzvC7krjU/RTo235sV776ymlsMtGKj8g==",
+ "version": "1.1.15",
+ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.15.tgz",
+ "integrity": "sha512-EwOCDEex4quD37XhqM3omwtMoJjr//isUZz1JopUNWms+4Z2ViyM/k1YIRePpoVNnQhENnxtFjLaxNHrT7xIUg==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -3849,9 +4534,9 @@
}
},
"node_modules/hasown": {
- "version": "2.0.3",
- "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.3.tgz",
- "integrity": "sha512-ej4AhfhfL2Q2zpMmLo7U1Uv9+PyhIZpgQLGT1F9miIGmiCJIoCgSmczFdrc97mWT4kVY72KA+WnnhJ5pghSvSg==",
+ "version": "2.0.4",
+ "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.4.tgz",
+ "integrity": "sha512-T2UbfbBEF32wiepXIsMlTW9+dDYC6wMh/t/vYA4tuOMKqWz/n3vr1NFSxQiyP+zk2mXsoMA/i/7qV6LKut1t1A==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -3874,6 +4559,19 @@
"node": ">=10"
}
},
+ "node_modules/html-encoding-sniffer": {
+ "version": "6.0.0",
+ "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-6.0.0.tgz",
+ "integrity": "sha512-CV9TW3Y3f8/wT0BRFc1/KAVQ3TUHiXmaAb6VW9vtiMFf7SLoMd1PdAc4W3KFOFETBJUb90KatHqlsZMWV+R9Gg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@exodus/bytes": "^1.6.0"
+ },
+ "engines": {
+ "node": "^20.19.0 || ^22.12.0 || >=24.0.0"
+ }
+ },
"node_modules/html-escaper": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz",
@@ -3946,30 +4644,13 @@
"url": "https://github.com/sponsors/typicode"
}
},
- "node_modules/iconv-corefoundation": {
- "version": "1.1.7",
- "resolved": "https://registry.npmjs.org/iconv-corefoundation/-/iconv-corefoundation-1.1.7.tgz",
- "integrity": "sha512-T10qvkw0zz4wnm560lOEg0PovVqUXuOFhhHAkixw8/sycy7TJt7v/RrkEKEQnAw2viPSJu6iAkErxnzR0g8PpQ==",
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "darwin"
- ],
- "dependencies": {
- "cli-truncate": "^2.1.0",
- "node-addon-api": "^1.6.3"
- },
- "engines": {
- "node": "^8.11.2 || >=10"
- }
- },
"node_modules/iconv-lite": {
"version": "0.6.3",
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz",
"integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==",
"dev": true,
"license": "MIT",
+ "optional": true,
"dependencies": {
"safer-buffer": ">= 2.1.2 < 3.0.0"
},
@@ -3996,8 +4677,7 @@
"url": "https://feross.org/support"
}
],
- "license": "BSD-3-Clause",
- "optional": true
+ "license": "BSD-3-Clause"
},
"node_modules/ignore": {
"version": "5.3.2",
@@ -4038,6 +4718,16 @@
"dev": true,
"license": "ISC"
},
+ "node_modules/ip-address": {
+ "version": "10.2.0",
+ "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-10.2.0.tgz",
+ "integrity": "sha512-/+S6j4E9AHvW9SWMSEY9Xfy66O5PWvVEJ08O0y5JGyEKQpojb0K0GKpz/v5HJ/G0vi3D2sjGK78119oXZeE0qA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 12"
+ }
+ },
"node_modules/is-extglob": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
@@ -4049,13 +4739,19 @@
}
},
"node_modules/is-fullwidth-code-point": {
- "version": "3.0.0",
- "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz",
- "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==",
+ "version": "5.1.0",
+ "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-5.1.0.tgz",
+ "integrity": "sha512-5XHYaSyiqADb4RnZ1Bdad6cPp8Toise4TzEjcOYDHZkTCbKgiUl7WTUCpNWHuxmDt91wnsZBc9xinNzopv3JMQ==",
"dev": true,
"license": "MIT",
+ "dependencies": {
+ "get-east-asian-width": "^1.3.1"
+ },
"engines": {
- "node": ">=8"
+ "node": ">=18"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/is-glob": {
@@ -4071,6 +4767,43 @@
"node": ">=0.10.0"
}
},
+ "node_modules/is-interactive": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/is-interactive/-/is-interactive-1.0.0.tgz",
+ "integrity": "sha512-2HvIEKRoqS62guEC+qBjpvRubdX910WCMuJTZ+I9yvqKU2/12eSL549HMwtabb4oupdj2sMP50k+XJfB/8JE6w==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/is-potential-custom-element-name": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz",
+ "integrity": "sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/is-unicode-supported": {
+ "version": "0.1.0",
+ "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz",
+ "integrity": "sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/isarray": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz",
+ "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==",
+ "dev": true,
+ "license": "MIT"
+ },
"node_modules/isbinaryfile": {
"version": "5.0.7",
"resolved": "https://registry.npmjs.org/isbinaryfile/-/isbinaryfile-5.0.7.tgz",
@@ -4130,6 +4863,22 @@
"node": ">=8"
}
},
+ "node_modules/jackspeak": {
+ "version": "3.4.3",
+ "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz",
+ "integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==",
+ "dev": true,
+ "license": "BlueOak-1.0.0",
+ "dependencies": {
+ "@isaacs/cliui": "^8.0.2"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/isaacs"
+ },
+ "optionalDependencies": {
+ "@pkgjs/parseargs": "^0.11.0"
+ }
+ },
"node_modules/jake": {
"version": "10.9.4",
"resolved": "https://registry.npmjs.org/jake/-/jake-10.9.4.tgz",
@@ -4166,9 +4915,19 @@
"license": "MIT"
},
"node_modules/js-yaml": {
- "version": "4.1.1",
- "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz",
- "integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==",
+ "version": "4.2.0",
+ "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.2.0.tgz",
+ "integrity": "sha512-ePWsvanv0DWuDRsW8dnt+R4jQ31SCRCQ7hhNcPXZPsoBZiemuZNYGf7adZdqX2D86j6rvKp3RpCxVTSb8WQlOw==",
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/puzrin"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/nodeca"
+ }
+ ],
"license": "MIT",
"dependencies": {
"argparse": "^2.0.1"
@@ -4177,6 +4936,57 @@
"js-yaml": "bin/js-yaml.js"
}
},
+ "node_modules/jsdom": {
+ "version": "29.1.1",
+ "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-29.1.1.tgz",
+ "integrity": "sha512-ECi4Fi2f7BdJtUKTflYRTiaMxIB0O6zfR1fX0GXpUrf6flp8QIYn1UT20YQqdSOfk2dfkCwS8LAFoJDEppNK5Q==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@asamuzakjp/css-color": "^5.1.11",
+ "@asamuzakjp/dom-selector": "^7.1.1",
+ "@bramus/specificity": "^2.4.2",
+ "@csstools/css-syntax-patches-for-csstree": "^1.1.3",
+ "@exodus/bytes": "^1.15.0",
+ "css-tree": "^3.2.1",
+ "data-urls": "^7.0.0",
+ "decimal.js": "^10.6.0",
+ "html-encoding-sniffer": "^6.0.0",
+ "is-potential-custom-element-name": "^1.0.1",
+ "lru-cache": "^11.3.5",
+ "parse5": "^8.0.1",
+ "saxes": "^6.0.0",
+ "symbol-tree": "^3.2.4",
+ "tough-cookie": "^6.0.1",
+ "undici": "^7.25.0",
+ "w3c-xmlserializer": "^5.0.0",
+ "webidl-conversions": "^8.0.1",
+ "whatwg-mimetype": "^5.0.0",
+ "whatwg-url": "^16.0.1",
+ "xml-name-validator": "^5.0.0"
+ },
+ "engines": {
+ "node": "^20.19.0 || ^22.13.0 || >=24.0.0"
+ },
+ "peerDependencies": {
+ "canvas": "^3.0.0"
+ },
+ "peerDependenciesMeta": {
+ "canvas": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/jsdom/node_modules/lru-cache": {
+ "version": "11.5.1",
+ "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.5.1.tgz",
+ "integrity": "sha512-RPimw/7aMdv2oqRrxKwvZXcPfwBrn/JZ2xYcY9Hus/6LaS3VOAKVWKWgNLCFSiOm1ESXinjsDlidVU7JlnCN2A==",
+ "dev": true,
+ "license": "BlueOak-1.0.0",
+ "engines": {
+ "node": "20 || >=22"
+ }
+ },
"node_modules/json-buffer": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz",
@@ -4185,9 +4995,9 @@
"license": "MIT"
},
"node_modules/json-schema-traverse": {
- "version": "0.4.1",
- "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz",
- "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==",
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz",
+ "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==",
"dev": true,
"license": "MIT"
},
@@ -4535,16 +5345,16 @@
}
},
"node_modules/lint-staged": {
- "version": "17.0.5",
- "resolved": "https://registry.npmjs.org/lint-staged/-/lint-staged-17.0.5.tgz",
- "integrity": "sha512-d12yC+/e8RhBjZtaxZn71FyrgU/P5e+uAPifhCLwdosQZP/zamSdKRWDC30ocVIbzDKiFG1McHc/LUgB92GIPw==",
+ "version": "17.0.7",
+ "resolved": "https://registry.npmjs.org/lint-staged/-/lint-staged-17.0.7.tgz",
+ "integrity": "sha512-JrSobt+tW3rH8IOMi8tDZd3foorM5yPEkLD/V2NxobgHrFfHWGee4MOLVuZeScgxftEwbHrPHIFA/ZL+nUJeuA==",
"dev": true,
"license": "MIT",
"dependencies": {
"listr2": "^10.2.1",
"picomatch": "^4.0.4",
"string-argv": "^0.3.2",
- "tinyexec": "^1.1.2"
+ "tinyexec": "^1.2.4"
},
"bin": {
"lint-staged": "bin/lint-staged.js"
@@ -4556,7 +5366,7 @@
"url": "https://opencollective.com/lint-staged"
},
"optionalDependencies": {
- "yaml": "^2.8.4"
+ "yaml": "^2.9.0"
}
},
"node_modules/listr2": {
@@ -4576,106 +5386,26 @@
"node": ">=22.13.0"
}
},
- "node_modules/listr2/node_modules/ansi-styles": {
- "version": "6.2.3",
- "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz",
- "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=12"
- },
- "funding": {
- "url": "https://github.com/chalk/ansi-styles?sponsor=1"
- }
- },
- "node_modules/listr2/node_modules/cli-truncate": {
- "version": "5.2.0",
- "resolved": "https://registry.npmjs.org/cli-truncate/-/cli-truncate-5.2.0.tgz",
- "integrity": "sha512-xRwvIOMGrfOAnM1JYtqQImuaNtDEv9v6oIYAs4LIHwTiKee8uwvIi363igssOC0O5U04i4AlENs79LQLu9tEMw==",
+ "node_modules/locate-path": {
+ "version": "6.0.0",
+ "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz",
+ "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==",
"dev": true,
"license": "MIT",
"dependencies": {
- "slice-ansi": "^8.0.0",
- "string-width": "^8.2.0"
+ "p-locate": "^5.0.0"
},
"engines": {
- "node": ">=20"
+ "node": ">=10"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
- "node_modules/listr2/node_modules/is-fullwidth-code-point": {
- "version": "5.1.0",
- "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-5.1.0.tgz",
- "integrity": "sha512-5XHYaSyiqADb4RnZ1Bdad6cPp8Toise4TzEjcOYDHZkTCbKgiUl7WTUCpNWHuxmDt91wnsZBc9xinNzopv3JMQ==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "get-east-asian-width": "^1.3.1"
- },
- "engines": {
- "node": ">=18"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
- }
- },
- "node_modules/listr2/node_modules/slice-ansi": {
- "version": "8.0.0",
- "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-8.0.0.tgz",
- "integrity": "sha512-stxByr12oeeOyY2BlviTNQlYV5xOj47GirPr4yA1hE9JCtxfQN0+tVbkxwCtYDQWhEKWFHsEK48ORg5jrouCAg==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "ansi-styles": "^6.2.3",
- "is-fullwidth-code-point": "^5.1.0"
- },
- "engines": {
- "node": ">=20"
- },
- "funding": {
- "url": "https://github.com/chalk/slice-ansi?sponsor=1"
- }
- },
- "node_modules/listr2/node_modules/string-width": {
- "version": "8.2.1",
- "resolved": "https://registry.npmjs.org/string-width/-/string-width-8.2.1.tgz",
- "integrity": "sha512-IIaP0g3iy9Cyy18w3M9YcaDudujEAVHKt3a3QJg1+sr/oX96TbaGUubG0hJyCjCBThFH+tFpcIyoUHUn1ogaLA==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "get-east-asian-width": "^1.5.0",
- "strip-ansi": "^7.1.2"
- },
- "engines": {
- "node": ">=20"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
- }
- },
- "node_modules/locate-path": {
- "version": "6.0.0",
- "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz",
- "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "p-locate": "^5.0.0"
- },
- "engines": {
- "node": ">=10"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
- }
- },
- "node_modules/lodash": {
- "version": "4.18.1",
- "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.18.1.tgz",
- "integrity": "sha512-dMInicTPVE8d1e5otfwmmjlxkZoUpiVLwyeTdUsi/Caj/gfzzblBcCE5sRHV/AsjuCmxWrte2TNGSYuCeCq+0Q==",
+ "node_modules/lodash": {
+ "version": "4.18.1",
+ "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.18.1.tgz",
+ "integrity": "sha512-dMInicTPVE8d1e5otfwmmjlxkZoUpiVLwyeTdUsi/Caj/gfzzblBcCE5sRHV/AsjuCmxWrte2TNGSYuCeCq+0Q==",
"dev": true,
"license": "MIT"
},
@@ -4692,6 +5422,23 @@
"deprecated": "This package is deprecated. Use require('node:util').isDeepStrictEqual instead.",
"license": "MIT"
},
+ "node_modules/log-symbols": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz",
+ "integrity": "sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "chalk": "^4.1.0",
+ "is-unicode-supported": "^0.1.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
"node_modules/log-update": {
"version": "6.1.0",
"resolved": "https://registry.npmjs.org/log-update/-/log-update-6.1.0.tgz",
@@ -4732,22 +5479,6 @@
"dev": true,
"license": "MIT"
},
- "node_modules/log-update/node_modules/is-fullwidth-code-point": {
- "version": "5.1.0",
- "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-5.1.0.tgz",
- "integrity": "sha512-5XHYaSyiqADb4RnZ1Bdad6cPp8Toise4TzEjcOYDHZkTCbKgiUl7WTUCpNWHuxmDt91wnsZBc9xinNzopv3JMQ==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "get-east-asian-width": "^1.3.1"
- },
- "engines": {
- "node": ">=18"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
- }
- },
"node_modules/log-update/node_modules/slice-ansi": {
"version": "7.1.2",
"resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-7.1.2.tgz",
@@ -4862,6 +5593,29 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
+ "node_modules/make-fetch-happen": {
+ "version": "14.0.3",
+ "resolved": "https://registry.npmjs.org/make-fetch-happen/-/make-fetch-happen-14.0.3.tgz",
+ "integrity": "sha512-QMjGbFTP0blj97EeidG5hk/QhKQ3T4ICckQGLgz38QF7Vgbk6e6FTARN8KhKxyBbWn8R0HU+bnw8aSoFPD4qtQ==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "@npmcli/agent": "^3.0.0",
+ "cacache": "^19.0.1",
+ "http-cache-semantics": "^4.1.1",
+ "minipass": "^7.0.2",
+ "minipass-fetch": "^4.0.0",
+ "minipass-flush": "^1.0.5",
+ "minipass-pipeline": "^1.2.4",
+ "negotiator": "^1.0.0",
+ "proc-log": "^5.0.0",
+ "promise-retry": "^2.0.1",
+ "ssri": "^12.0.0"
+ },
+ "engines": {
+ "node": "^18.17.0 || >=20.5.0"
+ }
+ },
"node_modules/matcher": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/matcher/-/matcher-3.0.0.tgz",
@@ -4886,6 +5640,13 @@
"node": ">= 0.4"
}
},
+ "node_modules/mdn-data": {
+ "version": "2.27.1",
+ "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.27.1.tgz",
+ "integrity": "sha512-9Yubnt3e8A0OKwxYSXyhLymGW4sCufcLG6VdiDdUGVkPhpqLxlvP5vl1983gQjJl3tqbrM731mjaZaP68AgosQ==",
+ "dev": true,
+ "license": "CC0-1.0"
+ },
"node_modules/mime": {
"version": "2.6.0",
"resolved": "https://registry.npmjs.org/mime/-/mime-2.6.0.tgz",
@@ -4922,6 +5683,16 @@
"node": ">= 0.6"
}
},
+ "node_modules/mimic-fn": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz",
+ "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ }
+ },
"node_modules/mimic-function": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/mimic-function/-/mimic-function-5.0.1.tgz",
@@ -4981,6 +5752,115 @@
"node": ">=16 || 14 >=14.17"
}
},
+ "node_modules/minipass-collect": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/minipass-collect/-/minipass-collect-2.0.1.tgz",
+ "integrity": "sha512-D7V8PO9oaz7PWGLbCACuI1qEOsq7UKfLotx/C0Aet43fCUB/wfQ7DYeq2oR/svFJGYDHPr38SHATeaj/ZoKHKw==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "minipass": "^7.0.3"
+ },
+ "engines": {
+ "node": ">=16 || 14 >=14.17"
+ }
+ },
+ "node_modules/minipass-fetch": {
+ "version": "4.0.1",
+ "resolved": "https://registry.npmjs.org/minipass-fetch/-/minipass-fetch-4.0.1.tgz",
+ "integrity": "sha512-j7U11C5HXigVuutxebFadoYBbd7VSdZWggSe64NVdvWNBqGAiXPL2QVCehjmw7lY1oF9gOllYbORh+hiNgfPgQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "minipass": "^7.0.3",
+ "minipass-sized": "^1.0.3",
+ "minizlib": "^3.0.1"
+ },
+ "engines": {
+ "node": "^18.17.0 || >=20.5.0"
+ },
+ "optionalDependencies": {
+ "encoding": "^0.1.13"
+ }
+ },
+ "node_modules/minipass-flush": {
+ "version": "1.0.7",
+ "resolved": "https://registry.npmjs.org/minipass-flush/-/minipass-flush-1.0.7.tgz",
+ "integrity": "sha512-TbqTz9cUwWyHS2Dy89P3ocAGUGxKjjLuR9z8w4WUTGAVgEj17/4nhgo2Du56i0Fm3Pm30g4iA8Lcqctc76jCzA==",
+ "dev": true,
+ "license": "BlueOak-1.0.0",
+ "dependencies": {
+ "minipass": "^3.0.0"
+ },
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/minipass-flush/node_modules/minipass": {
+ "version": "3.3.6",
+ "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz",
+ "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "yallist": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/minipass-pipeline": {
+ "version": "1.2.4",
+ "resolved": "https://registry.npmjs.org/minipass-pipeline/-/minipass-pipeline-1.2.4.tgz",
+ "integrity": "sha512-xuIq7cIOt09RPRJ19gdi4b+RiNvDFYe5JH+ggNvBqGqpQXcru3PcRmOZuHBKWK1Txf9+cQ+HMVN4d6z46LZP7A==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "minipass": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/minipass-pipeline/node_modules/minipass": {
+ "version": "3.3.6",
+ "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz",
+ "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "yallist": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/minipass-sized": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/minipass-sized/-/minipass-sized-1.0.3.tgz",
+ "integrity": "sha512-MbkQQ2CTiBMlA2Dm/5cY+9SWFEN8pzzOXi6rlM5Xxq0Yqbda5ZQy9sU75a673FE9ZK0Zsbr6Y5iP6u9nktfg2g==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "minipass": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/minipass-sized/node_modules/minipass": {
+ "version": "3.3.6",
+ "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz",
+ "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "yallist": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
"node_modules/minizlib": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/minizlib/-/minizlib-3.1.0.tgz",
@@ -5040,6 +5920,16 @@
"dev": true,
"license": "MIT"
},
+ "node_modules/negotiator": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-1.0.0.tgz",
+ "integrity": "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
"node_modules/node-abi": {
"version": "4.31.0",
"resolved": "https://registry.npmjs.org/node-abi/-/node-abi-4.31.0.tgz",
@@ -5053,14 +5943,6 @@
"node": ">=22.12.0"
}
},
- "node_modules/node-addon-api": {
- "version": "1.7.2",
- "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-1.7.2.tgz",
- "integrity": "sha512-ibPK3iA+vaY1eEjESkQkM0BbCqFOaZMiXRTtdB0u7b4djtY6JnsjvPdUHVMg6xQt3B8fpTTWHI9A+ADjM9frzg==",
- "dev": true,
- "license": "MIT",
- "optional": true
- },
"node_modules/node-api-version": {
"version": "0.2.1",
"resolved": "https://registry.npmjs.org/node-api-version/-/node-api-version-0.2.1.tgz",
@@ -5072,28 +5954,28 @@
}
},
"node_modules/node-gyp": {
- "version": "12.3.0",
- "resolved": "https://registry.npmjs.org/node-gyp/-/node-gyp-12.3.0.tgz",
- "integrity": "sha512-QNcUWM+HgJplcPzBvFBZ9VXacyGZ4+VTOb80PwWR+TlVzoHbRKULNEzpRsnaoxG3Wzr7Qh7BYxGDU3CbKib2Yg==",
+ "version": "11.5.0",
+ "resolved": "https://registry.npmjs.org/node-gyp/-/node-gyp-11.5.0.tgz",
+ "integrity": "sha512-ra7Kvlhxn5V9Slyus0ygMa2h+UqExPqUIkfk7Pc8QTLT956JLSy51uWFwHtIYy0vI8cB4BDhc/S03+880My/LQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"env-paths": "^2.2.0",
"exponential-backoff": "^3.1.1",
"graceful-fs": "^4.2.6",
- "nopt": "^9.0.0",
- "proc-log": "^6.0.0",
+ "make-fetch-happen": "^14.0.3",
+ "nopt": "^8.0.0",
+ "proc-log": "^5.0.0",
"semver": "^7.3.5",
- "tar": "^7.5.4",
+ "tar": "^7.4.3",
"tinyglobby": "^0.2.12",
- "undici": "^6.25.0",
- "which": "^6.0.0"
+ "which": "^5.0.0"
},
"bin": {
"node-gyp": "bin/node-gyp.js"
},
"engines": {
- "node": "^20.17.0 || >=22.9.0"
+ "node": "^18.17.0 || >=20.5.0"
}
},
"node_modules/node-gyp/node_modules/env-paths": {
@@ -5107,55 +5989,52 @@
}
},
"node_modules/node-gyp/node_modules/isexe": {
- "version": "4.0.0",
- "resolved": "https://registry.npmjs.org/isexe/-/isexe-4.0.0.tgz",
- "integrity": "sha512-FFUtZMpoZ8RqHS3XeXEmHWLA4thH+ZxCv2lOiPIn1Xc7CxrqhWzNSDzD+/chS/zbYezmiwWLdQC09JdQKmthOw==",
+ "version": "3.1.5",
+ "resolved": "https://registry.npmjs.org/isexe/-/isexe-3.1.5.tgz",
+ "integrity": "sha512-6B3tLtFqtQS4ekarvLVMZ+X+VlvQekbe4taUkf/rhVO3d/h0M2rfARm/pXLcPEsjjMsFgrFgSrhQIxcSVrBz8w==",
"dev": true,
"license": "BlueOak-1.0.0",
"engines": {
- "node": ">=20"
- }
- },
- "node_modules/node-gyp/node_modules/undici": {
- "version": "6.25.0",
- "resolved": "https://registry.npmjs.org/undici/-/undici-6.25.0.tgz",
- "integrity": "sha512-ZgpWDC5gmNiuY9CnLVXEH8rl50xhRCuLNA97fAUnKi8RRuV4E6KG31pDTsLVUKnohJE0I3XDrTeEydAXRw47xg==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=18.17"
+ "node": ">=18"
}
},
"node_modules/node-gyp/node_modules/which": {
- "version": "6.0.1",
- "resolved": "https://registry.npmjs.org/which/-/which-6.0.1.tgz",
- "integrity": "sha512-oGLe46MIrCRqX7ytPUf66EAYvdeMIZYn3WaocqqKZAxrBpkqHfL/qvTyJ/bTk5+AqHCjXmrv3CEWgy368zhRUg==",
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/which/-/which-5.0.0.tgz",
+ "integrity": "sha512-JEdGzHwwkrbWoGOlIHqQ5gtprKGOenpDHpxE9zVR1bWbOtYRyPPHMe9FaP6x61CmNaTThSkb0DAJte5jD+DmzQ==",
"dev": true,
"license": "ISC",
"dependencies": {
- "isexe": "^4.0.0"
+ "isexe": "^3.1.1"
},
"bin": {
"node-which": "bin/which.js"
},
"engines": {
- "node": "^20.17.0 || >=22.9.0"
+ "node": "^18.17.0 || >=20.5.0"
}
},
+ "node_modules/node-int64": {
+ "version": "0.4.0",
+ "resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz",
+ "integrity": "sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw==",
+ "dev": true,
+ "license": "MIT"
+ },
"node_modules/nopt": {
- "version": "9.0.0",
- "resolved": "https://registry.npmjs.org/nopt/-/nopt-9.0.0.tgz",
- "integrity": "sha512-Zhq3a+yFKrYwSBluL4H9XP3m3y5uvQkB/09CwDruCiRmR/UJYnn9W4R48ry0uGC70aeTPKLynBtscP9efFFcPw==",
+ "version": "8.1.0",
+ "resolved": "https://registry.npmjs.org/nopt/-/nopt-8.1.0.tgz",
+ "integrity": "sha512-ieGu42u/Qsa4TFktmaKEwM6MQH0pOWnaB3htzh0JRtx84+Mebc0cbZYN5bC+6WTZ4+77xrL9Pn5m7CV6VIkV7A==",
"dev": true,
"license": "ISC",
"dependencies": {
- "abbrev": "^4.0.0"
+ "abbrev": "^3.0.0"
},
"bin": {
"nopt": "bin/nopt.js"
},
"engines": {
- "node": "^20.17.0 || >=22.9.0"
+ "node": "^18.17.0 || >=20.5.0"
}
},
"node_modules/normalize-url": {
@@ -5183,15 +6062,18 @@
}
},
"node_modules/obug": {
- "version": "2.1.1",
- "resolved": "https://registry.npmjs.org/obug/-/obug-2.1.1.tgz",
- "integrity": "sha512-uTqF9MuPraAQ+IsnPf366RG4cP9RtUi7MLO1N3KEc+wb0a6yKpeL0lmk2IB1jY5KHPAlTc6T/JRdC/YqxHNwkQ==",
+ "version": "2.1.2",
+ "resolved": "https://registry.npmjs.org/obug/-/obug-2.1.2.tgz",
+ "integrity": "sha512-AWGB9WFcRXOQs48Z/udjI5ZcZMHXwX8XPByNpOydgcGsDLIzjGizhoMWJyKAWze7AVW/2W1i+/gPX4YtKe5cyg==",
"dev": true,
"funding": [
"https://github.com/sponsors/sxzz",
"https://opencollective.com/debug"
],
- "license": "MIT"
+ "license": "MIT",
+ "engines": {
+ "node": ">=12.20.0"
+ }
},
"node_modules/once": {
"version": "1.4.0",
@@ -5237,6 +6119,96 @@
"node": ">= 0.8.0"
}
},
+ "node_modules/ora": {
+ "version": "5.4.1",
+ "resolved": "https://registry.npmjs.org/ora/-/ora-5.4.1.tgz",
+ "integrity": "sha512-5b6Y85tPxZZ7QytO+BQzysW31HJku27cRIlkbAXaNx+BdcVi+LlRFmVXzeF6a7JCwJpyw5c4b+YSVImQIrBpuQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "bl": "^4.1.0",
+ "chalk": "^4.1.0",
+ "cli-cursor": "^3.1.0",
+ "cli-spinners": "^2.5.0",
+ "is-interactive": "^1.0.0",
+ "is-unicode-supported": "^0.1.0",
+ "log-symbols": "^4.1.0",
+ "strip-ansi": "^6.0.0",
+ "wcwidth": "^1.0.1"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/ora/node_modules/ansi-regex": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
+ "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/ora/node_modules/cli-cursor": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-3.1.0.tgz",
+ "integrity": "sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "restore-cursor": "^3.1.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/ora/node_modules/onetime": {
+ "version": "5.1.2",
+ "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz",
+ "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "mimic-fn": "^2.1.0"
+ },
+ "engines": {
+ "node": ">=6"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/ora/node_modules/restore-cursor": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-3.1.0.tgz",
+ "integrity": "sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "onetime": "^5.1.0",
+ "signal-exit": "^3.0.2"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/ora/node_modules/strip-ansi": {
+ "version": "6.0.1",
+ "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
+ "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "ansi-regex": "^5.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
"node_modules/p-cancelable": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/p-cancelable/-/p-cancelable-2.1.1.tgz",
@@ -5279,6 +6251,39 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
+ "node_modules/p-map": {
+ "version": "7.0.4",
+ "resolved": "https://registry.npmjs.org/p-map/-/p-map-7.0.4.tgz",
+ "integrity": "sha512-tkAQEw8ysMzmkhgw8k+1U/iPhWNhykKnSk4Rd5zLoPJCuJaGRPo6YposrZgaxHKzDHdDWWZvE/Sk7hsL2X/CpQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/package-json-from-dist": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz",
+ "integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==",
+ "dev": true,
+ "license": "BlueOak-1.0.0"
+ },
+ "node_modules/parse5": {
+ "version": "8.0.1",
+ "resolved": "https://registry.npmjs.org/parse5/-/parse5-8.0.1.tgz",
+ "integrity": "sha512-z1e/HMG90obSGeidlli3hj7cbocou0/wa5HacvI3ASx34PecNjNQeaHNo5WIZpWofN9kgkqV1q5YvXe3F0FoPw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "entities": "^8.0.0"
+ },
+ "funding": {
+ "url": "https://github.com/inikulin/parse5?sponsor=1"
+ }
+ },
"node_modules/path-exists": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz",
@@ -5309,6 +6314,30 @@
"node": ">=8"
}
},
+ "node_modules/path-scurry": {
+ "version": "1.11.1",
+ "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz",
+ "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==",
+ "dev": true,
+ "license": "BlueOak-1.0.0",
+ "dependencies": {
+ "lru-cache": "^10.2.0",
+ "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0"
+ },
+ "engines": {
+ "node": ">=16 || 14 >=14.18"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/isaacs"
+ }
+ },
+ "node_modules/path-scurry/node_modules/lru-cache": {
+ "version": "10.4.3",
+ "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz",
+ "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==",
+ "dev": true,
+ "license": "ISC"
+ },
"node_modules/pathe": {
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz",
@@ -5358,6 +6387,37 @@
"url": "https://github.com/sponsors/jonschlinkert"
}
},
+ "node_modules/pkijs": {
+ "version": "3.4.0",
+ "resolved": "https://registry.npmjs.org/pkijs/-/pkijs-3.4.0.tgz",
+ "integrity": "sha512-emEcLuomt2j03vxD54giVB4SxTjnsqkU692xZOZXHDVoYyypEm+b3jpiTcc+Cf+myooc+/Ly0z01jqeNHVgJGw==",
+ "dev": true,
+ "license": "BSD-3-Clause",
+ "dependencies": {
+ "@noble/hashes": "1.4.0",
+ "asn1js": "^3.0.6",
+ "bytestreamjs": "^2.0.1",
+ "pvtsutils": "^1.3.6",
+ "pvutils": "^1.1.3",
+ "tslib": "^2.8.1"
+ },
+ "engines": {
+ "node": ">=16.0.0"
+ }
+ },
+ "node_modules/pkijs/node_modules/@noble/hashes": {
+ "version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.4.0.tgz",
+ "integrity": "sha512-V1JJ1WTRUqHHrOSh597hURcMqVKVGL/ea3kv0gSnEdsEZ0/+VyPghM1lMNGc00z7CIQorSvbKpuJkxvuHbvdbg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 16"
+ },
+ "funding": {
+ "url": "https://paulmillr.com/funding/"
+ }
+ },
"node_modules/plist": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/plist/-/plist-3.1.0.tgz",
@@ -5459,15 +6519,22 @@
}
},
"node_modules/proc-log": {
- "version": "6.1.0",
- "resolved": "https://registry.npmjs.org/proc-log/-/proc-log-6.1.0.tgz",
- "integrity": "sha512-iG+GYldRf2BQ0UDUAd6JQ/RwzaQy6mXmsk/IzlYyal4A4SNFw54MeH4/tLkF4I5WoWG9SQwuqWzS99jaFQHBuQ==",
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/proc-log/-/proc-log-5.0.0.tgz",
+ "integrity": "sha512-Azwzvl90HaF0aCz1JrDdXQykFakSSNPaPoiZ9fm5qJIMHioDZEi7OAdRwSm6rSoPtY3Qutnm3L7ogmg3dc+wbQ==",
"dev": true,
"license": "ISC",
"engines": {
- "node": "^20.17.0 || >=22.9.0"
+ "node": "^18.17.0 || >=20.5.0"
}
},
+ "node_modules/process-nextick-args": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz",
+ "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==",
+ "dev": true,
+ "license": "MIT"
+ },
"node_modules/progress": {
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz",
@@ -5525,6 +6592,26 @@
"node": ">=6"
}
},
+ "node_modules/pvtsutils": {
+ "version": "1.3.6",
+ "resolved": "https://registry.npmjs.org/pvtsutils/-/pvtsutils-1.3.6.tgz",
+ "integrity": "sha512-PLgQXQ6H2FWCaeRak8vvk1GW462lMxB5s3Jm673N82zI4vqtVUPuZdffdZbPDFRoU8kAhItWFtPCWiPpp4/EDg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "tslib": "^2.8.1"
+ }
+ },
+ "node_modules/pvutils": {
+ "version": "1.1.5",
+ "resolved": "https://registry.npmjs.org/pvutils/-/pvutils-1.1.5.tgz",
+ "integrity": "sha512-KTqnxsgGiQ6ZAzZCVlJH5eOjSnvlyEgx1m8bkRJfOhmGRqfo5KLvmAlACQkrjEtOQ4B7wF9TdSLIs9O90MX9xA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=16.0.0"
+ }
+ },
"node_modules/quick-lru": {
"version": "5.1.1",
"resolved": "https://registry.npmjs.org/quick-lru/-/quick-lru-5.1.1.tgz",
@@ -5551,6 +6638,21 @@
"read-binary-file-arch": "cli.js"
}
},
+ "node_modules/readable-stream": {
+ "version": "3.6.2",
+ "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz",
+ "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "inherits": "^2.0.3",
+ "string_decoder": "^1.1.1",
+ "util-deprecate": "^1.0.1"
+ },
+ "engines": {
+ "node": ">= 6"
+ }
+ },
"node_modules/require-directory": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz",
@@ -5561,6 +6663,16 @@
"node": ">=0.10.0"
}
},
+ "node_modules/require-from-string": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz",
+ "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
"node_modules/resedit": {
"version": "1.7.2",
"resolved": "https://registry.npmjs.org/resedit/-/resedit-1.7.2.tgz",
@@ -5681,13 +6793,13 @@
}
},
"node_modules/rolldown": {
- "version": "1.0.2",
- "resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.0.2.tgz",
- "integrity": "sha512-oZx5zVDtVB44AW3eaifgDml1gWRDZGvjcfdxonE4swNPG98PrrXjaO/KrnUjzlMnztCCRVlUueA1kCXhARGk6g==",
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.0.3.tgz",
+ "integrity": "sha512-i00lAJ2ks1BYr7rjNjKC7BcqAS7nVfiT3QX1SI5aY+AFHblCmaUf9OE9dbdzDvW6dJxbi2ZCZiy9v3CcwOiX3g==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@oxc-project/types": "=0.132.0",
+ "@oxc-project/types": "=0.133.0",
"@rolldown/pluginutils": "^1.0.0"
},
"bin": {
@@ -5697,29 +6809,51 @@
"node": "^20.19.0 || >=22.12.0"
},
"optionalDependencies": {
- "@rolldown/binding-android-arm64": "1.0.2",
- "@rolldown/binding-darwin-arm64": "1.0.2",
- "@rolldown/binding-darwin-x64": "1.0.2",
- "@rolldown/binding-freebsd-x64": "1.0.2",
- "@rolldown/binding-linux-arm-gnueabihf": "1.0.2",
- "@rolldown/binding-linux-arm64-gnu": "1.0.2",
- "@rolldown/binding-linux-arm64-musl": "1.0.2",
- "@rolldown/binding-linux-ppc64-gnu": "1.0.2",
- "@rolldown/binding-linux-s390x-gnu": "1.0.2",
- "@rolldown/binding-linux-x64-gnu": "1.0.2",
- "@rolldown/binding-linux-x64-musl": "1.0.2",
- "@rolldown/binding-openharmony-arm64": "1.0.2",
- "@rolldown/binding-wasm32-wasi": "1.0.2",
- "@rolldown/binding-win32-arm64-msvc": "1.0.2",
- "@rolldown/binding-win32-x64-msvc": "1.0.2"
- }
+ "@rolldown/binding-android-arm64": "1.0.3",
+ "@rolldown/binding-darwin-arm64": "1.0.3",
+ "@rolldown/binding-darwin-x64": "1.0.3",
+ "@rolldown/binding-freebsd-x64": "1.0.3",
+ "@rolldown/binding-linux-arm-gnueabihf": "1.0.3",
+ "@rolldown/binding-linux-arm64-gnu": "1.0.3",
+ "@rolldown/binding-linux-arm64-musl": "1.0.3",
+ "@rolldown/binding-linux-ppc64-gnu": "1.0.3",
+ "@rolldown/binding-linux-s390x-gnu": "1.0.3",
+ "@rolldown/binding-linux-x64-gnu": "1.0.3",
+ "@rolldown/binding-linux-x64-musl": "1.0.3",
+ "@rolldown/binding-openharmony-arm64": "1.0.3",
+ "@rolldown/binding-wasm32-wasi": "1.0.3",
+ "@rolldown/binding-win32-arm64-msvc": "1.0.3",
+ "@rolldown/binding-win32-x64-msvc": "1.0.3"
+ }
+ },
+ "node_modules/safe-buffer": {
+ "version": "5.2.1",
+ "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
+ "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/feross"
+ },
+ {
+ "type": "patreon",
+ "url": "https://www.patreon.com/feross"
+ },
+ {
+ "type": "consulting",
+ "url": "https://feross.org/support"
+ }
+ ],
+ "license": "MIT"
},
"node_modules/safer-buffer": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
"integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==",
"dev": true,
- "license": "MIT"
+ "license": "MIT",
+ "optional": true
},
"node_modules/sanitize-filename": {
"version": "1.6.4",
@@ -5739,10 +6873,23 @@
"node": ">=11.0.0"
}
},
+ "node_modules/saxes": {
+ "version": "6.0.0",
+ "resolved": "https://registry.npmjs.org/saxes/-/saxes-6.0.0.tgz",
+ "integrity": "sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "xmlchars": "^2.2.0"
+ },
+ "engines": {
+ "node": ">=v12.22.7"
+ }
+ },
"node_modules/semver": {
- "version": "7.8.1",
- "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.1.tgz",
- "integrity": "sha512-rkVq3IXh+4FDGch+KwzX3aV9W3kO54GyEgpvBzSyctDA6Xtd7RJQV1xmXbeQp5v7+VzLOfVqiutSE6GICgPFvg==",
+ "version": "7.8.2",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.2.tgz",
+ "integrity": "sha512-c8jsqUZm3omBOI66G90z1Dyw5z622G8oLG+omfsHBJf3CWQTlOcwOjvOG6wtiNfW6anKm/eA39LMwMtMez2TiQ==",
"dev": true,
"license": "ISC",
"bin": {
@@ -5828,19 +6975,33 @@
}
},
"node_modules/slice-ansi": {
- "version": "3.0.0",
- "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-3.0.0.tgz",
- "integrity": "sha512-pSyv7bSTC7ig9Dcgbw9AuRNUb5k5V6oDudjZoMBSr13qpLBG7tB+zgCkARjq7xIUgdz5P1Qe8u+rSGdouOOIyQ==",
+ "version": "8.0.0",
+ "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-8.0.0.tgz",
+ "integrity": "sha512-stxByr12oeeOyY2BlviTNQlYV5xOj47GirPr4yA1hE9JCtxfQN0+tVbkxwCtYDQWhEKWFHsEK48ORg5jrouCAg==",
"dev": true,
"license": "MIT",
- "optional": true,
"dependencies": {
- "ansi-styles": "^4.0.0",
- "astral-regex": "^2.0.0",
- "is-fullwidth-code-point": "^3.0.0"
+ "ansi-styles": "^6.2.3",
+ "is-fullwidth-code-point": "^5.1.0"
},
"engines": {
- "node": ">=8"
+ "node": ">=20"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/slice-ansi?sponsor=1"
+ }
+ },
+ "node_modules/slice-ansi/node_modules/ansi-styles": {
+ "version": "6.2.3",
+ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz",
+ "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/ansi-styles?sponsor=1"
}
},
"node_modules/smart-buffer": {
@@ -5849,12 +7010,41 @@
"integrity": "sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==",
"dev": true,
"license": "MIT",
- "optional": true,
"engines": {
"node": ">= 6.0.0",
"npm": ">= 3.0.0"
}
},
+ "node_modules/socks": {
+ "version": "2.8.9",
+ "resolved": "https://registry.npmjs.org/socks/-/socks-2.8.9.tgz",
+ "integrity": "sha512-LJhUYUvItdQ0LkJTmPeaEObWXAqFyfmP85x0tch/ez9cahmhlBBLbIqDFnvBnUJGagb0JbIQrkBs1wJ+yRYpEw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "ip-address": "^10.1.1",
+ "smart-buffer": "^4.2.0"
+ },
+ "engines": {
+ "node": ">= 10.0.0",
+ "npm": ">= 3.0.0"
+ }
+ },
+ "node_modules/socks-proxy-agent": {
+ "version": "8.0.5",
+ "resolved": "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-8.0.5.tgz",
+ "integrity": "sha512-HehCEsotFqbPW9sJ8WVYB6UbmIMv7kUUORIF2Nncq4VQvBfNBLibW9YZR5dlYCSUhwcD628pRllm7n+E+YTzJw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "agent-base": "^7.1.2",
+ "debug": "^4.3.4",
+ "socks": "^2.8.3"
+ },
+ "engines": {
+ "node": ">= 14"
+ }
+ },
"node_modules/source-map": {
"version": "0.6.1",
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
@@ -5894,6 +7084,19 @@
"license": "BSD-3-Clause",
"optional": true
},
+ "node_modules/ssri": {
+ "version": "12.0.0",
+ "resolved": "https://registry.npmjs.org/ssri/-/ssri-12.0.0.tgz",
+ "integrity": "sha512-S7iGNosepx9RadX82oimUkvr0Ct7IjJbEbs4mJcTxst8um95J3sDYU1RBEOvdu6oL1Wek2ODI5i4MAw+dZ6cAQ==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "minipass": "^7.0.3"
+ },
+ "engines": {
+ "node": "^18.17.0 || >=20.5.0"
+ }
+ },
"node_modules/stackback": {
"version": "0.0.2",
"resolved": "https://registry.npmjs.org/stackback/-/stackback-0.0.2.tgz",
@@ -5918,6 +7121,16 @@
"dev": true,
"license": "MIT"
},
+ "node_modules/string_decoder": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz",
+ "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "safe-buffer": "~5.2.0"
+ }
+ },
"node_modules/string-argv": {
"version": "0.3.2",
"resolved": "https://registry.npmjs.org/string-argv/-/string-argv-0.3.2.tgz",
@@ -5929,6 +7142,24 @@
}
},
"node_modules/string-width": {
+ "version": "8.2.1",
+ "resolved": "https://registry.npmjs.org/string-width/-/string-width-8.2.1.tgz",
+ "integrity": "sha512-IIaP0g3iy9Cyy18w3M9YcaDudujEAVHKt3a3QJg1+sr/oX96TbaGUubG0hJyCjCBThFH+tFpcIyoUHUn1ogaLA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "get-east-asian-width": "^1.5.0",
+ "strip-ansi": "^7.1.2"
+ },
+ "engines": {
+ "node": ">=20"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/string-width-cjs": {
+ "name": "string-width",
"version": "4.2.3",
"resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
"integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
@@ -5943,7 +7174,7 @@
"node": ">=8"
}
},
- "node_modules/string-width/node_modules/ansi-regex": {
+ "node_modules/string-width-cjs/node_modules/ansi-regex": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
"integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
@@ -5953,7 +7184,17 @@
"node": ">=8"
}
},
- "node_modules/string-width/node_modules/strip-ansi": {
+ "node_modules/string-width-cjs/node_modules/is-fullwidth-code-point": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz",
+ "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/string-width-cjs/node_modules/strip-ansi": {
"version": "6.0.1",
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
"integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
@@ -5972,14 +7213,38 @@
"integrity": "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==",
"dev": true,
"license": "MIT",
- "dependencies": {
- "ansi-regex": "^6.2.2"
- },
+ "dependencies": {
+ "ansi-regex": "^6.2.2"
+ },
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/strip-ansi?sponsor=1"
+ }
+ },
+ "node_modules/strip-ansi-cjs": {
+ "name": "strip-ansi",
+ "version": "6.0.1",
+ "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
+ "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "ansi-regex": "^5.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/strip-ansi-cjs/node_modules/ansi-regex": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
+ "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
+ "dev": true,
+ "license": "MIT",
"engines": {
- "node": ">=12"
- },
- "funding": {
- "url": "https://github.com/chalk/strip-ansi?sponsor=1"
+ "node": ">=8"
}
},
"node_modules/sumchecker": {
@@ -6008,10 +7273,17 @@
"node": ">=8"
}
},
+ "node_modules/symbol-tree": {
+ "version": "3.2.4",
+ "resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.4.tgz",
+ "integrity": "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==",
+ "dev": true,
+ "license": "MIT"
+ },
"node_modules/tar": {
- "version": "7.5.15",
- "resolved": "https://registry.npmjs.org/tar/-/tar-7.5.15.tgz",
- "integrity": "sha512-dzGK0boVlC4W5QFuQN1EFSl3bIDYsk7Tj40U6eIBnK2k/8ml7TZ5agbI5j5+qnoVcAA+rNtBml8SEiLxZpNqRQ==",
+ "version": "7.5.16",
+ "resolved": "https://registry.npmjs.org/tar/-/tar-7.5.16.tgz",
+ "integrity": "sha512-56adEpPMouktRlBLXiaYFFzZ/3+JXa8P9n7WbR+ibIjtviN55mEaOkiysCnPnWm+7kkui1Dn8J9l+g6zV8731w==",
"dev": true,
"license": "BlueOak-1.0.0",
"dependencies": {
@@ -6095,9 +7367,9 @@
"license": "MIT"
},
"node_modules/tinyexec": {
- "version": "1.2.2",
- "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-1.2.2.tgz",
- "integrity": "sha512-M/Q0B2cp4K7kynaT/vnED1j8TlLY+Pp7C6Wl2bl/7u/F0mUVwdyOpwomQb8JpYLitHUssAJRmLZdMCGsrx7i+g==",
+ "version": "1.2.4",
+ "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-1.2.4.tgz",
+ "integrity": "sha512-SHf/r48b7vOrjve9PxJo3MN5v5yuyjHvdUcrQffT3WXMUfnGmHDVbC4k3sHJaJTgZCwpUplIaAo5ANtMyp3YHg==",
"dev": true,
"license": "MIT",
"engines": {
@@ -6105,9 +7377,9 @@
}
},
"node_modules/tinyglobby": {
- "version": "0.2.16",
- "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.16.tgz",
- "integrity": "sha512-pn99VhoACYR8nFHhxqix+uvsbXineAasWm5ojXoN8xEwK5Kd3/TrhNn1wByuD52UxWRLy8pu+kRMniEi6Eq9Zg==",
+ "version": "0.2.17",
+ "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.17.tgz",
+ "integrity": "sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -6131,10 +7403,30 @@
"node": ">=14.0.0"
}
},
+ "node_modules/tldts": {
+ "version": "7.4.2",
+ "resolved": "https://registry.npmjs.org/tldts/-/tldts-7.4.2.tgz",
+ "integrity": "sha512-kCwffuaH8ntKtygnWe1b4BJKWiCUH30n5KfoTr6IchcXOwR7chAOFJxFrH3vjANafUYrIA4a7SDL+nn7SiR4Sw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "tldts-core": "^7.4.2"
+ },
+ "bin": {
+ "tldts": "bin/cli.js"
+ }
+ },
+ "node_modules/tldts-core": {
+ "version": "7.4.2",
+ "resolved": "https://registry.npmjs.org/tldts-core/-/tldts-core-7.4.2.tgz",
+ "integrity": "sha512-nwEyF4vl4RSJjwSjBUmOSxc3BFPoIFdlRthJ6e+5v9P3bHNsoD06UjuqMUspqp7vsEZ1beaHi1km+optiE17yA==",
+ "dev": true,
+ "license": "MIT"
+ },
"node_modules/tmp": {
- "version": "0.2.5",
- "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.5.tgz",
- "integrity": "sha512-voyz6MApa1rQGUxT3E+BK7/ROe8itEx7vD8/HEvt4xwXucvQ5G5oeEiHkmHZJuBO21RpOf+YYm9MOivj709jow==",
+ "version": "0.2.7",
+ "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.7.tgz",
+ "integrity": "sha512-e0votIpp4Uo2AJYSzVHV6xCcawuiez3DzqDAbrTc3YxBkplN6e+dM13ZeIcZnDg/QpSuU2zfZ3rzwY8ukEnaXw==",
"dev": true,
"license": "MIT",
"engines": {
@@ -6151,6 +7443,32 @@
"tmp": "^0.2.0"
}
},
+ "node_modules/tough-cookie": {
+ "version": "6.0.1",
+ "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-6.0.1.tgz",
+ "integrity": "sha512-LktZQb3IeoUWB9lqR5EWTHgW/VTITCXg4D21M+lvybRVdylLrRMnqaIONLVb5mav8vM19m44HIcGq4qASeu2Qw==",
+ "dev": true,
+ "license": "BSD-3-Clause",
+ "dependencies": {
+ "tldts": "^7.0.5"
+ },
+ "engines": {
+ "node": ">=16"
+ }
+ },
+ "node_modules/tr46": {
+ "version": "6.0.0",
+ "resolved": "https://registry.npmjs.org/tr46/-/tr46-6.0.0.tgz",
+ "integrity": "sha512-bLVMLPtstlZ4iMQHpFHTR7GAGj2jxi8Dg0s2h2MafAE4uSWF98FC/3MomU51iQAMf8/qDUbKWf5GxuvvVcXEhw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "punycode": "^2.3.1"
+ },
+ "engines": {
+ "node": ">=20"
+ }
+ },
"node_modules/truncate-utf8-bytes": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/truncate-utf8-bytes/-/truncate-utf8-bytes-1.0.2.tgz",
@@ -6178,8 +7496,7 @@
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz",
"integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==",
"dev": true,
- "license": "0BSD",
- "optional": true
+ "license": "0BSD"
},
"node_modules/type-check": {
"version": "0.4.0",
@@ -6223,16 +7540,16 @@
}
},
"node_modules/typescript-eslint": {
- "version": "8.59.4",
- "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.59.4.tgz",
- "integrity": "sha512-Rw6+44QNFaXtgHSjPy+Kw8hrJniMYzR85E9yLmOLcfZ91/rz+JXQbDTCmc6ccxMPY6K6PgAq26f0JCBfR7LIPQ==",
+ "version": "8.60.1",
+ "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.60.1.tgz",
+ "integrity": "sha512-6m5hkkRAp8lKvhVpcprAIn5KkehQEh+47oHH2VGnExEh7dhNxXlg6GPAOIu6TxbVQxhebrJDvjl3020ooiWCMA==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@typescript-eslint/eslint-plugin": "8.59.4",
- "@typescript-eslint/parser": "8.59.4",
- "@typescript-eslint/typescript-estree": "8.59.4",
- "@typescript-eslint/utils": "8.59.4"
+ "@typescript-eslint/eslint-plugin": "8.60.1",
+ "@typescript-eslint/parser": "8.60.1",
+ "@typescript-eslint/typescript-estree": "8.60.1",
+ "@typescript-eslint/utils": "8.60.1"
},
"engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
@@ -6247,12 +7564,11 @@
}
},
"node_modules/undici": {
- "version": "7.25.0",
- "resolved": "https://registry.npmjs.org/undici/-/undici-7.25.0.tgz",
- "integrity": "sha512-xXnp4kTyor2Zq+J1FfPI6Eq3ew5h6Vl0F/8d9XU5zZQf1tX9s2Su1/3PiMmUANFULpmksxkClamIZcaUqryHsQ==",
+ "version": "7.27.1",
+ "resolved": "https://registry.npmjs.org/undici/-/undici-7.27.1.tgz",
+ "integrity": "sha512-UDdpiex+mzigiyrXrGbiUaF4HzTNhKbh2vRNFaTMzcqmLIPrZxaCtwo/1TMSuWoM1Xz3WiTo9KdgI3kRqYzJGg==",
"dev": true,
"license": "MIT",
- "optional": true,
"engines": {
"node": ">=20.18.1"
}
@@ -6264,6 +7580,32 @@
"dev": true,
"license": "MIT"
},
+ "node_modules/unique-filename": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/unique-filename/-/unique-filename-4.0.0.tgz",
+ "integrity": "sha512-XSnEewXmQ+veP7xX2dS5Q4yZAvO40cBN2MWkJ7D/6sW4Dg6wYBNwM1Vrnz1FhH5AdeLIlUXRI9e28z1YZi71NQ==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "unique-slug": "^5.0.0"
+ },
+ "engines": {
+ "node": "^18.17.0 || >=20.5.0"
+ }
+ },
+ "node_modules/unique-slug": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/unique-slug/-/unique-slug-5.0.0.tgz",
+ "integrity": "sha512-9OdaqO5kwqR+1kVgHAhsp5vPNU0hnxRa26rBFNfNgM7M6pNtgzeBn3s/xbyCQL3dcjzOatcef6UUHpB/6MaETg==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "imurmurhash": "^0.1.4"
+ },
+ "engines": {
+ "node": "^18.17.0 || >=20.5.0"
+ }
+ },
"node_modules/universalify": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz",
@@ -6273,6 +7615,35 @@
"node": ">= 10.0.0"
}
},
+ "node_modules/unzipper": {
+ "version": "0.12.3",
+ "resolved": "https://registry.npmjs.org/unzipper/-/unzipper-0.12.3.tgz",
+ "integrity": "sha512-PZ8hTS+AqcGxsaQntl3IRBw65QrBI6lxzqDEL7IAo/XCEqRTKGfOX56Vea5TH9SZczRVxuzk1re04z/YjuYCJA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "bluebird": "~3.7.2",
+ "duplexer2": "~0.1.4",
+ "fs-extra": "^11.2.0",
+ "graceful-fs": "^4.2.2",
+ "node-int64": "^0.4.0"
+ }
+ },
+ "node_modules/unzipper/node_modules/fs-extra": {
+ "version": "11.3.5",
+ "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.3.5.tgz",
+ "integrity": "sha512-eKpRKAovdpZtR1WopLHxlBWvAgPny3c4gX1G5Jhwmmw4XJj0ifSD5qB5TOo8hmA0wlRKDAOAhEE1yVPgs6Fgcg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "graceful-fs": "^4.2.0",
+ "jsonfile": "^6.0.1",
+ "universalify": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=14.14"
+ }
+ },
"node_modules/uri-js": {
"version": "4.4.1",
"resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz",
@@ -6289,34 +7660,25 @@
"integrity": "sha512-Xn0w3MtiQ6zoz2vFyUVruaCL53O/DwUvkEeOvj+uulMm0BkUGYWmBYVyElqZaSLhY6ZD0ulfU3aBra2aVT4xfA==",
"license": "(WTFPL OR MIT)"
},
- "node_modules/verror": {
- "version": "1.10.1",
- "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.1.tgz",
- "integrity": "sha512-veufcmxri4e3XSrT0xwfUR7kguIkaxBeosDg00yDWhk49wdwkSUrvvsm7nc75e1PUyvIeZj6nS8VQRYz2/S4Xg==",
+ "node_modules/util-deprecate": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
+ "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==",
"dev": true,
- "license": "MIT",
- "optional": true,
- "dependencies": {
- "assert-plus": "^1.0.0",
- "core-util-is": "1.0.2",
- "extsprintf": "^1.2.0"
- },
- "engines": {
- "node": ">=0.6.0"
- }
+ "license": "MIT"
},
"node_modules/vite": {
- "version": "8.0.14",
- "resolved": "https://registry.npmjs.org/vite/-/vite-8.0.14.tgz",
- "integrity": "sha512-s4BJJ+5y1pYL6Otw51FHhVJQhPnuRinKig64g/1+EUNaJsd3gCKdD31IPFvswUgW9/60QT9oFHbZHbQK5imcxw==",
+ "version": "8.0.16",
+ "resolved": "https://registry.npmjs.org/vite/-/vite-8.0.16.tgz",
+ "integrity": "sha512-h9bXPmJichP5fLmVQo3PyaGSDE2n3aPuomeAlVRm0JLmt4rY6zmPKd59HYI4LNW8oTK7tlTsuC7l/m7awx9Jcw==",
"dev": true,
"license": "MIT",
"dependencies": {
"lightningcss": "^1.32.0",
"picomatch": "^4.0.4",
"postcss": "^8.5.15",
- "rolldown": "1.0.2",
- "tinyglobby": "^0.2.16"
+ "rolldown": "1.0.3",
+ "tinyglobby": "^0.2.17"
},
"bin": {
"vite": "bin/vite.js"
@@ -6384,19 +7746,19 @@
}
},
"node_modules/vitest": {
- "version": "4.1.7",
- "resolved": "https://registry.npmjs.org/vitest/-/vitest-4.1.7.tgz",
- "integrity": "sha512-flYyaFd2CgoCoU+0UKt3pxksgC+S02iTDN0n3LtqaMeXsI9SBcdNujc2k0DeFLzUn/0k538yNjOSdwgCqcrwJA==",
+ "version": "4.1.8",
+ "resolved": "https://registry.npmjs.org/vitest/-/vitest-4.1.8.tgz",
+ "integrity": "sha512-flY6ScbCIt9HThs+C5HS7jvGOB560DJtk/Z15IQROTA6zEy49Nh8T/dofWTQL+n3vswqn87sbJNiuqw1SDp5Ig==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@vitest/expect": "4.1.7",
- "@vitest/mocker": "4.1.7",
- "@vitest/pretty-format": "4.1.7",
- "@vitest/runner": "4.1.7",
- "@vitest/snapshot": "4.1.7",
- "@vitest/spy": "4.1.7",
- "@vitest/utils": "4.1.7",
+ "@vitest/expect": "4.1.8",
+ "@vitest/mocker": "4.1.8",
+ "@vitest/pretty-format": "4.1.8",
+ "@vitest/runner": "4.1.8",
+ "@vitest/snapshot": "4.1.8",
+ "@vitest/spy": "4.1.8",
+ "@vitest/utils": "4.1.8",
"es-module-lexer": "^2.0.0",
"expect-type": "^1.3.0",
"magic-string": "^0.30.21",
@@ -6424,12 +7786,12 @@
"@edge-runtime/vm": "*",
"@opentelemetry/api": "^1.9.0",
"@types/node": "^20.0.0 || ^22.0.0 || >=24.0.0",
- "@vitest/browser-playwright": "4.1.7",
- "@vitest/browser-preview": "4.1.7",
- "@vitest/browser-webdriverio": "4.1.7",
- "@vitest/coverage-istanbul": "4.1.7",
- "@vitest/coverage-v8": "4.1.7",
- "@vitest/ui": "4.1.7",
+ "@vitest/browser-playwright": "4.1.8",
+ "@vitest/browser-preview": "4.1.8",
+ "@vitest/browser-webdriverio": "4.1.8",
+ "@vitest/coverage-istanbul": "4.1.8",
+ "@vitest/coverage-v8": "4.1.8",
+ "@vitest/ui": "4.1.8",
"happy-dom": "*",
"jsdom": "*",
"vite": "^6.0.0 || ^7.0.0 || ^8.0.0"
@@ -6473,6 +7835,78 @@
}
}
},
+ "node_modules/w3c-xmlserializer": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-5.0.0.tgz",
+ "integrity": "sha512-o8qghlI8NZHU1lLPrpi2+Uq7abh4GGPpYANlalzWxyWteJOCsr/P+oPBA49TOLu5FTZO4d3F9MnWJfiMo4BkmA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "xml-name-validator": "^5.0.0"
+ },
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/wcwidth": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/wcwidth/-/wcwidth-1.0.1.tgz",
+ "integrity": "sha512-XHPEwS0q6TaxcvG85+8EYkbiCux2XtWG2mkc47Ng2A77BQu9+DqIOJldST4HgPkuea7dvKSj5VgX3P1d4rW8Tg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "defaults": "^1.0.3"
+ }
+ },
+ "node_modules/webcrypto-core": {
+ "version": "1.9.2",
+ "resolved": "https://registry.npmjs.org/webcrypto-core/-/webcrypto-core-1.9.2.tgz",
+ "integrity": "sha512-gsXecm82UQNlTBURJGuqOWy1Ww08S3kZUcr3aOJS02Pk0xLtkfeUAVC0u0xhgdonFme80edSJUIJyuvL/7250Q==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@peculiar/asn1-schema": "^2.7.0",
+ "@peculiar/json-schema": "^1.1.12",
+ "@peculiar/utils": "^2.0.2",
+ "asn1js": "^3.0.10",
+ "tslib": "^2.8.1"
+ }
+ },
+ "node_modules/webidl-conversions": {
+ "version": "8.0.1",
+ "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-8.0.1.tgz",
+ "integrity": "sha512-BMhLD/Sw+GbJC21C/UgyaZX41nPt8bUTg+jWyDeg7e7YN4xOM05YPSIXceACnXVtqyEw/LMClUQMtMZ+PGGpqQ==",
+ "dev": true,
+ "license": "BSD-2-Clause",
+ "engines": {
+ "node": ">=20"
+ }
+ },
+ "node_modules/whatwg-mimetype": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-5.0.0.tgz",
+ "integrity": "sha512-sXcNcHOC51uPGF0P/D4NVtrkjSU2fNsm9iog4ZvZJsL3rjoDAzXZhkm2MWt1y+PUdggKAYVoMAIYcs78wJ51Cw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=20"
+ }
+ },
+ "node_modules/whatwg-url": {
+ "version": "16.0.1",
+ "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-16.0.1.tgz",
+ "integrity": "sha512-1to4zXBxmXHV3IiSSEInrreIlu02vUOvrhxJJH5vcxYTBDAx51cqZiKdyTxlecdKNSjj8EcxGBxNf6Vg+945gw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@exodus/bytes": "^1.11.0",
+ "tr46": "^6.0.0",
+ "webidl-conversions": "^8.0.1"
+ },
+ "engines": {
+ "node": "^20.19.0 || ^22.12.0 || >=24.0.0"
+ }
+ },
"node_modules/which": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
@@ -6534,34 +7968,84 @@
"url": "https://github.com/chalk/wrap-ansi?sponsor=1"
}
},
- "node_modules/wrap-ansi/node_modules/ansi-styles": {
- "version": "6.2.3",
- "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz",
- "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==",
+ "node_modules/wrap-ansi-cjs": {
+ "name": "wrap-ansi",
+ "version": "7.0.0",
+ "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz",
+ "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==",
"dev": true,
"license": "MIT",
+ "dependencies": {
+ "ansi-styles": "^4.0.0",
+ "string-width": "^4.1.0",
+ "strip-ansi": "^6.0.0"
+ },
"engines": {
- "node": ">=12"
+ "node": ">=10"
},
"funding": {
- "url": "https://github.com/chalk/ansi-styles?sponsor=1"
+ "url": "https://github.com/chalk/wrap-ansi?sponsor=1"
}
},
- "node_modules/wrap-ansi/node_modules/string-width": {
- "version": "8.2.1",
- "resolved": "https://registry.npmjs.org/string-width/-/string-width-8.2.1.tgz",
- "integrity": "sha512-IIaP0g3iy9Cyy18w3M9YcaDudujEAVHKt3a3QJg1+sr/oX96TbaGUubG0hJyCjCBThFH+tFpcIyoUHUn1ogaLA==",
+ "node_modules/wrap-ansi-cjs/node_modules/ansi-regex": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
+ "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/wrap-ansi-cjs/node_modules/is-fullwidth-code-point": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz",
+ "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/wrap-ansi-cjs/node_modules/string-width": {
+ "version": "4.2.3",
+ "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
+ "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
"dev": true,
"license": "MIT",
"dependencies": {
- "get-east-asian-width": "^1.5.0",
- "strip-ansi": "^7.1.2"
+ "emoji-regex": "^8.0.0",
+ "is-fullwidth-code-point": "^3.0.0",
+ "strip-ansi": "^6.0.1"
},
"engines": {
- "node": ">=20"
+ "node": ">=8"
+ }
+ },
+ "node_modules/wrap-ansi-cjs/node_modules/strip-ansi": {
+ "version": "6.0.1",
+ "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
+ "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "ansi-regex": "^5.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/wrap-ansi/node_modules/ansi-styles": {
+ "version": "6.2.3",
+ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz",
+ "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=12"
},
"funding": {
- "url": "https://github.com/sponsors/sindresorhus"
+ "url": "https://github.com/chalk/ansi-styles?sponsor=1"
}
},
"node_modules/wrappy": {
@@ -6571,6 +8055,16 @@
"dev": true,
"license": "ISC"
},
+ "node_modules/xml-name-validator": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-5.0.0.tgz",
+ "integrity": "sha512-EvGK8EJ3DhaHfbRlETOWAS5pO9MZITeauHKJyb8wyajUfQUenkIg2MvLDTZ4T/TgIcm3HU0TFBgWWboAZ30UHg==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "engines": {
+ "node": ">=18"
+ }
+ },
"node_modules/xmlbuilder": {
"version": "15.1.1",
"resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-15.1.1.tgz",
@@ -6581,6 +8075,13 @@
"node": ">=8.0"
}
},
+ "node_modules/xmlchars": {
+ "version": "2.2.0",
+ "resolved": "https://registry.npmjs.org/xmlchars/-/xmlchars-2.2.0.tgz",
+ "integrity": "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==",
+ "dev": true,
+ "license": "MIT"
+ },
"node_modules/y18n": {
"version": "5.0.8",
"resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz",
@@ -6644,6 +8145,54 @@
"node": ">=12"
}
},
+ "node_modules/yargs/node_modules/ansi-regex": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
+ "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/yargs/node_modules/is-fullwidth-code-point": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz",
+ "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/yargs/node_modules/string-width": {
+ "version": "4.2.3",
+ "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
+ "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "emoji-regex": "^8.0.0",
+ "is-fullwidth-code-point": "^3.0.0",
+ "strip-ansi": "^6.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/yargs/node_modules/strip-ansi": {
+ "version": "6.0.1",
+ "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
+ "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "ansi-regex": "^5.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
"node_modules/yauzl": {
"version": "2.10.0",
"resolved": "https://registry.npmjs.org/yauzl/-/yauzl-2.10.0.tgz",
diff --git a/package.json b/package.json
index 6dae64f..5425aed 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "rosi",
- "version": "4.0.13",
+ "version": "4.1.0-beta.1",
"private": true,
"description": "Electron GUI for yt-dlp",
"keywords": [
@@ -141,6 +141,7 @@
"eslint": "^10.3.0",
"husky": "^9.1.7",
"js-yaml": "^4.1.0",
+ "jsdom": "^29.1.1",
"lint-staged": "^17.0.2",
"prettier": "^3.8.1",
"typescript": "^6.0.2",
diff --git a/resources/ffmpeg/NOTICE.txt b/resources/ffmpeg/NOTICE.txt
index f01e70e..78636bb 100644
--- a/resources/ffmpeg/NOTICE.txt
+++ b/resources/ffmpeg/NOTICE.txt
@@ -17,8 +17,12 @@ License: GPL-2.0-or-later (this build includes GPL components)
Binary Sources
--------------
-The pre-built FFmpeg 8.0 binaries included with this application are built
-and distributed by BurntToasters for all platforms (Windows, macOS, and Linux).
+The pre-built FFmpeg binaries included with this application are built and
+distributed by BurntToasters for all platforms (Windows, macOS, and Linux).
+
+Current versions in this distribution:
+ - FFmpeg 8.1: Windows (x64, arm64), Linux (x64, arm64), macOS (arm64)
+ - FFmpeg 8.0.1: macOS (x64) - unchanged; no Intel Mac hardware available to rebuild
Build repository: https://github.com/BurntToasters/ffmpeg-static-builds
@@ -46,7 +50,9 @@ BurntToasters under the written offer included at:
ffmpeg/SOURCE_OFFER.txt
Source code, build scripts, and release archives are hosted at:
-https://github.com/BurntToasters/ffmpeg-static-builds/releases/tag/ffmpeg-v8.0.1
+https://github.com/BurntToasters/ffmpeg-static-builds/releases/tag/ffmpeg-v8.1
+(FFmpeg 8.0.1 source for macOS x64 binaries:
+https://github.com/BurntToasters/ffmpeg-static-builds/releases/tag/ffmpeg-v8.0.1)
ROSI Application Notice
------------------------
diff --git a/resources/ffmpeg/README.md b/resources/ffmpeg/README.md
index bee8208..c0dc31d 100644
--- a/resources/ffmpeg/README.md
+++ b/resources/ffmpeg/README.md
@@ -2,13 +2,22 @@
The FFmpeg binaries are not included in the repository due to size constraints.
-## Download Sources
+## Build Source
-| Platform | Source | Build Type |
-| -------- | -------------------------------------------------------------------- | ------------------ |
-| Windows | [BtbN/FFmpeg-Builds](https://github.com/BtbN/FFmpeg-Builds/releases) | GPL (n8.0 release) |
-| Linux | [BtbN/FFmpeg-Builds](https://github.com/BtbN/FFmpeg-Builds/releases) | GPL (n8.0 release) |
-| macOS | [osxexperts.net](https://www.osxexperts.net/) | GPL |
+All bundled binaries are built by BurntToasters:
+
+- **Repository:** https://github.com/BurntToasters/ffmpeg-static-builds
+- **FFmpeg 8.1 source release:** https://github.com/BurntToasters/ffmpeg-static-builds/releases/tag/ffmpeg-v8.1
+- **FFmpeg 8.0.1 source release (macOS x64 only):** https://github.com/BurntToasters/ffmpeg-static-builds/releases/tag/ffmpeg-v8.0.1
+
+## Current Versions
+
+| Platform | Version |
+| ------------------- | ---------------------------------------------------- |
+| Windows x64 / arm64 | 8.1 |
+| Linux x64 / arm64 | 8.1 |
+| macOS arm64 | 8.1 |
+| macOS x64 | 8.0.1 (unchanged - no Intel Mac hardware to rebuild) |
## Required Structure
@@ -20,25 +29,31 @@ resources/ffmpeg/
βββ README.md
βββ win/
β βββ x64/
-β β βββ ffmpeg.exe
+β β βββ ffmpeg.exe
+β β βββ ffprobe.exe
β βββ arm64/
-β βββ ffmpeg.exe
+β βββ ffmpeg.exe
+β βββ ffprobe.exe
βββ mac/
β βββ x64/
-β β βββ ffmpeg
+β β βββ ffmpeg
+β β βββ ffprobe
β βββ arm64/
-β βββ ffmpeg
+β βββ ffmpeg
+β βββ ffprobe
βββ linux/
βββ x64/
- β βββ ffmpeg
+ β βββ ffmpeg
+ β βββ ffprobe
βββ arm64/
- βββ ffmpeg
+ βββ ffmpeg
+ βββ ffprobe
```
## Notes
-- Use **GPL builds** (not LGPL) if you need x264/x265 support
+- Use **GPL builds** (not LGPL) for x264/x265 support
- Use **static builds** (not shared) for easier bundling
- Ensure binaries have execute permissions on macOS/Linux (`chmod +x`)
-- ffprobe is NOT required β ROSI only uses ffmpeg
- Verify binaries before a build with `npm run ffmpeg:check:all`
+- License text and GPLv2 written source offer ship with the app at `ffmpeg/LICENSE.txt` and `ffmpeg/SOURCE_OFFER.txt`
diff --git a/resources/ffmpeg/SOURCE_OFFER.txt b/resources/ffmpeg/SOURCE_OFFER.txt
index c322c0e..81c0c15 100644
--- a/resources/ffmpeg/SOURCE_OFFER.txt
+++ b/resources/ffmpeg/SOURCE_OFFER.txt
@@ -19,8 +19,11 @@ Build Source Details (current distribution)
Source repository and build scripts:
https://github.com/BurntToasters/ffmpeg-static-builds
-Source release for current binaries (FFmpeg 8.0):
-https://github.com/BurntToasters/ffmpeg-static-builds/releases/tag/ffmpeg-v8.0.1
+Source releases for current binaries:
+ - FFmpeg 8.1 (Windows x64/arm64, Linux x64/arm64, macOS arm64):
+ https://github.com/BurntToasters/ffmpeg-static-builds/releases/tag/ffmpeg-v8.1
+ - FFmpeg 8.0.1 (macOS x64 only):
+ https://github.com/BurntToasters/ffmpeg-static-builds/releases/tag/ffmpeg-v8.0.1
This repository contains the complete corresponding source code, build scripts,
and configuration used to produce the FFmpeg binaries distributed with ROSI
diff --git a/resources/ffmpeg/ffmpeg_license.txt b/resources/ffmpeg/ffmpeg_license.txt
index 1f93e91..d159169 100644
--- a/resources/ffmpeg/ffmpeg_license.txt
+++ b/resources/ffmpeg/ffmpeg_license.txt
@@ -1,8 +1,339 @@
-The FFmpeg binaries distributed with this application are licensed under
-the GNU General Public License version 2 or later (GPL-2.0-or-later).
+ GNU GENERAL PUBLIC LICENSE
+ Version 2, June 1991
-The full text of the GPL-2.0 license is available at:
-https://www.gnu.org/licenses/old-licenses/gpl-2.0.html
+ Copyright (C) 1989, 1991 Free Software Foundation, Inc.,
+ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
-A copy of the license can also be found at the FFmpeg project website:
-https://ffmpeg.org/legal.html
+ Preamble
+
+ The licenses for most software are designed to take away your
+freedom to share and change it. By contrast, the GNU General Public
+License is intended to guarantee your freedom to share and change free
+software--to make sure the software is free for all its users. This
+General Public License applies to most of the Free Software
+Foundation's software and to any other program whose authors commit to
+using it. (Some other Free Software Foundation software is covered by
+the GNU Lesser General Public License instead.) You can apply it to
+your programs, too.
+
+ When we speak of free software, we are referring to freedom, not
+price. Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+this service if you wish), that you receive source code or can get it
+if you want it, that you can change the software or use pieces of it
+in new free programs; and that you know you can do these things.
+
+ To protect your rights, we need to make restrictions that forbid
+anyone to deny you these rights or to ask you to surrender the rights.
+These restrictions translate to certain responsibilities for you if you
+distribute copies of the software, or if you modify it.
+
+ For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must give the recipients all the rights that
+you have. You must make sure that they, too, receive or can get the
+source code. And you must show them these terms so they know their
+rights.
+
+ We protect your rights with two steps: (1) copyright the software, and
+(2) offer you this license which gives you legal permission to copy,
+distribute and/or modify the software.
+
+ Also, for each author's protection and ours, we want to make certain
+that everyone understands that there is no warranty for this free
+software. If the software is modified by someone else and passed on, we
+want its recipients to know that what they have is not the original, so
+that any problems introduced by others will not reflect on the original
+authors' reputations.
+
+ Finally, any free program is threatened constantly by software
+patents. We wish to avoid the danger that redistributors of a free
+program will individually obtain patent licenses, in effect making the
+program proprietary. To prevent this, we have made it clear that any
+patent must be licensed for everyone's free use or not licensed at all.
+
+ The precise terms and conditions for copying, distribution and
+modification follow.
+
+ GNU GENERAL PUBLIC LICENSE
+ TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
+
+ 0. This License applies to any program or other work which contains
+a notice placed by the copyright holder saying it may be distributed
+under the terms of this General Public License. The "Program", below,
+refers to any such program or work, and a "work based on the Program"
+means either the Program or any derivative work under copyright law:
+that is to say, a work containing the Program or a portion of it,
+either verbatim or with modifications and/or translated into another
+language. (Hereinafter, translation is included without limitation in
+the term "modification".) Each licensee is addressed as "you".
+
+Activities other than copying, distribution and modification are not
+covered by this License; they are outside its scope. The act of
+running the Program is not restricted, and the output from the Program
+is covered only if its contents constitute a work based on the
+Program (independent of having been made by running the Program).
+Whether that is true depends on what the Program does.
+
+ 1. You may copy and distribute verbatim copies of the Program's
+source code as you receive it, in any medium, provided that you
+conspicuously and appropriately publish on each copy an appropriate
+copyright notice and disclaimer of warranty; keep intact all the
+notices that refer to this License and to the absence of any warranty;
+and give any other recipients of the Program a copy of this License
+along with the Program.
+
+You may charge a fee for the physical act of transferring a copy, and
+you may at your option offer warranty protection in exchange for a fee.
+
+ 2. You may modify your copy or copies of the Program or any portion
+of it, thus forming a work based on the Program, and copy and
+distribute such modifications or work under the terms of Section 1
+above, provided that you also meet all of these conditions:
+
+ a) You must cause the modified files to carry prominent notices
+ stating that you changed the files and the date of any change.
+
+ b) You must cause any work that you distribute or publish, that in
+ whole or in part contains or is derived from the Program or any
+ part thereof, to be licensed as a whole at no charge to all third
+ parties under the terms of this License.
+
+ c) If the modified program normally reads commands interactively
+ when run, you must cause it, when started running for such
+ interactive use in the most ordinary way, to print or display an
+ announcement including an appropriate copyright notice and a
+ notice that there is no warranty (or else, saying that you provide
+ a warranty) and that users may redistribute the program under
+ these conditions, and telling the user how to view a copy of this
+ License. (Exception: if the Program itself is interactive but
+ does not normally print such an announcement, your work based on
+ the Program is not required to print an announcement.)
+
+These requirements apply to the modified work as a whole. If
+identifiable sections of that work are not derived from the Program,
+and can be reasonably considered independent and separate works in
+themselves, then this License, and its terms, do not apply to those
+sections when you distribute them as separate works. But when you
+distribute the same sections as part of a whole which is a work based
+on the Program, the distribution of the whole must be on the terms of
+this License, whose permissions for other licensees extend to the
+entire whole, and thus to each and every part regardless of who wrote it.
+
+Thus, it is not the intent of this section to claim rights or contest
+your rights to work written entirely by you; rather, the intent is to
+exercise the right to control the distribution of derivative or
+collective works based on the Program.
+
+In addition, mere aggregation of another work not based on the Program
+with the Program (or with a work based on the Program) on a volume of
+a storage or distribution medium does not bring the other work under
+the scope of this License.
+
+ 3. You may copy and distribute the Program (or a work based on it,
+under Section 2) in object code or executable form under the terms of
+Sections 1 and 2 above provided that you also do one of the following:
+
+ a) Accompany it with the complete corresponding machine-readable
+ source code, which must be distributed under the terms of Sections
+ 1 and 2 above on a medium customarily used for software interchange; or,
+
+ b) Accompany it with a written offer, valid for at least three
+ years, to give any third party, for a charge no more than your
+ cost of physically performing source distribution, a complete
+ machine-readable copy of the corresponding source code, to be
+ distributed under the terms of Sections 1 and 2 above on a medium
+ customarily used for software interchange; or,
+
+ c) Accompany it with the information you received as to the offer
+ to distribute corresponding source code. (This alternative is
+ allowed only for noncommercial distribution and only if you
+ received the program in object code or executable form with such
+ an offer, in accord with Subsection b above.)
+
+The source code for a work means the preferred form of the work for
+making modifications to it. For an executable work, complete source
+code means all the source code for all modules it contains, plus any
+associated interface definition files, plus the scripts used to
+control compilation and installation of the executable. However, as a
+special exception, the source code distributed need not include
+anything that is normally distributed (in either source or binary
+form) with the major components (compiler, kernel, and so on) of the
+operating system on which the executable runs, unless that component
+itself accompanies the executable.
+
+If distribution of executable or object code is made by offering
+access to copy from a designated place, then offering equivalent
+access to copy the source code from the same place counts as
+distribution of the source code, even though third parties are not
+compelled to copy the source along with the object code.
+
+ 4. You may not copy, modify, sublicense, or distribute the Program
+except as expressly provided under this License. Any attempt
+otherwise to copy, modify, sublicense or distribute the Program is
+void, and will automatically terminate your rights under this License.
+However, parties who have received copies, or rights, from you under
+this License will not have their licenses terminated so long as such
+parties remain in full compliance.
+
+ 5. You are not required to accept this License, since you have not
+signed it. However, nothing else grants you permission to modify or
+distribute the Program or its derivative works. These actions are
+prohibited by law if you do not accept this License. Therefore, by
+modifying or distributing the Program (or any work based on the
+Program), you indicate your acceptance of this License to do so, and
+all its terms and conditions for copying, distributing or modifying
+the Program or works based on it.
+
+ 6. Each time you redistribute the Program (or any work based on the
+Program), the recipient automatically receives a license from the
+original licensor to copy, distribute or modify the Program subject to
+these terms and conditions. You may not impose any further
+restrictions on the recipients' exercise of the rights granted herein.
+You are not responsible for enforcing compliance by third parties to
+this License.
+
+ 7. If, as a consequence of a court judgment or allegation of patent
+infringement or for any other reason (not limited to patent issues),
+conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License. If you cannot
+distribute so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you
+may not distribute the Program at all. For example, if a patent
+license would not permit royalty-free redistribution of the Program by
+all those who receive copies directly or indirectly through you, then
+the only way you could satisfy both it and this License would be to
+refrain entirely from distribution of the Program.
+
+If any portion of this section is held invalid or unenforceable under
+any particular circumstance, the balance of the section is intended to
+apply and the section as a whole is intended to apply in other
+circumstances.
+
+It is not the purpose of this section to induce you to infringe any
+patents or other property right claims or to contest validity of any
+such claims; this section has the sole purpose of protecting the
+integrity of the free software distribution system, which is
+implemented by public license practices. Many people have made
+generous contributions to the wide range of software distributed
+through that system in reliance on consistent application of that
+system; it is up to the author/donor to decide if he or she is willing
+to distribute software through any other system and a licensee cannot
+impose that choice.
+
+This section is intended to make thoroughly clear what is believed to
+be a consequence of the rest of this License.
+
+ 8. If the distribution and/or use of the Program is restricted in
+certain countries either by patents or by copyrighted interfaces, the
+original copyright holder who places the Program under this License
+may add an explicit geographical distribution limitation excluding
+those countries, so that distribution is permitted only in or among
+countries not thus excluded. In such case, this License incorporates
+the limitation as if written in the body of this License.
+
+ 9. The Free Software Foundation may publish revised and/or new versions
+of the General Public License from time to time. Such new versions will
+be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+Each version is given a distinguishing version number. If the Program
+specifies a version number of this License which applies to it and "any
+later version", you have the option of following the terms and conditions
+either of that version or of any later version published by the Free
+Software Foundation. If the Program does not specify a version number of
+this License, you may choose any version ever published by the Free Software
+Foundation.
+
+ 10. If you wish to incorporate parts of the Program into other free
+programs whose distribution conditions are different, write to the author
+to ask for permission. For software which is copyrighted by the Free
+Software Foundation, write to the Free Software Foundation; we sometimes
+make exceptions for this. Our decision will be guided by the two goals
+of preserving the free status of all derivatives of our free software and
+of promoting the sharing and reuse of software generally.
+
+ NO WARRANTY
+
+ 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
+FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
+OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
+PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
+OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
+TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
+PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
+REPAIR OR CORRECTION.
+
+ 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
+REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
+INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
+OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
+TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
+YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
+PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
+POSSIBILITY OF SUCH DAMAGES.
+
+ END OF TERMS AND CONDITIONS
+
+ How to Apply These Terms to Your New Programs
+
+ If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+ To do so, attach the following notices to the program. It is safest
+to attach them to the start of each source file to most effectively
+convey the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+
+ Copyright (C)
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along
+ with this program; if not, write to the Free Software Foundation, Inc.,
+ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+Also add information on how to contact you by electronic and paper mail.
+
+If the program is interactive, make it output a short notice like this
+when it starts in an interactive mode:
+
+ Gnomovision version 69, Copyright (C) year name of author
+ Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
+ This is free software, and you are welcome to redistribute it
+ under certain conditions; type `show c' for details.
+
+The hypothetical commands `show w' and `show c' should show the appropriate
+parts of the General Public License. Of course, the commands you use may
+be called something other than `show w' and `show c'; they could even be
+mouse-clicks or menu items--whatever suits your program.
+
+You should also get your employer (if you work as a programmer) or your
+school, if any, to sign a "copyright disclaimer" for the program, if
+necessary. Here is a sample; alter the names:
+
+ Yoyodyne, Inc., hereby disclaims all copyright interest in the program
+ `Gnomovision' (which makes passes at compilers) written by James Hacker.
+
+ , 1 April 1989
+ Ty Coon, President of Vice
+
+This General Public License does not permit incorporating your program into
+proprietary programs. If your program is a subroutine library, you may
+consider it more useful to permit linking proprietary applications with the
+library. If this is what you want to do, use the GNU Lesser General
+Public License instead of this License.
diff --git a/resources/ffmpeg/linux/arm64/PLACE_BINARIES_HERE.txt b/resources/ffmpeg/linux/arm64/PLACE_BINARIES_HERE.txt
index ef37457..74dfae6 100644
--- a/resources/ffmpeg/linux/arm64/PLACE_BINARIES_HERE.txt
+++ b/resources/ffmpeg/linux/arm64/PLACE_BINARIES_HERE.txt
@@ -1,2 +1,7 @@
-Place the platform-specific ffmpeg binary in this directory.
-See resources/ffmpeg/README.md for details.
+Place Linux arm64 FFmpeg binaries here:
+- ffmpeg
+- ffprobe
+
+Build from: https://github.com/BurntToasters/ffmpeg-static-builds
+
+Ensure execute permissions: chmod +x ffmpeg ffprobe
diff --git a/resources/ffmpeg/linux/x64/PLACE_BINARIES_HERE.txt b/resources/ffmpeg/linux/x64/PLACE_BINARIES_HERE.txt
index ef37457..3f56f97 100644
--- a/resources/ffmpeg/linux/x64/PLACE_BINARIES_HERE.txt
+++ b/resources/ffmpeg/linux/x64/PLACE_BINARIES_HERE.txt
@@ -1,2 +1,7 @@
-Place the platform-specific ffmpeg binary in this directory.
-See resources/ffmpeg/README.md for details.
+Place Linux x64 FFmpeg binaries here:
+- ffmpeg
+- ffprobe
+
+Build from: https://github.com/BurntToasters/ffmpeg-static-builds
+
+Ensure execute permissions: chmod +x ffmpeg ffprobe
diff --git a/resources/ffmpeg/mac/arm64/PLACE_BINARIES_HERE.txt b/resources/ffmpeg/mac/arm64/PLACE_BINARIES_HERE.txt
index ef37457..8b021ed 100644
--- a/resources/ffmpeg/mac/arm64/PLACE_BINARIES_HERE.txt
+++ b/resources/ffmpeg/mac/arm64/PLACE_BINARIES_HERE.txt
@@ -1,2 +1,7 @@
-Place the platform-specific ffmpeg binary in this directory.
-See resources/ffmpeg/README.md for details.
+Place macOS arm64 (Apple Silicon) FFmpeg binaries here:
+- ffmpeg
+- ffprobe
+
+Build from: https://github.com/BurntToasters/ffmpeg-static-builds
+
+Ensure execute permissions: chmod +x ffmpeg ffprobe
diff --git a/resources/ffmpeg/mac/x64/PLACE_BINARIES_HERE.txt b/resources/ffmpeg/mac/x64/PLACE_BINARIES_HERE.txt
index ef37457..45951c6 100644
--- a/resources/ffmpeg/mac/x64/PLACE_BINARIES_HERE.txt
+++ b/resources/ffmpeg/mac/x64/PLACE_BINARIES_HERE.txt
@@ -1,2 +1,7 @@
-Place the platform-specific ffmpeg binary in this directory.
-See resources/ffmpeg/README.md for details.
+Place macOS x64 (Intel) FFmpeg binaries here:
+- ffmpeg
+- ffprobe
+
+Build from: https://github.com/BurntToasters/ffmpeg-static-builds
+
+Ensure execute permissions: chmod +x ffmpeg ffprobe
diff --git a/resources/ffmpeg/win/arm64/PLACE_BINARIES_HERE.txt b/resources/ffmpeg/win/arm64/PLACE_BINARIES_HERE.txt
index ef37457..daaa8a9 100644
--- a/resources/ffmpeg/win/arm64/PLACE_BINARIES_HERE.txt
+++ b/resources/ffmpeg/win/arm64/PLACE_BINARIES_HERE.txt
@@ -1,2 +1,5 @@
-Place the platform-specific ffmpeg binary in this directory.
-See resources/ffmpeg/README.md for details.
+Place Windows arm64 FFmpeg binaries here:
+- ffmpeg.exe
+- ffprobe.exe
+
+Build from: https://github.com/BurntToasters/ffmpeg-static-builds
diff --git a/resources/ffmpeg/win/x64/PLACE_BINARIES_HERE.txt b/resources/ffmpeg/win/x64/PLACE_BINARIES_HERE.txt
index ef37457..f0cab36 100644
--- a/resources/ffmpeg/win/x64/PLACE_BINARIES_HERE.txt
+++ b/resources/ffmpeg/win/x64/PLACE_BINARIES_HERE.txt
@@ -1,2 +1,5 @@
-Place the platform-specific ffmpeg binary in this directory.
-See resources/ffmpeg/README.md for details.
+Place Windows x64 FFmpeg binaries here:
+- ffmpeg.exe
+- ffprobe.exe
+
+Build from: https://github.com/BurntToasters/ffmpeg-static-builds
diff --git a/src/main/constants.ts b/src/main/constants.ts
index 0a4ea44..3ca9da1 100644
--- a/src/main/constants.ts
+++ b/src/main/constants.ts
@@ -8,9 +8,12 @@ export const FFMPEG_CONVERT_TIMEOUT_MS = 600_000;
export const MAX_OUTPUT_BUFFER = 500_000;
export const MAX_ERROR_BUFFER = 100_000;
+export const FORMAT_ID_PATTERN = /^[A-Za-z0-9][A-Za-z0-9._-]{0,63}$/;
+export const SUBTITLE_LANGS_PATTERN = /^[A-Za-z0-9.*-]+(,[A-Za-z0-9.*-]+)*$/;
+
export const ALLOWED_AUDIO_FORMATS = new Set(['mp3', 'flac', 'ogg', 'wav', 'm4a', 'opus']);
export const ALLOWED_CONVERT_FORMATS = new Set(['mp4', 'mov', 'mp3', 'm4a']);
export const MAX_QUEUE_SIZE = 500;
export const MAX_FORMAT_COUNTS = 10_000;
export const MAX_SETTINGS_IMPORT_BYTES = 1_048_576;
-export const CURRENT_SETTINGS_VERSION = 2;
+export const CURRENT_SETTINGS_VERSION = 3;
diff --git a/src/main/download/commandBuilders.ts b/src/main/download/commandBuilders.ts
index 5313d70..8e4f9db 100644
--- a/src/main/download/commandBuilders.ts
+++ b/src/main/download/commandBuilders.ts
@@ -1,8 +1,16 @@
+import log from 'electron-log/main.js';
import type { DownloadRequestOptions, GpuDetectionResult, Settings } from '../../types';
import { detectGpu } from '../gpu';
-import { ALLOWED_AUDIO_FORMATS } from '../constants';
+import { spawnWithEnv } from '../platform';
+import {
+ ALLOWED_AUDIO_FORMATS,
+ FORMAT_ID_PATTERN,
+ MAX_ERROR_BUFFER,
+ SUBTITLE_LANGS_PATTERN,
+} from '../constants';
-const VALID_FORMAT_ID = /^\d{1,8}$/;
+const VALID_FORMAT_ID = FORMAT_ID_PATTERN;
+const CODEC_PROBE_TIMEOUT_MS = 30_000;
const ALLOWED_BROWSERS = new Set([
'brave',
'chrome',
@@ -15,8 +23,70 @@ const ALLOWED_BROWSERS = new Set([
'whale',
]);
-export async function resolveVideoEncoder(settings: Settings): Promise {
- if (!settings.gpuAcceleration) return 'copy';
+export interface SourceCodecs {
+ video?: string;
+ audio?: string;
+}
+
+export function probeMediaCodecs(ffmpegCommand: string, inputPath: string): Promise {
+ return new Promise((resolve) => {
+ let settled = false;
+ let stderr = '';
+ let proc: ReturnType;
+ const finish = (codecs: SourceCodecs) => {
+ if (settled) return;
+ settled = true;
+ clearTimeout(timeout);
+ resolve(codecs);
+ };
+
+ const parse = (): SourceCodecs => {
+ const codecs: SourceCodecs = {};
+ const videoMatch = stderr.match(/Stream #\d+:\d+.*: Video: (\w+)/);
+ const audioMatch = stderr.match(/Stream #\d+:\d+.*: Audio: (\w+)/);
+ if (videoMatch?.[1]) codecs.video = videoMatch[1];
+ if (audioMatch?.[1]) codecs.audio = audioMatch[1];
+ return codecs;
+ };
+
+ const timeout = setTimeout(() => {
+ try {
+ proc?.kill('SIGKILL');
+ } catch {}
+ finish(parse());
+ }, CODEC_PROBE_TIMEOUT_MS);
+
+ try {
+ proc = spawnWithEnv(ffmpegCommand, ['-hide_banner', '-i', inputPath], { shell: false });
+ } catch (err) {
+ log.warn('Failed to spawn ffmpeg codec probe:', err);
+ finish({});
+ return;
+ }
+
+ proc.stderr?.on('data', (data) => {
+ if (stderr.length < MAX_ERROR_BUFFER) stderr += data.toString();
+ });
+ proc.on('close', () => finish(parse()));
+ proc.on('error', (err) => {
+ log.warn('ffmpeg codec probe error:', err);
+ finish({});
+ });
+ });
+}
+
+const CONTAINER_COMPATIBLE_VIDEO = new Set([
+ 'h264',
+ 'avc1',
+ 'hevc',
+ 'h265',
+ 'av1',
+ 'av01',
+ 'mpeg4',
+]);
+const CONTAINER_COMPATIBLE_AUDIO = new Set(['aac', 'mp4a', 'mp3', 'ac3', 'alac']);
+
+export async function resolveGpuVideoEncoder(settings: Settings): Promise {
if (settings.gpuType === 'nvidia') return 'h264_nvenc';
if (settings.gpuType === 'amd') return 'h264_amf';
if (settings.gpuType === 'intel') return 'h264_qsv';
@@ -25,34 +95,45 @@ export async function resolveVideoEncoder(settings: Settings): Promise {
if (detected.nvidia) return 'h264_nvenc';
if (detected.amd) return 'h264_amf';
if (detected.intel) return 'h264_qsv';
- return 'copy';
+ return 'libx264';
+}
+
+export async function resolveVideoEncoder(settings: Settings): Promise {
+ if (!settings.gpuAcceleration) return 'copy';
+ const encoder = await resolveGpuVideoEncoder(settings);
+ return encoder === 'libx264' ? 'copy' : encoder;
}
export function buildFfmpegArgs(
inputPath: string,
outputPath: string,
targetFormat: string,
- videoEncoder: string
+ videoEncoder: string,
+ srcCodecs?: SourceCodecs
): string[] {
if (targetFormat === 'mp3' || targetFormat === 'm4a') {
- return [
- '-i',
- inputPath,
- '-vn',
- '-c:a',
- targetFormat === 'mp3' ? 'libmp3lame' : 'aac',
- '-y',
- outputPath,
- ];
+ const targetCodec = targetFormat === 'mp3' ? 'libmp3lame' : 'aac';
+ const srcAudio = srcCodecs?.audio?.toLowerCase();
+ const canCopyAudio =
+ (targetFormat === 'm4a' && (srcAudio === 'aac' || srcAudio === 'mp4a')) ||
+ (targetFormat === 'mp3' && srcAudio === 'mp3');
+ return ['-i', inputPath, '-vn', '-c:a', canCopyAudio ? 'copy' : targetCodec, '-y', outputPath];
}
+ const srcVideo = srcCodecs?.video?.toLowerCase();
+ const srcAudio = srcCodecs?.audio?.toLowerCase();
+ const videoCanCopy = !!srcVideo && CONTAINER_COMPATIBLE_VIDEO.has(srcVideo);
+ const resolvedVideo = srcCodecs ? (videoCanCopy ? 'copy' : videoEncoder) : videoEncoder;
+ const resolvedAudio =
+ srcCodecs && srcAudio && CONTAINER_COMPATIBLE_AUDIO.has(srcAudio) ? 'copy' : 'aac';
+
return [
'-i',
inputPath,
'-c:v',
- videoEncoder,
+ resolvedVideo,
'-c:a',
- 'aac',
+ resolvedAudio,
'-movflags',
'+faststart',
'-y',
@@ -66,6 +147,7 @@ interface BuildYtdlpArgsInput {
settings: Settings;
options: DownloadRequestOptions;
ffmpegLocation: string | null;
+ pathOutputFile?: string | null;
}
export interface BuildYtdlpArgsResult {
@@ -79,6 +161,7 @@ export function buildYtdlpArgs({
settings,
options,
ffmpegLocation,
+ pathOutputFile,
}: BuildYtdlpArgsInput): BuildYtdlpArgsResult {
const args = [
'-P',
@@ -97,6 +180,10 @@ export function buildYtdlpArgs({
];
const statusMessages: string[] = [];
+ if (pathOutputFile) {
+ args.splice(args.indexOf('--'), 0, '--print-to-file', 'after_move:filepath', pathOutputFile);
+ }
+
if (ffmpegLocation) {
args.splice(args.length - 1, 0, '--ffmpeg-location', ffmpegLocation);
}
@@ -133,5 +220,28 @@ export function buildYtdlpArgs({
}
}
+ if (settings.writeSubtitles) {
+ const langs = SUBTITLE_LANGS_PATTERN.test(settings.subtitleLangs)
+ ? settings.subtitleLangs
+ : 'en';
+ args.splice(-1, 0, '--write-subs', '--embed-subs', '--sub-langs', langs);
+ statusMessages.push(`π¬ Subtitles enabled (${langs})`);
+ }
+
+ if (settings.embedThumbnail) {
+ args.splice(-1, 0, '--embed-thumbnail');
+ statusMessages.push('πΌοΈ Embedding thumbnail');
+ }
+
+ if (settings.embedMetadata) {
+ args.splice(-1, 0, '--embed-metadata');
+ statusMessages.push('π·οΈ Embedding metadata');
+ }
+
+ if (settings.sponsorblockRemove) {
+ args.splice(-1, 0, '--sponsorblock-remove', 'default');
+ statusMessages.push('βοΈ SponsorBlock: removing segments');
+ }
+
return { args, statusMessages };
}
diff --git a/src/main/download/videoInfo.ts b/src/main/download/videoInfo.ts
new file mode 100644
index 0000000..109395a
--- /dev/null
+++ b/src/main/download/videoInfo.ts
@@ -0,0 +1,158 @@
+import * as fs from 'fs';
+import log from 'electron-log/main.js';
+import { spawnWithEnv } from '../platform';
+import { isSafeHttpUrl } from '../../utils/validation';
+import { MAX_OUTPUT_BUFFER, MAX_ERROR_BUFFER, FORMAT_FETCH_TIMEOUT_MS } from '../constants';
+import type { FormatsProcess, VideoInfo } from '../../types';
+
+let infoProcess: FormatsProcess | null = null;
+
+function pickString(value: unknown): string | null {
+ return typeof value === 'string' && value.trim() !== '' ? value : null;
+}
+
+function pickNumber(value: unknown): number | null {
+ return typeof value === 'number' && Number.isFinite(value) ? value : null;
+}
+
+function pickThumbnail(value: unknown): string | null {
+ return typeof value === 'string' && isSafeHttpUrl(value) ? value : null;
+}
+
+export function parseVideoInfo(jsonString: string): VideoInfo | null {
+ let parsed: unknown;
+ try {
+ parsed = JSON.parse(jsonString);
+ } catch {
+ return null;
+ }
+ if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) return null;
+
+ const data = parsed as Record;
+ const isPlaylist = data._type === 'playlist' || Array.isArray(data.entries);
+ const entries = Array.isArray(data.entries) ? data.entries : null;
+
+ let thumbnail = pickThumbnail(data.thumbnail);
+ if (!thumbnail && Array.isArray(data.thumbnails) && data.thumbnails.length > 0) {
+ const last = data.thumbnails[data.thumbnails.length - 1];
+ if (last && typeof last === 'object') {
+ thumbnail = pickThumbnail((last as Record).url);
+ }
+ }
+ if (!thumbnail && entries && entries.length > 0) {
+ const first = entries[0];
+ if (first && typeof first === 'object') {
+ thumbnail = pickThumbnail((first as Record).thumbnail);
+ }
+ }
+
+ const title =
+ pickString(data.title) ?? pickString(data.fulltitle) ?? (isPlaylist ? 'Playlist' : 'Untitled');
+
+ return {
+ title,
+ uploader: pickString(data.uploader) ?? pickString(data.channel) ?? pickString(data.creator),
+ durationSeconds: pickNumber(data.duration),
+ thumbnail,
+ ext: pickString(data.ext),
+ viewCount: pickNumber(data.view_count),
+ isPlaylist,
+ playlistCount: pickNumber(data.playlist_count) ?? (entries ? entries.length : null),
+ webpageUrl: pickThumbnail(data.webpage_url),
+ };
+}
+
+export function cancelVideoInfo(): void {
+ if (infoProcess?.proc && !infoProcess.proc.killed) {
+ infoProcess.cancelled = true;
+ try {
+ infoProcess.proc.kill();
+ } catch (error) {
+ log.warn('Error killing video info process:', error);
+ }
+ }
+}
+
+export function fetchVideoInfo(ytdlpPath: string, url: string): Promise {
+ if (!isSafeHttpUrl(url)) {
+ return Promise.reject('Invalid URL provided');
+ }
+ return new Promise((resolve, reject) => {
+ if (!fs.existsSync(ytdlpPath)) {
+ return reject(`yt-dlp binary not found at ${ytdlpPath}`);
+ }
+ if (infoProcess?.proc && !infoProcess.proc.killed) {
+ try {
+ infoProcess.cancelled = true;
+ infoProcess.proc.kill();
+ } catch (error) {
+ log.warn('Error killing previous video info process:', error);
+ }
+ }
+
+ const proc = spawnWithEnv(ytdlpPath, [
+ '--dump-single-json',
+ '--no-playlist',
+ '--no-warnings',
+ '--skip-download',
+ '--',
+ url,
+ ]);
+ infoProcess = { proc, cancelled: false };
+ let outputData = '';
+ let errorData = '';
+
+ const timeout = setTimeout(() => {
+ try {
+ infoProcess!.cancelled = true;
+ proc.kill();
+ } catch (error) {
+ log.warn('Error killing video info process on timeout:', error);
+ }
+ reject('Video info request timed out. The server may be slow or unresponsive.');
+ }, FORMAT_FETCH_TIMEOUT_MS);
+
+ proc.stdout?.on('data', (data) => {
+ if (outputData.length < MAX_OUTPUT_BUFFER) outputData += data;
+ });
+ proc.stderr?.on('data', (data) => {
+ if (errorData.length < MAX_ERROR_BUFFER) errorData += data;
+ });
+
+ proc.on('close', (code) => {
+ clearTimeout(timeout);
+ proc.removeAllListeners();
+ proc.stdout?.removeAllListeners();
+ proc.stderr?.removeAllListeners();
+ const wasCancelled = infoProcess?.proc === proc && infoProcess.cancelled;
+ if (infoProcess?.proc === proc) {
+ infoProcess = null;
+ }
+ if (wasCancelled) {
+ reject('Video info request cancelled.');
+ return;
+ }
+ if (code !== 0) {
+ reject(`yt-dlp exited with code ${code}.\n${errorData}`);
+ return;
+ }
+ const info = parseVideoInfo(outputData);
+ if (!info) {
+ reject('Could not parse video information.');
+ return;
+ }
+ resolve(info);
+ });
+
+ proc.on('error', (err) => {
+ clearTimeout(timeout);
+ proc.removeAllListeners();
+ proc.stdout?.removeAllListeners();
+ proc.stderr?.removeAllListeners();
+ if (infoProcess?.proc === proc) {
+ infoProcess = null;
+ }
+ reject(`Failed to start yt-dlp: ${err.message}`);
+ });
+ });
+}
diff --git a/src/main/downloader.ts b/src/main/downloader.ts
index 446817f..1772aba 100644
--- a/src/main/downloader.ts
+++ b/src/main/downloader.ts
@@ -5,7 +5,12 @@ import { dialog } from 'electron';
import log from 'electron-log/main.js';
import { spawnWithEnv, getEffectiveFfmpegPath, ytdlpBinary, isWindows } from './platform';
import { loadSettings, recordDownload } from './settings';
-import { buildFfmpegArgs, buildYtdlpArgs, resolveVideoEncoder } from './download/commandBuilders';
+import {
+ buildFfmpegArgs,
+ buildYtdlpArgs,
+ resolveVideoEncoder,
+ probeMediaCodecs,
+} from './download/commandBuilders';
import { isSafeHttpUrl } from '../utils/validation';
import { isMac } from './platform';
import {
@@ -22,7 +27,13 @@ import {
FFMPEG_CONVERT_TIMEOUT_MS,
} from './constants';
import type { ChildProcess } from 'child_process';
-import type { DownloadSession, DownloadRequestOptions, FormatsProcess, Settings } from '../types';
+import type {
+ DownloadSession,
+ DownloadRequestOptions,
+ DownloadOutcome,
+ FormatsProcess,
+ Settings,
+} from '../types';
let activeDownloadSession: DownloadSession | null = null;
let downloadSessionCounter = 0;
@@ -47,38 +58,47 @@ function sendProgress(session: DownloadSession | null, message: string) {
safeSend(session.sender, 'progress', message);
}
+interface CompletionMeta {
+ format?: string;
+ bytes?: number;
+ progressMessage?: string | null;
+}
+
+function statFileSize(filePath: string): number | undefined {
+ try {
+ const stat = fs.statSync(filePath);
+ return stat.isFile() && stat.size > 0 ? stat.size : undefined;
+ } catch {
+ return undefined;
+ }
+}
+
function completeSession(
session: DownloadSession | null,
statusMessage: string,
- progressMessage: string | null = null
+ outcome: DownloadOutcome,
+ meta: CompletionMeta = {}
) {
if (!session || !isActiveSession(session)) return;
if (!shouldEmitTerminalEvent(session.lifecycle)) return;
session.lifecycle = markTerminalEventEmitted(session.lifecycle);
- if (progressMessage) {
- safeSend(session.sender, 'progress', progressMessage);
+ if (meta.progressMessage) {
+ safeSend(session.sender, 'progress', meta.progressMessage);
}
safeSend(session.sender, 'complete', statusMessage);
if (typeof session.onComplete === 'function') {
try {
- session.onComplete(statusMessage);
+ session.onComplete(statusMessage, outcome);
} catch (error) {
log.error('Error in download completion callback:', error);
}
}
- const normalizedMsg = statusMessage.toLowerCase();
- if (normalizedMsg.includes('cancel')) {
- recordDownload('cancelled');
- } else if (
- normalizedMsg.includes('β
') ||
- normalizedMsg.includes('complete') ||
- normalizedMsg.includes('done')
- ) {
- recordDownload('success');
- } else if (normalizedMsg.includes('β') || normalizedMsg.includes('fail')) {
- recordDownload('failed');
+ if (outcome === 'success') {
+ recordDownload('success', meta.format, meta.bytes);
+ } else {
+ recordDownload(outcome);
}
activeDownloadSession = null;
@@ -111,13 +131,15 @@ export function cancelActiveSession(notify = true) {
if (!isActiveSession(session)) return;
if (notify) {
- completeSession(session, 'βΉοΈ Cancelled.', 'βΉοΈ Download/Conversion cancelled by user.');
+ completeSession(session, 'βΉοΈ Cancelled.', 'cancelled', {
+ progressMessage: 'βΉοΈ Download/Conversion cancelled by user.',
+ });
return;
}
if (typeof session.onComplete === 'function') {
try {
- session.onComplete('βΉοΈ Cancelled.');
+ session.onComplete('βΉοΈ Cancelled.', 'cancelled');
} catch (error) {
log.error('Error in download cancellation callback:', error);
}
@@ -259,7 +281,10 @@ async function runConversion(
session,
`βΉοΈ Downloaded file is already ${targetFormat.toUpperCase()} (${inputFilename}). Skipping conversion.`
);
- completeSession(session, `β
Done (Already ${targetFormat.toUpperCase()}).`);
+ completeSession(session, `β
Done (Already ${targetFormat.toUpperCase()}).`, 'success', {
+ format: targetFormat,
+ bytes: statFileSize(inputPath),
+ });
return;
}
@@ -269,15 +294,22 @@ async function runConversion(
sendProgress(session, `π¬ Converting ${inputFilename} to ${targetFormat.toUpperCase()}...`);
+ const srcCodecs = await probeMediaCodecs(ffmpegCommand, inputPath);
const videoEncoder = await resolveVideoEncoder(effectiveSettings);
- const useGpu = effectiveSettings.gpuAcceleration && videoEncoder !== 'copy';
- if (useGpu) {
+ const ffmpegArgs = buildFfmpegArgs(
+ inputPath,
+ outputPath,
+ targetFormat,
+ videoEncoder,
+ srcCodecs
+ );
+
+ const reencodesVideo = ffmpegArgs.includes('-c:v') && !ffmpegArgs.includes('copy');
+ if (effectiveSettings.gpuAcceleration && videoEncoder !== 'copy' && reencodesVideo) {
sendProgress(session, `π₯οΈ Using GPU acceleration (${videoEncoder})`);
}
- const ffmpegArgs = buildFfmpegArgs(inputPath, outputPath, targetFormat, videoEncoder);
-
const ffProc = spawnWithEnv(ffmpegCommand, ffmpegArgs);
session.ffmpegProcess = ffProc;
@@ -303,7 +335,9 @@ async function runConversion(
const ffExitType = classifyDownloadExit(session.lifecycle, ffmpegCode ?? 1);
if (ffExitType === 'cancelled') {
- completeSession(session, 'βΉοΈ Cancelled.', 'βΉοΈ Download/Conversion cancelled by user.');
+ completeSession(session, 'βΉοΈ Cancelled.', 'cancelled', {
+ progressMessage: 'βΉοΈ Download/Conversion cancelled by user.',
+ });
return;
}
@@ -332,7 +366,10 @@ async function runConversion(
`βΉοΈ Input and output paths resolved to the same file (${inputPath}), cannot delete original.`
);
}
- completeSession(session, 'π¬ Conversion complete.');
+ completeSession(session, 'π¬ Conversion complete.', 'success', {
+ format: targetFormat,
+ bytes: statFileSize(outputPath),
+ });
} else {
sendProgress(
session,
@@ -342,7 +379,7 @@ async function runConversion(
try {
if (fs.existsSync(outputPath)) fs.unlinkSync(outputPath);
} catch {}
- completeSession(session, 'β Conversion failed.');
+ completeSession(session, 'β Conversion failed.', 'failed');
}
});
@@ -351,7 +388,9 @@ async function runConversion(
if (!isActiveSession(session)) return;
session.ffmpegProcess = null;
if (classifyDownloadExit(session.lifecycle, 1) === 'cancelled') {
- completeSession(session, 'βΉοΈ Cancelled.', 'βΉοΈ Download/Conversion cancelled by user.');
+ completeSession(session, 'βΉοΈ Cancelled.', 'cancelled', {
+ progressMessage: 'βΉοΈ Download/Conversion cancelled by user.',
+ });
return;
}
if (err.message.includes('ENOENT')) {
@@ -359,7 +398,7 @@ async function runConversion(
session,
`β Failed to start conversion: FFmpeg was not found at ${ffmpegCommand}.`
);
- completeSession(session, 'β Conversion failed (FFmpeg not found).');
+ completeSession(session, 'β Conversion failed (FFmpeg not found).', 'failed');
if (mainWindow && !mainWindow.isDestroyed()) {
void dialog.showMessageBox(mainWindow, {
type: 'error',
@@ -371,12 +410,12 @@ async function runConversion(
}
} else {
sendProgress(session, `β Failed to start conversion process: ${err.message}`);
- completeSession(session, 'β Conversion failed (ffmpeg spawn error).');
+ completeSession(session, 'β Conversion failed (ffmpeg spawn error).', 'failed');
}
});
} catch (err) {
sendProgress(session, `β Error setting up conversion: ${(err as Error).message}`);
- completeSession(session, 'β Conversion failed (setup error).');
+ completeSession(session, 'β Conversion failed (setup error).', 'failed');
}
}
@@ -424,17 +463,17 @@ export function startDownload(
if (!isSafeHttpUrl(url)) {
sendProgress(session, 'β οΈ Invalid or missing URL.');
- completeSession(session, 'β Failed (Invalid URL).');
+ completeSession(session, 'β Failed (Invalid URL).', 'failed');
return;
}
if (!downloadDir || typeof downloadDir !== 'string' || downloadDir.trim() === '') {
sendProgress(session, 'β οΈ Invalid or missing download folder.');
- completeSession(session, 'β Failed (Invalid Folder).');
+ completeSession(session, 'β Failed (Invalid Folder).', 'failed');
return;
}
if (!fs.existsSync(ytdlpPath)) {
sendProgress(session, `β Error: yt-dlp binary not found at ${ytdlpPath}`);
- completeSession(session, 'β Failed (Missing Dependency).');
+ completeSession(session, 'β Failed (Missing Dependency).', 'failed');
return;
}
@@ -447,17 +486,28 @@ export function startDownload(
const stats = fs.statSync(normalizedDownloadDir);
if (!stats.isDirectory()) {
sendProgress(session, `β Download path is not a directory: ${normalizedDownloadDir}`);
- completeSession(session, 'β Failed (Invalid Folder).');
+ completeSession(session, 'β Failed (Invalid Folder).', 'failed');
return;
}
}
+ const pathOutputFile = path.join(
+ normalizedDownloadDir,
+ `.rosi-path-${session.id}-${Date.now()}.txt`
+ );
+ const cleanupPathFile = () => {
+ try {
+ if (fs.existsSync(pathOutputFile)) fs.rmSync(pathOutputFile, { force: true });
+ } catch {}
+ };
+
const { args: ytdlpArgs, statusMessages } = buildYtdlpArgs({
normalizedDownloadDir,
url,
settings: effectiveSettings,
options,
ffmpegLocation,
+ pathOutputFile,
});
statusMessages.forEach((message) => sendProgress(session, message));
@@ -496,25 +546,47 @@ export function startDownload(
const exitType = classifyDownloadExit(session.lifecycle, code ?? 1);
if (exitType === 'cancelled') {
- completeSession(session, 'βΉοΈ Cancelled.', 'βΉοΈ Download/Conversion cancelled by user.');
+ cleanupPathFile();
+ completeSession(session, 'βΉοΈ Cancelled.', 'cancelled', {
+ progressMessage: 'βΉοΈ Download/Conversion cancelled by user.',
+ });
return;
}
if (exitType === 'failed') {
+ cleanupPathFile();
sendProgress(session, `β Download failed: yt-dlp process exited with code ${code}`);
sendProgress(session, ` Check console and stderr output above for details.`);
- completeSession(session, 'β Download failed.');
+ completeSession(session, 'β Download failed.', 'failed');
return;
}
let downloadedFilePath: string;
try {
- const outputLines = downloadOutputData.trim().split('\n');
- const pathLines = outputLines
- .map((l) => l.trim())
- .filter((l) => l.length > 0 && !l.startsWith('[') && !l.startsWith('WARNING'));
- const rawPath: string | null =
- pathLines.length > 0 ? (pathLines[pathLines.length - 1] ?? null) : null;
+ let rawPath: string | null = null;
+ try {
+ if (fs.existsSync(pathOutputFile)) {
+ const fileLines = fs
+ .readFileSync(pathOutputFile, 'utf-8')
+ .split('\n')
+ .map((l) => l.trim())
+ .filter((l) => l.length > 0);
+ if (fileLines.length > 0) {
+ rawPath = fileLines[fileLines.length - 1] ?? null;
+ }
+ }
+ } catch (readErr) {
+ log.warn('Failed to read yt-dlp path output file:', readErr);
+ }
+
+ if (!rawPath) {
+ const outputLines = downloadOutputData.trim().split('\n');
+ const pathLines = outputLines
+ .map((l) => l.trim())
+ .filter((l) => l.length > 0 && !l.startsWith('[') && !l.startsWith('WARNING'));
+ rawPath = pathLines.length > 0 ? (pathLines[pathLines.length - 1] ?? null) : null;
+ }
+
if (!rawPath || rawPath.trim() === '') {
throw new Error("Could not find a valid filepath in yt-dlp's output.");
}
@@ -536,11 +608,13 @@ export function startDownload(
);
}
downloadedFilePath = resolvedFilePath;
+ cleanupPathFile();
sendProgress(session, `β
Download finished. Identified file: ${downloadedFilePath}`);
} catch (extractError) {
+ cleanupPathFile();
sendProgress(session, `β Error determining downloaded file path after download.`);
sendProgress(session, ` Error: ${(extractError as Error).message}`);
- completeSession(session, 'β Failed (File Path Error).');
+ completeSession(session, 'β Failed (File Path Error).', 'failed');
return;
}
@@ -554,22 +628,29 @@ export function startDownload(
);
} else {
sendProgress(session, 'βΉοΈ Conversion not enabled for this download.');
- completeSession(session, 'β
Download complete (no conversion).');
+ const ext = path.extname(downloadedFilePath).replace('.', '').toLowerCase() || undefined;
+ completeSession(session, 'β
Download complete (no conversion).', 'success', {
+ format: ext,
+ bytes: statFileSize(downloadedFilePath),
+ });
}
});
ytProc.on('error', (err) => {
if (!isActiveSession(session)) return;
session.ytdlpProcess = null;
+ cleanupPathFile();
if (classifyDownloadExit(session.lifecycle, 1) === 'cancelled') {
- completeSession(session, 'βΉοΈ Cancelled.', 'βΉοΈ Download/Conversion cancelled by user.');
+ completeSession(session, 'βΉοΈ Cancelled.', 'cancelled', {
+ progressMessage: 'βΉοΈ Download/Conversion cancelled by user.',
+ });
return;
}
sendProgress(session, `β Failed to start download process: ${err.message}`);
- completeSession(session, 'β Download failed (process spawn error).');
+ completeSession(session, 'β Download failed (process spawn error).', 'failed');
});
} catch (error) {
sendProgress(session, `β Error before starting download: ${(error as Error).message}`);
- completeSession(session, 'β Failed (Initial Setup Error).');
+ completeSession(session, 'β Failed (Initial Setup Error).', 'failed');
}
}
diff --git a/src/main/gpu.ts b/src/main/gpu.ts
index 02bf927..b9b58be 100644
--- a/src/main/gpu.ts
+++ b/src/main/gpu.ts
@@ -2,7 +2,7 @@ import log from 'electron-log/main.js';
import { spawnWithEnv } from './platform';
import { loadSettings } from './settings';
import { getEffectiveFfmpegPath } from './platform';
-import { GPU_DETECT_TIMEOUT_MS, MAX_ERROR_BUFFER } from './constants';
+import { GPU_DETECT_TIMEOUT_MS } from './constants';
import type { GpuDetectionResult } from '../types';
let cachedGpuResult: GpuDetectionResult | null = null;
@@ -14,6 +14,61 @@ export function clearGpuCache(): void {
gpuCacheTimestamp = 0;
}
+function probeEncoder(ffmpegCommand: string, encoder: string): Promise {
+ return new Promise((resolve) => {
+ let settled = false;
+ let proc: ReturnType;
+ const finish = (value: boolean) => {
+ if (settled) return;
+ settled = true;
+ clearTimeout(timeout);
+ resolve(value);
+ };
+
+ const timeout = setTimeout(() => {
+ try {
+ proc?.kill('SIGTERM');
+ setTimeout(() => {
+ try {
+ if (proc && !proc.killed) proc.kill('SIGKILL');
+ } catch {}
+ }, 2000);
+ } catch (killErr) {
+ log.warn(`Error killing GPU probe (${encoder}):`, killErr);
+ }
+ finish(false);
+ }, GPU_DETECT_TIMEOUT_MS);
+
+ try {
+ proc = spawnWithEnv(
+ ffmpegCommand,
+ [
+ '-hide_banner',
+ '-loglevel',
+ 'error',
+ '-f',
+ 'lavfi',
+ '-i',
+ 'nullsrc=s=64x64:d=0.1',
+ '-c:v',
+ encoder,
+ '-f',
+ 'null',
+ '-',
+ ],
+ { shell: false }
+ );
+ } catch (err) {
+ log.warn(`Failed to spawn GPU probe (${encoder}):`, err);
+ finish(false);
+ return;
+ }
+
+ proc.on('close', (code) => finish(code === 0));
+ proc.on('error', () => finish(false));
+ });
+}
+
export async function detectGpu(): Promise {
if (cachedGpuResult && Date.now() - gpuCacheTimestamp < GPU_CACHE_TTL_MS) return cachedGpuResult;
const result: GpuDetectionResult = { nvidia: false, amd: false, intel: false };
@@ -21,50 +76,14 @@ export async function detectGpu(): Promise {
const ffmpegCommand = getEffectiveFfmpegPath(settings.ffmpegPath);
try {
- const proc = spawnWithEnv(ffmpegCommand, ['-hide_banner', '-encoders'], { shell: false });
- const output = await new Promise((resolve) => {
- let buf = '';
- let settled = false;
- const timeout = setTimeout(() => {
- if (settled) return;
- settled = true;
- try {
- proc.kill('SIGTERM');
- setTimeout(() => {
- try {
- if (!proc.killed) proc.kill('SIGKILL');
- } catch {}
- }, 3000);
- } catch (killErr) {
- log.warn('Error killing GPU detection process on timeout:', killErr);
- }
- resolve('');
- }, GPU_DETECT_TIMEOUT_MS);
-
- proc.stdout?.on('data', (data) => {
- if (buf.length < MAX_ERROR_BUFFER) buf += data.toString();
- });
- proc.stderr?.on('data', (data) => {
- if (buf.length < MAX_ERROR_BUFFER) buf += data.toString();
- });
- proc.on('close', () => {
- if (settled) return;
- settled = true;
- clearTimeout(timeout);
- resolve(buf);
- });
- proc.on('error', (err) => {
- if (settled) return;
- settled = true;
- clearTimeout(timeout);
- log.warn('GPU detection process error:', err);
- resolve('');
- });
- });
-
- result.nvidia = output.includes('h264_nvenc');
- result.amd = output.includes('h264_amf');
- result.intel = output.includes('h264_qsv');
+ const [nvidia, amd, intel] = await Promise.all([
+ probeEncoder(ffmpegCommand, 'h264_nvenc'),
+ probeEncoder(ffmpegCommand, 'h264_amf'),
+ probeEncoder(ffmpegCommand, 'h264_qsv'),
+ ]);
+ result.nvidia = nvidia;
+ result.amd = amd;
+ result.intel = intel;
} catch (err) {
log.error('GPU detection error:', err);
}
diff --git a/src/main/main.ts b/src/main/main.ts
index 3d42424..4b4a776 100644
--- a/src/main/main.ts
+++ b/src/main/main.ts
@@ -28,6 +28,7 @@ import {
fetchFormats,
cancelFormats,
} from './downloader';
+import { fetchVideoInfo, cancelVideoInfo } from './download/videoInfo';
import { isSafeExternalUrl, isSafeHttpUrl, isAllowedNavigationUrl } from '../utils/validation';
import {
errorResult,
@@ -39,10 +40,12 @@ import {
validateSettingsPatchPayload,
} from '../utils/ipcValidation';
import { SPLASH_SHOW_DELAY_MS, SPLASH_FADE_DELAY_MS, MAX_QUEUE_SIZE } from './constants';
-import type { DownloadRequestOptions, QueueItem } from '../types';
+import type { DownloadRequestOptions, DownloadOutcome, QueueItem } from '../types';
log.initialize();
+process.setMaxListeners(20);
+
process.on('uncaughtException', (error) => {
log.error('Uncaught exception:', error);
try {
@@ -245,8 +248,6 @@ function createWindow() {
height: 900,
minWidth: 900,
minHeight: 700,
- maxWidth: 1800,
- maxHeight: 1400,
icon: path.join(__dirname, '..', '..', 'src', 'renderer', 'app.png'),
webPreferences: {
preload: path.join(__dirname, 'preload.js'),
@@ -416,6 +417,11 @@ app.on('before-quit', () => {
} catch (error) {
log.error('Error cancelling formats on quit:', error);
}
+ try {
+ cancelVideoInfo();
+ } catch (error) {
+ log.error('Error cancelling video info on quit:', error);
+ }
});
if (!process.windowsStore) {
@@ -552,6 +558,35 @@ ipcMain.on('cancel-formats', (event) => {
cancelFormats();
});
+ipcMain.handle('get-video-info', async (_, url) => {
+ if (typeof url !== 'string' || !isSafeHttpUrl(url)) {
+ return errorResult('INVALID_URL', 'Invalid URL provided.');
+ }
+
+ try {
+ const info = await fetchVideoInfo(ytdlpPath, url);
+ return okResult(info);
+ } catch (error) {
+ const message =
+ typeof error === 'string'
+ ? error
+ : error instanceof Error
+ ? error.message
+ : 'Failed to fetch video info.';
+ if (message.toLowerCase().includes('cancel')) {
+ return errorResult('NOT_AVAILABLE', message);
+ }
+ return errorResult('INTERNAL_ERROR', message);
+ }
+});
+
+ipcMain.on('cancel-video-info', (event) => {
+ if (mainWindow && !mainWindow.isDestroyed() && event.sender?.id !== mainWindow.webContents.id) {
+ return;
+ }
+ cancelVideoInfo();
+});
+
ipcMain.handle('download-video', (event, options) => {
if (mainWindow && !mainWindow.isDestroyed() && event.sender?.id !== mainWindow.webContents.id) {
return errorResult('VALIDATION_ERROR', 'Unauthorized sender.');
@@ -804,15 +839,14 @@ async function processQueue() {
};
let settled = false;
- const completeListener = (statusMessage: string) => {
+ const completeListener = (statusMessage: string, outcome?: DownloadOutcome) => {
if (settled) return;
settled = true;
- const msg = String(statusMessage || '').toLowerCase();
- if (msg.includes('cancel')) {
+ if (outcome === 'cancelled') {
nextItem.status = 'cancelled';
nextItem.error = undefined;
- } else if (msg.includes('\u2705') || msg.includes('complete') || msg.includes('done')) {
+ } else if (outcome === 'success') {
nextItem.status = 'completed';
nextItem.error = undefined;
} else {
diff --git a/src/main/preload.ts b/src/main/preload.ts
index ab51e79..38e98e8 100644
--- a/src/main/preload.ts
+++ b/src/main/preload.ts
@@ -16,6 +16,8 @@ const api: RendererApi = {
getChannel: () =>
process.env.CHANNEL === 'msstore' || process.windowsStore ? 'msstore' : 'github',
getFormats: (url: string) => ipcRenderer.invoke('getFormats', url),
+ getVideoInfo: (url: string) => ipcRenderer.invoke('get-video-info', url),
+ cancelVideoInfo: () => ipcRenderer.send('cancel-video-info'),
selectDownloadLocation: () => ipcRenderer.invoke('select-download-location'),
getSettings: () => ipcRenderer.invoke('get-settings'),
saveSettings: (settings: Partial) => ipcRenderer.invoke('save-settings', settings),
diff --git a/src/main/settings.ts b/src/main/settings.ts
index 5b32918..9bcd7a2 100644
--- a/src/main/settings.ts
+++ b/src/main/settings.ts
@@ -9,6 +9,7 @@ import {
MAX_FORMAT_COUNTS,
MAX_SETTINGS_IMPORT_BYTES,
CURRENT_SETTINGS_VERSION,
+ SUBTITLE_LANGS_PATTERN,
} from './constants';
const settingsPath = path.join(app.getPath('userData'), 'settings.json');
@@ -39,6 +40,11 @@ const defaultSettings: Settings = {
hideSupportModal: false,
checkUpdatesOnStartup: true,
updateChannel: 'auto',
+ writeSubtitles: false,
+ subtitleLangs: 'en',
+ embedThumbnail: false,
+ embedMetadata: false,
+ sponsorblockRemove: false,
};
export function getDefaultSettings(): Settings {
@@ -69,6 +75,15 @@ function readConvertFormat(value: unknown): string {
: defaultSettings.convertFormat;
}
+function readSubtitleLangs(value: unknown): string {
+ if (typeof value !== 'string') return defaultSettings.subtitleLangs;
+ const trimmed = value.trim();
+ if (!trimmed || trimmed.length > 256 || !SUBTITLE_LANGS_PATTERN.test(trimmed)) {
+ return defaultSettings.subtitleLangs;
+ }
+ return trimmed;
+}
+
function readUpdateChannel(value: unknown): Settings['updateChannel'] {
return value === 'stable' || value === 'beta' || value === 'auto'
? value
@@ -143,6 +158,14 @@ export function migrateSettings(rawSettings: unknown): Settings {
defaultSettings.checkUpdatesOnStartup
),
updateChannel: readUpdateChannel(rawSettings.updateChannel),
+ writeSubtitles: readBoolean(rawSettings.writeSubtitles, defaultSettings.writeSubtitles),
+ subtitleLangs: readSubtitleLangs(rawSettings.subtitleLangs),
+ embedThumbnail: readBoolean(rawSettings.embedThumbnail, defaultSettings.embedThumbnail),
+ embedMetadata: readBoolean(rawSettings.embedMetadata, defaultSettings.embedMetadata),
+ sponsorblockRemove: readBoolean(
+ rawSettings.sponsorblockRemove,
+ defaultSettings.sponsorblockRemove
+ ),
};
}
diff --git a/src/renderer/css/01-base.css b/src/renderer/css/01-base.css
new file mode 100644
index 0000000..7b04d42
--- /dev/null
+++ b/src/renderer/css/01-base.css
@@ -0,0 +1,333 @@
+@font-face {
+ font-family: 'IBM Plex Mono';
+ font-style: normal;
+ font-weight: 400;
+ font-display: swap;
+ src: url('../fonts/IBMPlexMono-400.ttf') format('truetype');
+}
+@font-face {
+ font-family: 'IBM Plex Mono';
+ font-style: normal;
+ font-weight: 500;
+ font-display: swap;
+ src: url('../fonts/IBMPlexMono-500.ttf') format('truetype');
+}
+@font-face {
+ font-family: 'Manrope';
+ font-style: normal;
+ font-weight: 400;
+ font-display: swap;
+ src: url('../fonts/Manrope-400.ttf') format('truetype');
+}
+@font-face {
+ font-family: 'Manrope';
+ font-style: normal;
+ font-weight: 500;
+ font-display: swap;
+ src: url('../fonts/Manrope-500.ttf') format('truetype');
+}
+@font-face {
+ font-family: 'Manrope';
+ font-style: normal;
+ font-weight: 600;
+ font-display: swap;
+ src: url('../fonts/Manrope-600.ttf') format('truetype');
+}
+@font-face {
+ font-family: 'Manrope';
+ font-style: normal;
+ font-weight: 700;
+ font-display: swap;
+ src: url('../fonts/Manrope-700.ttf') format('truetype');
+}
+
+:root {
+ --font-sans: 'Manrope', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
+ --font-mono: 'IBM Plex Mono', 'SF Mono', 'Fira Code', 'Consolas', monospace;
+
+ --text-xs: 0.6rem;
+ --text-sm: 0.75rem;
+ --text-base: 0.92rem;
+ --text-md: 1rem;
+ --text-lg: 1.22rem;
+ --text-xl: 2rem;
+
+ --weight-normal: 400;
+ --weight-medium: 500;
+ --weight-semibold: 600;
+ --weight-bold: 700;
+
+ --bg-gradient-start: #0a0c10;
+ --bg-gradient-mid: #0d0f14;
+ --bg-gradient-end: #10131a;
+ --surface-1: rgba(255, 255, 255, 0.04);
+ --surface-2: rgba(255, 255, 255, 0.07);
+ --surface-3: rgba(255, 255, 255, 0.1);
+ --surface-hover: rgba(255, 255, 255, 0.14);
+
+ --border-subtle: rgba(255, 255, 255, 0.1);
+ --border-default: rgba(255, 255, 255, 0.16);
+ --border-strong: rgba(255, 255, 255, 0.24);
+
+ --text-primary: #ffffff;
+ --text-secondary: rgba(255, 255, 255, 0.8);
+ --text-tertiary: rgba(255, 255, 255, 0.7);
+ --text-muted: rgba(255, 255, 255, 0.45);
+
+ --accent: #22d3ee;
+ --accent-light: #67e8f9;
+ --accent-dark: #0891b2;
+ --accent-hover: #45def3;
+ --accent-contrast: #05232e;
+ --accent-glow: rgba(34, 211, 238, 0.18);
+ --accent-subtle: rgba(34, 211, 238, 0.14);
+ --input-bg: var(--surface-2);
+
+ --success: #22c55e;
+ --success-light: #34d399;
+ --success-subtle: rgba(34, 197, 94, 0.15);
+ --warning: #f59e0b;
+ --warning-light: #fbbf24;
+ --warning-dark: #d97706;
+ --warning-subtle: rgba(245, 158, 11, 0.15);
+ --danger: #ef4444;
+ --danger-subtle: rgba(239, 68, 68, 0.15);
+
+ --overlay-bg: rgba(0, 0, 0, 0.58);
+ --overlay-bg-heavy: rgba(0, 0, 0, 0.72);
+ --sidebar-bg-start: rgba(8, 10, 14, 0.98);
+ --sidebar-bg-end: rgba(4, 6, 9, 0.98);
+ --modal-bg-start: rgba(16, 20, 27, 0.98);
+ --modal-bg-end: rgba(8, 10, 14, 0.98);
+ --console-bg: rgba(0, 0, 0, 0.5);
+ --console-header-bg: rgba(0, 0, 0, 0.42);
+ --console-header-hover-bg: rgba(0, 0, 0, 0.52);
+ --console-text: #86efac;
+
+ --spacing-xs: 0.25rem;
+ --spacing-sm: 0.5rem;
+ --spacing-md: 0.95rem;
+ --spacing-lg: 1.35rem;
+ --spacing-xl: 1.9rem;
+ --spacing-2xl: 2.6rem;
+
+ --radius-sm: 8px;
+ --radius-md: 12px;
+ --radius-lg: 18px;
+ --radius-xl: 26px;
+ --radius-full: 50%;
+
+ --shadow-sm: 0 1px 4px rgba(0, 0, 0, 0.15);
+ --shadow-md: 0 2px 8px rgba(0, 0, 0, 0.2);
+ --shadow-lg: 0 4px 16px rgba(0, 0, 0, 0.25);
+ --shadow-glow: none;
+
+ --transition-fast: 150ms ease;
+ --transition-normal: 250ms ease;
+ --transition-slow: 400ms cubic-bezier(0.16, 1, 0.3, 1);
+
+ --blur-sm: 4px;
+ --blur-md: 8px;
+ --blur-lg: 20px;
+
+ --icon-btn-size: 40px;
+ --icon-btn-size-sm: 36px;
+ --icon-btn-size-xs: 28px;
+ --toggle-width: 44px;
+ --toggle-height: 24px;
+ --toggle-dot: 18px;
+ --toggle-sm-width: 36px;
+ --toggle-sm-height: 20px;
+ --toggle-sm-dot: 14px;
+ --progress-bar-height: 8px;
+ --console-height: 150px;
+ --download-card-max-width: 760px;
+ --modal-max-width: 440px;
+ --licenses-max-width: 900px;
+ --scrollbar-width: 6px;
+
+ --z-sidebar-overlay: 998;
+ --z-sidebar: 999;
+ --z-modal: 1000;
+ --z-update-banner: 100;
+
+ --sidebar-width: 320px;
+ --app-shell-max-width: 1180px;
+ --main-stage-max-width: 1040px;
+ --main-stage-gap: 14px;
+ --top-bar-height: 68px;
+ --control-height: 54px;
+ --header-inline-padding: 22px;
+ --card-outline: 1px solid var(--border-subtle);
+ --footer-height: 40px;
+ --url-valid-border: var(--success);
+ --url-invalid-border: var(--danger);
+ --topbar-shadow-color: rgba(0, 0, 0, 0.26);
+ --logo-char-white-color: #ffffff;
+ --logo-char-white-shadow: 0 0 1px rgba(255, 255, 255, 0.85), 0 0 4px rgba(255, 255, 255, 0.35);
+ --logo-char-black-color: #050505;
+ --logo-char-black-stroke: 0.5px rgba(255, 255, 255, 0.45);
+ --logo-char-black-shadow:
+ -1px -1px 0 rgba(255, 255, 255, 0.34), 1px -1px 0 rgba(255, 255, 255, 0.34),
+ -1px 1px 0 rgba(255, 255, 255, 0.34), 1px 1px 0 rgba(255, 255, 255, 0.34),
+ 0 0 5px rgba(255, 255, 255, 0.2);
+ --download-btn-radial: none;
+}
+
+:root[data-theme='dark'] {
+ --bg-gradient-start: #0a0c10;
+ --bg-gradient-mid: #0d0f14;
+ --bg-gradient-end: #10131a;
+}
+
+:root[data-theme='purple'] {
+ --bg-gradient-start: #120e1e;
+ --bg-gradient-mid: #18122a;
+ --bg-gradient-end: #1e163a;
+ --surface-1: rgba(255, 255, 255, 0.03);
+ --surface-2: rgba(255, 255, 255, 0.06);
+ --surface-3: rgba(255, 255, 255, 0.09);
+ --surface-hover: rgba(255, 255, 255, 0.12);
+ --border-subtle: rgba(255, 255, 255, 0.08);
+ --border-default: rgba(255, 255, 255, 0.12);
+ --border-strong: rgba(255, 255, 255, 0.2);
+ --text-primary: #ffffff;
+ --text-secondary: rgba(255, 255, 255, 0.78);
+ --text-tertiary: rgba(255, 255, 255, 0.7);
+ --text-muted: rgba(255, 255, 255, 0.45);
+ --accent: #8b5cf6;
+ --accent-light: #a78bfa;
+ --accent-dark: #7c3aed;
+ --accent-hover: #7c3aed;
+ --accent-contrast: #ffffff;
+ --accent-glow: rgba(139, 92, 246, 0.2);
+ --accent-subtle: rgba(139, 92, 246, 0.12);
+ --overlay-bg: rgba(0, 0, 0, 0.5);
+ --overlay-bg-heavy: rgba(0, 0, 0, 0.6);
+ --sidebar-bg-start: rgba(20, 15, 35, 0.98);
+ --sidebar-bg-end: rgba(18, 13, 30, 0.98);
+ --modal-bg-start: rgba(25, 20, 42, 0.98);
+ --modal-bg-end: rgba(20, 15, 35, 0.98);
+ --console-bg: rgba(0, 0, 0, 0.4);
+ --console-header-bg: rgba(0, 0, 0, 0.3);
+ --console-header-hover-bg: rgba(0, 0, 0, 0.4);
+ --console-text: #4ade80;
+ --topbar-shadow-color: rgba(0, 0, 0, 0.2);
+ --logo-char-white-color: #ffffff;
+ --logo-char-white-shadow: 0 0 1px rgba(255, 255, 255, 0.9), 0 0 4px rgba(255, 255, 255, 0.38);
+ --logo-char-black-color: #000000;
+ --logo-char-black-stroke: 0.5px rgba(255, 255, 255, 0.6);
+ --logo-char-black-shadow:
+ -1px -1px 0 rgba(255, 255, 255, 0.5), 1px -1px 0 rgba(255, 255, 255, 0.5),
+ -1px 1px 0 rgba(255, 255, 255, 0.5), 1px 1px 0 rgba(255, 255, 255, 0.5),
+ 0 0 5px rgba(255, 255, 255, 0.24);
+}
+
+:root[data-theme='light'] {
+ --bg-gradient-start: #f4f7fb;
+ --bg-gradient-mid: #eef3f8;
+ --bg-gradient-end: #e7edf5;
+ --surface-1: rgba(255, 255, 255, 0.76);
+ --surface-2: rgba(255, 255, 255, 0.88);
+ --surface-3: rgba(255, 255, 255, 0.95);
+ --surface-hover: rgba(232, 239, 249, 0.95);
+ --border-subtle: rgba(15, 23, 42, 0.12);
+ --border-default: rgba(15, 23, 42, 0.22);
+ --border-strong: rgba(15, 23, 42, 0.32);
+ --text-primary: #0f172a;
+ --text-secondary: rgba(15, 23, 42, 0.82);
+ --text-tertiary: rgba(15, 23, 42, 0.7);
+ --text-muted: rgba(15, 23, 42, 0.52);
+ --accent: #2563eb;
+ --accent-light: #3b82f6;
+ --accent-dark: #1d4ed8;
+ --accent-hover: #1d4ed8;
+ --accent-contrast: #ffffff;
+ --accent-glow: rgba(37, 99, 235, 0.16);
+ --accent-subtle: rgba(37, 99, 235, 0.12);
+ --input-bg: var(--surface-3);
+ --success-subtle: rgba(34, 197, 94, 0.2);
+ --warning-subtle: rgba(245, 158, 11, 0.2);
+ --danger-subtle: rgba(239, 68, 68, 0.2);
+ --overlay-bg: rgba(15, 23, 42, 0.34);
+ --overlay-bg-heavy: rgba(15, 23, 42, 0.5);
+ --sidebar-bg-start: rgba(247, 250, 255, 0.98);
+ --sidebar-bg-end: rgba(237, 244, 253, 0.98);
+ --modal-bg-start: rgba(253, 255, 255, 0.98);
+ --modal-bg-end: rgba(243, 248, 255, 0.98);
+ --console-bg: rgba(15, 23, 42, 0.07);
+ --console-header-bg: rgba(15, 23, 42, 0.1);
+ --console-header-hover-bg: rgba(15, 23, 42, 0.14);
+ --console-text: #166534;
+ --shadow-sm: 0 1px 4px rgba(15, 23, 42, 0.06);
+ --shadow-md: 0 2px 8px rgba(15, 23, 42, 0.1);
+ --shadow-lg: 0 4px 16px rgba(15, 23, 42, 0.14);
+ --topbar-shadow-color: rgba(15, 23, 42, 0.08);
+ --logo-char-white-color: #f8fafc;
+ --logo-char-white-shadow: 0 0 1px rgba(15, 23, 42, 0.2), 0 0 3px rgba(15, 23, 42, 0.12);
+ --logo-char-black-color: #0f172a;
+ --logo-char-black-stroke: 0.5px rgba(248, 250, 252, 0.7);
+ --logo-char-black-shadow:
+ -1px -1px 0 rgba(248, 250, 252, 0.5), 1px -1px 0 rgba(248, 250, 252, 0.5),
+ -1px 1px 0 rgba(248, 250, 252, 0.5), 1px 1px 0 rgba(248, 250, 252, 0.5),
+ 0 0 5px rgba(248, 250, 252, 0.2);
+ --download-btn-radial: none;
+}
+
+*,
+*::before,
+*::after {
+ box-sizing: border-box;
+ margin: 0;
+ padding: 0;
+}
+
+html {
+ font-size: 16px;
+ -webkit-font-smoothing: antialiased;
+ -moz-osx-font-smoothing: grayscale;
+}
+
+body {
+ font-family: var(--font-sans);
+ background: var(--bg-gradient-mid);
+ background-attachment: fixed;
+ min-height: 100vh;
+ color: var(--text-primary);
+ line-height: 1.5;
+ overflow-y: auto;
+ overflow-x: hidden;
+}
+
+body.animate-bg {
+ background: var(--bg-gradient-mid);
+}
+
+.skip-link {
+ position: absolute;
+ top: -100%;
+ left: 50%;
+ transform: translateX(-50%);
+ background: var(--accent);
+ color: var(--accent-contrast);
+ padding: var(--spacing-sm) var(--spacing-md);
+ border-radius: var(--radius-sm);
+ font-weight: var(--weight-semibold);
+ z-index: 10000;
+ text-decoration: none;
+ transition: top var(--transition-fast);
+}
+
+.skip-link:focus {
+ top: var(--spacing-sm);
+}
+
+@keyframes gradientShift {
+ 0%,
+ 100% {
+ background-position: 0% 50%;
+ }
+ 50% {
+ background-position: 0% 50%;
+ }
+}
diff --git a/src/renderer/css/02-sidebar.css b/src/renderer/css/02-sidebar.css
new file mode 100644
index 0000000..7a76bcb
--- /dev/null
+++ b/src/renderer/css/02-sidebar.css
@@ -0,0 +1,463 @@
+.sidebar-overlay {
+ position: fixed;
+ inset: 0;
+ background: var(--overlay-bg);
+ backdrop-filter: blur(var(--blur-sm));
+ opacity: 0;
+ visibility: hidden;
+ transition: var(--transition-normal);
+ z-index: var(--z-sidebar-overlay);
+}
+
+.sidebar-overlay.active {
+ opacity: 1;
+ visibility: visible;
+}
+
+.sidebar {
+ position: fixed;
+ top: 0;
+ left: 0;
+ width: var(--sidebar-width);
+ height: 100vh;
+ background: var(--sidebar-bg-start);
+ backdrop-filter: blur(var(--blur-lg));
+ border-right: 1px solid var(--border-subtle);
+ transform: translateX(-100%);
+ transition: transform var(--transition-slow);
+ z-index: var(--z-sidebar);
+ display: flex;
+ flex-direction: column;
+ box-shadow: var(--shadow-lg);
+}
+
+.sidebar.open {
+ transform: translateX(0);
+}
+
+.sidebar-header {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ padding: var(--spacing-lg) var(--spacing-xl);
+ border-bottom: 1px solid var(--border-subtle);
+}
+
+.sidebar-header h2 {
+ font-size: var(--text-lg);
+ font-weight: var(--weight-semibold);
+ color: var(--text-primary);
+}
+
+.sidebar-close {
+ width: var(--icon-btn-size-sm);
+ height: var(--icon-btn-size-sm);
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ background: var(--surface-2);
+ border: 1px solid var(--border-subtle);
+ border-radius: var(--radius-md);
+ color: var(--text-secondary);
+ cursor: pointer;
+ transition: var(--transition-fast);
+}
+
+.sidebar-close:hover {
+ background: var(--danger-subtle);
+ border-color: var(--danger);
+ color: var(--danger);
+}
+
+.sidebar-content {
+ flex: 1;
+ overflow-y: auto;
+ padding: var(--spacing-lg);
+}
+
+.sidebar-content::-webkit-scrollbar {
+ width: var(--scrollbar-width);
+}
+
+.sidebar-content::-webkit-scrollbar-track {
+ background: transparent;
+}
+
+.sidebar-content::-webkit-scrollbar-thumb {
+ background: var(--border-default);
+ border-radius: 3px;
+}
+
+.sidebar-content::-webkit-scrollbar-thumb:hover {
+ background: var(--border-strong);
+}
+
+.settings-section {
+ margin-bottom: var(--spacing-lg);
+}
+
+.settings-section-header {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ cursor: pointer;
+ padding: var(--spacing-xs) 0;
+ margin-bottom: var(--spacing-xs);
+ border-radius: var(--radius-sm);
+ transition: var(--transition-fast);
+}
+
+.settings-section-header:hover .settings-section-title {
+ color: var(--text-secondary);
+}
+
+.settings-chevron {
+ color: var(--text-muted);
+ transition: transform var(--transition-normal);
+ flex-shrink: 0;
+}
+
+.settings-section.collapsed .settings-chevron {
+ transform: rotate(-90deg);
+}
+
+.settings-section-body {
+ overflow: hidden;
+ max-height: 800px;
+ transition:
+ max-height var(--transition-slow),
+ opacity var(--transition-normal);
+ opacity: 1;
+}
+
+.settings-section.collapsed .settings-section-body {
+ max-height: 0;
+ opacity: 0;
+}
+
+.settings-section-title {
+ font-size: 0.7rem;
+ font-weight: var(--weight-semibold);
+ text-transform: uppercase;
+ letter-spacing: 0.08em;
+ color: var(--text-tertiary);
+ margin-bottom: 0;
+ padding-left: var(--spacing-xs);
+ transition: color var(--transition-fast);
+}
+
+.toggle-switch {
+ display: flex;
+ align-items: center;
+ gap: var(--spacing-sm);
+ padding: var(--spacing-sm) var(--spacing-md);
+ background: var(--surface-1);
+ border: 1px solid var(--border-subtle);
+ border-radius: var(--radius-md);
+ cursor: pointer;
+ transition: var(--transition-fast);
+ margin-bottom: var(--spacing-sm);
+}
+
+.toggle-switch:hover {
+ background: var(--surface-2);
+ border-color: var(--border-default);
+}
+
+.toggle-switch input {
+ position: absolute;
+ opacity: 0;
+ pointer-events: none;
+}
+
+.toggle-slider {
+ position: relative;
+ width: var(--toggle-width);
+ height: var(--toggle-height);
+ background: var(--surface-3);
+ border-radius: 12px;
+ flex-shrink: 0;
+ transition: var(--transition-fast);
+}
+
+.toggle-slider::after {
+ content: '';
+ position: absolute;
+ top: 3px;
+ left: 3px;
+ width: var(--toggle-dot);
+ height: var(--toggle-dot);
+ background: var(--text-secondary);
+ border-radius: var(--radius-full);
+ transition: var(--transition-fast);
+}
+
+.toggle-switch input:checked + .toggle-slider {
+ background: var(--accent);
+}
+
+.toggle-switch input:checked + .toggle-slider::after {
+ transform: translateX(20px);
+ background: var(--text-primary);
+}
+
+.toggle-text {
+ font-size: var(--text-base);
+ color: var(--text-secondary);
+ display: flex;
+ align-items: center;
+ gap: var(--spacing-sm);
+}
+
+.toggle-switch.small {
+ padding: var(--spacing-xs) var(--spacing-sm);
+}
+
+.toggle-switch.small .toggle-slider {
+ width: var(--toggle-sm-width);
+ height: var(--toggle-sm-height);
+}
+
+.toggle-switch.small .toggle-slider::after {
+ width: var(--toggle-sm-dot);
+ height: var(--toggle-sm-dot);
+}
+
+.toggle-switch.small input:checked + .toggle-slider::after {
+ transform: translateX(16px);
+}
+
+.toggle-switch.small .toggle-text {
+ font-size: var(--text-sm);
+}
+
+#keepOriginalLabel,
+#gpuAccelerationLabel {
+ display: none;
+}
+
+#keepOriginalLabel.visible,
+#gpuAccelerationLabel.visible {
+ display: flex;
+}
+
+#gpuTypeContainer {
+ margin-left: 0;
+ margin-top: var(--spacing-sm);
+}
+
+.toggle-switch.disabled {
+ opacity: 0.5;
+ cursor: not-allowed;
+ pointer-events: none;
+}
+
+.toggle-switch.disabled .toggle-slider {
+ background: var(--surface-2);
+}
+
+.sub-option {
+ display: none;
+ padding: var(--spacing-sm) var(--spacing-md);
+ margin-left: var(--spacing-md);
+ margin-bottom: var(--spacing-xs);
+ background: var(--surface-1);
+ border-left: 2px solid var(--accent);
+ border-radius: 0 var(--radius-md) var(--radius-md) 0;
+}
+
+.sub-option.visible {
+ display: block;
+}
+
+.select-label {
+ display: flex;
+ flex-direction: column;
+ gap: var(--spacing-xs);
+ margin-bottom: var(--spacing-sm);
+}
+
+.select-label span {
+ font-size: var(--text-sm);
+ color: var(--text-tertiary);
+}
+
+.select-label select,
+.select-label input,
+.sidebar select {
+ width: 100%;
+ padding: var(--spacing-sm) var(--spacing-md);
+ background: var(--surface-2);
+ border: 1px solid var(--border-subtle);
+ border-radius: var(--radius-sm);
+ color: var(--text-primary);
+ font-size: var(--text-base);
+ cursor: pointer;
+ transition: var(--transition-fast);
+}
+
+.select-label select:hover,
+.select-label input:hover,
+.sidebar select:hover {
+ border-color: var(--border-default);
+}
+
+.select-label select:focus,
+.select-label input:focus,
+.sidebar select:focus {
+ outline: none;
+ border-color: var(--accent);
+ box-shadow: 0 0 0 3px var(--accent-subtle);
+}
+
+select option {
+ background: var(--bg-gradient-mid);
+ color: var(--text-primary);
+}
+
+.settings-hint {
+ font-size: 0.78rem;
+ color: var(--text-tertiary);
+ margin-top: calc(var(--spacing-xs) * -1);
+ margin-bottom: var(--spacing-sm);
+ padding-left: var(--spacing-xs);
+}
+
+.help-icon {
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ width: 18px;
+ height: 18px;
+ background: var(--accent-subtle);
+ border-radius: var(--radius-full);
+ font-size: 0.7rem;
+ font-weight: var(--weight-semibold);
+ color: var(--accent-light);
+ text-decoration: none;
+ transition: var(--transition-fast);
+}
+
+.help-icon:hover {
+ background: var(--accent);
+ color: var(--text-primary);
+}
+
+.settings-link-btn {
+ background: var(--surface-1);
+ border: 1px solid var(--border-subtle);
+ border-radius: var(--radius-sm);
+ color: var(--text-secondary);
+ font-size: var(--text-sm);
+ cursor: pointer;
+ padding: var(--spacing-xs) var(--spacing-sm);
+ margin-left: var(--spacing-md);
+ margin-bottom: var(--spacing-xs);
+ transition: var(--transition-fast);
+}
+
+.settings-link-btn:hover {
+ background: var(--surface-2);
+ border-color: var(--accent);
+ color: var(--accent);
+}
+
+.settings-btn {
+ width: 100%;
+ display: flex;
+ align-items: center;
+ gap: var(--spacing-sm);
+ padding: var(--spacing-sm) var(--spacing-md);
+ background: var(--surface-1);
+ border: 1px solid var(--border-subtle);
+ border-radius: var(--radius-md);
+ color: var(--text-secondary);
+ font-size: var(--text-base);
+ cursor: pointer;
+ transition: var(--transition-fast);
+ margin-bottom: var(--spacing-sm);
+}
+
+.settings-btn:hover {
+ background: var(--surface-2);
+ border-color: var(--border-default);
+ color: var(--text-primary);
+ transform: translateX(2px);
+}
+
+.settings-btn:active {
+ transform: translateX(0);
+}
+
+.settings-btn svg {
+ flex-shrink: 0;
+ opacity: 0.7;
+}
+
+.sidebar-footer {
+ padding: var(--spacing-lg);
+ border-top: 1px solid var(--border-subtle);
+}
+
+.reset-btn {
+ width: 100%;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ gap: var(--spacing-sm);
+ padding: var(--spacing-md);
+ background: var(--danger-subtle);
+ border: 1px solid transparent;
+ border-radius: var(--radius-md);
+ color: var(--danger);
+ font-size: 0.85rem;
+ font-weight: var(--weight-medium);
+ cursor: pointer;
+ transition: var(--transition-fast);
+}
+
+.reset-btn:hover {
+ background: var(--danger);
+ color: var(--text-primary);
+}
+
+.version-info {
+ text-align: center;
+ margin-top: var(--spacing-md);
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ gap: var(--spacing-sm);
+}
+
+.version-info a {
+ font-size: var(--text-sm);
+ color: var(--text-muted);
+ text-decoration: none;
+ transition: var(--transition-fast);
+}
+
+.version-info a:hover {
+ color: var(--text-secondary);
+}
+
+.version-info a.beta-version {
+ color: var(--warning);
+}
+
+.version-info a.beta-version:hover {
+ color: var(--warning-light);
+}
+
+.beta-badge {
+ display: inline-block;
+ font-size: var(--text-xs);
+ font-weight: var(--weight-bold);
+ letter-spacing: 0.05em;
+ text-transform: uppercase;
+ padding: 1px 6px;
+ border-radius: 4px;
+ background: var(--warning);
+ color: var(--text-primary);
+ line-height: 1.4;
+}
diff --git a/src/renderer/css/03-main.css b/src/renderer/css/03-main.css
new file mode 100644
index 0000000..41fe81d
--- /dev/null
+++ b/src/renderer/css/03-main.css
@@ -0,0 +1,551 @@
+.main-content {
+ min-height: 100vh;
+ display: flex;
+ flex-direction: column;
+ transition: var(--transition-slow);
+}
+
+.top-bar {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ padding: var(--spacing-md) var(--spacing-xl);
+ background: var(--surface-1);
+ border-bottom: none;
+ box-shadow:
+ 0 1px 0 var(--border-subtle),
+ 0 4px 16px var(--topbar-shadow-color);
+ -webkit-app-region: drag;
+}
+
+.icon-btn {
+ width: var(--icon-btn-size);
+ height: var(--icon-btn-size);
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ background: var(--surface-2);
+ border: 1px solid var(--border-subtle);
+ border-radius: var(--radius-md);
+ color: var(--text-secondary);
+ cursor: pointer;
+ transition: var(--transition-fast);
+ -webkit-app-region: no-drag;
+}
+
+.icon-btn:hover {
+ background: var(--accent-subtle);
+ border-color: var(--accent);
+ color: var(--accent-light);
+}
+
+.icon-btn:active {
+ transform: scale(0.97);
+}
+
+.app-brand {
+ display: flex;
+ align-items: center;
+ gap: var(--spacing-sm);
+}
+
+.app-logo {
+ width: 32px;
+ height: 32px;
+ filter: none;
+}
+
+.app-name {
+ font-size: var(--text-lg);
+ font-weight: var(--weight-bold);
+}
+
+.char-white {
+ color: var(--logo-char-white-color);
+ text-shadow: var(--logo-char-white-shadow);
+}
+
+.char-black {
+ color: var(--logo-char-black-color);
+ -webkit-text-stroke: var(--logo-char-black-stroke);
+ text-shadow: var(--logo-char-black-shadow);
+}
+
+.top-bar-links {
+ display: flex;
+ gap: var(--spacing-sm);
+ -webkit-app-region: no-drag;
+}
+
+.top-bar-links a {
+ width: var(--icon-btn-size-sm);
+ height: var(--icon-btn-size-sm);
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ color: var(--text-tertiary);
+ border-radius: var(--radius-sm);
+ transition:
+ background var(--transition-fast),
+ color var(--transition-fast),
+ transform var(--transition-fast);
+}
+
+.top-bar-links a:hover {
+ background: var(--surface-2);
+ color: var(--text-primary);
+}
+
+.top-bar-links a:active {
+ transform: scale(0.97);
+}
+
+.download-section {
+ flex: 1;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ padding: var(--spacing-xl);
+ min-height: 400px;
+}
+
+.download-card {
+ position: relative;
+ width: 100%;
+ max-width: var(--download-card-max-width);
+ padding: var(--spacing-2xl);
+ background: var(--surface-1);
+ border: 1px solid var(--border-subtle);
+ border-radius: var(--radius-xl);
+ box-shadow: var(--shadow-lg);
+ animation: cardEntrance 0.6s cubic-bezier(0.16, 1, 0.3, 1) both;
+}
+
+.download-card::before {
+ content: none;
+}
+
+@keyframes cardEntrance {
+ from {
+ opacity: 0;
+ transform: translateY(20px) scale(0.98);
+ }
+ to {
+ opacity: 1;
+ transform: translateY(0) scale(1);
+ }
+}
+
+.download-card.drag-over {
+ border-color: var(--accent);
+ box-shadow: var(--shadow-md);
+ transform: scale(1.01);
+ transition:
+ transform var(--transition-fast),
+ border-color var(--transition-fast),
+ box-shadow var(--transition-fast);
+}
+
+.download-title {
+ font-size: var(--text-xl);
+ font-weight: var(--weight-bold);
+ text-align: center;
+ margin-bottom: var(--spacing-sm);
+ color: var(--text-primary);
+}
+
+.download-subtitle {
+ text-align: center;
+ color: var(--text-tertiary);
+ margin-bottom: var(--spacing-xl);
+}
+
+.url-input-wrapper {
+ display: flex;
+ gap: var(--spacing-md);
+ margin-bottom: var(--spacing-lg);
+}
+
+.url-input-container {
+ flex: 1;
+ position: relative;
+ display: flex;
+ align-items: center;
+}
+
+.url-icon {
+ position: absolute;
+ left: var(--spacing-md);
+ color: var(--text-tertiary);
+ pointer-events: none;
+}
+
+.url-input-container input {
+ width: 100%;
+ padding: var(--spacing-md) var(--spacing-md) var(--spacing-md) 48px;
+ background: var(--surface-2);
+ border: 2px solid var(--border-subtle);
+ border-radius: var(--radius-lg);
+ color: var(--text-primary);
+ font-size: var(--text-md);
+ transition: var(--transition-fast);
+}
+
+.url-input-container input::placeholder {
+ color: var(--text-muted);
+}
+
+.url-input-container input:focus {
+ outline: none;
+ border-color: var(--accent);
+ background: var(--surface-3);
+ box-shadow: 0 0 0 4px var(--accent-subtle);
+}
+
+.paste-btn {
+ position: absolute;
+ right: var(--spacing-sm);
+ width: var(--icon-btn-size-xs);
+ height: var(--icon-btn-size-xs);
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ background: var(--surface-3);
+ border: none;
+ border-radius: var(--radius-sm);
+ color: var(--text-tertiary);
+ cursor: pointer;
+ transition: var(--transition-fast);
+}
+
+.paste-btn:hover {
+ background: var(--accent-subtle);
+ color: var(--accent-light);
+}
+
+.paste-btn.hidden {
+ display: none !important;
+}
+
+.clear-btn {
+ position: absolute;
+ right: var(--spacing-sm);
+ width: var(--icon-btn-size-xs);
+ height: var(--icon-btn-size-xs);
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ background: var(--surface-3);
+ border: none;
+ border-radius: var(--radius-sm);
+ color: var(--text-tertiary);
+ cursor: pointer;
+ transition: var(--transition-fast);
+}
+
+.clear-btn:hover {
+ background: var(--danger-subtle);
+ color: var(--danger);
+}
+
+.download-btn {
+ position: relative;
+ display: flex;
+ align-items: center;
+ gap: var(--spacing-sm);
+ padding: var(--spacing-md) var(--spacing-xl);
+ background: var(--accent);
+ border: none;
+ border-radius: var(--radius-lg);
+ color: var(--text-primary);
+ font-size: var(--text-md);
+ font-weight: var(--weight-semibold);
+ cursor: pointer;
+ transition:
+ transform var(--transition-fast),
+ box-shadow var(--transition-fast),
+ background var(--transition-fast);
+ white-space: nowrap;
+ overflow: hidden;
+}
+
+.download-btn::after {
+ content: none;
+}
+
+.download-btn:hover {
+ background: var(--accent-hover);
+ transform: translateY(-1px);
+ box-shadow: var(--shadow-md);
+}
+
+.download-btn:active {
+ transform: translateY(0);
+}
+
+.download-btn.loading {
+ pointer-events: none;
+ opacity: 0.8;
+}
+
+.download-btn .loader-icon {
+ width: 20px;
+ height: 20px;
+ animation: spin 1s linear infinite;
+}
+
+@keyframes spin {
+ to {
+ transform: rotate(360deg);
+ }
+}
+
+.progress-container {
+ display: none;
+ padding: var(--spacing-lg);
+ background: var(--surface-2);
+ border: 1px solid var(--border-subtle);
+ border-radius: var(--radius-lg);
+ margin-bottom: var(--spacing-lg);
+ position: relative;
+}
+
+.progress-container.visible {
+ display: block;
+ animation: slideDown 0.3s ease;
+}
+
+.progress-phases {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ gap: 0;
+ margin-bottom: var(--spacing-md);
+}
+
+.progress-phase {
+ display: flex;
+ align-items: center;
+ gap: var(--spacing-xs);
+ opacity: 0.3;
+ transition: opacity var(--transition-normal);
+}
+
+.progress-phase.active {
+ opacity: 1;
+}
+
+.progress-phase.completed {
+ opacity: 0.7;
+}
+
+.phase-dot {
+ width: 8px;
+ height: 8px;
+ border-radius: var(--radius-full);
+ background: var(--text-muted);
+ transition:
+ background var(--transition-normal),
+ box-shadow var(--transition-normal);
+}
+
+.progress-phase.active .phase-dot {
+ background: var(--accent);
+ box-shadow: none;
+ animation: none;
+}
+
+.progress-phase.completed .phase-dot {
+ background: var(--success);
+ box-shadow: none;
+ animation: none;
+}
+
+@keyframes pulseDot {
+ 0%,
+ 100% {
+ box-shadow: 0 0 4px var(--accent-glow);
+ }
+ 50% {
+ box-shadow: 0 0 12px var(--accent-glow);
+ }
+}
+
+.phase-label {
+ font-size: var(--text-sm);
+ color: inherit;
+}
+
+.progress-phase-connector {
+ width: 24px;
+ height: 1px;
+ background: var(--border-default);
+ margin: 0 var(--spacing-sm);
+}
+
+.progress-bar.active-glow {
+ box-shadow: none;
+}
+
+.progress-complete-icon {
+ display: none;
+ justify-content: center;
+ align-items: center;
+ margin-top: var(--spacing-md);
+ color: var(--success);
+}
+
+.progress-complete-icon.visible {
+ display: flex;
+ animation: checkmarkPop 0.4s cubic-bezier(0.34, 1.56, 0.64, 1) both;
+}
+
+@keyframes checkmarkPop {
+ from {
+ opacity: 0;
+ transform: scale(0.5);
+ }
+ to {
+ opacity: 1;
+ transform: scale(1);
+ }
+}
+
+@keyframes slideDown {
+ from {
+ opacity: 0;
+ transform: translateY(-10px);
+ }
+ to {
+ opacity: 1;
+ transform: translateY(0);
+ }
+}
+
+.progress-header {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ margin-bottom: var(--spacing-sm);
+}
+
+.progress-status {
+ font-size: var(--text-base);
+ color: var(--text-secondary);
+}
+
+.progress-percent {
+ font-size: var(--text-base);
+ font-weight: var(--weight-semibold);
+ color: var(--accent-light);
+}
+
+.progress-bar-wrapper {
+ height: var(--progress-bar-height);
+ background: var(--surface-1);
+ border-radius: 4px;
+ overflow: hidden;
+}
+
+.progress-bar {
+ height: 100%;
+ width: 0%;
+ background: var(--accent);
+ border-radius: 4px;
+ transition: width 0.3s ease;
+}
+
+.progress-bar.indeterminate {
+ width: 100%;
+ background: linear-gradient(90deg, transparent, var(--accent), transparent);
+ background-size: 200% 100%;
+ animation: indeterminate 1.5s linear infinite;
+}
+
+@keyframes indeterminate {
+ 0% {
+ background-position: 200% 0;
+ }
+ 100% {
+ background-position: -200% 0;
+ }
+}
+
+.progress-details {
+ margin-top: var(--spacing-sm);
+ font-size: var(--text-sm);
+ color: var(--text-muted);
+ text-align: center;
+}
+
+.format-options {
+ display: none;
+ padding: var(--spacing-lg);
+ background: var(--surface-2);
+ border: 1px solid var(--border-subtle);
+ border-radius: var(--radius-lg);
+ margin-top: var(--spacing-md);
+}
+
+.format-options.visible {
+ display: block;
+ animation: slideDown 0.3s ease;
+}
+
+.fetch-formats-btn {
+ width: 100%;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ gap: var(--spacing-sm);
+ padding: var(--spacing-md);
+ background: var(--surface-3);
+ border: 1px solid var(--border-subtle);
+ border-radius: var(--radius-md);
+ color: var(--text-secondary);
+ font-size: var(--text-base);
+ cursor: pointer;
+ transition: var(--transition-fast);
+ margin-bottom: var(--spacing-lg);
+}
+
+.fetch-formats-btn:hover {
+ background: var(--accent-subtle);
+ border-color: var(--accent);
+ color: var(--accent-light);
+}
+
+.format-grid {
+ display: grid;
+ grid-template-columns: 1fr 1fr;
+ gap: var(--spacing-md);
+}
+
+.format-select-label {
+ display: flex;
+ flex-direction: column;
+ gap: var(--spacing-xs);
+}
+
+.format-select-label span {
+ font-size: var(--text-sm);
+ color: var(--text-tertiary);
+}
+
+.format-select-label select {
+ width: 100%;
+ padding: var(--spacing-sm) var(--spacing-md);
+ background: var(--surface-1);
+ border: 1px solid var(--border-subtle);
+ border-radius: var(--radius-sm);
+ color: var(--text-primary);
+ font-size: var(--text-base);
+ cursor: pointer;
+ transition: var(--transition-fast);
+}
+
+.format-select-label select:focus {
+ outline: none;
+ border-color: var(--accent);
+ box-shadow: 0 0 0 3px var(--accent-subtle);
+}
diff --git a/src/renderer/css/04-panels.css b/src/renderer/css/04-panels.css
new file mode 100644
index 0000000..26c173a
--- /dev/null
+++ b/src/renderer/css/04-panels.css
@@ -0,0 +1,355 @@
+.download-history {
+ display: none;
+ margin: 0 var(--spacing-xl) var(--spacing-md);
+ background: var(--console-bg);
+ border: 1px solid var(--border-subtle);
+ border-radius: var(--radius-lg);
+ overflow: hidden;
+ flex-shrink: 0;
+}
+
+.download-history.visible {
+ display: flex;
+ flex-direction: column;
+ animation: slideDown 0.3s ease;
+}
+
+.history-header {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ padding: var(--spacing-sm) var(--spacing-md);
+ background: var(--console-header-bg);
+ border-bottom: 1px solid var(--border-subtle);
+ cursor: pointer;
+ user-select: none;
+ transition: var(--transition-fast);
+}
+
+.history-header:hover {
+ background: var(--console-header-hover-bg);
+}
+
+.history-header-left {
+ display: flex;
+ align-items: center;
+ gap: var(--spacing-sm);
+}
+
+.history-collapse-icon {
+ width: 16px;
+ height: 16px;
+ color: var(--text-tertiary);
+ transition: transform var(--transition-normal);
+}
+
+.download-history.collapsed .history-collapse-icon {
+ transform: rotate(-90deg);
+}
+
+.history-title {
+ display: flex;
+ align-items: center;
+ gap: var(--spacing-sm);
+ font-size: var(--text-sm);
+ color: var(--text-tertiary);
+ font-weight: var(--weight-semibold);
+ letter-spacing: 0.015em;
+}
+
+.history-title svg {
+ color: var(--accent-light);
+}
+
+.history-count {
+ font-size: var(--text-xs);
+ font-weight: var(--weight-semibold);
+ color: var(--text-muted);
+ background: var(--surface-3);
+ padding: 1px 6px;
+ border-radius: 10px;
+ min-width: 20px;
+ text-align: center;
+ border: 1px solid color-mix(in srgb, var(--border-subtle) 70%, transparent);
+}
+
+.history-clear-btn {
+ width: var(--icon-btn-size-xs);
+ height: var(--icon-btn-size-xs);
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ background: transparent;
+ border: none;
+ border-radius: var(--radius-sm);
+ color: var(--text-muted);
+ cursor: pointer;
+ transition: var(--transition-fast);
+}
+
+.history-clear-btn:hover {
+ background: var(--danger-subtle);
+ color: var(--danger);
+}
+
+.history-list {
+ max-height: 200px;
+ overflow-y: auto;
+ transition:
+ max-height var(--transition-normal),
+ padding var(--transition-normal);
+}
+
+.download-history.collapsed .history-list {
+ max-height: 0;
+ overflow: hidden;
+}
+
+.history-list::-webkit-scrollbar {
+ width: 6px;
+}
+
+.history-list::-webkit-scrollbar-track {
+ background: transparent;
+}
+
+.history-list::-webkit-scrollbar-thumb {
+ background: var(--border-default);
+ border-radius: 3px;
+}
+
+.history-item {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ gap: var(--spacing-md);
+ padding: var(--spacing-sm) var(--spacing-md);
+ border-bottom: 1px solid var(--border-subtle);
+ transition: var(--transition-fast);
+}
+
+.history-item:last-child {
+ border-bottom: none;
+}
+
+.history-item:hover {
+ background: var(--surface-1);
+}
+
+.history-item-info {
+ flex: 1;
+ min-width: 0;
+ display: flex;
+ flex-direction: column;
+ gap: 2px;
+}
+
+.history-filename {
+ font-size: var(--text-sm);
+ color: var(--text-secondary);
+ white-space: nowrap;
+ overflow: hidden;
+ text-overflow: ellipsis;
+}
+
+.history-time {
+ font-size: var(--text-xs);
+ color: var(--text-muted);
+}
+
+.history-item-actions {
+ display: flex;
+ align-items: center;
+ gap: var(--spacing-sm);
+ flex-shrink: 0;
+}
+
+.history-status {
+ font-size: var(--text-xs);
+ font-weight: var(--weight-medium);
+ padding: 2px 8px;
+ border-radius: 10px;
+}
+
+.history-status.success {
+ color: var(--success);
+ background: var(--success-subtle);
+}
+
+.history-status.failed {
+ color: var(--danger);
+ background: var(--danger-subtle);
+}
+
+.history-status.cancelled {
+ color: var(--warning);
+ background: var(--warning-subtle);
+}
+
+.history-open-btn {
+ font-size: var(--text-xs);
+ color: var(--accent-light);
+ background: var(--accent-subtle);
+ border: none;
+ border-radius: var(--radius-sm);
+ padding: 2px 8px;
+ cursor: pointer;
+ transition: var(--transition-fast);
+ white-space: nowrap;
+}
+
+.history-open-btn:hover {
+ background: var(--accent);
+ color: var(--text-primary);
+}
+
+.history-empty {
+ padding: var(--spacing-md);
+ text-align: center;
+ color: var(--text-muted);
+ font-size: var(--text-sm);
+}
+
+.console-section {
+ display: none;
+ margin: 0 var(--spacing-xl) var(--spacing-lg);
+ background: var(--console-bg);
+ border: 1px solid var(--border-subtle);
+ border-radius: var(--radius-lg);
+ overflow: hidden;
+ flex-shrink: 0;
+}
+
+.console-section.visible {
+ display: flex;
+ flex-direction: column;
+}
+
+.console-header {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ padding: var(--spacing-sm) var(--spacing-md);
+ background: var(--console-header-bg);
+ border-bottom: 1px solid var(--border-subtle);
+ cursor: pointer;
+ user-select: none;
+ transition: var(--transition-fast);
+}
+
+.console-header:hover {
+ background: var(--console-header-hover-bg);
+}
+
+.console-header-left {
+ display: flex;
+ align-items: center;
+ gap: var(--spacing-sm);
+}
+
+.console-collapse-icon {
+ width: 16px;
+ height: 16px;
+ color: var(--text-tertiary);
+ transition: transform var(--transition-normal);
+}
+
+.console-section.collapsed .console-collapse-icon {
+ transform: rotate(-90deg);
+}
+
+.console-title {
+ display: flex;
+ align-items: center;
+ gap: var(--spacing-sm);
+ font-size: var(--text-sm);
+ color: var(--text-tertiary);
+ font-weight: var(--weight-semibold);
+ letter-spacing: 0.015em;
+}
+
+.console-title svg {
+ color: var(--console-text);
+}
+
+.console-clear-btn {
+ width: var(--icon-btn-size-xs);
+ height: var(--icon-btn-size-xs);
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ background: transparent;
+ border: none;
+ border-radius: var(--radius-sm);
+ color: var(--text-muted);
+ cursor: pointer;
+ transition: var(--transition-fast);
+}
+
+.console-clear-btn:hover {
+ background: var(--danger-subtle);
+ color: var(--danger);
+}
+
+.console-output {
+ height: var(--console-height);
+ padding: var(--spacing-md);
+ font-family: var(--font-mono);
+ font-size: var(--text-sm);
+ color: var(--console-text);
+ overflow-y: auto;
+ white-space: pre-wrap;
+ word-break: break-word;
+ line-height: 1.6;
+ transition:
+ height var(--transition-normal),
+ padding var(--transition-normal);
+}
+
+.console-section.collapsed .console-output {
+ height: 0;
+ padding-top: 0;
+ padding-bottom: 0;
+ overflow: hidden;
+}
+
+.console-output::-webkit-scrollbar {
+ width: 8px;
+}
+
+.console-output::-webkit-scrollbar-track {
+ background: transparent;
+}
+
+.console-output::-webkit-scrollbar-thumb {
+ background: var(--border-default);
+ border-radius: 4px;
+}
+
+.main-footer {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ gap: var(--spacing-sm);
+ padding: var(--spacing-sm) var(--spacing-md);
+ font-size: var(--text-sm);
+ color: var(--text-muted);
+}
+
+.main-footer a {
+ color: var(--text-tertiary);
+ text-decoration: none;
+ transition: var(--transition-fast);
+}
+
+.main-footer a:hover {
+ color: var(--text-primary);
+}
+
+.footer-divider {
+ opacity: 0.3;
+}
+
+.footer-credit {
+ color: var(--text-muted);
+}
diff --git a/src/renderer/css/05-overlays.css b/src/renderer/css/05-overlays.css
new file mode 100644
index 0000000..d79d44f
--- /dev/null
+++ b/src/renderer/css/05-overlays.css
@@ -0,0 +1,407 @@
+.modal-overlay {
+ position: fixed;
+ inset: 0;
+ display: none;
+ align-items: center;
+ justify-content: center;
+ background: var(--overlay-bg-heavy);
+ backdrop-filter: blur(var(--blur-md));
+ z-index: var(--z-modal);
+ opacity: 0;
+ transition: opacity 0.2s ease;
+}
+
+.modal-overlay.active {
+ display: flex;
+ opacity: 1;
+}
+
+.modal-overlay.showing {
+ opacity: 0;
+}
+
+.modal-overlay.hiding {
+ opacity: 0;
+}
+
+.modal-box {
+ width: 90%;
+ max-width: var(--modal-max-width);
+ padding: var(--spacing-xl);
+ background: var(--modal-bg-start);
+ border: 1px solid var(--border-subtle);
+ border-radius: var(--radius-xl);
+ box-shadow: var(--shadow-lg);
+ transform: scale(1) translateY(0);
+ opacity: 1;
+ transition:
+ transform 0.25s cubic-bezier(0.34, 1.56, 0.64, 1),
+ opacity 0.2s ease;
+}
+
+.modal-overlay.showing .modal-box {
+ transform: scale(0.9) translateY(-20px);
+ opacity: 0;
+}
+
+.modal-overlay.hiding .modal-box {
+ transform: scale(0.95) translateY(10px);
+ opacity: 0;
+}
+
+.modal-box h3 {
+ font-size: var(--text-lg);
+ font-weight: var(--weight-semibold);
+ margin-bottom: var(--spacing-md);
+ text-align: center;
+}
+
+.modal-message {
+ color: var(--text-secondary);
+ text-align: center;
+ margin-bottom: var(--spacing-xl);
+ line-height: 1.7;
+ white-space: pre-line;
+}
+
+.modal-extra:empty {
+ display: none;
+}
+
+.modal-buttons {
+ display: flex;
+ gap: var(--spacing-md);
+ justify-content: center;
+ flex-wrap: wrap;
+}
+
+.modal-buttons button {
+ min-width: 100px;
+ padding: var(--spacing-sm) var(--spacing-lg);
+ background: var(--surface-2);
+ border: 1px solid var(--border-subtle);
+ border-radius: var(--radius-md);
+ color: var(--text-secondary);
+ font-size: var(--text-base);
+ font-weight: var(--weight-medium);
+ cursor: pointer;
+ transition: var(--transition-fast);
+}
+
+.modal-buttons button:hover {
+ background: var(--surface-3);
+ border-color: var(--border-default);
+ color: var(--text-primary);
+}
+
+.modal-buttons button:first-child {
+ background: var(--accent);
+ border-color: transparent;
+ color: var(--text-primary);
+}
+
+.modal-buttons button:first-child:hover {
+ background: var(--accent-hover);
+}
+
+.licenses-container {
+ width: 90%;
+ max-width: var(--licenses-max-width);
+ height: 80vh;
+ background: var(--modal-bg-start);
+ border: 1px solid var(--border-subtle);
+ border-radius: var(--radius-xl);
+ box-shadow: var(--shadow-lg);
+ overflow: hidden;
+ display: flex;
+ flex-direction: column;
+}
+
+.licenses-header {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ padding: var(--spacing-lg) var(--spacing-xl);
+ border-bottom: 1px solid var(--border-subtle);
+}
+
+.licenses-header h3 {
+ font-size: 1.1rem;
+ font-weight: var(--weight-semibold);
+ margin: 0;
+}
+
+.modal-close-btn {
+ width: var(--icon-btn-size-sm);
+ height: var(--icon-btn-size-sm);
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ background: var(--surface-2);
+ border: 1px solid var(--border-subtle);
+ border-radius: var(--radius-md);
+ color: var(--text-secondary);
+ cursor: pointer;
+ transition: var(--transition-fast);
+}
+
+.modal-close-btn:hover {
+ background: var(--danger-subtle);
+ border-color: var(--danger);
+ color: var(--danger);
+}
+
+.licenses-container iframe {
+ flex: 1;
+ width: 100%;
+ border: none;
+ background: transparent;
+}
+
+.update-progress-container {
+ margin: var(--spacing-md) 0;
+}
+
+.update-progress-bar-wrapper {
+ height: 6px;
+ background: var(--surface-2);
+ border-radius: 3px;
+ overflow: hidden;
+ margin-bottom: var(--spacing-sm);
+}
+
+.update-progress-bar {
+ height: 100%;
+ background: var(--success);
+ border-radius: 3px;
+ transition: width 0.3s ease;
+}
+
+.update-progress-info {
+ font-size: var(--text-sm);
+ color: var(--text-muted);
+ text-align: center;
+}
+
+.update-banner {
+ position: fixed;
+ bottom: 0;
+ left: 0;
+ right: 0;
+ background: var(--surface-1);
+ border-top: 1px solid var(--border-subtle);
+ padding: var(--spacing-sm) var(--spacing-xl);
+ z-index: var(--z-update-banner);
+ transform: translateY(100%);
+ transition: transform 0.3s ease;
+ pointer-events: none;
+}
+
+.update-banner.active {
+ transform: translateY(0);
+ pointer-events: auto;
+}
+
+.update-banner-content {
+ display: flex;
+ align-items: center;
+ gap: var(--spacing-md);
+}
+
+.update-banner-text {
+ font-size: 0.85rem;
+ color: var(--text-secondary);
+ white-space: nowrap;
+ font-weight: var(--weight-medium);
+}
+
+.update-banner-progress {
+ flex: 1;
+ height: 4px;
+ background: var(--surface-2);
+ border-radius: 2px;
+ overflow: hidden;
+ min-width: 80px;
+}
+
+.update-banner-bar {
+ height: 100%;
+ background: var(--success);
+ border-radius: 2px;
+ transition: width 0.3s ease;
+ width: 0%;
+}
+
+.update-banner-info {
+ font-size: var(--text-sm);
+ color: var(--text-muted);
+ white-space: nowrap;
+}
+
+.update-banner-cancel {
+ background: var(--surface-2);
+ border: 1px solid var(--border-subtle);
+ color: var(--text-secondary);
+ padding: var(--spacing-xs) var(--spacing-sm);
+ border-radius: var(--radius-sm);
+ font-size: var(--text-sm);
+ cursor: pointer;
+ white-space: nowrap;
+ transition: var(--transition-fast);
+}
+
+.update-banner-cancel:hover {
+ background: var(--danger-subtle);
+ border-color: var(--danger);
+ color: var(--danger);
+}
+
+.toast-container {
+ position: fixed;
+ bottom: var(--spacing-xl);
+ right: var(--spacing-xl);
+ display: flex;
+ flex-direction: column-reverse;
+ gap: var(--spacing-sm);
+ z-index: 1001;
+ pointer-events: none;
+}
+
+.toast {
+ pointer-events: auto;
+ display: flex;
+ align-items: center;
+ gap: var(--spacing-md);
+ padding: var(--spacing-md) var(--spacing-lg);
+ background: var(--surface-2);
+ border: 1px solid var(--border-subtle);
+ border-radius: var(--radius-md);
+ backdrop-filter: blur(var(--blur-lg));
+ box-shadow: var(--shadow-md);
+ color: var(--text-primary);
+ font-size: var(--text-base);
+ max-width: 380px;
+ min-width: 240px;
+ transform: translateX(calc(100% + var(--spacing-xl)));
+ opacity: 0;
+ transition:
+ transform var(--transition-slow),
+ opacity var(--transition-normal);
+}
+
+.toast.visible {
+ transform: translateX(0);
+ opacity: 1;
+}
+
+.toast.hiding {
+ transform: translateX(calc(100% + var(--spacing-xl)));
+ opacity: 0;
+}
+
+.toast-icon {
+ flex-shrink: 0;
+ width: 20px;
+ height: 20px;
+}
+
+.toast-message {
+ flex: 1;
+ line-height: 1.4;
+}
+
+.toast-dismiss {
+ flex-shrink: 0;
+ width: 20px;
+ height: 20px;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ background: none;
+ border: none;
+ color: var(--text-muted);
+ cursor: pointer;
+ padding: 0;
+ border-radius: var(--radius-sm);
+ transition: var(--transition-fast);
+}
+
+.toast-dismiss:hover {
+ color: var(--text-primary);
+ background: var(--surface-3);
+}
+
+.toast-warning {
+ border-left: 3px solid var(--warning);
+}
+
+.toast-warning .toast-icon {
+ color: var(--warning);
+}
+
+.toast-error {
+ border-left: 3px solid var(--danger);
+}
+
+.toast-error .toast-icon {
+ color: var(--danger);
+}
+
+.toast-success {
+ border-left: 3px solid var(--success);
+}
+
+.toast-success .toast-icon {
+ color: var(--success);
+}
+
+.toast-info {
+ border-left: 3px solid var(--accent);
+}
+
+.toast-info .toast-icon {
+ color: var(--accent-light);
+}
+
+.hidden {
+ display: none !important;
+}
+
+.sr-only {
+ position: absolute;
+ width: 1px;
+ height: 1px;
+ padding: 0;
+ margin: -1px;
+ overflow: hidden;
+ clip: rect(0, 0, 0, 0);
+ border: 0;
+}
+
+:focus-visible {
+ outline: 2px solid var(--accent);
+ outline-offset: 2px;
+}
+
+.icon-btn:focus-visible,
+.download-btn:focus-visible,
+.settings-btn:focus-visible,
+.reset-btn:focus-visible,
+.modal-buttons button:focus-visible,
+.fetch-formats-btn:focus-visible,
+.settings-link-btn:focus-visible,
+.update-banner-cancel:focus-visible,
+.modal-close-btn:focus-visible,
+.sidebar-close:focus-visible,
+.console-clear-btn:focus-visible,
+.clear-btn:focus-visible {
+ outline: 2px solid var(--accent);
+ outline-offset: 2px;
+ box-shadow: 0 0 0 4px var(--accent-subtle);
+}
+
+.toggle-switch:focus-within {
+ outline: 2px solid var(--accent);
+ outline-offset: 2px;
+ box-shadow: 0 0 0 4px var(--accent-subtle);
+}
diff --git a/src/renderer/css/06-responsive.css b/src/renderer/css/06-responsive.css
new file mode 100644
index 0000000..c019a74
--- /dev/null
+++ b/src/renderer/css/06-responsive.css
@@ -0,0 +1,504 @@
+/* Responsive adjustments for smaller windows */
+@media (max-height: 800px) {
+ .download-section {
+ min-height: 350px;
+ padding: var(--spacing-md);
+ }
+
+ .console-output {
+ height: 120px;
+ }
+}
+
+@media (max-height: 700px) {
+ .download-section {
+ min-height: 300px;
+ }
+
+ .console-output {
+ height: 100px;
+ }
+}
+
+@media (max-width: 560px) {
+ .url-input-wrapper {
+ flex-direction: column;
+ }
+
+ .download-card {
+ padding: var(--spacing-xl);
+ }
+
+ .download-title {
+ font-size: var(--text-lg);
+ }
+
+ .main-footer {
+ flex-wrap: wrap;
+ gap: var(--spacing-xs);
+ }
+}
+
+@media (prefers-reduced-motion: reduce) {
+ *,
+ *::before,
+ *::after {
+ animation-duration: 0.01ms !important;
+ animation-iteration-count: 1 !important;
+ transition-duration: 0.01ms !important;
+ }
+}
+
+.main-content {
+ width: 100%;
+ min-height: 100vh;
+}
+
+.app-shell {
+ width: 100%;
+ max-width: none;
+ margin: 0;
+ min-height: 100vh;
+ display: flex;
+ flex-direction: column;
+ padding: 0;
+ gap: var(--spacing-sm);
+}
+
+.main-stage {
+ width: min(var(--main-stage-max-width), 100%);
+ max-width: none;
+ margin: 0 auto;
+ padding: 0 var(--spacing-md);
+ display: flex;
+ flex-direction: column;
+ gap: var(--main-stage-gap);
+ flex: 1;
+}
+
+.top-bar {
+ height: var(--top-bar-height);
+ padding: 0 clamp(12px, 2.2vw, var(--header-inline-padding));
+ border-radius: 0;
+ border: none;
+ border-bottom: 1px solid color-mix(in srgb, var(--border-default) 70%, transparent);
+ backdrop-filter: blur(var(--blur-sm));
+ box-shadow: 0 1px 0 color-mix(in srgb, var(--border-default) 70%, transparent);
+ background: color-mix(in srgb, var(--surface-1) 92%, transparent);
+}
+
+.download-section {
+ align-items: flex-start;
+ padding: clamp(16px, 4vh, 48px) 0 0;
+ min-height: auto;
+}
+
+.download-card {
+ margin: 0 auto;
+ width: 100%;
+ max-width: var(--download-card-max-width);
+ padding: clamp(1.35rem, 3vw, 2.15rem);
+ box-shadow: var(--shadow-md);
+ border: 1px solid color-mix(in srgb, var(--border-default) 85%, transparent);
+ background: color-mix(in srgb, var(--surface-1) 90%, transparent);
+ backdrop-filter: blur(var(--blur-md));
+}
+
+.download-card::before {
+ content: none;
+}
+
+.download-card::after {
+ content: none;
+}
+
+.download-card.drag-over {
+ box-shadow: var(--shadow-md);
+ border-color: color-mix(in srgb, var(--accent) 45%, var(--border-default));
+ transform: none;
+}
+
+.download-title {
+ font-size: clamp(1.7rem, 2.8vw, 2.25rem);
+ line-height: 1.1;
+ margin-bottom: var(--spacing-xs);
+ letter-spacing: -0.02em;
+}
+
+.download-subtitle {
+ font-size: clamp(0.97rem, 1.35vw, 1.1rem);
+ margin-bottom: var(--spacing-lg);
+ color: color-mix(in srgb, var(--text-secondary) 88%, transparent);
+}
+
+.url-input-wrapper {
+ display: grid;
+ grid-template-columns: 1fr auto;
+ align-items: stretch;
+ gap: var(--spacing-sm);
+}
+
+.url-validation-message {
+ min-height: 1.1rem;
+ margin-top: var(--spacing-xs);
+ color: var(--danger);
+ font-size: var(--text-sm);
+}
+
+.preview-actions {
+ display: flex;
+ justify-content: flex-end;
+ margin-top: var(--spacing-xs);
+}
+
+.preview-btn {
+ display: inline-flex;
+ align-items: center;
+ gap: var(--spacing-xs);
+ padding: 0.4rem 0.7rem;
+ font-size: var(--text-sm);
+ color: var(--text-secondary);
+ background: var(--surface-1);
+ border: 1px solid var(--border-subtle);
+ border-radius: var(--radius-md);
+ cursor: pointer;
+ transition:
+ background 0.15s ease,
+ border-color 0.15s ease,
+ color 0.15s ease;
+}
+
+.preview-btn:hover:not(:disabled) {
+ background: var(--surface-2);
+ border-color: var(--border-strong);
+ color: var(--text-primary);
+}
+
+.preview-btn:disabled {
+ opacity: 0.5;
+ cursor: not-allowed;
+}
+
+.preview-card {
+ display: none;
+ position: relative;
+ gap: var(--spacing-md);
+ margin-top: var(--spacing-md);
+ padding: var(--spacing-md);
+ background: var(--surface-1);
+ border: 1px solid var(--border-subtle);
+ border-radius: var(--radius-lg);
+ box-shadow: var(--shadow-md);
+}
+
+.preview-card.visible {
+ display: flex;
+ align-items: center;
+}
+
+.preview-card.loading {
+ opacity: 0.7;
+}
+
+.preview-thumb-wrap {
+ position: relative;
+ flex-shrink: 0;
+ width: 160px;
+ aspect-ratio: 16 / 9;
+ border-radius: var(--radius-md);
+ overflow: hidden;
+ background: var(--surface-3);
+}
+
+.preview-thumb {
+ width: 100%;
+ height: 100%;
+ object-fit: cover;
+ display: block;
+}
+
+.preview-duration {
+ position: absolute;
+ right: 6px;
+ bottom: 6px;
+ padding: 1px 5px;
+ font-size: var(--text-xs);
+ font-variant-numeric: tabular-nums;
+ color: #fff;
+ background: rgba(0, 0, 0, 0.78);
+ border-radius: 4px;
+}
+
+.preview-playlist-badge {
+ display: none;
+ position: absolute;
+ left: 6px;
+ top: 6px;
+ padding: 1px 6px;
+ font-size: var(--text-xs);
+ font-weight: var(--weight-semibold);
+ color: var(--accent-contrast);
+ background: var(--accent);
+ border-radius: 4px;
+}
+
+.preview-card.is-playlist .preview-playlist-badge {
+ display: inline-block;
+}
+
+.preview-meta {
+ min-width: 0;
+ flex: 1;
+}
+
+.preview-title {
+ margin: 0;
+ font-size: var(--text-base);
+ font-weight: var(--weight-semibold);
+ color: var(--text-primary);
+ display: -webkit-box;
+ -webkit-line-clamp: 2;
+ line-clamp: 2;
+ -webkit-box-orient: vertical;
+ overflow: hidden;
+}
+
+.preview-sub {
+ margin: var(--spacing-xs) 0 0;
+ font-size: var(--text-sm);
+ color: var(--text-muted);
+}
+
+.preview-close {
+ position: absolute;
+ top: 6px;
+ right: 6px;
+ display: inline-flex;
+ padding: 4px;
+ color: var(--text-muted);
+ background: transparent;
+ border: none;
+ border-radius: var(--radius-sm);
+ cursor: pointer;
+}
+
+.preview-close:hover {
+ color: var(--text-primary);
+ background: var(--surface-2);
+}
+
+@media (max-width: 560px) {
+ .preview-thumb-wrap {
+ width: 110px;
+ }
+}
+
+.url-input-container {
+ min-height: var(--control-height);
+}
+
+.url-input-container input {
+ min-height: var(--control-height);
+ border-width: 1px;
+ border-radius: calc(var(--radius-lg) - 2px);
+ padding-right: 92px;
+}
+
+.url-input-container.is-valid input {
+ border-color: var(--url-valid-border);
+ box-shadow: 0 0 0 2px color-mix(in srgb, var(--url-valid-border) 20%, transparent);
+}
+
+.url-input-container.is-invalid input {
+ border-color: var(--url-invalid-border);
+ box-shadow: 0 0 0 2px color-mix(in srgb, var(--url-invalid-border) 22%, transparent);
+}
+
+.url-input-container.is-empty input {
+ border-color: var(--border-subtle);
+ box-shadow: none;
+}
+
+.paste-btn,
+.clear-btn {
+ width: 30px;
+ height: 30px;
+}
+
+.download-btn {
+ min-height: var(--control-height);
+ justify-content: center;
+ min-width: 170px;
+ border: 1px solid color-mix(in srgb, var(--accent-light) 35%, transparent);
+ letter-spacing: 0.018em;
+ box-shadow: var(--shadow-sm);
+}
+
+.download-btn:hover {
+ transform: translateY(-1px);
+ box-shadow: var(--shadow-md);
+}
+
+.download-btn:disabled,
+.download-btn.is-disabled {
+ opacity: 0.55;
+ cursor: not-allowed;
+ transform: none;
+ box-shadow: none;
+ pointer-events: none;
+}
+
+.download-btn.loading {
+ pointer-events: auto;
+}
+
+.progress-container,
+.format-options {
+ border-radius: calc(var(--radius-lg) - 2px);
+}
+
+.download-history,
+.console-section {
+ width: 100%;
+ margin: 0;
+ border: 1px solid color-mix(in srgb, var(--border-default) 80%, transparent);
+ background: color-mix(in srgb, var(--surface-1) 90%, transparent);
+ backdrop-filter: blur(var(--blur-sm));
+ box-shadow: var(--shadow-sm);
+}
+
+.app-logo {
+ filter: none;
+}
+
+.progress-phase.active .phase-dot {
+ box-shadow: none;
+ animation: none;
+}
+
+.progress-bar.active-glow {
+ box-shadow: none;
+}
+
+.history-header,
+.console-header {
+ min-height: 44px;
+ background: color-mix(in srgb, var(--console-header-bg) 94%, transparent);
+ border-bottom: 1px solid color-mix(in srgb, var(--border-default) 65%, transparent);
+}
+
+.main-footer {
+ width: 100%;
+ min-height: var(--footer-height);
+ margin-top: auto;
+ border-top: 1px solid var(--border-subtle);
+ background: color-mix(in srgb, var(--surface-1) 88%, transparent);
+ border-radius: 0;
+ padding-inline: clamp(10px, 2vw, 20px);
+}
+
+.sidebar {
+ width: min(360px, calc(100vw - 22px));
+ border-right: 1px solid var(--border-default);
+}
+
+.sidebar-header {
+ padding: 1.1rem 1.35rem;
+}
+
+.sidebar-content {
+ padding: 0.95rem;
+}
+
+.settings-section {
+ margin-bottom: var(--spacing-md);
+}
+
+.settings-section-header {
+ padding: 0.42rem 0.52rem;
+ margin-bottom: 0.36rem;
+ border: 1px solid transparent;
+ background: color-mix(in srgb, var(--surface-1) 68%, transparent);
+}
+
+.settings-section-header:hover {
+ background: color-mix(in srgb, var(--surface-2) 72%, transparent);
+ border-color: color-mix(in srgb, var(--border-default) 60%, transparent);
+}
+
+.settings-section-header:focus-visible {
+ outline: 2px solid var(--accent);
+ outline-offset: 2px;
+ box-shadow: 0 0 0 4px var(--accent-subtle);
+}
+
+.settings-section-title {
+ letter-spacing: 0.11em;
+}
+
+.toggle-switch,
+.settings-btn {
+ border-radius: calc(var(--radius-md) - 2px);
+}
+
+.settings-btn,
+.settings-link-btn,
+.icon-btn,
+.top-bar-links a,
+.queue-action-btn {
+ backdrop-filter: blur(var(--blur-sm));
+}
+
+.settings-btn:hover,
+.settings-link-btn:hover {
+ transform: none;
+}
+
+.modal-box {
+ max-width: 500px;
+ border: 1px solid var(--border-default);
+ box-shadow: var(--shadow-lg);
+}
+
+.modal-buttons button {
+ min-height: 40px;
+}
+
+.toast-container {
+ bottom: calc(var(--footer-height) + var(--spacing-md));
+}
+
+.toast {
+ border-radius: calc(var(--radius-md) + 2px);
+ border: 1px solid var(--border-default);
+}
+
+body::before {
+ content: none;
+}
+@media (max-width: 960px) {
+ .top-bar {
+ padding: 0 var(--spacing-sm);
+ }
+
+ .main-stage {
+ padding: 0 var(--spacing-sm);
+ }
+}
+
+@media (max-width: 780px) {
+ .url-input-wrapper {
+ grid-template-columns: 1fr;
+ }
+
+ .download-btn {
+ width: 100%;
+ min-width: auto;
+ }
+
+ .main-footer {
+ flex-wrap: wrap;
+ gap: var(--spacing-xs);
+ padding: var(--spacing-xs) var(--spacing-sm);
+ }
+}
diff --git a/src/renderer/css/07-queue.css b/src/renderer/css/07-queue.css
new file mode 100644
index 0000000..b1a5e53
--- /dev/null
+++ b/src/renderer/css/07-queue.css
@@ -0,0 +1,278 @@
+/* ββ Queue Section ββ */
+
+.queue-section {
+ background: color-mix(in srgb, var(--surface-1) 90%, transparent);
+ border: 1px solid color-mix(in srgb, var(--border-default) 80%, transparent);
+ border-radius: var(--radius-lg);
+ overflow: hidden;
+ display: flex;
+ flex-direction: column;
+ animation: slideDown 0.3s ease;
+ box-shadow: var(--shadow-sm);
+}
+
+.queue-header {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ padding: var(--spacing-sm) var(--spacing-md);
+ background: color-mix(in srgb, var(--console-header-bg) 94%, transparent);
+ border-bottom: 1px solid color-mix(in srgb, var(--border-default) 65%, transparent);
+}
+
+.queue-header-left {
+ display: flex;
+ align-items: center;
+ gap: var(--spacing-sm);
+}
+
+.queue-title {
+ font-size: var(--text-sm);
+ color: var(--text-tertiary);
+ font-weight: var(--weight-semibold);
+ letter-spacing: 0.015em;
+}
+
+.queue-count {
+ font-size: var(--text-xs);
+ font-weight: var(--weight-semibold);
+ color: var(--text-muted);
+ background: var(--surface-3);
+ padding: 1px 6px;
+ border-radius: 10px;
+ min-width: 20px;
+ text-align: center;
+ border: 1px solid color-mix(in srgb, var(--border-subtle) 70%, transparent);
+}
+
+.queue-input-row {
+ display: flex;
+ gap: var(--spacing-sm);
+ padding: var(--spacing-sm) var(--spacing-md);
+ align-items: flex-start;
+}
+
+.queue-url-input {
+ flex: 1;
+ min-height: 40px;
+ max-height: 80px;
+ padding: var(--spacing-xs) var(--spacing-sm);
+ background: var(--input-bg);
+ color: var(--text-primary);
+ border: 1px solid var(--border-default);
+ border-radius: var(--radius-md);
+ font-family: var(--font-mono);
+ font-size: var(--text-sm);
+ resize: vertical;
+}
+
+.queue-url-input::placeholder {
+ color: var(--text-muted);
+}
+
+.queue-url-input:focus {
+ outline: none;
+ border-color: var(--accent);
+ box-shadow: 0 0 0 2px var(--accent-subtle);
+}
+
+.queue-url-input:disabled {
+ opacity: 0.7;
+ cursor: not-allowed;
+}
+
+.queue-controls {
+ display: flex;
+ gap: var(--spacing-xs);
+ padding: 0 var(--spacing-md) var(--spacing-sm);
+ flex-wrap: wrap;
+}
+
+.queue-status-message {
+ margin: 0;
+ padding: 0 var(--spacing-md) var(--spacing-sm);
+ color: var(--text-muted);
+ font-size: var(--text-xs);
+ line-height: 1.4;
+}
+
+.queue-action-btn {
+ display: inline-flex;
+ align-items: center;
+ gap: var(--spacing-xs);
+ padding: var(--spacing-xs) var(--spacing-sm);
+ font-size: var(--text-xs);
+ font-weight: var(--weight-medium);
+ border: 1px solid var(--border-default);
+ border-radius: var(--radius-md);
+ background: var(--surface-1);
+ color: var(--text-secondary);
+ cursor: pointer;
+ transition: var(--transition-fast);
+ min-height: 32px;
+}
+
+.queue-action-btn:hover {
+ background: var(--surface-2);
+ border-color: var(--border-strong);
+}
+
+.queue-action-btn:disabled {
+ opacity: 0.6;
+ cursor: not-allowed;
+ pointer-events: none;
+}
+
+.queue-add-btn {
+ background: var(--accent);
+ color: var(--accent-contrast);
+ border-color: var(--accent);
+}
+
+.queue-add-btn:hover {
+ background: var(--accent-hover);
+ border-color: var(--accent-hover);
+}
+
+.queue-list {
+ max-height: 250px;
+ overflow-y: auto;
+}
+
+.queue-list::-webkit-scrollbar {
+ width: 6px;
+}
+
+.queue-list::-webkit-scrollbar-track {
+ background: transparent;
+}
+
+.queue-list::-webkit-scrollbar-thumb {
+ background: var(--border-default);
+ border-radius: 3px;
+}
+
+.queue-item {
+ display: flex;
+ align-items: center;
+ gap: var(--spacing-sm);
+ padding: var(--spacing-xs) var(--spacing-md);
+ border-top: 1px solid var(--border-subtle);
+ font-size: var(--text-sm);
+ transition: var(--transition-fast);
+}
+
+.queue-item:hover {
+ background: var(--surface-1);
+}
+
+.queue-item-status {
+ flex-shrink: 0;
+ width: 20px;
+ text-align: center;
+}
+
+.queue-item-url {
+ flex: 1;
+ min-width: 0;
+ color: var(--text-secondary);
+ white-space: nowrap;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ font-family: var(--font-mono);
+ font-size: var(--text-xs);
+}
+
+.queue-item-remove {
+ flex-shrink: 0;
+ width: 20px;
+ height: 20px;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ background: transparent;
+ border: none;
+ border-radius: var(--radius-sm);
+ color: var(--text-muted);
+ cursor: pointer;
+ font-size: var(--text-sm);
+ transition: var(--transition-fast);
+}
+
+.queue-item-remove:hover {
+ background: var(--danger-subtle);
+ color: var(--danger);
+}
+
+.queue-downloading {
+ background: var(--accent-subtle);
+}
+
+.queue-completed .queue-item-url {
+ color: var(--success);
+}
+
+.queue-failed .queue-item-url {
+ color: var(--danger);
+}
+
+.queue-cancelled .queue-item-url {
+ color: var(--text-muted);
+}
+
+.wizard-overlay {
+ position: fixed;
+ inset: 0;
+ display: none;
+ align-items: center;
+ justify-content: center;
+ background: rgba(0, 0, 0, 0.7);
+ backdrop-filter: blur(8px);
+ z-index: 2000;
+}
+
+.wizard-overlay.active {
+ display: flex;
+}
+
+.wizard-card {
+ width: 90%;
+ max-width: 520px;
+ background: var(--surface-1);
+ border: 1px solid var(--border-subtle);
+ border-radius: var(--radius-xl);
+ box-shadow: var(--shadow-lg);
+ padding: var(--spacing-2xl) var(--spacing-xl) var(--spacing-xl);
+ display: flex;
+ flex-direction: column;
+ animation: cardEntrance 0.5s cubic-bezier(0.16, 1, 0.3, 1) both;
+}
+
+.wizard-progress {
+ height: 3px;
+ background: var(--surface-3);
+ border-radius: 2px;
+ margin-bottom: var(--spacing-xl);
+ overflow: hidden;
+}
+
+.wizard-progress-bar {
+ height: 100%;
+ width: 25%;
+ background: var(--accent);
+ border-radius: 2px;
+ transition: width var(--transition-normal);
+}
+
+.wizard-step {
+ display: none;
+ flex-direction: column;
+ align-items: center;
+ text-align: center;
+ min-height: 280px;
+}
+
+.wizard-step.active {
+ display: flex;
+ animation: wizardFadeIn 0.3s ease both;
+}
diff --git a/src/renderer/css/08-wizard.css b/src/renderer/css/08-wizard.css
new file mode 100644
index 0000000..ea47342
--- /dev/null
+++ b/src/renderer/css/08-wizard.css
@@ -0,0 +1,226 @@
+@keyframes wizardFadeIn {
+ from {
+ opacity: 0;
+ transform: translateY(8px);
+ }
+ to {
+ opacity: 1;
+ transform: translateY(0);
+ }
+}
+
+.wizard-icon {
+ margin-bottom: var(--spacing-lg);
+}
+
+.wizard-heading {
+ font-size: 1.5rem;
+ font-weight: var(--weight-bold);
+ color: var(--text-primary);
+ margin-bottom: var(--spacing-sm);
+}
+
+.wizard-description {
+ font-size: var(--text-base);
+ color: var(--text-secondary);
+ line-height: 1.6;
+ margin-bottom: var(--spacing-lg);
+ max-width: 400px;
+}
+
+.wizard-theme-grid {
+ display: grid;
+ grid-template-columns: repeat(4, 1fr);
+ gap: var(--spacing-sm);
+ width: 100%;
+ max-width: 420px;
+}
+
+.wizard-theme-option input {
+ position: absolute;
+ opacity: 0;
+ pointer-events: none;
+}
+
+.wizard-theme-card {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ gap: var(--spacing-xs);
+ padding: var(--spacing-sm);
+ border: 2px solid var(--border-subtle);
+ border-radius: var(--radius-md);
+ cursor: pointer;
+ transition: var(--transition-fast);
+}
+
+.wizard-theme-option input:checked + .wizard-theme-card {
+ border-color: var(--accent);
+ background: var(--accent-subtle);
+}
+
+.wizard-theme-card:hover {
+ border-color: var(--border-default);
+}
+
+.wizard-theme-preview {
+ width: 100%;
+ aspect-ratio: 3 / 4;
+ border-radius: var(--radius-sm);
+ border: 1px solid var(--border-subtle);
+}
+
+.wizard-preview-system {
+ background: linear-gradient(135deg, #1a1a2e 50%, #f0f0f5 50%);
+}
+
+.wizard-preview-dark {
+ background: #0d0f14;
+}
+
+.wizard-preview-light {
+ background: #eef3f8;
+}
+
+.wizard-preview-purple {
+ background: #18122a;
+}
+
+.wizard-theme-label {
+ font-size: var(--text-sm);
+ color: var(--text-secondary);
+ font-weight: var(--weight-medium);
+}
+
+.wizard-options {
+ width: 100%;
+ max-width: 420px;
+ display: flex;
+ flex-direction: column;
+ gap: var(--spacing-sm);
+ text-align: left;
+}
+
+.wizard-toggle {
+ display: flex;
+ align-items: center;
+ gap: var(--spacing-sm);
+ padding: var(--spacing-sm) var(--spacing-md);
+ background: var(--surface-2);
+ border: 1px solid var(--border-subtle);
+ border-radius: var(--radius-md);
+ cursor: pointer;
+ transition: var(--transition-fast);
+ position: relative;
+}
+
+.wizard-toggle:hover {
+ background: var(--surface-3);
+ border-color: var(--border-default);
+}
+
+.wizard-toggle input {
+ position: absolute;
+ opacity: 0;
+ pointer-events: none;
+}
+
+.wizard-toggle input:checked + .toggle-slider {
+ background: var(--accent);
+}
+
+.wizard-toggle input:checked + .toggle-slider::after {
+ transform: translateX(20px);
+ background: var(--text-primary);
+}
+
+.wizard-toggle-text {
+ display: flex;
+ flex-direction: column;
+ gap: 2px;
+}
+
+.wizard-toggle-text strong {
+ font-size: var(--text-base);
+ font-weight: var(--weight-medium);
+ color: var(--text-primary);
+}
+
+.wizard-toggle-text small {
+ font-size: var(--text-sm);
+ color: var(--text-muted);
+ line-height: 1.3;
+}
+
+.wizard-nav {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ margin-top: var(--spacing-xl);
+ padding-top: var(--spacing-lg);
+ border-top: 1px solid var(--border-subtle);
+}
+
+.wizard-dots {
+ display: flex;
+ gap: var(--spacing-xs);
+}
+
+.wizard-dot {
+ width: 8px;
+ height: 8px;
+ border-radius: var(--radius-full);
+ background: var(--surface-3);
+ transition: var(--transition-fast);
+}
+
+.wizard-dot.active {
+ background: var(--accent);
+ width: 20px;
+ border-radius: 4px;
+}
+
+.wizard-btn {
+ padding: var(--spacing-sm) var(--spacing-lg);
+ border: none;
+ border-radius: var(--radius-md);
+ font-size: var(--text-base);
+ font-weight: var(--weight-medium);
+ cursor: pointer;
+ transition: var(--transition-fast);
+ min-width: 100px;
+}
+
+.wizard-btn-primary {
+ background: var(--accent);
+ color: var(--accent-contrast);
+}
+
+.wizard-btn-primary:hover {
+ background: var(--accent-hover);
+}
+
+.wizard-btn-secondary {
+ background: var(--surface-2);
+ color: var(--text-secondary);
+ border: 1px solid var(--border-subtle);
+}
+
+.wizard-btn-secondary:hover {
+ background: var(--surface-3);
+ color: var(--text-primary);
+}
+
+.wizard-btn-secondary.hidden {
+ visibility: hidden;
+}
+
+@media (max-width: 480px) {
+ .wizard-theme-grid {
+ grid-template-columns: repeat(2, 1fr);
+ }
+
+ .wizard-card {
+ padding: var(--spacing-xl) var(--spacing-md) var(--spacing-md);
+ }
+}
diff --git a/src/renderer/fonts/IBMPlexMono-400.ttf b/src/renderer/fonts/IBMPlexMono-400.ttf
new file mode 100644
index 0000000..02d682c
Binary files /dev/null and b/src/renderer/fonts/IBMPlexMono-400.ttf differ
diff --git a/src/renderer/fonts/IBMPlexMono-500.ttf b/src/renderer/fonts/IBMPlexMono-500.ttf
new file mode 100644
index 0000000..54927d5
Binary files /dev/null and b/src/renderer/fonts/IBMPlexMono-500.ttf differ
diff --git a/src/renderer/fonts/Manrope-400.ttf b/src/renderer/fonts/Manrope-400.ttf
new file mode 100644
index 0000000..8594569
Binary files /dev/null and b/src/renderer/fonts/Manrope-400.ttf differ
diff --git a/src/renderer/fonts/Manrope-500.ttf b/src/renderer/fonts/Manrope-500.ttf
new file mode 100644
index 0000000..2a169e6
Binary files /dev/null and b/src/renderer/fonts/Manrope-500.ttf differ
diff --git a/src/renderer/fonts/Manrope-600.ttf b/src/renderer/fonts/Manrope-600.ttf
new file mode 100644
index 0000000..595e7dc
Binary files /dev/null and b/src/renderer/fonts/Manrope-600.ttf differ
diff --git a/src/renderer/fonts/Manrope-700.ttf b/src/renderer/fonts/Manrope-700.ttf
new file mode 100644
index 0000000..746764d
Binary files /dev/null and b/src/renderer/fonts/Manrope-700.ttf differ
diff --git a/src/renderer/index.html b/src/renderer/index.html
index 3dc4f3a..c49e8d8 100644
--- a/src/renderer/index.html
+++ b/src/renderer/index.html
@@ -4,16 +4,10 @@
Rosi
-
-
-
Skip to main content
@@ -163,6 +157,82 @@ Download Options
+
+
@@ -614,6 +688,70 @@ Download Videos
aria-live="polite"
>
+
+
+
+
+
+
+
+
+
+
+
+
+
Playlist
+
+
+
+
diff --git a/src/renderer/licenses-iframe.html b/src/renderer/licenses-iframe.html
index 0cd10dd..3628d78 100644
--- a/src/renderer/licenses-iframe.html
+++ b/src/renderer/licenses-iframe.html
@@ -679,8 +679,8 @@
3RD PARTY LICENSES/CREDITS
License:
GNU General Public License v2.0 or later (GPL-2.0-or-later)
-
ROSI ships pre-built FFmpeg 8.0 binaries (GPL build) used for recording, converting, and streaming audio and video. ROSI invokes FFmpeg as an external process and is not a derivative work of FFmpeg; the inclusion of FFmpeg binaries does not change ROSI's Mozilla Public License 2.0 terms.
-
Source code offer (GPLv2 § 3(b)): In compliance with the GPL, BurntToasters offers to provide the complete corresponding source code for the exact FFmpeg binaries (and all statically linked components) distributed with ROSI, including the build scripts and configuration used to produce them. The source, build scripts, and release archives are hosted at github.com/BurntToasters/ffmpeg-static-builds (ffmpeg-v8.0.1) . This offer is valid for at least three years from the date you received the binaries. To request the source on physical media, contact code@rosie.run .
+
ROSI ships pre-built FFmpeg binaries (GPL build) used for recording, converting, and streaming audio and video. Windows, Linux, and macOS arm64 builds use FFmpeg 8.1; macOS x64 remains on FFmpeg 8.0.1. ROSI invokes FFmpeg as an external process and is not a derivative work of FFmpeg; the inclusion of FFmpeg binaries does not change ROSI's Mozilla Public License 2.0 terms.
+
Source code offer (GPLv2 § 3(b)): In compliance with the GPL, BurntToasters offers to provide the complete corresponding source code for the exact FFmpeg binaries (and all statically linked components) distributed with ROSI, including the build scripts and configuration used to produce them. The FFmpeg 8.1 source, build scripts, and release archives are hosted at github.com/BurntToasters/ffmpeg-static-builds (ffmpeg-v8.1) ; macOS x64 FFmpeg 8.0.1 source remains available at ffmpeg-v8.0.1 . This offer is valid for at least three years from the date you received the binaries. To request the source on physical media, contact code@rosie.run .
Statically linked components in this build: FFmpeg (GPL-2.0-or-later), x264 (GPL-2.0-or-later), x265 (GPL-2.0-or-later), lame / libmp3lame (LGPL-2.0), libass (ISC), fribidi (LGPL-2.1-or-later), freetype (FTL / GPL-2.0), fontconfig (MIT-like), libiconv (LGPL-2.1), enca (GPL-2.0), expat (MIT). Upstream source URLs and the full notice are bundled with the application at resources/ffmpeg/NOTICE.txt and resources/ffmpeg/SOURCE_OFFER.txt.
diff --git a/src/renderer/main.css b/src/renderer/main.css
index 2bfefa3..a28911f 100644
--- a/src/renderer/main.css
+++ b/src/renderer/main.css
@@ -1,2933 +1,8 @@
-:root {
- --font-sans: 'Manrope', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
- --font-mono: 'IBM Plex Mono', 'SF Mono', 'Fira Code', 'Consolas', monospace;
-
- --text-xs: 0.6rem;
- --text-sm: 0.75rem;
- --text-base: 0.92rem;
- --text-md: 1rem;
- --text-lg: 1.22rem;
- --text-xl: 2rem;
-
- --weight-normal: 400;
- --weight-medium: 500;
- --weight-semibold: 600;
- --weight-bold: 700;
-
- --bg-gradient-start: #0a0c10;
- --bg-gradient-mid: #0d0f14;
- --bg-gradient-end: #10131a;
- --surface-1: rgba(255, 255, 255, 0.04);
- --surface-2: rgba(255, 255, 255, 0.07);
- --surface-3: rgba(255, 255, 255, 0.1);
- --surface-hover: rgba(255, 255, 255, 0.14);
-
- --border-subtle: rgba(255, 255, 255, 0.1);
- --border-default: rgba(255, 255, 255, 0.16);
- --border-strong: rgba(255, 255, 255, 0.24);
-
- --text-primary: #ffffff;
- --text-secondary: rgba(255, 255, 255, 0.8);
- --text-tertiary: rgba(255, 255, 255, 0.7);
- --text-muted: rgba(255, 255, 255, 0.45);
-
- --accent: #22d3ee;
- --accent-light: #67e8f9;
- --accent-dark: #0891b2;
- --accent-hover: #45def3;
- --accent-contrast: #05232e;
- --accent-glow: rgba(34, 211, 238, 0.18);
- --accent-subtle: rgba(34, 211, 238, 0.14);
- --input-bg: var(--surface-2);
-
- --success: #22c55e;
- --success-light: #34d399;
- --success-subtle: rgba(34, 197, 94, 0.15);
- --warning: #f59e0b;
- --warning-light: #fbbf24;
- --warning-dark: #d97706;
- --warning-subtle: rgba(245, 158, 11, 0.15);
- --danger: #ef4444;
- --danger-subtle: rgba(239, 68, 68, 0.15);
-
- --overlay-bg: rgba(0, 0, 0, 0.58);
- --overlay-bg-heavy: rgba(0, 0, 0, 0.72);
- --sidebar-bg-start: rgba(8, 10, 14, 0.98);
- --sidebar-bg-end: rgba(4, 6, 9, 0.98);
- --modal-bg-start: rgba(16, 20, 27, 0.98);
- --modal-bg-end: rgba(8, 10, 14, 0.98);
- --console-bg: rgba(0, 0, 0, 0.5);
- --console-header-bg: rgba(0, 0, 0, 0.42);
- --console-header-hover-bg: rgba(0, 0, 0, 0.52);
- --console-text: #86efac;
-
- --spacing-xs: 0.25rem;
- --spacing-sm: 0.5rem;
- --spacing-md: 0.95rem;
- --spacing-lg: 1.35rem;
- --spacing-xl: 1.9rem;
- --spacing-2xl: 2.6rem;
-
- --radius-sm: 8px;
- --radius-md: 12px;
- --radius-lg: 18px;
- --radius-xl: 26px;
- --radius-full: 50%;
-
- --shadow-sm: 0 1px 4px rgba(0, 0, 0, 0.15);
- --shadow-md: 0 2px 8px rgba(0, 0, 0, 0.2);
- --shadow-lg: 0 4px 16px rgba(0, 0, 0, 0.25);
- --shadow-glow: none;
-
- --transition-fast: 150ms ease;
- --transition-normal: 250ms ease;
- --transition-slow: 400ms cubic-bezier(0.16, 1, 0.3, 1);
-
- --blur-sm: 4px;
- --blur-md: 8px;
- --blur-lg: 20px;
-
- --icon-btn-size: 40px;
- --icon-btn-size-sm: 36px;
- --icon-btn-size-xs: 28px;
- --toggle-width: 44px;
- --toggle-height: 24px;
- --toggle-dot: 18px;
- --toggle-sm-width: 36px;
- --toggle-sm-height: 20px;
- --toggle-sm-dot: 14px;
- --progress-bar-height: 8px;
- --console-height: 150px;
- --download-card-max-width: 760px;
- --modal-max-width: 440px;
- --licenses-max-width: 900px;
- --scrollbar-width: 6px;
-
- --z-sidebar-overlay: 998;
- --z-sidebar: 999;
- --z-modal: 1000;
- --z-update-banner: 100;
-
- --sidebar-width: 320px;
- --app-shell-max-width: 1180px;
- --main-stage-max-width: 1040px;
- --main-stage-gap: 14px;
- --top-bar-height: 68px;
- --control-height: 54px;
- --header-inline-padding: 22px;
- --card-outline: 1px solid var(--border-subtle);
- --footer-height: 40px;
- --url-valid-border: var(--success);
- --url-invalid-border: var(--danger);
- --topbar-shadow-color: rgba(0, 0, 0, 0.26);
- --logo-char-white-color: #ffffff;
- --logo-char-white-shadow: 0 0 1px rgba(255, 255, 255, 0.85), 0 0 4px rgba(255, 255, 255, 0.35);
- --logo-char-black-color: #050505;
- --logo-char-black-stroke: 0.5px rgba(255, 255, 255, 0.45);
- --logo-char-black-shadow:
- -1px -1px 0 rgba(255, 255, 255, 0.34), 1px -1px 0 rgba(255, 255, 255, 0.34),
- -1px 1px 0 rgba(255, 255, 255, 0.34), 1px 1px 0 rgba(255, 255, 255, 0.34),
- 0 0 5px rgba(255, 255, 255, 0.2);
- --download-btn-radial: none;
-}
-
-:root[data-theme='dark'] {
- --bg-gradient-start: #0a0c10;
- --bg-gradient-mid: #0d0f14;
- --bg-gradient-end: #10131a;
-}
-
-:root[data-theme='purple'] {
- --bg-gradient-start: #120e1e;
- --bg-gradient-mid: #18122a;
- --bg-gradient-end: #1e163a;
- --surface-1: rgba(255, 255, 255, 0.03);
- --surface-2: rgba(255, 255, 255, 0.06);
- --surface-3: rgba(255, 255, 255, 0.09);
- --surface-hover: rgba(255, 255, 255, 0.12);
- --border-subtle: rgba(255, 255, 255, 0.08);
- --border-default: rgba(255, 255, 255, 0.12);
- --border-strong: rgba(255, 255, 255, 0.2);
- --text-primary: #ffffff;
- --text-secondary: rgba(255, 255, 255, 0.78);
- --text-tertiary: rgba(255, 255, 255, 0.7);
- --text-muted: rgba(255, 255, 255, 0.45);
- --accent: #8b5cf6;
- --accent-light: #a78bfa;
- --accent-dark: #7c3aed;
- --accent-hover: #7c3aed;
- --accent-contrast: #ffffff;
- --accent-glow: rgba(139, 92, 246, 0.2);
- --accent-subtle: rgba(139, 92, 246, 0.12);
- --overlay-bg: rgba(0, 0, 0, 0.5);
- --overlay-bg-heavy: rgba(0, 0, 0, 0.6);
- --sidebar-bg-start: rgba(20, 15, 35, 0.98);
- --sidebar-bg-end: rgba(18, 13, 30, 0.98);
- --modal-bg-start: rgba(25, 20, 42, 0.98);
- --modal-bg-end: rgba(20, 15, 35, 0.98);
- --console-bg: rgba(0, 0, 0, 0.4);
- --console-header-bg: rgba(0, 0, 0, 0.3);
- --console-header-hover-bg: rgba(0, 0, 0, 0.4);
- --console-text: #4ade80;
- --topbar-shadow-color: rgba(0, 0, 0, 0.2);
- --logo-char-white-color: #ffffff;
- --logo-char-white-shadow: 0 0 1px rgba(255, 255, 255, 0.9), 0 0 4px rgba(255, 255, 255, 0.38);
- --logo-char-black-color: #000000;
- --logo-char-black-stroke: 0.5px rgba(255, 255, 255, 0.6);
- --logo-char-black-shadow:
- -1px -1px 0 rgba(255, 255, 255, 0.5), 1px -1px 0 rgba(255, 255, 255, 0.5),
- -1px 1px 0 rgba(255, 255, 255, 0.5), 1px 1px 0 rgba(255, 255, 255, 0.5),
- 0 0 5px rgba(255, 255, 255, 0.24);
-}
-
-:root[data-theme='light'] {
- --bg-gradient-start: #f4f7fb;
- --bg-gradient-mid: #eef3f8;
- --bg-gradient-end: #e7edf5;
- --surface-1: rgba(255, 255, 255, 0.76);
- --surface-2: rgba(255, 255, 255, 0.88);
- --surface-3: rgba(255, 255, 255, 0.95);
- --surface-hover: rgba(232, 239, 249, 0.95);
- --border-subtle: rgba(15, 23, 42, 0.12);
- --border-default: rgba(15, 23, 42, 0.22);
- --border-strong: rgba(15, 23, 42, 0.32);
- --text-primary: #0f172a;
- --text-secondary: rgba(15, 23, 42, 0.82);
- --text-tertiary: rgba(15, 23, 42, 0.7);
- --text-muted: rgba(15, 23, 42, 0.52);
- --accent: #2563eb;
- --accent-light: #3b82f6;
- --accent-dark: #1d4ed8;
- --accent-hover: #1d4ed8;
- --accent-contrast: #ffffff;
- --accent-glow: rgba(37, 99, 235, 0.16);
- --accent-subtle: rgba(37, 99, 235, 0.12);
- --input-bg: var(--surface-3);
- --success-subtle: rgba(34, 197, 94, 0.2);
- --warning-subtle: rgba(245, 158, 11, 0.2);
- --danger-subtle: rgba(239, 68, 68, 0.2);
- --overlay-bg: rgba(15, 23, 42, 0.34);
- --overlay-bg-heavy: rgba(15, 23, 42, 0.5);
- --sidebar-bg-start: rgba(247, 250, 255, 0.98);
- --sidebar-bg-end: rgba(237, 244, 253, 0.98);
- --modal-bg-start: rgba(253, 255, 255, 0.98);
- --modal-bg-end: rgba(243, 248, 255, 0.98);
- --console-bg: rgba(15, 23, 42, 0.07);
- --console-header-bg: rgba(15, 23, 42, 0.1);
- --console-header-hover-bg: rgba(15, 23, 42, 0.14);
- --console-text: #166534;
- --shadow-sm: 0 1px 4px rgba(15, 23, 42, 0.06);
- --shadow-md: 0 2px 8px rgba(15, 23, 42, 0.1);
- --shadow-lg: 0 4px 16px rgba(15, 23, 42, 0.14);
- --topbar-shadow-color: rgba(15, 23, 42, 0.08);
- --logo-char-white-color: #f8fafc;
- --logo-char-white-shadow: 0 0 1px rgba(15, 23, 42, 0.2), 0 0 3px rgba(15, 23, 42, 0.12);
- --logo-char-black-color: #0f172a;
- --logo-char-black-stroke: 0.5px rgba(248, 250, 252, 0.7);
- --logo-char-black-shadow:
- -1px -1px 0 rgba(248, 250, 252, 0.5), 1px -1px 0 rgba(248, 250, 252, 0.5),
- -1px 1px 0 rgba(248, 250, 252, 0.5), 1px 1px 0 rgba(248, 250, 252, 0.5),
- 0 0 5px rgba(248, 250, 252, 0.2);
- --download-btn-radial: none;
-}
-
-*,
-*::before,
-*::after {
- box-sizing: border-box;
- margin: 0;
- padding: 0;
-}
-
-html {
- font-size: 16px;
- -webkit-font-smoothing: antialiased;
- -moz-osx-font-smoothing: grayscale;
-}
-
-body {
- font-family: var(--font-sans);
- background: var(--bg-gradient-mid);
- background-attachment: fixed;
- min-height: 100vh;
- color: var(--text-primary);
- line-height: 1.5;
- overflow-y: auto;
- overflow-x: hidden;
-}
-
-body.animate-bg {
- background: var(--bg-gradient-mid);
-}
-
-.skip-link {
- position: absolute;
- top: -100%;
- left: 50%;
- transform: translateX(-50%);
- background: var(--accent);
- color: var(--accent-contrast);
- padding: var(--spacing-sm) var(--spacing-md);
- border-radius: var(--radius-sm);
- font-weight: var(--weight-semibold);
- z-index: 10000;
- text-decoration: none;
- transition: top var(--transition-fast);
-}
-
-.skip-link:focus {
- top: var(--spacing-sm);
-}
-
-@keyframes gradientShift {
- 0%,
- 100% {
- background-position: 0% 50%;
- }
- 50% {
- background-position: 0% 50%;
- }
-}
-
-.sidebar-overlay {
- position: fixed;
- inset: 0;
- background: var(--overlay-bg);
- backdrop-filter: blur(var(--blur-sm));
- opacity: 0;
- visibility: hidden;
- transition: var(--transition-normal);
- z-index: var(--z-sidebar-overlay);
-}
-
-.sidebar-overlay.active {
- opacity: 1;
- visibility: visible;
-}
-
-.sidebar {
- position: fixed;
- top: 0;
- left: 0;
- width: var(--sidebar-width);
- height: 100vh;
- background: var(--sidebar-bg-start);
- backdrop-filter: blur(var(--blur-lg));
- border-right: 1px solid var(--border-subtle);
- transform: translateX(-100%);
- transition: transform var(--transition-slow);
- z-index: var(--z-sidebar);
- display: flex;
- flex-direction: column;
- box-shadow: var(--shadow-lg);
-}
-
-.sidebar.open {
- transform: translateX(0);
-}
-
-.sidebar-header {
- display: flex;
- align-items: center;
- justify-content: space-between;
- padding: var(--spacing-lg) var(--spacing-xl);
- border-bottom: 1px solid var(--border-subtle);
-}
-
-.sidebar-header h2 {
- font-size: var(--text-lg);
- font-weight: var(--weight-semibold);
- color: var(--text-primary);
-}
-
-.sidebar-close {
- width: var(--icon-btn-size-sm);
- height: var(--icon-btn-size-sm);
- display: flex;
- align-items: center;
- justify-content: center;
- background: var(--surface-2);
- border: 1px solid var(--border-subtle);
- border-radius: var(--radius-md);
- color: var(--text-secondary);
- cursor: pointer;
- transition: var(--transition-fast);
-}
-
-.sidebar-close:hover {
- background: var(--danger-subtle);
- border-color: var(--danger);
- color: var(--danger);
-}
-
-.sidebar-content {
- flex: 1;
- overflow-y: auto;
- padding: var(--spacing-lg);
-}
-
-.sidebar-content::-webkit-scrollbar {
- width: var(--scrollbar-width);
-}
-
-.sidebar-content::-webkit-scrollbar-track {
- background: transparent;
-}
-
-.sidebar-content::-webkit-scrollbar-thumb {
- background: var(--border-default);
- border-radius: 3px;
-}
-
-.sidebar-content::-webkit-scrollbar-thumb:hover {
- background: var(--border-strong);
-}
-
-.settings-section {
- margin-bottom: var(--spacing-lg);
-}
-
-.settings-section-header {
- display: flex;
- justify-content: space-between;
- align-items: center;
- cursor: pointer;
- padding: var(--spacing-xs) 0;
- margin-bottom: var(--spacing-xs);
- border-radius: var(--radius-sm);
- transition: var(--transition-fast);
-}
-
-.settings-section-header:hover .settings-section-title {
- color: var(--text-secondary);
-}
-
-.settings-chevron {
- color: var(--text-muted);
- transition: transform var(--transition-normal);
- flex-shrink: 0;
-}
-
-.settings-section.collapsed .settings-chevron {
- transform: rotate(-90deg);
-}
-
-.settings-section-body {
- overflow: hidden;
- max-height: 800px;
- transition:
- max-height var(--transition-slow),
- opacity var(--transition-normal);
- opacity: 1;
-}
-
-.settings-section.collapsed .settings-section-body {
- max-height: 0;
- opacity: 0;
-}
-
-.settings-section-title {
- font-size: 0.7rem;
- font-weight: var(--weight-semibold);
- text-transform: uppercase;
- letter-spacing: 0.08em;
- color: var(--text-tertiary);
- margin-bottom: 0;
- padding-left: var(--spacing-xs);
- transition: color var(--transition-fast);
-}
-
-.toggle-switch {
- display: flex;
- align-items: center;
- gap: var(--spacing-sm);
- padding: var(--spacing-sm) var(--spacing-md);
- background: var(--surface-1);
- border: 1px solid var(--border-subtle);
- border-radius: var(--radius-md);
- cursor: pointer;
- transition: var(--transition-fast);
- margin-bottom: var(--spacing-sm);
-}
-
-.toggle-switch:hover {
- background: var(--surface-2);
- border-color: var(--border-default);
-}
-
-.toggle-switch input {
- position: absolute;
- opacity: 0;
- pointer-events: none;
-}
-
-.toggle-slider {
- position: relative;
- width: var(--toggle-width);
- height: var(--toggle-height);
- background: var(--surface-3);
- border-radius: 12px;
- flex-shrink: 0;
- transition: var(--transition-fast);
-}
-
-.toggle-slider::after {
- content: '';
- position: absolute;
- top: 3px;
- left: 3px;
- width: var(--toggle-dot);
- height: var(--toggle-dot);
- background: var(--text-secondary);
- border-radius: var(--radius-full);
- transition: var(--transition-fast);
-}
-
-.toggle-switch input:checked + .toggle-slider {
- background: var(--accent);
-}
-
-.toggle-switch input:checked + .toggle-slider::after {
- transform: translateX(20px);
- background: var(--text-primary);
-}
-
-.toggle-text {
- font-size: var(--text-base);
- color: var(--text-secondary);
- display: flex;
- align-items: center;
- gap: var(--spacing-sm);
-}
-
-.toggle-switch.small {
- padding: var(--spacing-xs) var(--spacing-sm);
-}
-
-.toggle-switch.small .toggle-slider {
- width: var(--toggle-sm-width);
- height: var(--toggle-sm-height);
-}
-
-.toggle-switch.small .toggle-slider::after {
- width: var(--toggle-sm-dot);
- height: var(--toggle-sm-dot);
-}
-
-.toggle-switch.small input:checked + .toggle-slider::after {
- transform: translateX(16px);
-}
-
-.toggle-switch.small .toggle-text {
- font-size: var(--text-sm);
-}
-
-#keepOriginalLabel,
-#gpuAccelerationLabel {
- display: none;
-}
-
-#keepOriginalLabel.visible,
-#gpuAccelerationLabel.visible {
- display: flex;
-}
-
-#gpuTypeContainer {
- margin-left: 0;
- margin-top: var(--spacing-sm);
-}
-
-.toggle-switch.disabled {
- opacity: 0.5;
- cursor: not-allowed;
- pointer-events: none;
-}
-
-.toggle-switch.disabled .toggle-slider {
- background: var(--surface-2);
-}
-
-.sub-option {
- display: none;
- padding: var(--spacing-sm) var(--spacing-md);
- margin-left: var(--spacing-md);
- margin-bottom: var(--spacing-xs);
- background: var(--surface-1);
- border-left: 2px solid var(--accent);
- border-radius: 0 var(--radius-md) var(--radius-md) 0;
-}
-
-.sub-option.visible {
- display: block;
-}
-
-.select-label {
- display: flex;
- flex-direction: column;
- gap: var(--spacing-xs);
- margin-bottom: var(--spacing-sm);
-}
-
-.select-label span {
- font-size: var(--text-sm);
- color: var(--text-tertiary);
-}
-
-.select-label select,
-.select-label input,
-.sidebar select {
- width: 100%;
- padding: var(--spacing-sm) var(--spacing-md);
- background: var(--surface-2);
- border: 1px solid var(--border-subtle);
- border-radius: var(--radius-sm);
- color: var(--text-primary);
- font-size: var(--text-base);
- cursor: pointer;
- transition: var(--transition-fast);
-}
-
-.select-label select:hover,
-.select-label input:hover,
-.sidebar select:hover {
- border-color: var(--border-default);
-}
-
-.select-label select:focus,
-.select-label input:focus,
-.sidebar select:focus {
- outline: none;
- border-color: var(--accent);
- box-shadow: 0 0 0 3px var(--accent-subtle);
-}
-
-select option {
- background: var(--bg-gradient-mid);
- color: var(--text-primary);
-}
-
-.settings-hint {
- font-size: 0.78rem;
- color: var(--text-tertiary);
- margin-top: calc(var(--spacing-xs) * -1);
- margin-bottom: var(--spacing-sm);
- padding-left: var(--spacing-xs);
-}
-
-.help-icon {
- display: inline-flex;
- align-items: center;
- justify-content: center;
- width: 18px;
- height: 18px;
- background: var(--accent-subtle);
- border-radius: var(--radius-full);
- font-size: 0.7rem;
- font-weight: var(--weight-semibold);
- color: var(--accent-light);
- text-decoration: none;
- transition: var(--transition-fast);
-}
-
-.help-icon:hover {
- background: var(--accent);
- color: var(--text-primary);
-}
-
-.settings-link-btn {
- background: var(--surface-1);
- border: 1px solid var(--border-subtle);
- border-radius: var(--radius-sm);
- color: var(--text-secondary);
- font-size: var(--text-sm);
- cursor: pointer;
- padding: var(--spacing-xs) var(--spacing-sm);
- margin-left: var(--spacing-md);
- margin-bottom: var(--spacing-xs);
- transition: var(--transition-fast);
-}
-
-.settings-link-btn:hover {
- background: var(--surface-2);
- border-color: var(--accent);
- color: var(--accent);
-}
-
-.settings-btn {
- width: 100%;
- display: flex;
- align-items: center;
- gap: var(--spacing-sm);
- padding: var(--spacing-sm) var(--spacing-md);
- background: var(--surface-1);
- border: 1px solid var(--border-subtle);
- border-radius: var(--radius-md);
- color: var(--text-secondary);
- font-size: var(--text-base);
- cursor: pointer;
- transition: var(--transition-fast);
- margin-bottom: var(--spacing-sm);
-}
-
-.settings-btn:hover {
- background: var(--surface-2);
- border-color: var(--border-default);
- color: var(--text-primary);
- transform: translateX(2px);
-}
-
-.settings-btn:active {
- transform: translateX(0);
-}
-
-.settings-btn svg {
- flex-shrink: 0;
- opacity: 0.7;
-}
-
-.sidebar-footer {
- padding: var(--spacing-lg);
- border-top: 1px solid var(--border-subtle);
-}
-
-.reset-btn {
- width: 100%;
- display: flex;
- align-items: center;
- justify-content: center;
- gap: var(--spacing-sm);
- padding: var(--spacing-md);
- background: var(--danger-subtle);
- border: 1px solid transparent;
- border-radius: var(--radius-md);
- color: var(--danger);
- font-size: 0.85rem;
- font-weight: var(--weight-medium);
- cursor: pointer;
- transition: var(--transition-fast);
-}
-
-.reset-btn:hover {
- background: var(--danger);
- color: var(--text-primary);
-}
-
-.version-info {
- text-align: center;
- margin-top: var(--spacing-md);
- display: flex;
- align-items: center;
- justify-content: center;
- gap: var(--spacing-sm);
-}
-
-.version-info a {
- font-size: var(--text-sm);
- color: var(--text-muted);
- text-decoration: none;
- transition: var(--transition-fast);
-}
-
-.version-info a:hover {
- color: var(--text-secondary);
-}
-
-.version-info a.beta-version {
- color: var(--warning);
-}
-
-.version-info a.beta-version:hover {
- color: var(--warning-light);
-}
-
-.beta-badge {
- display: inline-block;
- font-size: var(--text-xs);
- font-weight: var(--weight-bold);
- letter-spacing: 0.05em;
- text-transform: uppercase;
- padding: 1px 6px;
- border-radius: 4px;
- background: var(--warning);
- color: var(--text-primary);
- line-height: 1.4;
-}
-
-.main-content {
- min-height: 100vh;
- display: flex;
- flex-direction: column;
- transition: var(--transition-slow);
-}
-
-.top-bar {
- display: flex;
- align-items: center;
- justify-content: space-between;
- padding: var(--spacing-md) var(--spacing-xl);
- background: var(--surface-1);
- border-bottom: none;
- box-shadow:
- 0 1px 0 var(--border-subtle),
- 0 4px 16px var(--topbar-shadow-color);
- -webkit-app-region: drag;
-}
-
-.icon-btn {
- width: var(--icon-btn-size);
- height: var(--icon-btn-size);
- display: flex;
- align-items: center;
- justify-content: center;
- background: var(--surface-2);
- border: 1px solid var(--border-subtle);
- border-radius: var(--radius-md);
- color: var(--text-secondary);
- cursor: pointer;
- transition: var(--transition-fast);
- -webkit-app-region: no-drag;
-}
-
-.icon-btn:hover {
- background: var(--accent-subtle);
- border-color: var(--accent);
- color: var(--accent-light);
-}
-
-.icon-btn:active {
- transform: scale(0.97);
-}
-
-.app-brand {
- display: flex;
- align-items: center;
- gap: var(--spacing-sm);
-}
-
-.app-logo {
- width: 32px;
- height: 32px;
- filter: none;
-}
-
-.app-name {
- font-size: var(--text-lg);
- font-weight: var(--weight-bold);
-}
-
-.char-white {
- color: var(--logo-char-white-color);
- text-shadow: var(--logo-char-white-shadow);
-}
-
-.char-black {
- color: var(--logo-char-black-color);
- -webkit-text-stroke: var(--logo-char-black-stroke);
- text-shadow: var(--logo-char-black-shadow);
-}
-
-.top-bar-links {
- display: flex;
- gap: var(--spacing-sm);
- -webkit-app-region: no-drag;
-}
-
-.top-bar-links a {
- width: var(--icon-btn-size-sm);
- height: var(--icon-btn-size-sm);
- display: flex;
- align-items: center;
- justify-content: center;
- color: var(--text-tertiary);
- border-radius: var(--radius-sm);
- transition:
- background var(--transition-fast),
- color var(--transition-fast),
- transform var(--transition-fast);
-}
-
-.top-bar-links a:hover {
- background: var(--surface-2);
- color: var(--text-primary);
-}
-
-.top-bar-links a:active {
- transform: scale(0.97);
-}
-
-.download-section {
- flex: 1;
- display: flex;
- align-items: center;
- justify-content: center;
- padding: var(--spacing-xl);
- min-height: 400px;
-}
-
-.download-card {
- position: relative;
- width: 100%;
- max-width: var(--download-card-max-width);
- padding: var(--spacing-2xl);
- background: var(--surface-1);
- border: 1px solid var(--border-subtle);
- border-radius: var(--radius-xl);
- box-shadow: var(--shadow-lg);
- animation: cardEntrance 0.6s cubic-bezier(0.16, 1, 0.3, 1) both;
-}
-
-.download-card::before {
- content: none;
-}
-
-@keyframes cardEntrance {
- from {
- opacity: 0;
- transform: translateY(20px) scale(0.98);
- }
- to {
- opacity: 1;
- transform: translateY(0) scale(1);
- }
-}
-
-.download-card.drag-over {
- border-color: var(--accent);
- box-shadow: var(--shadow-md);
- transform: scale(1.01);
- transition:
- transform var(--transition-fast),
- border-color var(--transition-fast),
- box-shadow var(--transition-fast);
-}
-
-.download-title {
- font-size: var(--text-xl);
- font-weight: var(--weight-bold);
- text-align: center;
- margin-bottom: var(--spacing-sm);
- color: var(--text-primary);
-}
-
-.download-subtitle {
- text-align: center;
- color: var(--text-tertiary);
- margin-bottom: var(--spacing-xl);
-}
-
-.url-input-wrapper {
- display: flex;
- gap: var(--spacing-md);
- margin-bottom: var(--spacing-lg);
-}
-
-.url-input-container {
- flex: 1;
- position: relative;
- display: flex;
- align-items: center;
-}
-
-.url-icon {
- position: absolute;
- left: var(--spacing-md);
- color: var(--text-tertiary);
- pointer-events: none;
-}
-
-.url-input-container input {
- width: 100%;
- padding: var(--spacing-md) var(--spacing-md) var(--spacing-md) 48px;
- background: var(--surface-2);
- border: 2px solid var(--border-subtle);
- border-radius: var(--radius-lg);
- color: var(--text-primary);
- font-size: var(--text-md);
- transition: var(--transition-fast);
-}
-
-.url-input-container input::placeholder {
- color: var(--text-muted);
-}
-
-.url-input-container input:focus {
- outline: none;
- border-color: var(--accent);
- background: var(--surface-3);
- box-shadow: 0 0 0 4px var(--accent-subtle);
-}
-
-.paste-btn {
- position: absolute;
- right: var(--spacing-sm);
- width: var(--icon-btn-size-xs);
- height: var(--icon-btn-size-xs);
- display: flex;
- align-items: center;
- justify-content: center;
- background: var(--surface-3);
- border: none;
- border-radius: var(--radius-sm);
- color: var(--text-tertiary);
- cursor: pointer;
- transition: var(--transition-fast);
-}
-
-.paste-btn:hover {
- background: var(--accent-subtle);
- color: var(--accent-light);
-}
-
-.paste-btn.hidden {
- display: none !important;
-}
-
-.clear-btn {
- position: absolute;
- right: var(--spacing-sm);
- width: var(--icon-btn-size-xs);
- height: var(--icon-btn-size-xs);
- display: flex;
- align-items: center;
- justify-content: center;
- background: var(--surface-3);
- border: none;
- border-radius: var(--radius-sm);
- color: var(--text-tertiary);
- cursor: pointer;
- transition: var(--transition-fast);
-}
-
-.clear-btn:hover {
- background: var(--danger-subtle);
- color: var(--danger);
-}
-
-.download-btn {
- position: relative;
- display: flex;
- align-items: center;
- gap: var(--spacing-sm);
- padding: var(--spacing-md) var(--spacing-xl);
- background: var(--accent);
- border: none;
- border-radius: var(--radius-lg);
- color: var(--text-primary);
- font-size: var(--text-md);
- font-weight: var(--weight-semibold);
- cursor: pointer;
- transition:
- transform var(--transition-fast),
- box-shadow var(--transition-fast),
- background var(--transition-fast);
- white-space: nowrap;
- overflow: hidden;
-}
-
-.download-btn::after {
- content: none;
-}
-
-.download-btn:hover {
- background: var(--accent-hover);
- transform: translateY(-1px);
- box-shadow: var(--shadow-md);
-}
-
-.download-btn:active {
- transform: translateY(0);
-}
-
-.download-btn.loading {
- pointer-events: none;
- opacity: 0.8;
-}
-
-.download-btn .loader-icon {
- width: 20px;
- height: 20px;
- animation: spin 1s linear infinite;
-}
-
-@keyframes spin {
- to {
- transform: rotate(360deg);
- }
-}
-
-.progress-container {
- display: none;
- padding: var(--spacing-lg);
- background: var(--surface-2);
- border: 1px solid var(--border-subtle);
- border-radius: var(--radius-lg);
- margin-bottom: var(--spacing-lg);
- position: relative;
-}
-
-.progress-container.visible {
- display: block;
- animation: slideDown 0.3s ease;
-}
-
-.progress-phases {
- display: flex;
- align-items: center;
- justify-content: center;
- gap: 0;
- margin-bottom: var(--spacing-md);
-}
-
-.progress-phase {
- display: flex;
- align-items: center;
- gap: var(--spacing-xs);
- opacity: 0.3;
- transition: opacity var(--transition-normal);
-}
-
-.progress-phase.active {
- opacity: 1;
-}
-
-.progress-phase.completed {
- opacity: 0.7;
-}
-
-.phase-dot {
- width: 8px;
- height: 8px;
- border-radius: var(--radius-full);
- background: var(--text-muted);
- transition:
- background var(--transition-normal),
- box-shadow var(--transition-normal);
-}
-
-.progress-phase.active .phase-dot {
- background: var(--accent);
- box-shadow: none;
- animation: none;
-}
-
-.progress-phase.completed .phase-dot {
- background: var(--success);
- box-shadow: none;
- animation: none;
-}
-
-@keyframes pulseDot {
- 0%,
- 100% {
- box-shadow: 0 0 4px var(--accent-glow);
- }
- 50% {
- box-shadow: 0 0 12px var(--accent-glow);
- }
-}
-
-.phase-label {
- font-size: var(--text-sm);
- color: inherit;
-}
-
-.progress-phase-connector {
- width: 24px;
- height: 1px;
- background: var(--border-default);
- margin: 0 var(--spacing-sm);
-}
-
-.progress-bar.active-glow {
- box-shadow: none;
-}
-
-.progress-complete-icon {
- display: none;
- justify-content: center;
- align-items: center;
- margin-top: var(--spacing-md);
- color: var(--success);
-}
-
-.progress-complete-icon.visible {
- display: flex;
- animation: checkmarkPop 0.4s cubic-bezier(0.34, 1.56, 0.64, 1) both;
-}
-
-@keyframes checkmarkPop {
- from {
- opacity: 0;
- transform: scale(0.5);
- }
- to {
- opacity: 1;
- transform: scale(1);
- }
-}
-
-@keyframes slideDown {
- from {
- opacity: 0;
- transform: translateY(-10px);
- }
- to {
- opacity: 1;
- transform: translateY(0);
- }
-}
-
-.progress-header {
- display: flex;
- justify-content: space-between;
- align-items: center;
- margin-bottom: var(--spacing-sm);
-}
-
-.progress-status {
- font-size: var(--text-base);
- color: var(--text-secondary);
-}
-
-.progress-percent {
- font-size: var(--text-base);
- font-weight: var(--weight-semibold);
- color: var(--accent-light);
-}
-
-.progress-bar-wrapper {
- height: var(--progress-bar-height);
- background: var(--surface-1);
- border-radius: 4px;
- overflow: hidden;
-}
-
-.progress-bar {
- height: 100%;
- width: 0%;
- background: var(--accent);
- border-radius: 4px;
- transition: width 0.3s ease;
-}
-
-.progress-bar.indeterminate {
- width: 100%;
- background: linear-gradient(90deg, transparent, var(--accent), transparent);
- background-size: 200% 100%;
- animation: indeterminate 1.5s linear infinite;
-}
-
-@keyframes indeterminate {
- 0% {
- background-position: 200% 0;
- }
- 100% {
- background-position: -200% 0;
- }
-}
-
-.progress-details {
- margin-top: var(--spacing-sm);
- font-size: var(--text-sm);
- color: var(--text-muted);
- text-align: center;
-}
-
-.format-options {
- display: none;
- padding: var(--spacing-lg);
- background: var(--surface-2);
- border: 1px solid var(--border-subtle);
- border-radius: var(--radius-lg);
- margin-top: var(--spacing-md);
-}
-
-.format-options.visible {
- display: block;
- animation: slideDown 0.3s ease;
-}
-
-.fetch-formats-btn {
- width: 100%;
- display: flex;
- align-items: center;
- justify-content: center;
- gap: var(--spacing-sm);
- padding: var(--spacing-md);
- background: var(--surface-3);
- border: 1px solid var(--border-subtle);
- border-radius: var(--radius-md);
- color: var(--text-secondary);
- font-size: var(--text-base);
- cursor: pointer;
- transition: var(--transition-fast);
- margin-bottom: var(--spacing-lg);
-}
-
-.fetch-formats-btn:hover {
- background: var(--accent-subtle);
- border-color: var(--accent);
- color: var(--accent-light);
-}
-
-.format-grid {
- display: grid;
- grid-template-columns: 1fr 1fr;
- gap: var(--spacing-md);
-}
-
-.format-select-label {
- display: flex;
- flex-direction: column;
- gap: var(--spacing-xs);
-}
-
-.format-select-label span {
- font-size: var(--text-sm);
- color: var(--text-tertiary);
-}
-
-.format-select-label select {
- width: 100%;
- padding: var(--spacing-sm) var(--spacing-md);
- background: var(--surface-1);
- border: 1px solid var(--border-subtle);
- border-radius: var(--radius-sm);
- color: var(--text-primary);
- font-size: var(--text-base);
- cursor: pointer;
- transition: var(--transition-fast);
-}
-
-.format-select-label select:focus {
- outline: none;
- border-color: var(--accent);
- box-shadow: 0 0 0 3px var(--accent-subtle);
-}
-
-.download-history {
- display: none;
- margin: 0 var(--spacing-xl) var(--spacing-md);
- background: var(--console-bg);
- border: 1px solid var(--border-subtle);
- border-radius: var(--radius-lg);
- overflow: hidden;
- flex-shrink: 0;
-}
-
-.download-history.visible {
- display: flex;
- flex-direction: column;
- animation: slideDown 0.3s ease;
-}
-
-.history-header {
- display: flex;
- align-items: center;
- justify-content: space-between;
- padding: var(--spacing-sm) var(--spacing-md);
- background: var(--console-header-bg);
- border-bottom: 1px solid var(--border-subtle);
- cursor: pointer;
- user-select: none;
- transition: var(--transition-fast);
-}
-
-.history-header:hover {
- background: var(--console-header-hover-bg);
-}
-
-.history-header-left {
- display: flex;
- align-items: center;
- gap: var(--spacing-sm);
-}
-
-.history-collapse-icon {
- width: 16px;
- height: 16px;
- color: var(--text-tertiary);
- transition: transform var(--transition-normal);
-}
-
-.download-history.collapsed .history-collapse-icon {
- transform: rotate(-90deg);
-}
-
-.history-title {
- display: flex;
- align-items: center;
- gap: var(--spacing-sm);
- font-size: var(--text-sm);
- color: var(--text-tertiary);
- font-weight: var(--weight-semibold);
- letter-spacing: 0.015em;
-}
-
-.history-title svg {
- color: var(--accent-light);
-}
-
-.history-count {
- font-size: var(--text-xs);
- font-weight: var(--weight-semibold);
- color: var(--text-muted);
- background: var(--surface-3);
- padding: 1px 6px;
- border-radius: 10px;
- min-width: 20px;
- text-align: center;
- border: 1px solid color-mix(in srgb, var(--border-subtle) 70%, transparent);
-}
-
-.history-clear-btn {
- width: var(--icon-btn-size-xs);
- height: var(--icon-btn-size-xs);
- display: flex;
- align-items: center;
- justify-content: center;
- background: transparent;
- border: none;
- border-radius: var(--radius-sm);
- color: var(--text-muted);
- cursor: pointer;
- transition: var(--transition-fast);
-}
-
-.history-clear-btn:hover {
- background: var(--danger-subtle);
- color: var(--danger);
-}
-
-.history-list {
- max-height: 200px;
- overflow-y: auto;
- transition:
- max-height var(--transition-normal),
- padding var(--transition-normal);
-}
-
-.download-history.collapsed .history-list {
- max-height: 0;
- overflow: hidden;
-}
-
-.history-list::-webkit-scrollbar {
- width: 6px;
-}
-
-.history-list::-webkit-scrollbar-track {
- background: transparent;
-}
-
-.history-list::-webkit-scrollbar-thumb {
- background: var(--border-default);
- border-radius: 3px;
-}
-
-.history-item {
- display: flex;
- align-items: center;
- justify-content: space-between;
- gap: var(--spacing-md);
- padding: var(--spacing-sm) var(--spacing-md);
- border-bottom: 1px solid var(--border-subtle);
- transition: var(--transition-fast);
-}
-
-.history-item:last-child {
- border-bottom: none;
-}
-
-.history-item:hover {
- background: var(--surface-1);
-}
-
-.history-item-info {
- flex: 1;
- min-width: 0;
- display: flex;
- flex-direction: column;
- gap: 2px;
-}
-
-.history-filename {
- font-size: var(--text-sm);
- color: var(--text-secondary);
- white-space: nowrap;
- overflow: hidden;
- text-overflow: ellipsis;
-}
-
-.history-time {
- font-size: var(--text-xs);
- color: var(--text-muted);
-}
-
-.history-item-actions {
- display: flex;
- align-items: center;
- gap: var(--spacing-sm);
- flex-shrink: 0;
-}
-
-.history-status {
- font-size: var(--text-xs);
- font-weight: var(--weight-medium);
- padding: 2px 8px;
- border-radius: 10px;
-}
-
-.history-status.success {
- color: var(--success);
- background: var(--success-subtle);
-}
-
-.history-status.failed {
- color: var(--danger);
- background: var(--danger-subtle);
-}
-
-.history-status.cancelled {
- color: var(--warning);
- background: var(--warning-subtle);
-}
-
-.history-open-btn {
- font-size: var(--text-xs);
- color: var(--accent-light);
- background: var(--accent-subtle);
- border: none;
- border-radius: var(--radius-sm);
- padding: 2px 8px;
- cursor: pointer;
- transition: var(--transition-fast);
- white-space: nowrap;
-}
-
-.history-open-btn:hover {
- background: var(--accent);
- color: var(--text-primary);
-}
-
-.history-empty {
- padding: var(--spacing-md);
- text-align: center;
- color: var(--text-muted);
- font-size: var(--text-sm);
-}
-
-.console-section {
- display: none;
- margin: 0 var(--spacing-xl) var(--spacing-lg);
- background: var(--console-bg);
- border: 1px solid var(--border-subtle);
- border-radius: var(--radius-lg);
- overflow: hidden;
- flex-shrink: 0;
-}
-
-.console-section.visible {
- display: flex;
- flex-direction: column;
-}
-
-.console-header {
- display: flex;
- align-items: center;
- justify-content: space-between;
- padding: var(--spacing-sm) var(--spacing-md);
- background: var(--console-header-bg);
- border-bottom: 1px solid var(--border-subtle);
- cursor: pointer;
- user-select: none;
- transition: var(--transition-fast);
-}
-
-.console-header:hover {
- background: var(--console-header-hover-bg);
-}
-
-.console-header-left {
- display: flex;
- align-items: center;
- gap: var(--spacing-sm);
-}
-
-.console-collapse-icon {
- width: 16px;
- height: 16px;
- color: var(--text-tertiary);
- transition: transform var(--transition-normal);
-}
-
-.console-section.collapsed .console-collapse-icon {
- transform: rotate(-90deg);
-}
-
-.console-title {
- display: flex;
- align-items: center;
- gap: var(--spacing-sm);
- font-size: var(--text-sm);
- color: var(--text-tertiary);
- font-weight: var(--weight-semibold);
- letter-spacing: 0.015em;
-}
-
-.console-title svg {
- color: var(--console-text);
-}
-
-.console-clear-btn {
- width: var(--icon-btn-size-xs);
- height: var(--icon-btn-size-xs);
- display: flex;
- align-items: center;
- justify-content: center;
- background: transparent;
- border: none;
- border-radius: var(--radius-sm);
- color: var(--text-muted);
- cursor: pointer;
- transition: var(--transition-fast);
-}
-
-.console-clear-btn:hover {
- background: var(--danger-subtle);
- color: var(--danger);
-}
-
-.console-output {
- height: var(--console-height);
- padding: var(--spacing-md);
- font-family: var(--font-mono);
- font-size: var(--text-sm);
- color: var(--console-text);
- overflow-y: auto;
- white-space: pre-wrap;
- word-break: break-word;
- line-height: 1.6;
- transition:
- height var(--transition-normal),
- padding var(--transition-normal);
-}
-
-.console-section.collapsed .console-output {
- height: 0;
- padding-top: 0;
- padding-bottom: 0;
- overflow: hidden;
-}
-
-.console-output::-webkit-scrollbar {
- width: 8px;
-}
-
-.console-output::-webkit-scrollbar-track {
- background: transparent;
-}
-
-.console-output::-webkit-scrollbar-thumb {
- background: var(--border-default);
- border-radius: 4px;
-}
-
-.main-footer {
- display: flex;
- align-items: center;
- justify-content: center;
- gap: var(--spacing-sm);
- padding: var(--spacing-sm) var(--spacing-md);
- font-size: var(--text-sm);
- color: var(--text-muted);
-}
-
-.main-footer a {
- color: var(--text-tertiary);
- text-decoration: none;
- transition: var(--transition-fast);
-}
-
-.main-footer a:hover {
- color: var(--text-primary);
-}
-
-.footer-divider {
- opacity: 0.3;
-}
-
-.footer-credit {
- color: var(--text-muted);
-}
-
-.modal-overlay {
- position: fixed;
- inset: 0;
- display: none;
- align-items: center;
- justify-content: center;
- background: var(--overlay-bg-heavy);
- backdrop-filter: blur(var(--blur-md));
- z-index: var(--z-modal);
- opacity: 0;
- transition: opacity 0.2s ease;
-}
-
-.modal-overlay.active {
- display: flex;
- opacity: 1;
-}
-
-.modal-overlay.showing {
- opacity: 0;
-}
-
-.modal-overlay.hiding {
- opacity: 0;
-}
-
-.modal-box {
- width: 90%;
- max-width: var(--modal-max-width);
- padding: var(--spacing-xl);
- background: var(--modal-bg-start);
- border: 1px solid var(--border-subtle);
- border-radius: var(--radius-xl);
- box-shadow: var(--shadow-lg);
- transform: scale(1) translateY(0);
- opacity: 1;
- transition:
- transform 0.25s cubic-bezier(0.34, 1.56, 0.64, 1),
- opacity 0.2s ease;
-}
-
-.modal-overlay.showing .modal-box {
- transform: scale(0.9) translateY(-20px);
- opacity: 0;
-}
-
-.modal-overlay.hiding .modal-box {
- transform: scale(0.95) translateY(10px);
- opacity: 0;
-}
-
-.modal-box h3 {
- font-size: var(--text-lg);
- font-weight: var(--weight-semibold);
- margin-bottom: var(--spacing-md);
- text-align: center;
-}
-
-.modal-message {
- color: var(--text-secondary);
- text-align: center;
- margin-bottom: var(--spacing-xl);
- line-height: 1.7;
- white-space: pre-line;
-}
-
-.modal-extra:empty {
- display: none;
-}
-
-.modal-buttons {
- display: flex;
- gap: var(--spacing-md);
- justify-content: center;
- flex-wrap: wrap;
-}
-
-.modal-buttons button {
- min-width: 100px;
- padding: var(--spacing-sm) var(--spacing-lg);
- background: var(--surface-2);
- border: 1px solid var(--border-subtle);
- border-radius: var(--radius-md);
- color: var(--text-secondary);
- font-size: var(--text-base);
- font-weight: var(--weight-medium);
- cursor: pointer;
- transition: var(--transition-fast);
-}
-
-.modal-buttons button:hover {
- background: var(--surface-3);
- border-color: var(--border-default);
- color: var(--text-primary);
-}
-
-.modal-buttons button:first-child {
- background: var(--accent);
- border-color: transparent;
- color: var(--text-primary);
-}
-
-.modal-buttons button:first-child:hover {
- background: var(--accent-hover);
-}
-
-.licenses-container {
- width: 90%;
- max-width: var(--licenses-max-width);
- height: 80vh;
- background: var(--modal-bg-start);
- border: 1px solid var(--border-subtle);
- border-radius: var(--radius-xl);
- box-shadow: var(--shadow-lg);
- overflow: hidden;
- display: flex;
- flex-direction: column;
-}
-
-.licenses-header {
- display: flex;
- align-items: center;
- justify-content: space-between;
- padding: var(--spacing-lg) var(--spacing-xl);
- border-bottom: 1px solid var(--border-subtle);
-}
-
-.licenses-header h3 {
- font-size: 1.1rem;
- font-weight: var(--weight-semibold);
- margin: 0;
-}
-
-.modal-close-btn {
- width: var(--icon-btn-size-sm);
- height: var(--icon-btn-size-sm);
- display: flex;
- align-items: center;
- justify-content: center;
- background: var(--surface-2);
- border: 1px solid var(--border-subtle);
- border-radius: var(--radius-md);
- color: var(--text-secondary);
- cursor: pointer;
- transition: var(--transition-fast);
-}
-
-.modal-close-btn:hover {
- background: var(--danger-subtle);
- border-color: var(--danger);
- color: var(--danger);
-}
-
-.licenses-container iframe {
- flex: 1;
- width: 100%;
- border: none;
- background: transparent;
-}
-
-.update-progress-container {
- margin: var(--spacing-md) 0;
-}
-
-.update-progress-bar-wrapper {
- height: 6px;
- background: var(--surface-2);
- border-radius: 3px;
- overflow: hidden;
- margin-bottom: var(--spacing-sm);
-}
-
-.update-progress-bar {
- height: 100%;
- background: var(--success);
- border-radius: 3px;
- transition: width 0.3s ease;
-}
-
-.update-progress-info {
- font-size: var(--text-sm);
- color: var(--text-muted);
- text-align: center;
-}
-
-.update-banner {
- position: fixed;
- bottom: 0;
- left: 0;
- right: 0;
- background: var(--surface-1);
- border-top: 1px solid var(--border-subtle);
- padding: var(--spacing-sm) var(--spacing-xl);
- z-index: var(--z-update-banner);
- transform: translateY(100%);
- transition: transform 0.3s ease;
- pointer-events: none;
-}
-
-.update-banner.active {
- transform: translateY(0);
- pointer-events: auto;
-}
-
-.update-banner-content {
- display: flex;
- align-items: center;
- gap: var(--spacing-md);
-}
-
-.update-banner-text {
- font-size: 0.85rem;
- color: var(--text-secondary);
- white-space: nowrap;
- font-weight: var(--weight-medium);
-}
-
-.update-banner-progress {
- flex: 1;
- height: 4px;
- background: var(--surface-2);
- border-radius: 2px;
- overflow: hidden;
- min-width: 80px;
-}
-
-.update-banner-bar {
- height: 100%;
- background: var(--success);
- border-radius: 2px;
- transition: width 0.3s ease;
- width: 0%;
-}
-
-.update-banner-info {
- font-size: var(--text-sm);
- color: var(--text-muted);
- white-space: nowrap;
-}
-
-.update-banner-cancel {
- background: var(--surface-2);
- border: 1px solid var(--border-subtle);
- color: var(--text-secondary);
- padding: var(--spacing-xs) var(--spacing-sm);
- border-radius: var(--radius-sm);
- font-size: var(--text-sm);
- cursor: pointer;
- white-space: nowrap;
- transition: var(--transition-fast);
-}
-
-.update-banner-cancel:hover {
- background: var(--danger-subtle);
- border-color: var(--danger);
- color: var(--danger);
-}
-
-.toast-container {
- position: fixed;
- bottom: var(--spacing-xl);
- right: var(--spacing-xl);
- display: flex;
- flex-direction: column-reverse;
- gap: var(--spacing-sm);
- z-index: 1001;
- pointer-events: none;
-}
-
-.toast {
- pointer-events: auto;
- display: flex;
- align-items: center;
- gap: var(--spacing-md);
- padding: var(--spacing-md) var(--spacing-lg);
- background: var(--surface-2);
- border: 1px solid var(--border-subtle);
- border-radius: var(--radius-md);
- backdrop-filter: blur(var(--blur-lg));
- box-shadow: var(--shadow-md);
- color: var(--text-primary);
- font-size: var(--text-base);
- max-width: 380px;
- min-width: 240px;
- transform: translateX(calc(100% + var(--spacing-xl)));
- opacity: 0;
- transition:
- transform var(--transition-slow),
- opacity var(--transition-normal);
-}
-
-.toast.visible {
- transform: translateX(0);
- opacity: 1;
-}
-
-.toast.hiding {
- transform: translateX(calc(100% + var(--spacing-xl)));
- opacity: 0;
-}
-
-.toast-icon {
- flex-shrink: 0;
- width: 20px;
- height: 20px;
-}
-
-.toast-message {
- flex: 1;
- line-height: 1.4;
-}
-
-.toast-dismiss {
- flex-shrink: 0;
- width: 20px;
- height: 20px;
- display: flex;
- align-items: center;
- justify-content: center;
- background: none;
- border: none;
- color: var(--text-muted);
- cursor: pointer;
- padding: 0;
- border-radius: var(--radius-sm);
- transition: var(--transition-fast);
-}
-
-.toast-dismiss:hover {
- color: var(--text-primary);
- background: var(--surface-3);
-}
-
-.toast-warning {
- border-left: 3px solid var(--warning);
-}
-
-.toast-warning .toast-icon {
- color: var(--warning);
-}
-
-.toast-error {
- border-left: 3px solid var(--danger);
-}
-
-.toast-error .toast-icon {
- color: var(--danger);
-}
-
-.toast-success {
- border-left: 3px solid var(--success);
-}
-
-.toast-success .toast-icon {
- color: var(--success);
-}
-
-.toast-info {
- border-left: 3px solid var(--accent);
-}
-
-.toast-info .toast-icon {
- color: var(--accent-light);
-}
-
-.hidden {
- display: none !important;
-}
-
-.sr-only {
- position: absolute;
- width: 1px;
- height: 1px;
- padding: 0;
- margin: -1px;
- overflow: hidden;
- clip: rect(0, 0, 0, 0);
- border: 0;
-}
-
-:focus-visible {
- outline: 2px solid var(--accent);
- outline-offset: 2px;
-}
-
-.icon-btn:focus-visible,
-.download-btn:focus-visible,
-.settings-btn:focus-visible,
-.reset-btn:focus-visible,
-.modal-buttons button:focus-visible,
-.fetch-formats-btn:focus-visible,
-.settings-link-btn:focus-visible,
-.update-banner-cancel:focus-visible,
-.modal-close-btn:focus-visible,
-.sidebar-close:focus-visible,
-.console-clear-btn:focus-visible,
-.clear-btn:focus-visible {
- outline: 2px solid var(--accent);
- outline-offset: 2px;
- box-shadow: 0 0 0 4px var(--accent-subtle);
-}
-
-.toggle-switch:focus-within {
- outline: 2px solid var(--accent);
- outline-offset: 2px;
- box-shadow: 0 0 0 4px var(--accent-subtle);
-}
-
-/* Responsive adjustments for smaller windows */
-@media (max-height: 800px) {
- .download-section {
- min-height: 350px;
- padding: var(--spacing-md);
- }
-
- .console-output {
- height: 120px;
- }
-}
-
-@media (max-height: 700px) {
- .download-section {
- min-height: 300px;
- }
-
- .console-output {
- height: 100px;
- }
-}
-
-@media (max-width: 560px) {
- .url-input-wrapper {
- flex-direction: column;
- }
-
- .download-card {
- padding: var(--spacing-xl);
- }
-
- .download-title {
- font-size: var(--text-lg);
- }
-
- .main-footer {
- flex-wrap: wrap;
- gap: var(--spacing-xs);
- }
-}
-
-@media (prefers-reduced-motion: reduce) {
- *,
- *::before,
- *::after {
- animation-duration: 0.01ms !important;
- animation-iteration-count: 1 !important;
- transition-duration: 0.01ms !important;
- }
-}
-
-.main-content {
- width: 100%;
- min-height: 100vh;
-}
-
-.app-shell {
- width: 100%;
- max-width: none;
- margin: 0;
- min-height: 100vh;
- display: flex;
- flex-direction: column;
- padding: 0;
- gap: var(--spacing-sm);
-}
-
-.main-stage {
- width: min(var(--main-stage-max-width), 100%);
- max-width: none;
- margin: 0 auto;
- padding: 0 var(--spacing-md);
- display: flex;
- flex-direction: column;
- gap: var(--main-stage-gap);
- flex: 1;
-}
-
-.top-bar {
- height: var(--top-bar-height);
- padding: 0 clamp(12px, 2.2vw, var(--header-inline-padding));
- border-radius: 0;
- border: none;
- border-bottom: 1px solid color-mix(in srgb, var(--border-default) 70%, transparent);
- backdrop-filter: blur(var(--blur-sm));
- box-shadow: 0 1px 0 color-mix(in srgb, var(--border-default) 70%, transparent);
- background: color-mix(in srgb, var(--surface-1) 92%, transparent);
-}
-
-.download-section {
- align-items: flex-start;
- padding: clamp(16px, 4vh, 48px) 0 0;
- min-height: auto;
-}
-
-.download-card {
- margin: 0 auto;
- width: 100%;
- max-width: var(--download-card-max-width);
- padding: clamp(1.35rem, 3vw, 2.15rem);
- box-shadow: var(--shadow-md);
- border: 1px solid color-mix(in srgb, var(--border-default) 85%, transparent);
- background: color-mix(in srgb, var(--surface-1) 90%, transparent);
- backdrop-filter: blur(var(--blur-md));
-}
-
-.download-card::before {
- content: none;
-}
-
-.download-card::after {
- content: none;
-}
-
-.download-card.drag-over {
- box-shadow: var(--shadow-md);
- border-color: color-mix(in srgb, var(--accent) 45%, var(--border-default));
- transform: none;
-}
-
-.download-title {
- font-size: clamp(1.7rem, 2.8vw, 2.25rem);
- line-height: 1.1;
- margin-bottom: var(--spacing-xs);
- letter-spacing: -0.02em;
-}
-
-.download-subtitle {
- font-size: clamp(0.97rem, 1.35vw, 1.1rem);
- margin-bottom: var(--spacing-lg);
- color: color-mix(in srgb, var(--text-secondary) 88%, transparent);
-}
-
-.url-input-wrapper {
- display: grid;
- grid-template-columns: 1fr auto;
- align-items: stretch;
- gap: var(--spacing-sm);
-}
-
-.url-validation-message {
- min-height: 1.1rem;
- margin-top: var(--spacing-xs);
- color: var(--danger);
- font-size: var(--text-sm);
-}
-
-.url-input-container {
- min-height: var(--control-height);
-}
-
-.url-input-container input {
- min-height: var(--control-height);
- border-width: 1px;
- border-radius: calc(var(--radius-lg) - 2px);
- padding-right: 92px;
-}
-
-.url-input-container.is-valid input {
- border-color: var(--url-valid-border);
- box-shadow: 0 0 0 2px color-mix(in srgb, var(--url-valid-border) 20%, transparent);
-}
-
-.url-input-container.is-invalid input {
- border-color: var(--url-invalid-border);
- box-shadow: 0 0 0 2px color-mix(in srgb, var(--url-invalid-border) 22%, transparent);
-}
-
-.url-input-container.is-empty input {
- border-color: var(--border-subtle);
- box-shadow: none;
-}
-
-.paste-btn,
-.clear-btn {
- width: 30px;
- height: 30px;
-}
-
-.download-btn {
- min-height: var(--control-height);
- justify-content: center;
- min-width: 170px;
- border: 1px solid color-mix(in srgb, var(--accent-light) 35%, transparent);
- letter-spacing: 0.018em;
- box-shadow: var(--shadow-sm);
-}
-
-.download-btn:hover {
- transform: translateY(-1px);
- box-shadow: var(--shadow-md);
-}
-
-.download-btn:disabled,
-.download-btn.is-disabled {
- opacity: 0.55;
- cursor: not-allowed;
- transform: none;
- box-shadow: none;
- pointer-events: none;
-}
-
-.download-btn.loading {
- pointer-events: auto;
-}
-
-.progress-container,
-.format-options {
- border-radius: calc(var(--radius-lg) - 2px);
-}
-
-.download-history,
-.console-section {
- width: 100%;
- margin: 0;
- border: 1px solid color-mix(in srgb, var(--border-default) 80%, transparent);
- background: color-mix(in srgb, var(--surface-1) 90%, transparent);
- backdrop-filter: blur(var(--blur-sm));
- box-shadow: var(--shadow-sm);
-}
-
-.app-logo {
- filter: none;
-}
-
-.progress-phase.active .phase-dot {
- box-shadow: none;
- animation: none;
-}
-
-.progress-bar.active-glow {
- box-shadow: none;
-}
-
-.history-header,
-.console-header {
- min-height: 44px;
- background: color-mix(in srgb, var(--console-header-bg) 94%, transparent);
- border-bottom: 1px solid color-mix(in srgb, var(--border-default) 65%, transparent);
-}
-
-.main-footer {
- width: 100%;
- min-height: var(--footer-height);
- margin-top: auto;
- border-top: 1px solid var(--border-subtle);
- background: color-mix(in srgb, var(--surface-1) 88%, transparent);
- border-radius: 0;
- padding-inline: clamp(10px, 2vw, 20px);
-}
-
-.sidebar {
- width: min(360px, calc(100vw - 22px));
- border-right: 1px solid var(--border-default);
-}
-
-.sidebar-header {
- padding: 1.1rem 1.35rem;
-}
-
-.sidebar-content {
- padding: 0.95rem;
-}
-
-.settings-section {
- margin-bottom: var(--spacing-md);
-}
-
-.settings-section-header {
- padding: 0.42rem 0.52rem;
- margin-bottom: 0.36rem;
- border: 1px solid transparent;
- background: color-mix(in srgb, var(--surface-1) 68%, transparent);
-}
-
-.settings-section-header:hover {
- background: color-mix(in srgb, var(--surface-2) 72%, transparent);
- border-color: color-mix(in srgb, var(--border-default) 60%, transparent);
-}
-
-.settings-section-header:focus-visible {
- outline: 2px solid var(--accent);
- outline-offset: 2px;
- box-shadow: 0 0 0 4px var(--accent-subtle);
-}
-
-.settings-section-title {
- letter-spacing: 0.11em;
-}
-
-.toggle-switch,
-.settings-btn {
- border-radius: calc(var(--radius-md) - 2px);
-}
-
-.settings-btn,
-.settings-link-btn,
-.icon-btn,
-.top-bar-links a,
-.queue-action-btn {
- backdrop-filter: blur(var(--blur-sm));
-}
-
-.settings-btn:hover,
-.settings-link-btn:hover {
- transform: none;
-}
-
-.modal-box {
- max-width: 500px;
- border: 1px solid var(--border-default);
- box-shadow: var(--shadow-lg);
-}
-
-.modal-buttons button {
- min-height: 40px;
-}
-
-.toast-container {
- bottom: calc(var(--footer-height) + var(--spacing-md));
-}
-
-.toast {
- border-radius: calc(var(--radius-md) + 2px);
- border: 1px solid var(--border-default);
-}
-
-body::before {
- content: none;
-}
-@media (max-width: 960px) {
- .top-bar {
- padding: 0 var(--spacing-sm);
- }
-
- .main-stage {
- padding: 0 var(--spacing-sm);
- }
-}
-
-@media (max-width: 780px) {
- .url-input-wrapper {
- grid-template-columns: 1fr;
- }
-
- .download-btn {
- width: 100%;
- min-width: auto;
- }
-
- .main-footer {
- flex-wrap: wrap;
- gap: var(--spacing-xs);
- padding: var(--spacing-xs) var(--spacing-sm);
- }
-}
-
-/* ββ Queue Section ββ */
-
-.queue-section {
- background: color-mix(in srgb, var(--surface-1) 90%, transparent);
- border: 1px solid color-mix(in srgb, var(--border-default) 80%, transparent);
- border-radius: var(--radius-lg);
- overflow: hidden;
- display: flex;
- flex-direction: column;
- animation: slideDown 0.3s ease;
- box-shadow: var(--shadow-sm);
-}
-
-.queue-header {
- display: flex;
- align-items: center;
- justify-content: space-between;
- padding: var(--spacing-sm) var(--spacing-md);
- background: color-mix(in srgb, var(--console-header-bg) 94%, transparent);
- border-bottom: 1px solid color-mix(in srgb, var(--border-default) 65%, transparent);
-}
-
-.queue-header-left {
- display: flex;
- align-items: center;
- gap: var(--spacing-sm);
-}
-
-.queue-title {
- font-size: var(--text-sm);
- color: var(--text-tertiary);
- font-weight: var(--weight-semibold);
- letter-spacing: 0.015em;
-}
-
-.queue-count {
- font-size: var(--text-xs);
- font-weight: var(--weight-semibold);
- color: var(--text-muted);
- background: var(--surface-3);
- padding: 1px 6px;
- border-radius: 10px;
- min-width: 20px;
- text-align: center;
- border: 1px solid color-mix(in srgb, var(--border-subtle) 70%, transparent);
-}
-
-.queue-input-row {
- display: flex;
- gap: var(--spacing-sm);
- padding: var(--spacing-sm) var(--spacing-md);
- align-items: flex-start;
-}
-
-.queue-url-input {
- flex: 1;
- min-height: 40px;
- max-height: 80px;
- padding: var(--spacing-xs) var(--spacing-sm);
- background: var(--input-bg);
- color: var(--text-primary);
- border: 1px solid var(--border-default);
- border-radius: var(--radius-md);
- font-family: var(--font-mono);
- font-size: var(--text-sm);
- resize: vertical;
-}
-
-.queue-url-input::placeholder {
- color: var(--text-muted);
-}
-
-.queue-url-input:focus {
- outline: none;
- border-color: var(--accent);
- box-shadow: 0 0 0 2px var(--accent-subtle);
-}
-
-.queue-url-input:disabled {
- opacity: 0.7;
- cursor: not-allowed;
-}
-
-.queue-controls {
- display: flex;
- gap: var(--spacing-xs);
- padding: 0 var(--spacing-md) var(--spacing-sm);
- flex-wrap: wrap;
-}
-
-.queue-status-message {
- margin: 0;
- padding: 0 var(--spacing-md) var(--spacing-sm);
- color: var(--text-muted);
- font-size: var(--text-xs);
- line-height: 1.4;
-}
-
-.queue-action-btn {
- display: inline-flex;
- align-items: center;
- gap: var(--spacing-xs);
- padding: var(--spacing-xs) var(--spacing-sm);
- font-size: var(--text-xs);
- font-weight: var(--weight-medium);
- border: 1px solid var(--border-default);
- border-radius: var(--radius-md);
- background: var(--surface-1);
- color: var(--text-secondary);
- cursor: pointer;
- transition: var(--transition-fast);
- min-height: 32px;
-}
-
-.queue-action-btn:hover {
- background: var(--surface-2);
- border-color: var(--border-strong);
-}
-
-.queue-action-btn:disabled {
- opacity: 0.6;
- cursor: not-allowed;
- pointer-events: none;
-}
-
-.queue-add-btn {
- background: var(--accent);
- color: var(--accent-contrast);
- border-color: var(--accent);
-}
-
-.queue-add-btn:hover {
- background: var(--accent-hover);
- border-color: var(--accent-hover);
-}
-
-.queue-list {
- max-height: 250px;
- overflow-y: auto;
-}
-
-.queue-list::-webkit-scrollbar {
- width: 6px;
-}
-
-.queue-list::-webkit-scrollbar-track {
- background: transparent;
-}
-
-.queue-list::-webkit-scrollbar-thumb {
- background: var(--border-default);
- border-radius: 3px;
-}
-
-.queue-item {
- display: flex;
- align-items: center;
- gap: var(--spacing-sm);
- padding: var(--spacing-xs) var(--spacing-md);
- border-top: 1px solid var(--border-subtle);
- font-size: var(--text-sm);
- transition: var(--transition-fast);
-}
-
-.queue-item:hover {
- background: var(--surface-1);
-}
-
-.queue-item-status {
- flex-shrink: 0;
- width: 20px;
- text-align: center;
-}
-
-.queue-item-url {
- flex: 1;
- min-width: 0;
- color: var(--text-secondary);
- white-space: nowrap;
- overflow: hidden;
- text-overflow: ellipsis;
- font-family: var(--font-mono);
- font-size: var(--text-xs);
-}
-
-.queue-item-remove {
- flex-shrink: 0;
- width: 20px;
- height: 20px;
- display: flex;
- align-items: center;
- justify-content: center;
- background: transparent;
- border: none;
- border-radius: var(--radius-sm);
- color: var(--text-muted);
- cursor: pointer;
- font-size: var(--text-sm);
- transition: var(--transition-fast);
-}
-
-.queue-item-remove:hover {
- background: var(--danger-subtle);
- color: var(--danger);
-}
-
-.queue-downloading {
- background: var(--accent-subtle);
-}
-
-.queue-completed .queue-item-url {
- color: var(--success);
-}
-
-.queue-failed .queue-item-url {
- color: var(--danger);
-}
-
-.queue-cancelled .queue-item-url {
- color: var(--text-muted);
-}
-
-.wizard-overlay {
- position: fixed;
- inset: 0;
- display: none;
- align-items: center;
- justify-content: center;
- background: rgba(0, 0, 0, 0.7);
- backdrop-filter: blur(8px);
- z-index: 2000;
-}
-
-.wizard-overlay.active {
- display: flex;
-}
-
-.wizard-card {
- width: 90%;
- max-width: 520px;
- background: var(--surface-1);
- border: 1px solid var(--border-subtle);
- border-radius: var(--radius-xl);
- box-shadow: var(--shadow-lg);
- padding: var(--spacing-2xl) var(--spacing-xl) var(--spacing-xl);
- display: flex;
- flex-direction: column;
- animation: cardEntrance 0.5s cubic-bezier(0.16, 1, 0.3, 1) both;
-}
-
-.wizard-progress {
- height: 3px;
- background: var(--surface-3);
- border-radius: 2px;
- margin-bottom: var(--spacing-xl);
- overflow: hidden;
-}
-
-.wizard-progress-bar {
- height: 100%;
- width: 25%;
- background: var(--accent);
- border-radius: 2px;
- transition: width var(--transition-normal);
-}
-
-.wizard-step {
- display: none;
- flex-direction: column;
- align-items: center;
- text-align: center;
- min-height: 280px;
-}
-
-.wizard-step.active {
- display: flex;
- animation: wizardFadeIn 0.3s ease both;
-}
-
-@keyframes wizardFadeIn {
- from {
- opacity: 0;
- transform: translateY(8px);
- }
- to {
- opacity: 1;
- transform: translateY(0);
- }
-}
-
-.wizard-icon {
- margin-bottom: var(--spacing-lg);
-}
-
-.wizard-heading {
- font-size: 1.5rem;
- font-weight: var(--weight-bold);
- color: var(--text-primary);
- margin-bottom: var(--spacing-sm);
-}
-
-.wizard-description {
- font-size: var(--text-base);
- color: var(--text-secondary);
- line-height: 1.6;
- margin-bottom: var(--spacing-lg);
- max-width: 400px;
-}
-
-.wizard-theme-grid {
- display: grid;
- grid-template-columns: repeat(4, 1fr);
- gap: var(--spacing-sm);
- width: 100%;
- max-width: 420px;
-}
-
-.wizard-theme-option input {
- position: absolute;
- opacity: 0;
- pointer-events: none;
-}
-
-.wizard-theme-card {
- display: flex;
- flex-direction: column;
- align-items: center;
- gap: var(--spacing-xs);
- padding: var(--spacing-sm);
- border: 2px solid var(--border-subtle);
- border-radius: var(--radius-md);
- cursor: pointer;
- transition: var(--transition-fast);
-}
-
-.wizard-theme-option input:checked + .wizard-theme-card {
- border-color: var(--accent);
- background: var(--accent-subtle);
-}
-
-.wizard-theme-card:hover {
- border-color: var(--border-default);
-}
-
-.wizard-theme-preview {
- width: 100%;
- aspect-ratio: 3 / 4;
- border-radius: var(--radius-sm);
- border: 1px solid var(--border-subtle);
-}
-
-.wizard-preview-system {
- background: linear-gradient(135deg, #1a1a2e 50%, #f0f0f5 50%);
-}
-
-.wizard-preview-dark {
- background: #0d0f14;
-}
-
-.wizard-preview-light {
- background: #eef3f8;
-}
-
-.wizard-preview-purple {
- background: #18122a;
-}
-
-.wizard-theme-label {
- font-size: var(--text-sm);
- color: var(--text-secondary);
- font-weight: var(--weight-medium);
-}
-
-.wizard-options {
- width: 100%;
- max-width: 420px;
- display: flex;
- flex-direction: column;
- gap: var(--spacing-sm);
- text-align: left;
-}
-
-.wizard-toggle {
- display: flex;
- align-items: center;
- gap: var(--spacing-sm);
- padding: var(--spacing-sm) var(--spacing-md);
- background: var(--surface-2);
- border: 1px solid var(--border-subtle);
- border-radius: var(--radius-md);
- cursor: pointer;
- transition: var(--transition-fast);
- position: relative;
-}
-
-.wizard-toggle:hover {
- background: var(--surface-3);
- border-color: var(--border-default);
-}
-
-.wizard-toggle input {
- position: absolute;
- opacity: 0;
- pointer-events: none;
-}
-
-.wizard-toggle input:checked + .toggle-slider {
- background: var(--accent);
-}
-
-.wizard-toggle input:checked + .toggle-slider::after {
- transform: translateX(20px);
- background: var(--text-primary);
-}
-
-.wizard-toggle-text {
- display: flex;
- flex-direction: column;
- gap: 2px;
-}
-
-.wizard-toggle-text strong {
- font-size: var(--text-base);
- font-weight: var(--weight-medium);
- color: var(--text-primary);
-}
-
-.wizard-toggle-text small {
- font-size: var(--text-sm);
- color: var(--text-muted);
- line-height: 1.3;
-}
-
-.wizard-nav {
- display: flex;
- align-items: center;
- justify-content: space-between;
- margin-top: var(--spacing-xl);
- padding-top: var(--spacing-lg);
- border-top: 1px solid var(--border-subtle);
-}
-
-.wizard-dots {
- display: flex;
- gap: var(--spacing-xs);
-}
-
-.wizard-dot {
- width: 8px;
- height: 8px;
- border-radius: var(--radius-full);
- background: var(--surface-3);
- transition: var(--transition-fast);
-}
-
-.wizard-dot.active {
- background: var(--accent);
- width: 20px;
- border-radius: 4px;
-}
-
-.wizard-btn {
- padding: var(--spacing-sm) var(--spacing-lg);
- border: none;
- border-radius: var(--radius-md);
- font-size: var(--text-base);
- font-weight: var(--weight-medium);
- cursor: pointer;
- transition: var(--transition-fast);
- min-width: 100px;
-}
-
-.wizard-btn-primary {
- background: var(--accent);
- color: var(--accent-contrast);
-}
-
-.wizard-btn-primary:hover {
- background: var(--accent-hover);
-}
-
-.wizard-btn-secondary {
- background: var(--surface-2);
- color: var(--text-secondary);
- border: 1px solid var(--border-subtle);
-}
-
-.wizard-btn-secondary:hover {
- background: var(--surface-3);
- color: var(--text-primary);
-}
-
-.wizard-btn-secondary.hidden {
- visibility: hidden;
-}
-
-@media (max-width: 480px) {
- .wizard-theme-grid {
- grid-template-columns: repeat(2, 1fr);
- }
-
- .wizard-card {
- padding: var(--spacing-xl) var(--spacing-md) var(--spacing-md);
- }
-}
+@import 'css/01-base.css';
+@import 'css/02-sidebar.css';
+@import 'css/03-main.css';
+@import 'css/04-panels.css';
+@import 'css/05-overlays.css';
+@import 'css/06-responsive.css';
+@import 'css/07-queue.css';
+@import 'css/08-wizard.css';
diff --git a/src/renderer/renderer-globals.d.ts b/src/renderer/renderer-globals.d.ts
new file mode 100644
index 0000000..9e03728
--- /dev/null
+++ b/src/renderer/renderer-globals.d.ts
@@ -0,0 +1,179 @@
+// Ambient global declarations for the renderer (rosiEngine.ts).
+// rosiEngine runs as a plain browser script (no ES module import/export),
+// so the window.api / window.rosiModules contracts are declared here rather
+// than imported. The authoritative RendererApi shape lives in src/types.ts and
+// is guarded by src/tests/preload.test.ts.
+
+interface RosiIpcError {
+ code: string;
+ message: string;
+ details?: string;
+}
+type RosiIpcResult = { ok: true; data: T } | { ok: false; error: RosiIpcError };
+
+interface RosiUpdaterStatusEvent {
+ status: 'checking' | 'available' | 'not-available' | 'error' | 'cancelled' | 'downloaded';
+ version?: string;
+ releaseNotes?: unknown;
+ isBeta?: boolean;
+ message?: string;
+}
+interface RosiUpdaterProgressEvent {
+ percent: number;
+ bytesPerSecond: number;
+ transferred: number;
+ total: number;
+}
+interface RosiDownloadStats {
+ totalDownloads: number;
+ successfulDownloads: number;
+ failedDownloads: number;
+ cancelledDownloads: number;
+ totalBytesDownloaded: number;
+ formatCounts: Record;
+ firstDownloadAt: number | null;
+ lastDownloadAt: number | null;
+}
+
+interface RosiRendererApi {
+ restartApp: () => Promise;
+ getChannel: () => 'github' | 'msstore';
+ getFormats: (url: string) => Promise>;
+ getVideoInfo: (url: string) => Promise>;
+ cancelVideoInfo: () => void;
+ selectDownloadLocation: () => Promise;
+ getSettings: () => Promise;
+ saveSettings: (settings: Record) => Promise>;
+ resetSettings: () => void;
+ openExternal: (url: string) => Promise>;
+ downloadVideo: (options: Record) => Promise>;
+ cancelDownload: () => void;
+ cancelFormats: () => void;
+ getAppVersion: () => Promise;
+ checkDenoInstalled: () => Promise;
+ installDeno: () => Promise<{
+ success?: boolean;
+ cancelled?: boolean;
+ output?: string;
+ error?: string;
+ }>;
+ detectGpu: () => Promise<{ nvidia: boolean; amd: boolean; intel: boolean }>;
+ isPackaged: () => Promise;
+ checkForUpdates: () => Promise<{ error: string; message?: string } | null>;
+ downloadUpdate: () => Promise<{ success?: boolean; cancelled?: boolean; error?: string }>;
+ cancelUpdateDownload: () => void;
+ installUpdate: () => void;
+ onUpdaterStatus: (callback: (data: RosiUpdaterStatusEvent) => void) => () => void;
+ onUpdaterProgress: (callback: (data: RosiUpdaterProgressEvent) => void) => () => void;
+ onDownloadProgress: (callback: (data: Record) => void) => () => void;
+ onProgress: (callback: (message: string) => void) => () => void;
+ onComplete: (callback: (message: string) => void) => () => void;
+ openFileLocation: (filePath: string) => Promise>;
+ showNotification: (options: {
+ title?: string;
+ body?: string;
+ filePath?: string;
+ }) => Promise>;
+ exportSettings: () => Promise>;
+ importSettings: () => Promise>;
+ getStats: () => Promise;
+ resetStats: () => Promise>;
+ logError: (message: string) => void;
+ notifySettingsFlushed: () => void;
+ addToQueue: (urls: string[]) => Promise>;
+ removeFromQueue: (id: string) => Promise>;
+ clearQueue: () => Promise>;
+ getQueue: () => Promise;
+ startQueue: () => Promise>;
+ cancelQueue: () => Promise>;
+ onPrepareForClose: (callback: () => void | Promise) => () => void;
+ onQueueUpdate: (callback: (queue: RosiQueueItem[]) => void) => () => void;
+}
+
+interface RosiUiModule {
+ appendConsoleOutput: (outputEl: HTMLElement | null, text: string) => void;
+ closeSidebar: () => void;
+ getModifierKey: () => 'metaKey' | 'ctrlKey';
+ getModifierKeyName: () => 'Cmd' | 'Ctrl';
+ isMac: () => boolean;
+ isValidUrl: (value: string) => boolean;
+ setButtonLoading: (
+ button: HTMLButtonElement | null,
+ isLoading: boolean,
+ onCancel?: (() => void) | null
+ ) => void;
+ showToast: (
+ message: unknown,
+ options?: { type?: 'warning' | 'error' | 'success' | 'info'; duration?: number }
+ ) => void;
+ toggleAdvancedUI: (show: boolean) => void;
+ toggleSidebar: () => void;
+ updateConsoleVisibility: (show: boolean) => void;
+}
+
+interface RosiParsedProgress {
+ percent: number;
+ totalSize: string;
+ speed: string | null;
+ eta: string | null;
+}
+
+interface RosiDownloadsModule {
+ formatBytes: (bytes: number) => string;
+ parseYtdlpProgress: (message: string) => RosiParsedProgress | null;
+}
+
+interface RosiQueueItem {
+ id: string;
+ status: 'pending' | 'downloading' | 'completed' | 'failed' | 'cancelled';
+ url: string;
+}
+
+interface RosiQueueModule {
+ renderQueue: (
+ queue: RosiQueueItem[],
+ elements: {
+ queueList: HTMLElement | null;
+ queueSection: HTMLElement | null;
+ queueCount: HTMLElement | null;
+ },
+ deps: {
+ escapeHtml: (value: string) => string;
+ removeFromQueue: (id: string) => Promise | unknown;
+ }
+ ) => void;
+ resolveQueueSectionElement: (root?: Document) => HTMLElement | null;
+}
+
+interface RosiSettingsModule {
+ bindExternalLink: (
+ element: HTMLElement | null,
+ url: string,
+ openExternal: (url: string) => unknown
+ ) => void;
+}
+
+interface RosiUpdatesModule {
+ formatUpdateProgressInfo: (
+ data: { bytesPerSecond: number; transferred: number; total: number; percent: number },
+ formatBytes: (bytes: number) => string
+ ) => string;
+ isPrereleaseVersion: (version: string) => boolean;
+}
+
+interface RosiModules {
+ ui?: RosiUiModule;
+ downloads?: RosiDownloadsModule;
+ queue?: RosiQueueModule;
+ settings?: RosiSettingsModule;
+ updates?: RosiUpdatesModule;
+}
+
+interface Window {
+ api: RosiRendererApi;
+ rosiModules?: RosiModules;
+}
+
+interface HTMLButtonElement {
+ _originalClick?: HTMLButtonElement['onclick'];
+}
diff --git a/src/renderer/rosiEngine.js b/src/renderer/rosiEngine.ts
similarity index 71%
rename from src/renderer/rosiEngine.js
rename to src/renderer/rosiEngine.ts
index 76121d2..bd849ab 100644
--- a/src/renderer/rosiEngine.js
+++ b/src/renderer/rosiEngine.ts
@@ -1,4 +1,4 @@
-function logError(context, error) {
+function logError(context: string, error?: unknown) {
const msg = error instanceof Error ? error.message : String(error || '');
const text = msg ? `${context}: ${msg}` : context;
if (window.api && typeof window.api.logError === 'function') {
@@ -6,6 +6,37 @@ function logError(context, error) {
}
}
+interface RosiSettings {
+ settingsVersion: number;
+ theme: 'system' | 'light' | 'dark' | 'purple';
+ showConsoleOutput: boolean;
+ consoleCollapsed: boolean;
+ advancedOptions: boolean;
+ audioOnly: boolean;
+ audioFormat: string;
+ convertEnabled: boolean;
+ convertFormat: string;
+ keepOriginalAfterConvert: boolean;
+ firstLaunch: boolean;
+ hookBrowser: boolean;
+ browserChoice: string;
+ animateBackground: boolean;
+ notifications: boolean;
+ denoReminderDismissed: boolean;
+ gpuAcceleration: boolean;
+ gpuType: 'auto' | 'nvidia' | 'amd' | 'intel';
+ bestQuality: boolean;
+ ffmpegPath: string;
+ hideSupportModal: boolean;
+ checkUpdatesOnStartup: boolean;
+ updateChannel: 'auto' | 'stable' | 'beta';
+ writeSubtitles: boolean;
+ subtitleLangs: string;
+ embedThumbnail: boolean;
+ embedMetadata: boolean;
+ sponsorblockRemove: boolean;
+}
+
const rosiModules = window.rosiModules || {};
const uiModule = rosiModules.ui || null;
const downloadsModule = rosiModules.downloads || null;
@@ -34,7 +65,7 @@ function getModifierKeyName() {
return isMac() ? 'Cmd' : 'Ctrl';
}
-function isValidUrl(string) {
+function isValidUrl(string: string) {
if (uiModule && typeof uiModule.isValidUrl === 'function') {
return uiModule.isValidUrl(string);
}
@@ -46,12 +77,14 @@ function isValidUrl(string) {
}
}
-let systemThemeMediaQuery = null;
-let systemThemeMediaQueryHandler = null;
-let appliedTheme = 'dark';
-let themePreference = 'system';
+type ThemeName = 'system' | 'light' | 'dark' | 'purple';
-function resolveAppliedTheme(preference) {
+let systemThemeMediaQuery: MediaQueryList | null = null;
+let systemThemeMediaQueryHandler: (() => void) | null = null;
+let appliedTheme: ThemeName = 'dark';
+let themePreference: ThemeName = 'system';
+
+function resolveAppliedTheme(preference: ThemeName): ThemeName {
if (preference === 'light' || preference === 'dark' || preference === 'purple') {
return preference;
}
@@ -63,9 +96,9 @@ function resolveAppliedTheme(preference) {
return query && query.matches ? 'dark' : 'light';
}
-function syncLicensesTheme(theme) {
+function syncLicensesTheme(theme: ThemeName) {
try {
- const frame = document.getElementById('licenses-frame');
+ const frame = document.getElementById('licenses-frame') as HTMLIFrameElement | null;
const root = frame?.contentDocument?.documentElement;
if (root) {
root.dataset.theme = theme;
@@ -113,10 +146,10 @@ function ensureSystemThemeListener() {
}
}
-function applyTheme(preference) {
+function applyTheme(preference: string) {
themePreference =
preference === 'light' || preference === 'dark' || preference === 'purple'
- ? preference
+ ? (preference as ThemeName)
: 'system';
ensureSystemThemeListener();
appliedTheme = resolveAppliedTheme(themePreference);
@@ -125,7 +158,7 @@ function applyTheme(preference) {
return appliedTheme;
}
-function updateConsoleVisibility(show) {
+function updateConsoleVisibility(show: boolean) {
if (uiModule && typeof uiModule.updateConsoleVisibility === 'function') {
uiModule.updateConsoleVisibility(show);
return;
@@ -137,19 +170,24 @@ function updateConsoleVisibility(show) {
document.body.classList.toggle('console-visible', !!show);
}
-function showToast(message, { type = 'info', duration = 4000 } = {}) {
+type ToastType = 'warning' | 'error' | 'success' | 'info';
+
+function showToast(
+ message: unknown,
+ { type = 'info', duration = 4000 }: { type?: ToastType; duration?: number } = {}
+) {
if (uiModule && typeof uiModule.showToast === 'function') {
uiModule.showToast(message, { type, duration });
}
}
-function appendConsoleOutput(outputEl, text) {
+function appendConsoleOutput(outputEl: HTMLElement | null, text: string) {
if (uiModule && typeof uiModule.appendConsoleOutput === 'function') {
uiModule.appendConsoleOutput(outputEl, text);
}
}
-function setConsoleCollapsed(collapsed) {
+function setConsoleCollapsed(collapsed: boolean) {
const consoleSection = document.getElementById('console-section');
const consoleHeader = document.getElementById('consoleHeader');
const output = document.getElementById('output');
@@ -162,7 +200,7 @@ function setConsoleCollapsed(collapsed) {
}
// Toggle console collapsed state
-function toggleConsoleCollapse(forceCollapsed) {
+function toggleConsoleCollapse(forceCollapsed?: boolean) {
const consoleSection = document.getElementById('console-section');
if (!consoleSection) return false;
if (typeof forceCollapsed === 'boolean') {
@@ -171,7 +209,11 @@ function toggleConsoleCollapse(forceCollapsed) {
return setConsoleCollapsed(!consoleSection.classList.contains('collapsed'));
}
-function setButtonLoading(button, isLoading, onCancel) {
+function setButtonLoading(
+ button: HTMLButtonElement | null,
+ isLoading: boolean,
+ onCancel?: (() => void) | null
+) {
if (uiModule && typeof uiModule.setButtonLoading === 'function') {
uiModule.setButtonLoading(button, isLoading, onCancel);
}
@@ -188,25 +230,37 @@ function closeSidebar() {
uiModule.closeSidebar();
}
}
-function toggleAdvancedUI(show) {
+function toggleAdvancedUI(show: boolean) {
if (uiModule && typeof uiModule.toggleAdvancedUI === 'function') {
uiModule.toggleAdvancedUI(show);
}
}
// Modal queue system
-const modalQueue = [];
+interface ModalButton {
+ label: string;
+ action?: () => void;
+}
+interface ModalData {
+ title: string;
+ message: unknown;
+ buttons?: ModalButton[];
+ priority?: boolean;
+ extra?: (() => Node | null) | Node | null;
+}
+
+const modalQueue: ModalData[] = [];
let isModalActive = false;
-let currentModalData = null;
-let previousFocus = null;
-let modalTrapHandler = null;
-let modalFocusinHandler = null;
-let licensesFocusinHandler = null;
+let currentModalData: ModalData | null = null;
+let previousFocus: Element | null = null;
+let modalTrapHandler: ((e: KeyboardEvent) => void) | null = null;
+let modalFocusinHandler: ((e: FocusEvent) => void) | null = null;
+let licensesFocusinHandler: ((e: FocusEvent) => void) | null = null;
-function getFocusableElements(container) {
+function getFocusableElements(container: unknown): HTMLElement[] {
if (!(container instanceof HTMLElement)) return [];
return Array.from(
- container.querySelectorAll(
+ container.querySelectorAll(
'button, input, select, textarea, a[href], [tabindex]:not([tabindex="-1"])'
)
).filter(
@@ -218,7 +272,7 @@ function getFocusableElements(container) {
);
}
-function focusFirstElement(container) {
+function focusFirstElement(container: unknown) {
const focusable = getFocusableElements(container);
const first = focusable[0];
if (first && typeof first.focus === 'function') {
@@ -228,8 +282,8 @@ function focusFirstElement(container) {
return false;
}
-function showModal({ title, message, buttons = [], priority = false, extra = null }) {
- const modalData = { title, message, buttons, priority, extra };
+function showModal({ title, message, buttons = [], priority = false, extra = null }: ModalData) {
+ const modalData: ModalData = { title, message, buttons, priority, extra };
if (priority && isModalActive) {
const modal = document.getElementById('app-modal');
if (modal) {
@@ -256,8 +310,12 @@ function displayNextModal() {
}
isModalActive = true;
- currentModalData = modalQueue.shift();
- const { title, message, buttons, extra } = currentModalData;
+ currentModalData = modalQueue.shift() ?? null;
+ if (!currentModalData) {
+ isModalActive = false;
+ return;
+ }
+ const { title, message, buttons = [], extra } = currentModalData;
const modal = document.getElementById('app-modal');
const titleEl = document.getElementById('modal-title');
@@ -278,7 +336,7 @@ function displayNextModal() {
if (extra) {
const extraNode = typeof extra === 'function' ? extra() : extra;
if (extraNode && extraNode.nodeType) {
- extraEl.appendChild(extraNode);
+ extraEl.appendChild(extraNode as Node);
}
}
}
@@ -310,7 +368,7 @@ function displayNextModal() {
if (modalFocusinHandler) {
document.removeEventListener('focusin', modalFocusinHandler, true);
}
- modalTrapHandler = (e) => {
+ modalTrapHandler = (e: KeyboardEvent) => {
if (e.key === 'Escape') {
e.preventDefault();
e.stopPropagation();
@@ -326,17 +384,17 @@ function displayNextModal() {
if (e.shiftKey) {
if (!active || active === first) {
e.preventDefault();
- last.focus();
+ last?.focus();
}
} else {
if (!active || active === last) {
e.preventDefault();
- first.focus();
+ first?.focus();
}
}
};
modal.addEventListener('keydown', modalTrapHandler);
- modalFocusinHandler = (e) => {
+ modalFocusinHandler = (e: FocusEvent) => {
if (!isModalActive || !modal.classList.contains('active')) return;
const target = e.target;
if (target instanceof Node && modal.contains(target)) return;
@@ -353,7 +411,7 @@ function displayNextModal() {
});
}
-function hideModal(modal, action) {
+function hideModal(modal: HTMLElement, action: (() => void) | null | undefined) {
modal.classList.add('hiding');
currentModalData = null;
if (modalTrapHandler) {
@@ -371,7 +429,7 @@ function hideModal(modal, action) {
if (!isModalActive) {
displayNextModal();
}
- if (!isModalActive && previousFocus && typeof previousFocus.focus === 'function') {
+ if (!isModalActive && previousFocus instanceof HTMLElement) {
previousFocus.focus();
previousFocus = null;
}
@@ -388,10 +446,10 @@ function showKeyboardShortcuts() {
}
let isFetchingFormats = false;
-let fetchFormatsAbort = null;
+let fetchFormatsAbort: (() => void) | null = null;
async function fetchFormats() {
- const btn = document.getElementById('fetchFormatsBtn');
- const urlInput = document.getElementById('url');
+ const btn = document.getElementById('fetchFormatsBtn') as HTMLButtonElement | null;
+ const urlInput = document.getElementById('url') as HTMLInputElement | null;
const videoUrl = urlInput ? urlInput.value : null;
try {
@@ -426,10 +484,10 @@ async function fetchFormats() {
if (window.api.cancelFormats) {
window.api.cancelFormats();
}
- fetchFormatsAbort();
+ fetchFormatsAbort?.();
});
- const videoSelect = document.getElementById('videoFormat');
- const audioSelect = document.getElementById('audioFormat');
+ const videoSelect = document.getElementById('videoFormat') as HTMLSelectElement | null;
+ const audioSelect = document.getElementById('audioFormat') as HTMLSelectElement | null;
if (videoSelect) videoSelect.innerHTML = 'Loading... ';
if (audioSelect) audioSelect.innerHTML = 'Loading... ';
try {
@@ -456,38 +514,43 @@ async function fetchFormats() {
if (audioSelect) audioSelect.innerHTML = 'Select Audio Format ';
let videoFormatsFound = 0,
audioFormatsFound = 0;
+ const FORMAT_ID = /^([A-Za-z0-9][A-Za-z0-9._-]{0,63})\s+\S/;
lines.forEach((line) => {
- if (/^\s*\d+\s+[a-zA-Z0-9]+/.test(line.trim())) {
- const parts = line.trim().split(/\s+/);
- const formatId = parts[0];
- const option = document.createElement('option');
- option.value = formatId;
- let labelText = line.trim();
- const resolutionMatch = labelText.match(/(\d{3,4}x\d{3,4}|\d{3,4}p)/);
- const fpsMatch = labelText.match(/@\s*(\d+fps)/);
- const sizeMatch = labelText.match(/(\d+(\.\d+)?(MiB|GiB|KiB))/);
- const codecMatch = line.match(/(avc1|vp9|av01|h264|h265|opus|mp4a|aac|vorbis)/i);
- let cleanLabel = `ID: ${formatId}`;
- if (resolutionMatch) cleanLabel += ` ${resolutionMatch[0]}`;
- if (fpsMatch) cleanLabel += ` ${fpsMatch[1]}`;
- if (codecMatch) cleanLabel += ` (${codecMatch[0]})`;
- if (sizeMatch) cleanLabel += ` ~${sizeMatch[0]}`;
- option.text = cleanLabel;
- option.title = line.trim();
- const isVideo = /video/.test(line.toLowerCase()) && !/audio only/i.test(line);
- const isAudio = /audio/.test(line.toLowerCase()) && !/video only/i.test(line);
- const isVideoOnly = /video only/i.test(line);
- const isAudioOnly = /audio only/i.test(line);
- if (isVideoOnly || (isVideo && !isAudio)) {
- if (videoSelect) videoSelect.appendChild(option);
- videoFormatsFound++;
- } else if (isAudioOnly || (isAudio && !isVideo)) {
- if (audioSelect) audioSelect.appendChild(option);
- audioFormatsFound++;
- } else if (isVideo && isAudio) {
- if (videoSelect) videoSelect.appendChild(option);
- videoFormatsFound++;
- }
+ const trimmed = line.trim();
+ const idMatch = trimmed.match(FORMAT_ID);
+ if (!idMatch) return;
+ const formatId = idMatch[1];
+ if (!formatId || formatId.toLowerCase() === 'id') return;
+ if (/storyboard|images? only|mhtml/i.test(line)) return;
+ const option = document.createElement('option');
+ option.value = formatId;
+ const labelText = trimmed;
+ const resolutionMatch = labelText.match(/(\d{3,4}x\d{3,4}|\d{3,4}p)/);
+ const fpsMatch = labelText.match(/@?\s*(\d+)\s*fps/i);
+ const sizeMatch = labelText.match(/(\d+(\.\d+)?(MiB|GiB|KiB))/);
+ const codecMatch = line.match(
+ /(avc1|vp9|vp09|av01|h264|h265|hevc|opus|mp4a|aac|vorbis|flac)/i
+ );
+ let cleanLabel = `ID: ${formatId}`;
+ if (resolutionMatch) cleanLabel += ` ${resolutionMatch[0]}`;
+ if (fpsMatch) cleanLabel += ` ${fpsMatch[1]}fps`;
+ if (codecMatch) cleanLabel += ` (${codecMatch[0]})`;
+ if (sizeMatch) cleanLabel += ` ~${sizeMatch[0]}`;
+ option.text = cleanLabel;
+ option.title = trimmed;
+ const isVideoOnly = /video only/i.test(line);
+ const isAudioOnly = /audio only/i.test(line);
+ const isVideo = /video/.test(line.toLowerCase()) && !isAudioOnly;
+ const isAudio = /audio/.test(line.toLowerCase()) && !isVideoOnly;
+ if (isVideoOnly || (isVideo && !isAudio)) {
+ if (videoSelect) videoSelect.appendChild(option);
+ videoFormatsFound++;
+ } else if (isAudioOnly || (isAudio && !isVideo)) {
+ if (audioSelect) audioSelect.appendChild(option);
+ audioFormatsFound++;
+ } else if (isVideo && isAudio) {
+ if (videoSelect) videoSelect.appendChild(option);
+ videoFormatsFound++;
}
});
if (videoFormatsFound === 0 && videoSelect)
@@ -495,7 +558,7 @@ async function fetchFormats() {
if (audioFormatsFound === 0 && audioSelect)
audioSelect.innerHTML = 'No audio formats found ';
} catch (e) {
- const errorMessage = typeof e === 'string' ? e : e.message || 'Unknown error';
+ const errorMessage = typeof e === 'string' ? e : (e as Error)?.message || 'Unknown error';
if (videoSelect) videoSelect.innerHTML = 'Error loading formats ';
if (audioSelect) audioSelect.innerHTML = 'Error loading formats ';
showModal({
@@ -521,18 +584,105 @@ async function fetchFormats() {
}
}
+function formatDuration(totalSeconds: number | null | undefined) {
+ if (typeof totalSeconds !== 'number' || !Number.isFinite(totalSeconds) || totalSeconds <= 0) {
+ return '';
+ }
+ const seconds = Math.floor(totalSeconds % 60);
+ const minutes = Math.floor((totalSeconds / 60) % 60);
+ const hours = Math.floor(totalSeconds / 3600);
+ const pad = (n: number) => String(n).padStart(2, '0');
+ return hours > 0 ? `${hours}:${pad(minutes)}:${pad(seconds)}` : `${minutes}:${pad(seconds)}`;
+}
+
+function formatViewCount(count: number | null | undefined) {
+ if (typeof count !== 'number' || !Number.isFinite(count) || count < 0) return '';
+ if (count >= 1_000_000) return `${(count / 1_000_000).toFixed(1)}M views`;
+ if (count >= 1_000) return `${(count / 1_000).toFixed(1)}K views`;
+ return `${count} views`;
+}
+
+interface RosiVideoInfo {
+ title: string;
+ uploader: string | null;
+ durationSeconds: number | null;
+ thumbnail: string | null;
+ ext: string | null;
+ viewCount: number | null;
+ isPlaylist: boolean;
+ playlistCount: number | null;
+ webpageUrl: string | null;
+}
+
+let isFetchingPreview = false;
+let previewAbort: (() => void) | null = null;
+
+function hideVideoPreview() {
+ const card = document.getElementById('preview-card');
+ const thumb = document.getElementById('preview-thumb') as HTMLImageElement | null;
+ if (card) card.classList.remove('visible', 'loading', 'is-playlist');
+ if (thumb) {
+ thumb.removeAttribute('src');
+ thumb.alt = '';
+ }
+}
+
+function renderVideoPreview(info: RosiVideoInfo) {
+ const card = document.getElementById('preview-card');
+ const thumb = document.getElementById('preview-thumb') as HTMLImageElement | null;
+ const titleEl = document.getElementById('preview-title');
+ const subEl = document.getElementById('preview-sub');
+ const durationEl = document.getElementById('preview-duration');
+ if (!card || !titleEl || !subEl) return;
+
+ card.classList.remove('loading');
+ card.classList.add('visible');
+ card.classList.toggle('is-playlist', !!info.isPlaylist);
+
+ titleEl.textContent = info.title || 'Untitled';
+
+ const subParts: string[] = [];
+ if (info.uploader) subParts.push(info.uploader);
+ if (info.isPlaylist && info.playlistCount) {
+ subParts.push(`${info.playlistCount} items`);
+ } else {
+ const views = formatViewCount(info.viewCount);
+ if (views) subParts.push(views);
+ }
+ subEl.textContent = subParts.join(' β’ ');
+
+ if (durationEl) {
+ const duration = formatDuration(info.durationSeconds);
+ durationEl.textContent = duration;
+ durationEl.style.display = duration ? 'inline-block' : 'none';
+ }
+
+ if (thumb) {
+ const wrap = thumb.parentElement as HTMLElement | null;
+ if (info.thumbnail) {
+ thumb.src = info.thumbnail;
+ thumb.alt = info.title || 'Video thumbnail';
+ if (wrap) wrap.style.display = '';
+ } else {
+ thumb.removeAttribute('src');
+ thumb.alt = '';
+ if (wrap) wrap.style.display = 'none';
+ }
+ }
+}
+
// handles download button logic
let isDownloading = false;
-let downloadAbort = null;
-let lastDownloadedFilePath = null;
+let downloadAbort: (() => void) | null = null;
+let lastDownloadedFilePath: string | null = null;
-function setProgressPhase(phase) {
- const phases = document.querySelectorAll('.progress-phase');
+function setProgressPhase(phase: string) {
+ const phases = document.querySelectorAll('.progress-phase');
const phaseOrder = ['download', 'merge', 'convert'];
const phaseIndex = phaseOrder.indexOf(phase);
phases.forEach((el) => {
- const elPhase = el.dataset.phase;
+ const elPhase = el.dataset.phase ?? '';
const elIndex = phaseOrder.indexOf(elPhase);
el.classList.remove('active', 'completed');
if (elIndex < phaseIndex) {
@@ -543,10 +693,10 @@ function setProgressPhase(phase) {
});
}
-function configureProgressPhases(showMerge, showConvert) {
- const mergePhase = document.querySelector('.progress-phase[data-phase="merge"]');
- const convertPhase = document.querySelector('.progress-phase[data-phase="convert"]');
- const connectors = document.querySelectorAll('.progress-phase-connector');
+function configureProgressPhases(showMerge: boolean, showConvert: boolean) {
+ const mergePhase = document.querySelector('.progress-phase[data-phase="merge"]');
+ const convertPhase = document.querySelector('.progress-phase[data-phase="convert"]');
+ const connectors = document.querySelectorAll('.progress-phase-connector');
if (mergePhase) mergePhase.style.display = showMerge ? 'flex' : 'none';
if (convertPhase) convertPhase.style.display = showConvert ? 'flex' : 'none';
@@ -571,7 +721,7 @@ function showProgressBar(status = 'Downloading...') {
const container = document.getElementById('progress-container');
const statusEl = document.getElementById('progress-status');
const percentEl = document.getElementById('progress-percent');
- const bar = document.getElementById('progress-bar');
+ const bar = document.getElementById('progress-bar') as HTMLElement | null;
const details = document.getElementById('progress-details');
if (container) {
@@ -589,10 +739,14 @@ function showProgressBar(status = 'Downloading...') {
setProgressPhase('download');
}
-function updateProgressBar(percent, statusText = null, detailsText = null) {
+function updateProgressBar(
+ percent: number,
+ statusText: string | null = null,
+ detailsText: string | null = null
+) {
const statusEl = document.getElementById('progress-status');
const percentEl = document.getElementById('progress-percent');
- const bar = document.getElementById('progress-bar');
+ const bar = document.getElementById('progress-bar') as HTMLElement | null;
const details = document.getElementById('progress-details');
const clamped = Math.max(0, Math.min(100, percent));
@@ -627,14 +781,14 @@ function hideProgressBar() {
hideProgressComplete();
}
-function parseYtdlpProgress(message) {
+function parseYtdlpProgress(message: string) {
if (downloadsModule && typeof downloadsModule.parseYtdlpProgress === 'function') {
return downloadsModule.parseYtdlpProgress(message);
}
return null;
}
-function formatBytes(bytes) {
+function formatBytes(bytes: number) {
if (downloadsModule && typeof downloadsModule.formatBytes === 'function') {
return downloadsModule.formatBytes(bytes);
}
@@ -644,7 +798,14 @@ function formatBytes(bytes) {
const HISTORY_KEY = 'rosi-download-history';
const HISTORY_MAX = 20;
-function loadHistory() {
+interface HistoryEntry {
+ filename: string;
+ path: string | null;
+ timestamp: number;
+ status: 'success' | 'failed' | 'cancelled';
+}
+
+function loadHistory(): HistoryEntry[] {
try {
const data = localStorage.getItem(HISTORY_KEY);
return data ? JSON.parse(data) : [];
@@ -653,11 +814,14 @@ function loadHistory() {
}
}
-function saveHistory(history) {
+function saveHistory(history: HistoryEntry[]) {
try {
localStorage.setItem(HISTORY_KEY, JSON.stringify(history));
} catch (e) {
- if (e instanceof DOMException && (e.name === 'QuotaExceededError' || e.code === 22)) {
+ if (
+ e instanceof DOMException &&
+ (e.name === 'QuotaExceededError' || (e as DOMException).code === 22)
+ ) {
history.length = Math.max(1, Math.floor(history.length / 2));
try {
localStorage.setItem(HISTORY_KEY, JSON.stringify(history));
@@ -668,7 +832,11 @@ function saveHistory(history) {
}
}
-function addHistoryEntry(entry) {
+function addHistoryEntry(entry: {
+ filename: string;
+ path: string | null;
+ status: HistoryEntry['status'];
+}) {
const history = loadHistory();
history.unshift({
filename: entry.filename,
@@ -681,7 +849,7 @@ function addHistoryEntry(entry) {
renderHistory();
}
-function formatRelativeTime(timestamp) {
+function formatRelativeTime(timestamp: number) {
const diff = Date.now() - timestamp;
const seconds = Math.floor(diff / 1000);
if (seconds < 60) return 'Just now';
@@ -694,7 +862,7 @@ function formatRelativeTime(timestamp) {
return new Date(timestamp).toLocaleDateString();
}
-function escapeHtml(str) {
+function escapeHtml(str: string) {
const div = document.createElement('div');
div.textContent = str;
return div.innerHTML;
@@ -746,7 +914,7 @@ function renderHistory() {
if (openBtn) {
openBtn.addEventListener('click', (e) => {
e.stopPropagation();
- window.api.openFileLocation(entry.path);
+ if (entry.path) void window.api.openFileLocation(entry.path);
});
}
}
@@ -833,11 +1001,11 @@ async function checkForUpdates() {
}
}
-let updaterCleanupFunctions = [];
+let updaterCleanupFunctions: Array<() => void> = [];
function showUpdateBanner() {
const banner = document.getElementById('update-banner');
- const bar = document.getElementById('update-banner-bar');
+ const bar = document.getElementById('update-banner-bar') as HTMLElement | null;
const info = document.getElementById('update-banner-info');
const text = document.getElementById('update-banner-text');
if (bar) bar.style.width = '0%';
@@ -874,14 +1042,15 @@ function setupAutoUpdater() {
break;
case 'available': {
- updateVersion = data.version;
+ const version = data.version ?? '';
+ updateVersion = version;
const wasManualCheck = isManualUpdateCheck;
isManualUpdateCheck = false;
const isBetaUpdate =
data.isBeta ||
(updatesModule && typeof updatesModule.isPrereleaseVersion === 'function'
- ? updatesModule.isPrereleaseVersion(data.version)
- : /-(beta|alpha|rc)/i.test(data.version));
+ ? updatesModule.isPrereleaseVersion(version)
+ : /-(beta|alpha|rc)/i.test(version));
showModal({
title: isBetaUpdate ? 'Beta Update Available!' : 'Update Available!',
message: isBetaUpdate
@@ -958,7 +1127,7 @@ function setupAutoUpdater() {
updaterCleanupFunctions.push(
window.api.onUpdaterProgress((data) => {
- const progressBar = document.getElementById('update-banner-bar');
+ const progressBar = document.getElementById('update-banner-bar') as HTMLElement | null;
const progressInfo = document.getElementById('update-banner-info');
if (progressBar) {
@@ -992,8 +1161,8 @@ function cleanupUpdaterListeners() {
updaterCleanupFunctions = [];
}
-let licensesPreviousFocus = null;
-let licensesTrapHandler = null;
+let licensesPreviousFocus: Element | null = null;
+let licensesTrapHandler: ((e: KeyboardEvent) => void) | null = null;
function showLicenses() {
const licensesOverlay = document.getElementById('licenses-overlay');
@@ -1004,7 +1173,7 @@ function showLicenses() {
document.body.classList.add('licenses-open');
document.body.style.overflow = 'hidden';
- const closeBtn = licensesOverlay.querySelector('#close-licenses');
+ const closeBtn = licensesOverlay.querySelector('#close-licenses');
licensesOverlay.setAttribute('tabindex', '-1');
requestAnimationFrame(() => {
if (closeBtn) {
@@ -1016,7 +1185,7 @@ function showLicenses() {
}
});
- licensesTrapHandler = (e) => {
+ licensesTrapHandler = (e: KeyboardEvent) => {
if (e.key === 'Escape') {
e.preventDefault();
e.stopPropagation();
@@ -1032,18 +1201,18 @@ function showLicenses() {
if (e.shiftKey) {
if (!active || active === first) {
e.preventDefault();
- last.focus();
+ last?.focus();
}
} else if (!active || active === last) {
e.preventDefault();
- first.focus();
+ first?.focus();
}
};
licensesOverlay.addEventListener('keydown', licensesTrapHandler);
if (licensesFocusinHandler) {
document.removeEventListener('focusin', licensesFocusinHandler, true);
}
- licensesFocusinHandler = (e) => {
+ licensesFocusinHandler = (e: FocusEvent) => {
if (!licensesOverlay.classList.contains('active')) return;
const target = e.target;
if (target instanceof Node && licensesOverlay.contains(target)) return;
@@ -1071,14 +1240,14 @@ function hideLicenses() {
document.body.style.overflow = '';
document.body.classList.remove('licenses-open');
}, 300);
- if (licensesPreviousFocus) {
+ if (licensesPreviousFocus instanceof HTMLElement) {
licensesPreviousFocus.focus();
licensesPreviousFocus = null;
}
}
}
-function updateBackgroundAnimation(animate) {
+function updateBackgroundAnimation(animate: boolean) {
const body = document.body;
if (animate) {
body.classList.add('animate-bg');
@@ -1088,7 +1257,7 @@ function updateBackgroundAnimation(animate) {
}
// check for Deno
-async function checkDenoInstallation(settings) {
+async function checkDenoInstallation(settings: RosiSettings, persist: () => void) {
if (settings.denoReminderDismissed) {
return;
}
@@ -1134,9 +1303,12 @@ async function checkDenoInstallation(settings) {
priority: true,
});
} catch (error) {
+ const errMsg =
+ (error as { error?: string })?.error ||
+ (error instanceof Error ? error.message : 'Unknown error');
showModal({
title: 'Installation Failed',
- message: `Failed to install Deno automatically.\n\nPlease install manually:\nMac/Linux: curl -fsSL https://deno.land/install.sh | sh\nWindows: irm https://deno.land/install.ps1 | iex\n\nError: ${error.error || 'Unknown error'}`,
+ message: `Failed to install Deno automatically.\n\nPlease install manually:\nMac/Linux: curl -fsSL https://deno.land/install.sh | sh\nWindows: irm https://deno.land/install.ps1 | iex\n\nError: ${errMsg}`,
buttons: [
{
label: 'Open Deno Website',
@@ -1154,7 +1326,7 @@ async function checkDenoInstallation(settings) {
label: "No, don't remind me",
action: () => {
settings.denoReminderDismissed = true;
- void persistSettings();
+ persist();
},
},
],
@@ -1165,16 +1337,23 @@ async function checkDenoInstallation(settings) {
}
}
-function launchSetupWizard(settings, applyThemeFn, persistSettingsFn, onComplete) {
+function launchSetupWizard(
+ settings: RosiSettings,
+ applyThemeFn: (preference: string) => void,
+ persistSettingsFn: (silent?: boolean, immediate?: boolean) => Promise | void,
+ onComplete: () => void
+) {
const TOTAL_STEPS = 4;
let currentStep = 0;
const overlay = document.getElementById('setup-wizard');
- const progressBar = document.getElementById('wizard-progress-bar');
+ const progressBar = document.getElementById('wizard-progress-bar') as HTMLElement | null;
const backBtn = document.getElementById('wizard-back');
const nextBtn = document.getElementById('wizard-next');
const dotsContainer = document.getElementById('wizard-dots');
- const steps = overlay ? overlay.querySelectorAll('.wizard-step') : [];
+ const steps = overlay
+ ? overlay.querySelectorAll('.wizard-step')
+ : ([] as unknown as NodeListOf);
if (!overlay || !progressBar || !backBtn || !nextBtn || !dotsContainer || steps.length === 0) {
settings.firstLaunch = false;
@@ -1183,16 +1362,22 @@ function launchSetupWizard(settings, applyThemeFn, persistSettingsFn, onComplete
return;
}
+ const overlayEl = overlay;
+ const progressBarEl = progressBar;
+ const backBtnEl = backBtn;
+ const nextBtnEl = nextBtn;
+ const dotsContainerEl = dotsContainer;
+
// Build dots
- dotsContainer.innerHTML = '';
+ dotsContainerEl.innerHTML = '';
for (let i = 0; i < TOTAL_STEPS; i++) {
const dot = document.createElement('span');
dot.className = 'wizard-dot' + (i === 0 ? ' active' : '');
- dotsContainer.appendChild(dot);
+ dotsContainerEl.appendChild(dot);
}
// Live theme preview
- const themeRadios = overlay.querySelectorAll('input[name="wizard-theme"]');
+ const themeRadios = overlayEl.querySelectorAll('input[name="wizard-theme"]');
themeRadios.forEach((radio) => {
radio.addEventListener('change', () => {
applyThemeFn(radio.value);
@@ -1206,40 +1391,44 @@ function launchSetupWizard(settings, applyThemeFn, persistSettingsFn, onComplete
});
// Progress bar
- progressBar.style.width = ((currentStep + 1) / TOTAL_STEPS) * 100 + '%';
+ progressBarEl.style.width = ((currentStep + 1) / TOTAL_STEPS) * 100 + '%';
// Dots
- const dots = dotsContainer.querySelectorAll('.wizard-dot');
+ const dots = dotsContainerEl.querySelectorAll('.wizard-dot');
dots.forEach((dot, i) => {
dot.classList.toggle('active', i === currentStep);
});
// Back button
- backBtn.classList.toggle('hidden', currentStep === 0);
+ backBtnEl.classList.toggle('hidden', currentStep === 0);
// Next button text
if (currentStep === 0) {
- nextBtn.textContent = 'Get Started';
+ nextBtnEl.textContent = 'Get Started';
} else if (currentStep === TOTAL_STEPS - 1) {
- nextBtn.textContent = 'Finish';
+ nextBtnEl.textContent = 'Finish';
} else {
- nextBtn.textContent = 'Next';
+ nextBtnEl.textContent = 'Next';
}
}
function gatherSettings() {
// Theme
- const selectedTheme = overlay.querySelector('input[name="wizard-theme"]:checked');
+ const selectedTheme = overlayEl.querySelector(
+ 'input[name="wizard-theme"]:checked'
+ );
if (selectedTheme) {
- settings.theme = selectedTheme.value;
+ settings.theme = selectedTheme.value as RosiSettings['theme'];
applyThemeFn(settings.theme);
}
// Download prefs
- const bestQuality = document.getElementById('wizard-best-quality');
- const audioOnly = document.getElementById('wizard-audio-only');
- const notifications = document.getElementById('wizard-notifications');
- const autoUpdates = document.getElementById('wizard-auto-updates');
+ const bestQuality = document.getElementById('wizard-best-quality') as HTMLInputElement | null;
+ const audioOnly = document.getElementById('wizard-audio-only') as HTMLInputElement | null;
+ const notifications = document.getElementById(
+ 'wizard-notifications'
+ ) as HTMLInputElement | null;
+ const autoUpdates = document.getElementById('wizard-auto-updates') as HTMLInputElement | null;
if (bestQuality) settings.bestQuality = bestQuality.checked;
if (audioOnly) settings.audioOnly = audioOnly.checked;
@@ -1251,18 +1440,24 @@ function launchSetupWizard(settings, applyThemeFn, persistSettingsFn, onComplete
gatherSettings();
settings.firstLaunch = false;
void persistSettingsFn(false, true);
- overlay.classList.remove('active');
+ overlayEl.classList.remove('active');
// Sync sidebar controls to reflect wizard choices
- const themeSelect = document.getElementById('themeSelect');
+ const themeSelect = document.getElementById('themeSelect') as HTMLSelectElement | null;
if (themeSelect) themeSelect.value = settings.theme || 'system';
- const bestQualityToggle = document.getElementById('bestQualityToggle');
+ const bestQualityToggle = document.getElementById(
+ 'bestQualityToggle'
+ ) as HTMLInputElement | null;
if (bestQualityToggle) bestQualityToggle.checked = settings.bestQuality;
- const audioOnlyToggle = document.getElementById('audioOnlyToggle');
+ const audioOnlyToggle = document.getElementById('audioOnlyToggle') as HTMLInputElement | null;
if (audioOnlyToggle) audioOnlyToggle.checked = settings.audioOnly;
- const notificationsToggle = document.getElementById('notificationsToggle');
+ const notificationsToggle = document.getElementById(
+ 'notificationsToggle'
+ ) as HTMLInputElement | null;
if (notificationsToggle) notificationsToggle.checked = settings.notifications;
- const checkUpdatesToggle = document.getElementById('checkUpdatesOnStartupToggle');
+ const checkUpdatesToggle = document.getElementById(
+ 'checkUpdatesOnStartupToggle'
+ ) as HTMLInputElement | null;
if (checkUpdatesToggle) checkUpdatesToggle.checked = settings.checkUpdatesOnStartup;
onComplete();
@@ -1277,7 +1472,7 @@ function launchSetupWizard(settings, applyThemeFn, persistSettingsFn, onComplete
}
});
- backBtn.addEventListener('click', () => {
+ backBtnEl.addEventListener('click', () => {
if (currentStep > 0) {
currentStep--;
updateUI();
@@ -1286,25 +1481,27 @@ function launchSetupWizard(settings, applyThemeFn, persistSettingsFn, onComplete
// Set initial selected theme radio to match current settings
const initialTheme = settings.theme || 'system';
- const matchingRadio = overlay.querySelector(
+ const matchingRadio = overlayEl.querySelector(
`input[name="wizard-theme"][value="${initialTheme}"]`
);
if (matchingRadio) matchingRadio.checked = true;
updateUI();
- overlay.classList.add('active');
+ overlayEl.classList.add('active');
}
document.addEventListener('DOMContentLoaded', async () => {
- let settings;
+ let settings: RosiSettings;
try {
- settings = await window.api.getSettings();
+ settings = (await window.api.getSettings()) as RosiSettings;
} catch (error) {
+ logError('Failed to load settings', error);
settings = {
- settingsVersion: 2,
+ settingsVersion: 3,
theme: 'system',
showConsoleOutput: false,
advancedOptions: false,
+ audioFormat: 'mp3',
convertEnabled: false,
convertFormat: 'mp4',
keepOriginalAfterConvert: true,
@@ -1316,12 +1513,18 @@ document.addEventListener('DOMContentLoaded', async () => {
denoReminderDismissed: false,
gpuAcceleration: false,
gpuType: 'auto',
+ bestQuality: false,
ffmpegPath: '',
hideSupportModal: false,
checkUpdatesOnStartup: true,
updateChannel: 'auto',
audioOnly: false,
consoleCollapsed: false,
+ writeSubtitles: false,
+ subtitleLangs: 'en',
+ embedThumbnail: false,
+ embedMetadata: false,
+ sponsorblockRemove: false,
};
showModal({
title: 'Settings Error',
@@ -1339,7 +1542,9 @@ document.addEventListener('DOMContentLoaded', async () => {
versionLink.textContent = `v${version}`;
versionLink.addEventListener('click', (event) => {
event.preventDefault();
- window.api.openExternal(`https://github.com/BurntToasters/ROSI/releases/tag/v${version}`);
+ void window.api.openExternal(
+ `https://github.com/BurntToasters/ROSI/releases/tag/v${version}`
+ );
});
const isBeta =
updatesModule && typeof updatesModule.isPrereleaseVersion === 'function'
@@ -1363,7 +1568,7 @@ document.addEventListener('DOMContentLoaded', async () => {
}
let settingsSaveErrorShownAt = 0;
- function showSettingsSaveError(message) {
+ function showSettingsSaveError(message: string) {
const now = Date.now();
if (now - settingsSaveErrorShownAt < 5000) {
return;
@@ -1377,13 +1582,15 @@ document.addEventListener('DOMContentLoaded', async () => {
});
}
- let persistDebounceTimer = null;
+ let persistDebounceTimer: ReturnType | null = null;
- async function persistSettings(silent = false, immediate = false) {
+ async function persistSettings(silent = false, immediate = false): Promise {
if (persistDebounceTimer) clearTimeout(persistDebounceTimer);
- const executeSave = async (resolve) => {
+ const executeSave = async (resolve: (value: boolean) => void) => {
try {
- const result = await window.api.saveSettings(settings);
+ const result = await window.api.saveSettings(
+ settings as unknown as Parameters[0]
+ );
if (!result || result.ok !== true) {
if (!silent) {
const message = result?.error?.message || 'Could not save settings.';
@@ -1392,7 +1599,7 @@ document.addEventListener('DOMContentLoaded', async () => {
resolve(false);
return;
}
- settings = result.data;
+ settings = result.data as RosiSettings;
resolve(true);
} catch (_error) {
if (!silent) {
@@ -1401,7 +1608,7 @@ document.addEventListener('DOMContentLoaded', async () => {
resolve(false);
}
};
- return new Promise((resolve) => {
+ return new Promise((resolve) => {
if (immediate) {
persistDebounceTimer = null;
void executeSave(resolve);
@@ -1414,74 +1621,86 @@ document.addEventListener('DOMContentLoaded', async () => {
});
}
- const consoleToggle = document.getElementById('consoleToggle');
- const advancedToggle = document.getElementById('advancedToggle');
- const keepOriginalToggle = document.getElementById('keepOriginalToggle');
- const hookBrowserToggle = document.getElementById('hookBrowserToggle');
- const browserChoiceContainer = document.getElementById('browserChoiceContainer');
- const browserChoiceSelect = document.getElementById('browserChoice');
- const convertToggle = document.getElementById('convertToggle');
- const convertFormatContainer = document.getElementById('convertFormatContainer');
- const convertFormatSelect = document.getElementById('convertFormat');
- const keepOriginalLabel = document.getElementById('keepOriginalLabel');
- const gpuAccelerationToggle = document.getElementById('gpuAccelerationToggle');
- const gpuAccelerationLabel = document.getElementById('gpuAccelerationLabel');
- const gpuTypeContainer = document.getElementById('gpuTypeContainer');
- const gpuTypeSelect = document.getElementById('gpuType');
- const ffmpegPathInput = document.getElementById('ffmpegPathInput');
- const outputEl = document.getElementById('output');
- const resetSettingsBtn = document.getElementById('resetSettings');
- const fetchFormatsBtn = document.getElementById('fetchFormatsBtn');
- const downloadBtn = document.getElementById('downloadBtn');
- const checkUpdateBtn = document.getElementById('checkUpdateBtn');
- const animateBackgroundToggle = document.getElementById('animateBackgroundToggle');
- const themeSelect = document.getElementById('themeSelect');
- const bestQualityToggle = document.getElementById('bestQualityToggle');
- const audioOnlyToggle = document.getElementById('audioOnlyToggle');
- const audioFormatContainer = document.getElementById('audioFormatContainer');
- const audioFormatSelect = document.getElementById('audioFormatSelect');
- const notificationsToggle = document.getElementById('notificationsToggle');
- const checkUpdatesOnStartupToggle = document.getElementById('checkUpdatesOnStartupToggle');
- const checkUpdatesOnStartupLabel = document.getElementById('checkUpdatesOnStartupLabel');
- const updateChannelSelect = document.getElementById('updateChannelSelect');
- const updateChannelContainer = document.getElementById('updateChannelContainer');
- const showUpdateChannelBtn = document.getElementById('showUpdateChannelBtn');
-
- const settingsBtn = document.getElementById('settingsBtn');
- const closeSidebarBtn = document.getElementById('closeSidebar');
- const sidebarOverlay = document.getElementById('sidebar-overlay');
- const shortcutsBtn = document.getElementById('shortcutsBtn');
- const clearUrlBtn = document.getElementById('clearUrl');
- const pasteUrlBtn = document.getElementById('pasteUrl');
- const clearConsoleBtn = document.getElementById('clearConsole');
- const urlInput = document.getElementById('url');
- const urlValidationMessage = document.getElementById('urlValidationMessage');
- const urlInputContainer = document.querySelector('.url-input-container');
- const downloadCard = document.querySelector('.download-card');
- const historyHeader = document.getElementById('historyHeader');
- const clearHistoryBtn = document.getElementById('clearHistory');
- const browserCookiesHelp = document.getElementById('browserCookiesHelp');
- const helpLink = document.getElementById('helpLink');
- const supportLink = document.getElementById('supportLink');
- const websiteLink = document.getElementById('websiteLink');
- const supportProjectLink = document.getElementById('supportProjectLink');
- const licensesLink = document.getElementById('licensesLink');
- const licensesFrame = document.getElementById('licenses-frame');
- const exportSettingsBtn = document.getElementById('exportSettingsBtn');
- const importSettingsBtn = document.getElementById('importSettingsBtn');
- const viewStatsBtn = document.getElementById('viewStatsBtn');
- const queueUrlInput = document.getElementById('queueUrlInput');
- const addToQueueBtn = document.getElementById('addToQueueBtn');
- const startQueueBtn = document.getElementById('startQueueBtn');
- const clearQueueBtn = document.getElementById('clearQueueBtn');
- const cancelQueueBtn = document.getElementById('cancelQueueBtn');
- const queueStatusMessage = document.getElementById('queueStatusMessage');
- const queueList = document.getElementById('queueList');
- const queueCount = document.getElementById('queueCount');
+ const byId = (id: string) =>
+ document.getElementById(id) as T | null;
+
+ const consoleToggle = byId('consoleToggle');
+ const advancedToggle = byId('advancedToggle');
+ const keepOriginalToggle = byId('keepOriginalToggle');
+ const hookBrowserToggle = byId('hookBrowserToggle');
+ const browserChoiceContainer = byId('browserChoiceContainer');
+ const browserChoiceSelect = byId('browserChoice');
+ const convertToggle = byId('convertToggle');
+ const convertFormatContainer = byId('convertFormatContainer');
+ const convertFormatSelect = byId('convertFormat');
+ const keepOriginalLabel = byId('keepOriginalLabel');
+ const gpuAccelerationToggle = byId('gpuAccelerationToggle');
+ const gpuAccelerationLabel = byId('gpuAccelerationLabel');
+ const gpuTypeContainer = byId('gpuTypeContainer');
+ const gpuTypeSelect = byId('gpuType');
+ const ffmpegPathInput = byId('ffmpegPathInput');
+ const outputEl = byId('output');
+ const resetSettingsBtn = byId('resetSettings');
+ const fetchFormatsBtn = byId('fetchFormatsBtn');
+ const downloadBtn = byId('downloadBtn');
+ const checkUpdateBtn = byId('checkUpdateBtn');
+ const animateBackgroundToggle = byId('animateBackgroundToggle');
+ const themeSelect = byId('themeSelect');
+ const bestQualityToggle = byId('bestQualityToggle');
+ const audioOnlyToggle = byId('audioOnlyToggle');
+ const audioFormatContainer = byId('audioFormatContainer');
+ const audioFormatSelect = byId('audioFormatSelect');
+ const notificationsToggle = byId('notificationsToggle');
+ const checkUpdatesOnStartupToggle = byId('checkUpdatesOnStartupToggle');
+ const checkUpdatesOnStartupLabel = byId('checkUpdatesOnStartupLabel');
+ const updateChannelSelect = byId('updateChannelSelect');
+ const updateChannelContainer = byId('updateChannelContainer');
+ const showUpdateChannelBtn = byId('showUpdateChannelBtn');
+
+ const settingsBtn = byId('settingsBtn');
+ const closeSidebarBtn = byId('closeSidebar');
+ const sidebarOverlay = byId('sidebar-overlay');
+ const shortcutsBtn = byId('shortcutsBtn');
+ const clearUrlBtn = byId('clearUrl');
+ const pasteUrlBtn = byId('pasteUrl');
+ const clearConsoleBtn = byId('clearConsole');
+ const urlInput = byId('url');
+ const urlValidationMessage = byId('urlValidationMessage');
+ const urlInputContainer = document.querySelector('.url-input-container');
+ const downloadCard = document.querySelector('.download-card');
+ const previewBtn = byId('previewBtn');
+ const previewCloseBtn = byId('previewClose');
+ const historyHeader = byId('historyHeader');
+ const clearHistoryBtn = byId('clearHistory');
+ const browserCookiesHelp = byId('browserCookiesHelp');
+ const helpLink = byId('helpLink');
+ const supportLink = byId('supportLink');
+ const websiteLink = byId('websiteLink');
+ const supportProjectLink = byId('supportProjectLink');
+ const licensesLink = byId('licensesLink');
+ const licensesFrame = byId('licenses-frame');
+ const exportSettingsBtn = byId('exportSettingsBtn');
+ const importSettingsBtn = byId('importSettingsBtn');
+ const viewStatsBtn = byId('viewStatsBtn');
+ const embedMetadataToggle = byId('embedMetadataToggle');
+ const embedThumbnailToggle = byId('embedThumbnailToggle');
+ const sponsorblockToggle = byId('sponsorblockToggle');
+ const sponsorblockHelp = byId('sponsorblockHelp');
+ const writeSubtitlesToggle = byId('writeSubtitlesToggle');
+ const subtitleLangsContainer = byId('subtitleLangsContainer');
+ const subtitleLangsInput = byId('subtitleLangsInput');
+ const queueUrlInput = byId('queueUrlInput');
+ const addToQueueBtn = byId('addToQueueBtn');
+ const startQueueBtn = byId('startQueueBtn');
+ const clearQueueBtn = byId('clearQueueBtn');
+ const cancelQueueBtn = byId('cancelQueueBtn');
+ const queueStatusMessage = byId('queueStatusMessage');
+ const queueList = byId('queueList');
+ const queueCount = byId('queueCount');
const queueSection =
(queueModule && typeof queueModule.resolveQueueSectionElement === 'function'
? queueModule.resolveQueueSectionElement(document)
- : null) || document.getElementById('queueSection');
+ : null) || byId('queueSection');
if (queueStatusMessage) {
queueStatusMessage.setAttribute('role', 'status');
@@ -1504,6 +1723,8 @@ document.addEventListener('DOMContentLoaded', async () => {
settings.browserChoice = 'Firefox';
void persistSettings();
}
+ const browserWindowsHint = document.getElementById('browserWindowsHint');
+ if (browserWindowsHint) browserWindowsHint.classList.remove('hidden');
}
// update UI from settings
@@ -1594,24 +1815,24 @@ document.addEventListener('DOMContentLoaded', async () => {
(settings.advancedOptions ?? false) || (settings.audioOnly ?? false);
bestQualityToggle.disabled = bestQualityDisabled;
if (bestQualityDisabled) {
- bestQualityToggle.parentElement.classList.add('disabled');
- bestQualityToggle.parentElement.title = settings.audioOnly
+ bestQualityToggle.parentElement!.classList.add('disabled');
+ bestQualityToggle.parentElement!.title = settings.audioOnly
? 'Disabled when Audio-only mode is enabled'
: 'Disabled when Advanced format selection is enabled';
} else {
- bestQualityToggle.parentElement.classList.remove('disabled');
- bestQualityToggle.parentElement.title = '';
+ bestQualityToggle.parentElement!.classList.remove('disabled');
+ bestQualityToggle.parentElement!.title = '';
}
}
if (audioOnlyToggle) {
audioOnlyToggle.checked = settings.audioOnly ?? false;
audioOnlyToggle.disabled = settings.advancedOptions ?? false;
if (audioOnlyToggle.disabled) {
- audioOnlyToggle.parentElement.classList.add('disabled');
- audioOnlyToggle.parentElement.title = 'Disabled when Advanced format selection is enabled';
+ audioOnlyToggle.parentElement!.classList.add('disabled');
+ audioOnlyToggle.parentElement!.title = 'Disabled when Advanced format selection is enabled';
} else {
- audioOnlyToggle.parentElement.classList.remove('disabled');
- audioOnlyToggle.parentElement.title = '';
+ audioOnlyToggle.parentElement!.classList.remove('disabled');
+ audioOnlyToggle.parentElement!.title = '';
}
}
if (audioFormatSelect) {
@@ -1628,17 +1849,36 @@ document.addEventListener('DOMContentLoaded', async () => {
if (convertToggle) {
convertToggle.disabled = settings.audioOnly ?? false;
if (settings.audioOnly) {
- convertToggle.parentElement.classList.add('disabled');
- convertToggle.parentElement.title = 'Disabled when Audio-only mode is enabled';
+ convertToggle.parentElement!.classList.add('disabled');
+ convertToggle.parentElement!.title = 'Disabled when Audio-only mode is enabled';
} else {
- convertToggle.parentElement.classList.remove('disabled');
- convertToggle.parentElement.title = '';
+ convertToggle.parentElement!.classList.remove('disabled');
+ convertToggle.parentElement!.title = '';
}
}
if (notificationsToggle) {
notificationsToggle.checked = settings.notifications ?? true;
}
+ if (embedMetadataToggle) {
+ embedMetadataToggle.checked = settings.embedMetadata ?? false;
+ }
+ if (embedThumbnailToggle) {
+ embedThumbnailToggle.checked = settings.embedThumbnail ?? false;
+ }
+ if (sponsorblockToggle) {
+ sponsorblockToggle.checked = settings.sponsorblockRemove ?? false;
+ }
+ if (writeSubtitlesToggle) {
+ writeSubtitlesToggle.checked = settings.writeSubtitles ?? false;
+ }
+ if (subtitleLangsInput) {
+ subtitleLangsInput.value = settings.subtitleLangs ?? 'en';
+ }
+ if (subtitleLangsContainer) {
+ subtitleLangsContainer.classList.toggle('visible', !!settings.writeSubtitles);
+ }
+
const channel = window.api.getChannel();
if (checkUpdatesOnStartupToggle) {
checkUpdatesOnStartupToggle.checked = settings.checkUpdatesOnStartup ?? true;
@@ -1662,7 +1902,12 @@ document.addEventListener('DOMContentLoaded', async () => {
logError('Failed to update UI from settings', e);
}
- if (!settings.hideSupportModal && !settings.firstLaunch) {
+ function maybeShowSupportModal() {
+ if (settings.hideSupportModal || settings.firstLaunch) return;
+ if (isModalActive || modalQueue.length > 0) {
+ setTimeout(maybeShowSupportModal, 1500);
+ return;
+ }
showModal({
title: 'Support This Project?',
message:
@@ -1671,7 +1916,7 @@ document.addEventListener('DOMContentLoaded', async () => {
{
label: 'β€οΈ Yes Support!',
action: () => {
- window.api.openExternal('https://rosie.run/support');
+ void window.api.openExternal('https://rosie.run/support');
settings.hideSupportModal = true;
void persistSettings();
},
@@ -1693,7 +1938,7 @@ document.addEventListener('DOMContentLoaded', async () => {
if (sidebarOverlay) sidebarOverlay.addEventListener('click', closeSidebar);
if (shortcutsBtn) shortcutsBtn.addEventListener('click', showKeyboardShortcuts);
- const bindExternalLink = (element, url) => {
+ const bindExternalLink = (element: HTMLElement | null, url: string) => {
if (settingsModule && typeof settingsModule.bindExternalLink === 'function') {
settingsModule.bindExternalLink(element, url, window.api.openExternal);
return;
@@ -1701,7 +1946,7 @@ document.addEventListener('DOMContentLoaded', async () => {
if (element) {
element.addEventListener('click', (event) => {
event.preventDefault();
- window.api.openExternal(url);
+ void window.api.openExternal(url);
});
}
};
@@ -1725,10 +1970,11 @@ document.addEventListener('DOMContentLoaded', async () => {
}
let hasUrlValidationIntent = false;
+ let lastPreviewUrl: string | null = null;
function syncPrimaryActionState() {
const hasInput = !!urlInput;
const hasPrimaryButton = !!downloadBtn;
- if (!hasInput || !hasPrimaryButton) return;
+ if (!hasInput || !hasPrimaryButton || !urlInput || !downloadBtn) return;
const raw = urlInput.value || '';
const trimmed = raw.trim();
const hasValue = trimmed.length > 0;
@@ -1760,10 +2006,18 @@ document.addEventListener('DOMContentLoaded', async () => {
downloadBtn.disabled = !validUrl;
downloadBtn.classList.toggle('is-disabled', !validUrl);
}
+
+ if (previewBtn && !previewBtn.classList.contains('loading')) {
+ previewBtn.disabled = !validUrl;
+ }
+ if (trimmed !== lastPreviewUrl) {
+ hideVideoPreview();
+ lastPreviewUrl = null;
+ }
}
function updateUrlButtons() {
- const hasValue = urlInput && urlInput.value.length > 0;
+ const hasValue = !!(urlInput && urlInput.value.length > 0);
if (clearUrlBtn) clearUrlBtn.classList.toggle('hidden', !hasValue);
if (pasteUrlBtn) pasteUrlBtn.classList.toggle('hidden', hasValue);
syncPrimaryActionState();
@@ -1815,10 +2069,12 @@ document.addEventListener('DOMContentLoaded', async () => {
downloadCard.classList.remove('drag-over');
});
downloadCard.addEventListener('drop', (e) => {
- e.preventDefault();
+ const dragEvent = e as DragEvent;
+ dragEvent.preventDefault();
downloadCard.classList.remove('drag-over');
- const text = e.dataTransfer.getData('text/uri-list') || e.dataTransfer.getData('text/plain');
- if (text && isValidUrl(text.trim())) {
+ const dt = dragEvent.dataTransfer;
+ const text = dt ? dt.getData('text/uri-list') || dt.getData('text/plain') : '';
+ if (text && isValidUrl(text.trim()) && urlInput) {
urlInput.value = text.trim();
urlInput.dispatchEvent(new Event('input'));
hasUrlValidationIntent = true;
@@ -1829,12 +2085,75 @@ document.addEventListener('DOMContentLoaded', async () => {
});
}
+ async function runVideoPreview() {
+ if (!urlInput || !previewBtn) return;
+ const url = urlInput.value.trim();
+ if (!url || !isValidUrl(url)) {
+ showToast('Enter a valid URL first.', { type: 'warning' });
+ return;
+ }
+ if (isFetchingPreview) return;
+ isFetchingPreview = true;
+
+ const card = document.getElementById('preview-card');
+ if (card) card.classList.add('visible', 'loading');
+
+ let wasCancelled = false;
+ previewAbort = () => {
+ wasCancelled = true;
+ isFetchingPreview = false;
+ setButtonLoading(previewBtn, false);
+ };
+ setButtonLoading(previewBtn, true, () => {
+ if (window.api.cancelVideoInfo) window.api.cancelVideoInfo();
+ previewAbort?.();
+ hideVideoPreview();
+ });
+
+ try {
+ const result = await window.api.getVideoInfo(url);
+ if (wasCancelled) return;
+ if (!result || result.ok !== true) {
+ const message = result?.error?.message || 'Could not load preview.';
+ if (typeof message === 'string' && message.toLowerCase().includes('cancel')) return;
+ hideVideoPreview();
+ showToast(`Could not load preview. ${message}`, { type: 'error' });
+ return;
+ }
+ lastPreviewUrl = url;
+ renderVideoPreview(result.data as RosiVideoInfo);
+ } catch (e) {
+ if (!wasCancelled) {
+ hideVideoPreview();
+ showToast('Could not load preview.', { type: 'error' });
+ logError('Video preview failed', e);
+ }
+ } finally {
+ if (!wasCancelled) {
+ isFetchingPreview = false;
+ setButtonLoading(previewBtn, false);
+ }
+ }
+ }
+
+ if (previewBtn) {
+ previewBtn._originalClick = runVideoPreview;
+ previewBtn.onclick = runVideoPreview;
+ }
+ if (previewCloseBtn) {
+ previewCloseBtn.addEventListener('click', () => {
+ if (window.api.cancelVideoInfo) window.api.cancelVideoInfo();
+ hideVideoPreview();
+ lastPreviewUrl = null;
+ });
+ }
+
renderHistory();
if (historyHeader) {
const historySection = document.getElementById('download-history');
const historyList = document.getElementById('history-list');
- const setHistoryCollapsed = (collapsed) => {
+ const setHistoryCollapsed = (collapsed: boolean) => {
if (!historySection) return false;
historySection.classList.toggle('collapsed', !!collapsed);
const isCollapsed = historySection.classList.contains('collapsed');
@@ -1851,11 +2170,11 @@ document.addEventListener('DOMContentLoaded', async () => {
}
historyHeader.addEventListener('click', (e) => {
- if (e.target.closest('.history-clear-btn')) return;
+ if ((e.target as HTMLElement).closest('.history-clear-btn')) return;
toggleHistoryCollapsed();
});
historyHeader.addEventListener('keydown', (e) => {
- if (e.target.closest('.history-clear-btn')) return;
+ if ((e.target as HTMLElement).closest('.history-clear-btn')) return;
if (e.key === 'Enter' || e.key === ' ') {
e.preventDefault();
toggleHistoryCollapsed();
@@ -1895,10 +2214,10 @@ document.addEventListener('DOMContentLoaded', async () => {
});
}
- const settingsHeaders = Array.from(document.querySelectorAll('.settings-section-header')).filter(
- (header) => header instanceof HTMLElement
- );
- const setSettingsSectionCollapsed = (header, collapsed) => {
+ const settingsHeaders = Array.from(
+ document.querySelectorAll('.settings-section-header')
+ ).filter((header) => header instanceof HTMLElement);
+ const setSettingsSectionCollapsed = (header: HTMLElement, collapsed: boolean) => {
if (!(header instanceof HTMLElement)) return false;
const section = header.closest('.settings-section');
if (!(section instanceof HTMLElement)) return false;
@@ -1971,24 +2290,24 @@ document.addEventListener('DOMContentLoaded', async () => {
if (e.key === 'ArrowDown') {
e.preventDefault();
const nextIndex = (currentIndex + 1) % settingsHeaders.length;
- settingsHeaders[nextIndex].focus();
+ settingsHeaders[nextIndex]?.focus();
} else if (e.key === 'ArrowUp') {
e.preventDefault();
const nextIndex = (currentIndex - 1 + settingsHeaders.length) % settingsHeaders.length;
- settingsHeaders[nextIndex].focus();
+ settingsHeaders[nextIndex]?.focus();
} else if (e.key === 'Home') {
e.preventDefault();
- settingsHeaders[0].focus();
+ settingsHeaders[0]?.focus();
} else if (e.key === 'End') {
e.preventDefault();
- settingsHeaders[settingsHeaders.length - 1].focus();
+ settingsHeaders[settingsHeaders.length - 1]?.focus();
}
});
});
if (consoleToggle)
consoleToggle.addEventListener('change', (e) => {
- settings.showConsoleOutput = e.target.checked;
+ settings.showConsoleOutput = (e.target as HTMLInputElement).checked;
void persistSettings();
updateConsoleVisibility(settings.showConsoleOutput);
});
@@ -1997,7 +2316,7 @@ document.addEventListener('DOMContentLoaded', async () => {
const consoleHeader = document.getElementById('consoleHeader');
if (consoleHeader) {
consoleHeader.addEventListener('click', (e) => {
- if (e.target.closest('#clearConsole')) return;
+ if ((e.target as HTMLElement).closest('#clearConsole')) return;
const isCollapsed = toggleConsoleCollapse();
settings.consoleCollapsed = isCollapsed;
void persistSettings();
@@ -2005,7 +2324,7 @@ document.addEventListener('DOMContentLoaded', async () => {
consoleHeader.addEventListener('keydown', (e) => {
if (e.key === 'Enter' || e.key === ' ') {
e.preventDefault();
- if (e.target.closest('#clearConsole')) return;
+ if ((e.target as HTMLElement).closest('#clearConsole')) return;
const isCollapsed = toggleConsoleCollapse();
settings.consoleCollapsed = isCollapsed;
void persistSettings();
@@ -2023,40 +2342,40 @@ document.addEventListener('DOMContentLoaded', async () => {
if (advancedToggle)
advancedToggle.addEventListener('change', (e) => {
- settings.advancedOptions = e.target.checked;
- toggleAdvancedUI(e.target.checked);
+ settings.advancedOptions = (e.target as HTMLInputElement).checked;
+ toggleAdvancedUI((e.target as HTMLInputElement).checked);
if (bestQualityToggle) {
- bestQualityToggle.disabled = e.target.checked;
- if (e.target.checked) {
+ bestQualityToggle.disabled = (e.target as HTMLInputElement).checked;
+ if ((e.target as HTMLInputElement).checked) {
bestQualityToggle.checked = false;
settings.bestQuality = false;
- bestQualityToggle.parentElement.classList.add('disabled');
- bestQualityToggle.parentElement.title =
+ bestQualityToggle.parentElement!.classList.add('disabled');
+ bestQualityToggle.parentElement!.title =
'Disabled when Advanced format selection is enabled';
} else {
- bestQualityToggle.parentElement.classList.remove('disabled');
- bestQualityToggle.parentElement.title = '';
+ bestQualityToggle.parentElement!.classList.remove('disabled');
+ bestQualityToggle.parentElement!.title = '';
}
}
if (audioOnlyToggle) {
- audioOnlyToggle.disabled = e.target.checked;
- if (e.target.checked) {
+ audioOnlyToggle.disabled = (e.target as HTMLInputElement).checked;
+ if ((e.target as HTMLInputElement).checked) {
audioOnlyToggle.checked = false;
settings.audioOnly = false;
- audioOnlyToggle.parentElement.classList.add('disabled');
- audioOnlyToggle.parentElement.title =
+ audioOnlyToggle.parentElement!.classList.add('disabled');
+ audioOnlyToggle.parentElement!.title =
'Disabled when Advanced format selection is enabled';
if (convertToggle) {
convertToggle.disabled = false;
- convertToggle.parentElement.classList.remove('disabled');
- convertToggle.parentElement.title = '';
+ convertToggle.parentElement!.classList.remove('disabled');
+ convertToggle.parentElement!.title = '';
}
} else {
- audioOnlyToggle.parentElement.classList.remove('disabled');
- audioOnlyToggle.parentElement.title = '';
+ audioOnlyToggle.parentElement!.classList.remove('disabled');
+ audioOnlyToggle.parentElement!.title = '';
}
}
@@ -2064,8 +2383,8 @@ document.addEventListener('DOMContentLoaded', async () => {
});
if (keepOriginalToggle)
keepOriginalToggle.addEventListener('change', (e) => {
- if (!e.target.disabled) {
- settings.keepOriginalAfterConvert = e.target.checked;
+ if (!(e.target as HTMLInputElement).disabled) {
+ settings.keepOriginalAfterConvert = (e.target as HTMLInputElement).checked;
void persistSettings();
} else {
e.preventDefault();
@@ -2073,9 +2392,9 @@ document.addEventListener('DOMContentLoaded', async () => {
});
if (hookBrowserToggle)
hookBrowserToggle.addEventListener('change', (e) => {
- settings.hookBrowser = e.target.checked;
+ settings.hookBrowser = (e.target as HTMLInputElement).checked;
if (browserChoiceContainer) {
- if (e.target.checked) {
+ if ((e.target as HTMLInputElement).checked) {
browserChoiceContainer.classList.add('visible');
} else {
browserChoiceContainer.classList.remove('visible');
@@ -2085,23 +2404,23 @@ document.addEventListener('DOMContentLoaded', async () => {
});
if (browserChoiceSelect)
browserChoiceSelect.addEventListener('change', (e) => {
- settings.browserChoice = e.target.value;
+ settings.browserChoice = (e.target as HTMLInputElement | HTMLSelectElement).value;
void persistSettings();
});
if (convertToggle)
convertToggle.addEventListener('change', (e) => {
- settings.convertEnabled = e.target.checked;
- if (e.target.checked) {
- convertFormatContainer.classList.add('visible');
- keepOriginalLabel.classList.add('visible');
+ settings.convertEnabled = (e.target as HTMLInputElement).checked;
+ if ((e.target as HTMLInputElement).checked) {
+ convertFormatContainer?.classList.add('visible');
+ keepOriginalLabel?.classList.add('visible');
if (gpuAccelerationLabel) gpuAccelerationLabel.classList.add('visible');
} else {
- convertFormatContainer.classList.remove('visible');
- keepOriginalLabel.classList.remove('visible');
+ convertFormatContainer?.classList.remove('visible');
+ keepOriginalLabel?.classList.remove('visible');
if (gpuAccelerationLabel) gpuAccelerationLabel.classList.remove('visible');
if (gpuTypeContainer) gpuTypeContainer.classList.remove('visible');
}
- if (!e.target.checked) {
+ if (!(e.target as HTMLInputElement).checked) {
settings.keepOriginalAfterConvert = true;
if (keepOriginalToggle) keepOriginalToggle.checked = true;
}
@@ -2109,24 +2428,24 @@ document.addEventListener('DOMContentLoaded', async () => {
});
if (convertFormatSelect)
convertFormatSelect.addEventListener('change', (e) => {
- settings.convertFormat = e.target.value;
+ settings.convertFormat = (e.target as HTMLInputElement | HTMLSelectElement).value;
void persistSettings();
});
if (ffmpegPathInput) {
ffmpegPathInput.addEventListener('input', (e) => {
- settings.ffmpegPath = e.target.value;
+ settings.ffmpegPath = (e.target as HTMLInputElement | HTMLSelectElement).value;
});
ffmpegPathInput.addEventListener('change', (e) => {
- settings.ffmpegPath = e.target.value;
+ settings.ffmpegPath = (e.target as HTMLInputElement | HTMLSelectElement).value;
void persistSettings();
});
}
// GPU acceleration toggle
if (gpuAccelerationToggle) {
gpuAccelerationToggle.addEventListener('change', (e) => {
- settings.gpuAcceleration = e.target.checked;
+ settings.gpuAcceleration = (e.target as HTMLInputElement).checked;
if (gpuTypeContainer) {
- if (e.target.checked) {
+ if ((e.target as HTMLInputElement).checked) {
gpuTypeContainer.classList.add('visible');
} else {
gpuTypeContainer.classList.remove('visible');
@@ -2137,21 +2456,21 @@ document.addEventListener('DOMContentLoaded', async () => {
}
if (gpuTypeSelect) {
gpuTypeSelect.addEventListener('change', (e) => {
- settings.gpuType = e.target.value;
+ settings.gpuType = (e.target as HTMLSelectElement).value as RosiSettings['gpuType'];
void persistSettings();
});
}
// Animate Background toggle
if (animateBackgroundToggle) {
animateBackgroundToggle.addEventListener('change', (e) => {
- settings.animateBackground = e.target.checked;
- updateBackgroundAnimation(e.target.checked);
+ settings.animateBackground = (e.target as HTMLInputElement).checked;
+ updateBackgroundAnimation((e.target as HTMLInputElement).checked);
void persistSettings();
});
}
if (themeSelect) {
themeSelect.addEventListener('change', (e) => {
- settings.theme = e.target.value;
+ settings.theme = (e.target as HTMLSelectElement).value as RosiSettings['theme'];
applyTheme(settings.theme);
void persistSettings();
});
@@ -2159,7 +2478,7 @@ document.addEventListener('DOMContentLoaded', async () => {
if (bestQualityToggle) {
bestQualityToggle.addEventListener('change', (e) => {
- settings.bestQuality = e.target.checked;
+ settings.bestQuality = (e.target as HTMLInputElement).checked;
void persistSettings();
});
}
@@ -2167,10 +2486,10 @@ document.addEventListener('DOMContentLoaded', async () => {
// Audio-only toggle
if (audioOnlyToggle) {
audioOnlyToggle.addEventListener('change', (e) => {
- settings.audioOnly = e.target.checked;
+ settings.audioOnly = (e.target as HTMLInputElement).checked;
if (audioFormatContainer) {
- if (e.target.checked) {
+ if ((e.target as HTMLInputElement).checked) {
audioFormatContainer.classList.add('visible');
} else {
audioFormatContainer.classList.remove('visible');
@@ -2178,30 +2497,30 @@ document.addEventListener('DOMContentLoaded', async () => {
}
if (bestQualityToggle) {
- bestQualityToggle.disabled = e.target.checked;
- if (e.target.checked) {
+ bestQualityToggle.disabled = (e.target as HTMLInputElement).checked;
+ if ((e.target as HTMLInputElement).checked) {
bestQualityToggle.checked = false;
settings.bestQuality = false;
- bestQualityToggle.parentElement.classList.add('disabled');
- bestQualityToggle.parentElement.title = 'Disabled when Audio-only mode is enabled';
+ bestQualityToggle.parentElement!.classList.add('disabled');
+ bestQualityToggle.parentElement!.title = 'Disabled when Audio-only mode is enabled';
} else {
- bestQualityToggle.parentElement.classList.remove('disabled');
- bestQualityToggle.parentElement.title = '';
+ bestQualityToggle.parentElement!.classList.remove('disabled');
+ bestQualityToggle.parentElement!.title = '';
}
}
if (convertToggle) {
- convertToggle.disabled = e.target.checked;
- if (e.target.checked) {
+ convertToggle.disabled = (e.target as HTMLInputElement).checked;
+ if ((e.target as HTMLInputElement).checked) {
convertToggle.checked = false;
settings.convertEnabled = false;
- convertToggle.parentElement.classList.add('disabled');
- convertToggle.parentElement.title = 'Disabled when Audio-only mode is enabled';
+ convertToggle.parentElement!.classList.add('disabled');
+ convertToggle.parentElement!.title = 'Disabled when Audio-only mode is enabled';
if (convertFormatContainer) convertFormatContainer.classList.remove('visible');
if (keepOriginalLabel) keepOriginalLabel.classList.remove('visible');
} else {
- convertToggle.parentElement.classList.remove('disabled');
- convertToggle.parentElement.title = '';
+ convertToggle.parentElement!.classList.remove('disabled');
+ convertToggle.parentElement!.title = '';
}
}
@@ -2211,7 +2530,7 @@ document.addEventListener('DOMContentLoaded', async () => {
if (audioFormatSelect) {
audioFormatSelect.addEventListener('change', (e) => {
- settings.audioFormat = e.target.value;
+ settings.audioFormat = (e.target as HTMLInputElement | HTMLSelectElement).value;
void persistSettings();
});
}
@@ -2219,15 +2538,64 @@ document.addEventListener('DOMContentLoaded', async () => {
// Notifications toggle
if (notificationsToggle) {
notificationsToggle.addEventListener('change', (e) => {
- settings.notifications = e.target.checked;
+ settings.notifications = (e.target as HTMLInputElement).checked;
+ void persistSettings();
+ });
+ }
+
+ if (embedMetadataToggle) {
+ embedMetadataToggle.addEventListener('change', (e) => {
+ settings.embedMetadata = (e.target as HTMLInputElement).checked;
+ void persistSettings();
+ });
+ }
+
+ if (embedThumbnailToggle) {
+ embedThumbnailToggle.addEventListener('change', (e) => {
+ settings.embedThumbnail = (e.target as HTMLInputElement).checked;
+ void persistSettings();
+ });
+ }
+
+ if (sponsorblockToggle) {
+ sponsorblockToggle.addEventListener('change', (e) => {
+ settings.sponsorblockRemove = (e.target as HTMLInputElement).checked;
+ void persistSettings();
+ });
+ }
+
+ bindExternalLink(sponsorblockHelp, 'https://sponsor.ajay.app/');
+
+ const SUBTITLE_LANGS_RE = /^[A-Za-z0-9.*-]+(,[A-Za-z0-9.*-]+)*$/;
+ if (writeSubtitlesToggle) {
+ writeSubtitlesToggle.addEventListener('change', (e) => {
+ settings.writeSubtitles = (e.target as HTMLInputElement).checked;
+ if (subtitleLangsContainer) {
+ subtitleLangsContainer.classList.toggle('visible', (e.target as HTMLInputElement).checked);
+ }
void persistSettings();
});
}
+ if (subtitleLangsInput) {
+ const commitSubtitleLangs = () => {
+ const raw = subtitleLangsInput.value.trim();
+ if (!raw || !SUBTITLE_LANGS_RE.test(raw) || raw.length > 256) {
+ settings.subtitleLangs = 'en';
+ subtitleLangsInput.value = 'en';
+ } else {
+ settings.subtitleLangs = raw;
+ }
+ void persistSettings();
+ };
+ subtitleLangsInput.addEventListener('change', commitSubtitleLangs);
+ subtitleLangsInput.addEventListener('blur', commitSubtitleLangs);
+ }
+
// Check updates on startup
if (checkUpdatesOnStartupToggle) {
checkUpdatesOnStartupToggle.addEventListener('change', (e) => {
- settings.checkUpdatesOnStartup = e.target.checked;
+ settings.checkUpdatesOnStartup = (e.target as HTMLInputElement).checked;
void persistSettings();
});
}
@@ -2249,7 +2617,8 @@ document.addEventListener('DOMContentLoaded', async () => {
if (updateChannelSelect) {
updateChannelSelect.addEventListener('change', (e) => {
- settings.updateChannel = e.target.value;
+ settings.updateChannel = (e.target as HTMLSelectElement)
+ .value as RosiSettings['updateChannel'];
void persistSettings();
});
}
@@ -2353,14 +2722,14 @@ document.addEventListener('DOMContentLoaded', async () => {
});
}
- function renderQueue(queue) {
+ function renderQueue(queue: RosiQueueItem[]) {
if (queueModule && typeof queueModule.renderQueue === 'function') {
queueModule.renderQueue(
queue,
{ queueList, queueSection, queueCount },
{
escapeHtml,
- removeFromQueue: async (id) => {
+ removeFromQueue: async (id: string) => {
const endQueueAction = beginQueueAction();
try {
const result = await window.api.removeFromQueue(id);
@@ -2386,9 +2755,10 @@ document.addEventListener('DOMContentLoaded', async () => {
}
}
- let queueStatusTimer = null;
- function setQueueStatusMessage(message) {
- if (!queueStatusMessage) return;
+ let queueStatusTimer: ReturnType | null = null;
+ function setQueueStatusMessage(message: unknown) {
+ const statusEl = queueStatusMessage;
+ if (!statusEl) return;
if (queueStatusTimer) {
clearTimeout(queueStatusTimer);
queueStatusTimer = null;
@@ -2396,21 +2766,21 @@ document.addEventListener('DOMContentLoaded', async () => {
const nextMessage =
typeof message === 'string' ? message.trim() : message == null ? '' : String(message).trim();
- queueStatusMessage.textContent = '';
+ statusEl.textContent = '';
if (!nextMessage) return;
queueStatusTimer = setTimeout(() => {
- queueStatusMessage.textContent = nextMessage;
+ statusEl.textContent = nextMessage;
queueStatusTimer = null;
}, 0);
}
- function queueMessageForCount(count, singular, plural) {
+ function queueMessageForCount(count: number, singular: string, plural: string) {
return count === 1 ? singular : plural.replace('{count}', String(count));
}
- function announceQueueAction(message, toastType = 'info') {
+ function announceQueueAction(message: string, toastType: ToastType = 'info') {
setQueueStatusMessage(message);
showToast(message, { type: toastType });
}
@@ -2565,7 +2935,7 @@ document.addEventListener('DOMContentLoaded', async () => {
hasUrlValidationIntent = true;
syncPrimaryActionState();
- const urlInput = document.getElementById('url');
+ const urlInput = document.getElementById('url') as HTMLInputElement | null;
const url = urlInput ? urlInput.value : null;
if (!url || url.trim() === '') {
isDownloading = false;
@@ -2584,8 +2954,8 @@ document.addEventListener('DOMContentLoaded', async () => {
return;
}
- const videoSelect = document.getElementById('videoFormat');
- const audioSelect = document.getElementById('audioFormat');
+ const videoSelect = document.getElementById('videoFormat') as HTMLSelectElement | null;
+ const audioSelect = document.getElementById('audioFormat') as HTMLSelectElement | null;
if (
settings.advancedOptions &&
(!videoSelect || !audioSelect || !videoSelect.value || !audioSelect.value)
@@ -2598,7 +2968,7 @@ document.addEventListener('DOMContentLoaded', async () => {
return;
}
- let savePath;
+ let savePath: string | null = null;
try {
savePath = await window.api.selectDownloadLocation();
} catch (dialogError) {
@@ -2625,18 +2995,18 @@ document.addEventListener('DOMContentLoaded', async () => {
};
setButtonLoading(downloadBtn, true, () => {
window.api.cancelDownload();
- downloadAbort();
+ downloadAbort?.();
hideProgressBar();
});
- const videoFormat = settings.advancedOptions ? videoSelect.value : undefined;
- const audioFormat = settings.advancedOptions ? audioSelect.value : undefined;
- const convertFormat = settings.convertEnabled ? convertFormatSelect.value : undefined;
+ const videoFormat = settings.advancedOptions ? videoSelect?.value : undefined;
+ const audioFormat = settings.advancedOptions ? audioSelect?.value : undefined;
+ const convertFormat = settings.convertEnabled ? convertFormatSelect?.value : undefined;
const needsMerge = settings.bestQuality || (videoFormat && audioFormat);
const needsConvert = settings.convertEnabled && convertFormat;
configureProgressPhases(!!needsMerge, !!needsConvert);
showProgressBar('Starting download...');
- const keepOriginal = settings.convertEnabled ? keepOriginalToggle.checked : undefined;
+ const keepOriginal = settings.convertEnabled ? keepOriginalToggle?.checked : undefined;
const startResult = await window.api.downloadVideo({
url,
videoFormat,
@@ -2673,7 +3043,7 @@ document.addEventListener('DOMContentLoaded', async () => {
if (checkUpdateBtn) {
checkUpdateBtn.onclick = checkForUpdates;
}
- const ipcCleanupFunctions = [];
+ const ipcCleanupFunctions: Array<() => void> = [];
ipcCleanupFunctions.push(
window.api.onProgress((message) => {
@@ -2727,12 +3097,12 @@ document.addEventListener('DOMContentLoaded', async () => {
showProgressComplete();
if (settings.notifications) {
- window.api.showNotification({
+ void window.api.showNotification({
title: 'Download Complete!',
body: lastDownloadedFilePath
? `Saved: ${lastDownloadedFilePath.split(/[/\\]/).pop()}`
: 'Your download has finished.',
- filePath: lastDownloadedFilePath,
+ filePath: lastDownloadedFilePath ?? undefined,
});
}
}
@@ -2742,7 +3112,7 @@ document.addEventListener('DOMContentLoaded', async () => {
}, 2000);
const filename = lastDownloadedFilePath
- ? lastDownloadedFilePath.split(/[/\\]/).pop()
+ ? (lastDownloadedFilePath.split(/[/\\]/).pop() ?? 'Unknown file')
: 'Unknown file';
addHistoryEntry({
filename,
@@ -2760,7 +3130,7 @@ document.addEventListener('DOMContentLoaded', async () => {
downloadBtn.innerHTML = `Open File Location `;
downloadBtn.disabled = false;
downloadBtn.onclick = () => {
- window.api.openFileLocation(filePath);
+ void window.api.openFileLocation(filePath);
};
setTimeout(() => {
restoreDefaultDownloadButton();
@@ -2839,13 +3209,13 @@ document.addEventListener('DOMContentLoaded', async () => {
if (settings.firstLaunch) {
launchSetupWizard(settings, applyTheme, persistSettings, () => {
- checkDenoInstallation(settings);
- checkUpdatesOnStartup();
+ void checkDenoInstallation(settings, () => void persistSettings());
+ void checkUpdatesOnStartup();
});
} else {
- // check Deno
- checkDenoInstallation(settings);
- checkUpdatesOnStartup();
+ void checkDenoInstallation(settings, () => void persistSettings());
+ void checkUpdatesOnStartup();
+ setTimeout(maybeShowSupportModal, 1500);
}
const closeBtn = document.getElementById('close-licenses');
@@ -2888,7 +3258,7 @@ document.addEventListener('DOMContentLoaded', async () => {
if (modifierPressed && event.key === 'f') {
event.preventDefault();
- const urlInput = document.getElementById('url');
+ const urlInput = document.getElementById('url') as HTMLInputElement | null;
if (urlInput) {
urlInput.focus();
urlInput.select();
diff --git a/src/renderer/splash.html b/src/renderer/splash.html
index ffdc5e9..cf48412 100644
--- a/src/renderer/splash.html
+++ b/src/renderer/splash.html
@@ -4,18 +4,12 @@
Loading ROSI
-
-
-