release: publish Twitch VOD Manager 1.0.4
Add smooth VOD bulk-action dock entrance and exit motion, including on Windows systems with reduced animations enabled. Refresh Windows shortcut icons through persistent versioned resources, preserve pinned shortcut compatibility across upgrades, and clean the icon cache on full uninstall. Update the public documentation and release metadata for version 1.0.4.
This commit is contained in:
@@ -1,5 +1,12 @@
|
|||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
|
## 1.0.4 - 2026-08-11
|
||||||
|
|
||||||
|
- Added smooth entrance and exit motion for the VOD selection action dock, including Windows systems with reduced animations enabled.
|
||||||
|
- Fixed stale Windows desktop and Start menu icons with version-specific shortcut icon resources and an explicit Shell refresh after installation.
|
||||||
|
- Kept icon resources available for pinned and copied shortcuts across upgrades while cleaning them up during a full uninstall.
|
||||||
|
- Updated the public product overview with a real Twitch channel example.
|
||||||
|
|
||||||
## 1.0.3 - 2026-08-10
|
## 1.0.3 - 2026-08-10
|
||||||
|
|
||||||
- Added animated selection markers across the main navigation, sidebar modes, language control, streamer list and settings pages.
|
- Added animated selection markers across the main navigation, sidebar modes, language control, streamer list and settings pages.
|
||||||
|
|||||||
@@ -60,7 +60,7 @@ The application works in public mode without a Twitch login. Connecting a Twitch
|
|||||||
## Installation
|
## Installation
|
||||||
|
|
||||||
1. Open the [latest GitHub release](https://github.com/Sucukdeluxe/Twitch-VOD-Manager/releases/latest).
|
1. Open the [latest GitHub release](https://github.com/Sucukdeluxe/Twitch-VOD-Manager/releases/latest).
|
||||||
2. Download `Twitch-VOD-Manager-Setup-1.0.3.exe`.
|
2. Download `Twitch-VOD-Manager-Setup-1.0.4.exe`.
|
||||||
3. Run the installer and choose the installation directory.
|
3. Run the installer and choose the installation directory.
|
||||||
4. Start Twitch VOD Manager and add a streamer.
|
4. Start Twitch VOD Manager and add a streamer.
|
||||||
|
|
||||||
|
|||||||
@@ -2,3 +2,26 @@
|
|||||||
; Kill running Twitch VOD Manager process before installation
|
; Kill running Twitch VOD Manager process before installation
|
||||||
nsExec::ExecToLog 'taskkill /F /IM "Twitch VOD Manager.exe"'
|
nsExec::ExecToLog 'taskkill /F /IM "Twitch VOD Manager.exe"'
|
||||||
!macroend
|
!macroend
|
||||||
|
|
||||||
|
!macro customInstall
|
||||||
|
CreateDirectory "$LOCALAPPDATA\Twitch VOD Manager\Shortcut Icons"
|
||||||
|
CopyFiles /SILENT "$INSTDIR\resources\app-icons\icon-${VERSION}.ico" "$LOCALAPPDATA\Twitch VOD Manager\Shortcut Icons"
|
||||||
|
StrCpy $0 "$LOCALAPPDATA\Twitch VOD Manager\Shortcut Icons\icon-${VERSION}.ico"
|
||||||
|
${if} ${FileExists} "$newDesktopLink"
|
||||||
|
CreateShortCut "$newDesktopLink" "$appExe" "" "$0" 0 "" "" "${APP_DESCRIPTION}"
|
||||||
|
ClearErrors
|
||||||
|
WinShell::SetLnkAUMI "$newDesktopLink" "${APP_ID}"
|
||||||
|
${endIf}
|
||||||
|
${if} ${FileExists} "$newStartMenuLink"
|
||||||
|
CreateShortCut "$newStartMenuLink" "$appExe" "" "$0" 0 "" "" "${APP_DESCRIPTION}"
|
||||||
|
ClearErrors
|
||||||
|
WinShell::SetLnkAUMI "$newStartMenuLink" "${APP_ID}"
|
||||||
|
${endIf}
|
||||||
|
System::Call 'shell32::SHChangeNotify(i 0x08000000, i 0x1000, i 0, i 0)'
|
||||||
|
!macroend
|
||||||
|
|
||||||
|
!macro customUnInstall
|
||||||
|
${ifNot} ${isUpdated}
|
||||||
|
RMDir /r "$LOCALAPPDATA\Twitch VOD Manager\Shortcut Icons"
|
||||||
|
${endIf}
|
||||||
|
!macroend
|
||||||
|
|||||||
Generated
+2
-2
@@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"name": "twitch-vod-manager",
|
"name": "twitch-vod-manager",
|
||||||
"version": "1.0.3",
|
"version": "1.0.4",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "twitch-vod-manager",
|
"name": "twitch-vod-manager",
|
||||||
"version": "1.0.3",
|
"version": "1.0.4",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"axios": "^1.16.1",
|
"axios": "^1.16.1",
|
||||||
|
|||||||
+7
-1
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "twitch-vod-manager",
|
"name": "twitch-vod-manager",
|
||||||
"version": "1.0.3",
|
"version": "1.0.4",
|
||||||
"description": "Twitch VOD Manager - Download Twitch VODs easily",
|
"description": "Twitch VOD Manager - Download Twitch VODs easily",
|
||||||
"main": "dist/main.js",
|
"main": "dist/main.js",
|
||||||
"author": "Sucukdeluxe",
|
"author": "Sucukdeluxe",
|
||||||
@@ -58,6 +58,12 @@
|
|||||||
"build/icon.png",
|
"build/icon.png",
|
||||||
"package.json"
|
"package.json"
|
||||||
],
|
],
|
||||||
|
"extraResources": [
|
||||||
|
{
|
||||||
|
"from": "build/icon.ico",
|
||||||
|
"to": "app-icons/icon-${version}.ico"
|
||||||
|
}
|
||||||
|
],
|
||||||
"directories": {
|
"directories": {
|
||||||
"output": "release"
|
"output": "release"
|
||||||
},
|
},
|
||||||
|
|||||||
+1
-1
@@ -83,7 +83,7 @@ if (process.platform === 'win32') {
|
|||||||
sourcePath: electronSourceExecutable,
|
sourcePath: electronSourceExecutable,
|
||||||
destinationPath: resolve(rootDirectory, 'node_modules', 'electron', 'dist', 'Twitch VOD Manager.exe'),
|
destinationPath: resolve(rootDirectory, 'node_modules', 'electron', 'dist', 'Twitch VOD Manager.exe'),
|
||||||
iconPath: resolve(rootDirectory, 'build', 'icon.ico'),
|
iconPath: resolve(rootDirectory, 'build', 'icon.ico'),
|
||||||
version: '1.0.3',
|
version: '1.0.4',
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ const packageJson = JSON.parse(fs.readFileSync(path.join(root, 'package.json'),
|
|||||||
const packageLock = JSON.parse(fs.readFileSync(path.join(root, 'package-lock.json'), 'utf8'));
|
const packageLock = JSON.parse(fs.readFileSync(path.join(root, 'package-lock.json'), 'utf8'));
|
||||||
const mainSource = fs.readFileSync(path.join(root, 'src', 'main.ts'), 'utf8');
|
const mainSource = fs.readFileSync(path.join(root, 'src', 'main.ts'), 'utf8');
|
||||||
const indexSource = fs.readFileSync(path.join(root, 'src', 'index.html'), 'utf8');
|
const indexSource = fs.readFileSync(path.join(root, 'src', 'index.html'), 'utf8');
|
||||||
|
const installerSource = fs.readFileSync(path.join(root, 'build', 'installer.nsh'), 'utf8');
|
||||||
const manifestPath = path.join(root, 'scripts', 'public-release-files.json');
|
const manifestPath = path.join(root, 'scripts', 'public-release-files.json');
|
||||||
const failures = [];
|
const failures = [];
|
||||||
|
|
||||||
@@ -13,9 +14,9 @@ function check(condition, message) {
|
|||||||
if (!condition) failures.push(message);
|
if (!condition) failures.push(message);
|
||||||
}
|
}
|
||||||
|
|
||||||
check(packageJson.version === '1.0.3', `package version is ${packageJson.version}`);
|
check(packageJson.version === '1.0.4', `package version is ${packageJson.version}`);
|
||||||
check(packageLock.version === '1.0.3', `lockfile version is ${packageLock.version}`);
|
check(packageLock.version === '1.0.4', `lockfile version is ${packageLock.version}`);
|
||||||
check(packageLock.packages?.['']?.version === '1.0.3', `lockfile root package version is ${packageLock.packages?.['']?.version}`);
|
check(packageLock.packages?.['']?.version === '1.0.4', `lockfile root package version is ${packageLock.packages?.['']?.version}`);
|
||||||
check(packageJson.build?.appId === 'io.github.sucukdeluxe.twitch-vod-manager', `appId is ${packageJson.build?.appId}`);
|
check(packageJson.build?.appId === 'io.github.sucukdeluxe.twitch-vod-manager', `appId is ${packageJson.build?.appId}`);
|
||||||
check(packageJson.build?.publish?.provider === 'generic', `publish provider is ${packageJson.build?.publish?.provider}`);
|
check(packageJson.build?.publish?.provider === 'generic', `publish provider is ${packageJson.build?.publish?.provider}`);
|
||||||
check(packageJson.build?.publish?.url === 'https://github.com/Sucukdeluxe/Twitch-VOD-Manager/releases/latest/download/', `publish URL is ${packageJson.build?.publish?.url}`);
|
check(packageJson.build?.publish?.url === 'https://github.com/Sucukdeluxe/Twitch-VOD-Manager/releases/latest/download/', `publish URL is ${packageJson.build?.publish?.url}`);
|
||||||
@@ -23,6 +24,14 @@ check(JSON.stringify(packageJson.build?.files) === JSON.stringify(['dist/**/*',
|
|||||||
check(packageJson.build?.win?.icon === 'build/icon.ico', `Windows icon is ${packageJson.build?.win?.icon}`);
|
check(packageJson.build?.win?.icon === 'build/icon.ico', `Windows icon is ${packageJson.build?.win?.icon}`);
|
||||||
check(packageJson.build?.nsis?.installerIcon === 'build/icon.ico', `installer icon is ${packageJson.build?.nsis?.installerIcon}`);
|
check(packageJson.build?.nsis?.installerIcon === 'build/icon.ico', `installer icon is ${packageJson.build?.nsis?.installerIcon}`);
|
||||||
check(packageJson.build?.nsis?.uninstallerIcon === 'build/icon.ico', `uninstaller icon is ${packageJson.build?.nsis?.uninstallerIcon}`);
|
check(packageJson.build?.nsis?.uninstallerIcon === 'build/icon.ico', `uninstaller icon is ${packageJson.build?.nsis?.uninstallerIcon}`);
|
||||||
|
const shortcutIconResource = packageJson.build?.extraResources?.find((entry) => entry?.from === 'build/icon.ico');
|
||||||
|
check(shortcutIconResource?.to === 'app-icons/icon-${version}.ico', `versioned shortcut icon resource is ${shortcutIconResource?.to}`);
|
||||||
|
check(installerSource.includes('"$LOCALAPPDATA\\Twitch VOD Manager\\Shortcut Icons\\icon-${VERSION}.ico"'), 'installed shortcuts do not use the persistent versioned icon resource');
|
||||||
|
check(installerSource.includes('CopyFiles /SILENT "$INSTDIR\\resources\\app-icons\\icon-${VERSION}.ico"'), 'versioned shortcut icon is not copied to persistent storage');
|
||||||
|
check(installerSource.includes('CreateShortCut "$newDesktopLink"'), 'desktop shortcut is not refreshed with the versioned icon resource');
|
||||||
|
check(installerSource.includes('CreateShortCut "$newStartMenuLink"'), 'start menu shortcut is not refreshed with the versioned icon resource');
|
||||||
|
check(installerSource.includes('SHChangeNotify(i 0x08000000, i 0x1000'), 'Windows shell icon cache is not flushed after shortcut refresh');
|
||||||
|
check(installerSource.includes('${ifNot} ${isUpdated}') && installerSource.includes('RMDir /r "$LOCALAPPDATA\\Twitch VOD Manager\\Shortcut Icons"'), 'persistent shortcut icons are not cleaned up on a real uninstall');
|
||||||
check(packageJson.build?.win?.signAndEditExecutable !== false, 'Windows executable resource editing is enabled');
|
check(packageJson.build?.win?.signAndEditExecutable !== false, 'Windows executable resource editing is enabled');
|
||||||
check(packageJson.build?.win?.signExecutable === false, 'Windows code signing remains disabled without suppressing icon resources');
|
check(packageJson.build?.win?.signExecutable === false, 'Windows code signing remains disabled without suppressing icon resources');
|
||||||
check(fs.existsSync(path.join(root, 'build', 'icon.png')), 'application PNG icon is missing');
|
check(fs.existsSync(path.join(root, 'build', 'icon.png')), 'application PNG icon is missing');
|
||||||
@@ -36,7 +45,7 @@ check(mainSource.includes('GITHUB_RELEASES_DOWNLOAD_BASE_URL'), 'GitHub releases
|
|||||||
check(mainSource.includes('https://api.github.com/repos/Sucukdeluxe/Twitch-VOD-Manager/releases/latest'), 'GitHub latest release API URL is missing');
|
check(mainSource.includes('https://api.github.com/repos/Sucukdeluxe/Twitch-VOD-Manager/releases/latest'), 'GitHub latest release API URL is missing');
|
||||||
check(mainSource.includes('https://github.com/Sucukdeluxe/Twitch-VOD-Manager/releases/download'), 'GitHub release download URL is missing');
|
check(mainSource.includes('https://github.com/Sucukdeluxe/Twitch-VOD-Manager/releases/download'), 'GitHub release download URL is missing');
|
||||||
check(!/storyboards\/\d{8,12}(?:-|\/)/.test(mainSource), 'numeric Twitch VOD example remains in the public source');
|
check(!/storyboards\/\d{8,12}(?:-|\/)/.test(mainSource), 'numeric Twitch VOD example remains in the public source');
|
||||||
check(indexSource.includes('Version: v1.0.3'), 'initial version label is not 1.0.3');
|
check(indexSource.includes('Version: v1.0.4'), 'initial version label is not 1.0.4');
|
||||||
check(!indexSource.includes('Version: v4.1.13'), 'legacy version label is still present');
|
check(!indexSource.includes('Version: v4.1.13'), 'legacy version label is still present');
|
||||||
check(fs.existsSync(manifestPath), 'public release manifest is missing');
|
check(fs.existsSync(manifestPath), 'public release manifest is missing');
|
||||||
|
|
||||||
|
|||||||
@@ -1353,6 +1353,7 @@ async function run() {
|
|||||||
});
|
});
|
||||||
const hoverCard = win.locator('.vod-card[data-vod-id="hover-hd-fixture"]');
|
const hoverCard = win.locator('.vod-card[data-vod-id="hover-hd-fixture"]');
|
||||||
const gridTopBeforeSelection = await win.locator('#vodGrid').evaluate((grid) => grid.getBoundingClientRect().top);
|
const gridTopBeforeSelection = await win.locator('#vodGrid').evaluate((grid) => grid.getBoundingClientRect().top);
|
||||||
|
await win.emulateMedia({ reducedMotion: 'reduce' });
|
||||||
await hoverCard.locator('.vod-thumbnail').click();
|
await hoverCard.locator('.vod-thumbnail').click();
|
||||||
const clickSelection = await win.evaluate(() => {
|
const clickSelection = await win.evaluate(() => {
|
||||||
const card = document.querySelector('.vod-card[data-vod-id="hover-hd-fixture"]');
|
const card = document.querySelector('.vod-card[data-vod-id="hover-hd-fixture"]');
|
||||||
@@ -1401,8 +1402,41 @@ async function run() {
|
|||||||
check(Math.abs(dockWithOneSelection.width - dockWithTwoSelections.width) <= 0.25, `VOD bulk dock width changes between one and two selections: ${dockWithOneSelection.width}/${dockWithTwoSelections.width}`);
|
check(Math.abs(dockWithOneSelection.width - dockWithTwoSelections.width) <= 0.25, `VOD bulk dock width changes between one and two selections: ${dockWithOneSelection.width}/${dockWithTwoSelections.width}`);
|
||||||
check(Math.abs(dockWithOneSelection.left - dockWithTwoSelections.left) <= 0.25, `VOD bulk dock shifts between one and two selections: ${dockWithOneSelection.left}/${dockWithTwoSelections.left}`);
|
check(Math.abs(dockWithOneSelection.left - dockWithTwoSelections.left) <= 0.25, `VOD bulk dock shifts between one and two selections: ${dockWithOneSelection.left}/${dockWithTwoSelections.left}`);
|
||||||
await secondDockCard.locator('.vod-thumbnail').click();
|
await secondDockCard.locator('.vod-thumbnail').click();
|
||||||
|
await hoverCard.locator('.vod-thumbnail').click();
|
||||||
|
await win.waitForTimeout(80);
|
||||||
|
const dockDuringExit = await win.locator('#vodBulkBar').evaluate((dock) => {
|
||||||
|
const style = getComputedStyle(dock);
|
||||||
|
return {
|
||||||
|
display: style.display,
|
||||||
|
opacity: Number(style.opacity),
|
||||||
|
visibility: style.visibility,
|
||||||
|
pointerEvents: style.pointerEvents,
|
||||||
|
animationCount: dock.getAnimations().length,
|
||||||
|
translateY: new DOMMatrix(style.transform).m42
|
||||||
|
};
|
||||||
|
});
|
||||||
|
await win.waitForFunction(() => getComputedStyle(document.getElementById('vodBulkBar')).visibility === 'hidden', null, { timeout: 1000 });
|
||||||
|
const dockAfterExit = await win.locator('#vodBulkBar').evaluate((dock) => {
|
||||||
|
const style = getComputedStyle(dock);
|
||||||
|
return {
|
||||||
|
display: style.display,
|
||||||
|
opacity: Number(style.opacity),
|
||||||
|
visibility: style.visibility,
|
||||||
|
pointerEvents: style.pointerEvents
|
||||||
|
};
|
||||||
|
});
|
||||||
|
checks.vodBulkDockExit = { during: dockDuringExit, after: dockAfterExit };
|
||||||
|
check(dockDuringExit.display === 'flex' && dockDuringExit.visibility === 'visible', `VOD bulk dock disappears before its exit motion: ${JSON.stringify(dockDuringExit)}`);
|
||||||
|
check(dockDuringExit.opacity > 0 && dockDuringExit.opacity < 1, `VOD bulk dock has no visible exit opacity frame: ${JSON.stringify(dockDuringExit)}`);
|
||||||
|
check(dockDuringExit.animationCount > 0, `VOD bulk dock exits without a running transition: ${JSON.stringify(dockDuringExit)}`);
|
||||||
|
check(dockDuringExit.translateY > 1, `VOD bulk dock exit has no downward movement: ${JSON.stringify(dockDuringExit)}`);
|
||||||
|
check(dockAfterExit.display === 'flex' && dockAfterExit.visibility === 'hidden' && dockAfterExit.pointerEvents === 'none' && dockAfterExit.opacity === 0, `VOD bulk dock does not finish in a non-interactive hidden state: ${JSON.stringify(dockAfterExit)}`);
|
||||||
|
await hoverCard.locator('.vod-thumbnail').click();
|
||||||
|
await win.waitForTimeout(280);
|
||||||
check(openCallsAfterClick.length === 0, `VOD image click still opens Twitch: ${JSON.stringify(openCallsAfterClick)}`);
|
check(openCallsAfterClick.length === 0, `VOD image click still opens Twitch: ${JSON.stringify(openCallsAfterClick)}`);
|
||||||
check(openCallsAfterContextMenu.length === 1 && openCallsAfterContextMenu[0] === 'https://example.invalid/hover-hd-fixture', `VOD context menu does not exclusively open Twitch: ${JSON.stringify(openCallsAfterContextMenu)}`);
|
check(openCallsAfterContextMenu.length === 1 && openCallsAfterContextMenu[0] === 'https://example.invalid/hover-hd-fixture', `VOD context menu does not exclusively open Twitch: ${JSON.stringify(openCallsAfterContextMenu)}`);
|
||||||
|
await win.locator('#toolbarRefreshVodsBtn').hover();
|
||||||
|
await win.waitForTimeout(40);
|
||||||
await hoverCard.hover();
|
await hoverCard.hover();
|
||||||
await win.waitForTimeout(320);
|
await win.waitForTimeout(320);
|
||||||
const hdHoverFirst = await win.evaluate(() => {
|
const hdHoverFirst = await win.evaluate(() => {
|
||||||
|
|||||||
+2
-2
@@ -395,7 +395,7 @@
|
|||||||
<option value="duration_asc">Shortest first</option>
|
<option value="duration_asc">Shortest first</option>
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
<div id="vodBulkBar" class="vod-bulk-bar is-hidden">
|
<div id="vodBulkBar" class="vod-bulk-bar">
|
||||||
<span id="vodBulkCount" class="vod-bulk-count">0 selected</span>
|
<span id="vodBulkCount" class="vod-bulk-count">0 selected</span>
|
||||||
<span class="vod-bulk-spacer"></span>
|
<span class="vod-bulk-spacer"></span>
|
||||||
<button id="vodBulkAddBtn" class="btn-pill primary" type="button" onclick="bulkAddSelectedVodsToQueue()">+ Queue</button>
|
<button id="vodBulkAddBtn" class="btn-pill primary" type="button" onclick="bulkAddSelectedVodsToQueue()">+ Queue</button>
|
||||||
@@ -783,7 +783,7 @@
|
|||||||
|
|
||||||
<div class="settings-card" data-settings-pane="updates" hidden>
|
<div class="settings-card" data-settings-pane="updates" hidden>
|
||||||
<h3 id="updateTitle">Updates</h3>
|
<h3 id="updateTitle">Updates</h3>
|
||||||
<p id="versionInfo" class="card-intro">Version: v1.0.3</p>
|
<p id="versionInfo" class="card-intro">Version: v1.0.4</p>
|
||||||
<button type="button" class="btn-secondary" id="checkUpdateBtn" onclick="checkUpdate()">Nach Updates suchen</button>
|
<button type="button" class="btn-secondary" id="checkUpdateBtn" onclick="checkUpdate()">Nach Updates suchen</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ describe.runIf(process.platform === 'win32')('prepareWindowsDevExecutable', () =
|
|||||||
sourcePath,
|
sourcePath,
|
||||||
destinationPath,
|
destinationPath,
|
||||||
iconPath,
|
iconPath,
|
||||||
version: '1.0.3'
|
version: '1.0.4'
|
||||||
});
|
});
|
||||||
|
|
||||||
const executable = ResEdit.NtExecutable.from(fs.readFileSync(destinationPath));
|
const executable = ResEdit.NtExecutable.from(fs.readFileSync(destinationPath));
|
||||||
|
|||||||
@@ -1198,7 +1198,7 @@ function updateVodBulkBar(): void {
|
|||||||
const bar = document.getElementById('vodBulkBar');
|
const bar = document.getElementById('vodBulkBar');
|
||||||
if (!bar) return;
|
if (!bar) return;
|
||||||
const count = selectedVodUrls.size;
|
const count = selectedVodUrls.size;
|
||||||
bar.classList.toggle('is-hidden', count === 0);
|
bar.classList.toggle('is-visible', count > 0);
|
||||||
const countEl = document.getElementById('vodBulkCount');
|
const countEl = document.getElementById('vodBulkCount');
|
||||||
if (countEl) {
|
if (countEl) {
|
||||||
countEl.textContent = UI_TEXT.vods.bulkSelectedCount.replace('{count}', String(count));
|
countEl.textContent = UI_TEXT.vods.bulkSelectedCount.replace('{count}', String(count));
|
||||||
|
|||||||
+22
-13
@@ -1577,8 +1577,20 @@ input[type="range"] {
|
|||||||
border-radius: 10px;
|
border-radius: 10px;
|
||||||
box-shadow: 0 12px 32px rgba(0, 0, 0, 0.48), 0 0 0 1px rgba(255, 255, 255, 0.04);
|
box-shadow: 0 12px 32px rgba(0, 0, 0, 0.48), 0 0 0 1px rgba(255, 255, 255, 0.04);
|
||||||
backdrop-filter: blur(12px);
|
backdrop-filter: blur(12px);
|
||||||
transform: translateX(-50%);
|
opacity: 0;
|
||||||
animation: vod-bulk-dock-in 240ms cubic-bezier(0.22, 0.76, 0.22, 1);
|
visibility: hidden;
|
||||||
|
pointer-events: none;
|
||||||
|
transform: translate(-50%, calc(100% + 24px)) scale(0.98);
|
||||||
|
animation: none;
|
||||||
|
transition: transform 280ms cubic-bezier(0.22, 0.76, 0.22, 1), opacity 220ms ease, visibility 0s linear 280ms;
|
||||||
|
}
|
||||||
|
|
||||||
|
.vod-bulk-bar.is-visible {
|
||||||
|
opacity: 1;
|
||||||
|
visibility: visible;
|
||||||
|
pointer-events: auto;
|
||||||
|
transform: translate(-50%, 0) scale(1);
|
||||||
|
transition-delay: 0s;
|
||||||
}
|
}
|
||||||
|
|
||||||
.vod-bulk-bar .vod-bulk-count {
|
.vod-bulk-bar .vod-bulk-count {
|
||||||
@@ -1623,17 +1635,6 @@ input[type="range"] {
|
|||||||
border-color: #22c55e;
|
border-color: #22c55e;
|
||||||
}
|
}
|
||||||
|
|
||||||
@keyframes vod-bulk-dock-in {
|
|
||||||
from {
|
|
||||||
opacity: 0;
|
|
||||||
transform: translate(-50%, 12px) scale(0.98);
|
|
||||||
}
|
|
||||||
to {
|
|
||||||
opacity: 1;
|
|
||||||
transform: translate(-50%, 0) scale(1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.vod-grid {
|
.vod-grid {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: repeat(auto-fill, minmax(250px, 1fr));
|
grid-template-columns: repeat(auto-fill, minmax(250px, 1fr));
|
||||||
@@ -2577,6 +2578,14 @@ input[type="checkbox"].vod-select-checkbox:focus-visible {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@media (prefers-reduced-motion: reduce) {
|
@media (prefers-reduced-motion: reduce) {
|
||||||
|
.vod-bulk-bar {
|
||||||
|
transition: transform 280ms cubic-bezier(0.22, 0.76, 0.22, 1), opacity 220ms ease, visibility 0s linear 280ms !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.vod-bulk-bar.is-visible {
|
||||||
|
transition-delay: 0s !important;
|
||||||
|
}
|
||||||
|
|
||||||
.top-nav::before {
|
.top-nav::before {
|
||||||
transition: transform 420ms cubic-bezier(0.22, 0.76, 0.22, 1), width 420ms cubic-bezier(0.22, 0.76, 0.22, 1), height 420ms cubic-bezier(0.22, 0.76, 0.22, 1) !important;
|
transition: transform 420ms cubic-bezier(0.22, 0.76, 0.22, 1), width 420ms cubic-bezier(0.22, 0.76, 0.22, 1), height 420ms cubic-bezier(0.22, 0.76, 0.22, 1) !important;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user