// DOM Elements const dependencyOverlay = document.getElementById('dependency-overlay'); const depTitle = document.getElementById('dep-title'); const depMsg = document.getElementById('dep-msg'); const depProgress = document.getElementById('dep-progress'); const depPercent = document.getElementById('dep-percent'); const urlInput = document.getElementById('url-input'); const analyzeBtn = document.getElementById('analyze-btn'); const outputPath = document.getElementById('output-path'); const changeFolderBtn = document.getElementById('change-folder-btn'); const useCookiesCheckbox = document.getElementById('use-cookies'); const cookieBrowserSelect = document.getElementById('cookie-browser'); const errorCard = document.getElementById('error-card'); const errorMessage = document.getElementById('error-message'); const step1 = document.getElementById('step-1'); const step2 = document.getElementById('step-2'); const backBtn = document.getElementById('back-btn'); const trackList = document.getElementById('track-list'); const formatMp3Btn = document.getElementById('format-mp3-btn'); const formatMp4Btn = document.getElementById('format-mp4-btn'); const qualityPillsContainer = document.getElementById('quality-pills'); const downloadBtn = document.getElementById('download-btn'); const downloadProgressPanel = document.getElementById('download-progress-panel'); const downloadStatus = document.getElementById('download-status'); const downloadPercent = document.getElementById('download-percent'); const downloadProgressBar = document.getElementById('download-progress-bar'); const downloadSubtext = document.getElementById('download-subtext'); const overallPercent = document.getElementById('overall-percent'); const overallProgressBar = document.getElementById('overall-progress-bar'); const overallCountText = document.getElementById('overall-count-text'); const overallRemainingText = document.getElementById('overall-remaining-text'); const languageSelect = document.getElementById('language-select'); // State Variables let analyzedUrl = ''; let currentFormat = 'mp3'; // 'mp3' or 'mp4' let currentQuality = '320'; // Selected quality value let currentVideoData = null; // Playlist State Variables const playlistHeader = document.getElementById('playlist-header'); const selectAllCheckbox = document.getElementById('select-all-checkbox'); const bulkMp3Btn = document.getElementById('bulk-mp3-btn'); const bulkMp4Btn = document.getElementById('bulk-mp4-btn'); let playlistTracks = []; let focusedTrackId = null; let isPlaylistMode = false; // Localization State let currentLang = 'tr'; let translations = {}; // 0. Translation Engine Functions async function loadTranslations(lang) { try { const res = await fetch(`locales/${lang}.json`); translations = await res.json(); currentLang = lang; localStorage.setItem('vibe_lang', lang); // Toggle direction attribute for Arabic (RTL) if (lang === 'ar') { document.documentElement.setAttribute('dir', 'rtl'); document.documentElement.setAttribute('lang', 'ar'); } else { document.documentElement.removeAttribute('dir'); document.documentElement.setAttribute('lang', lang); } // Apply translations to data-i18n attributes applyTranslations(); } catch (err) { console.error('Error loading language file:', err); } } function t(key, vars = {}) { let text = translations[key] || key; for (const [k, v] of Object.entries(vars)) { text = text.replace(`%{${k}}`, v); } return text; } function applyTranslations() { // Elements with data-i18n attribute document.querySelectorAll('[data-i18n]').forEach(el => { const key = el.dataset.i18n; el.textContent = t(key); }); // Elements with data-i18n-placeholder attribute document.querySelectorAll('[data-i18n-placeholder]').forEach(el => { const key = el.dataset.i18nPlaceholder; el.placeholder = t(key); }); // Elements with data-i18n-title attribute document.querySelectorAll('[data-i18n-title]').forEach(el => { const key = el.dataset.i18nTitle; el.title = t(key); }); // Dynamically redraw active quality pills based on current locale renderQualityPills(); // If track is rendered, translate its status and duration labels if (currentVideoData) { renderTrackList(currentVideoData); } } // Quality Configuration Generator (Dynamic for Localization) function getQualityConfigs() { return { mp3: [ { label: `320 kbps (${t('quality_best')})`, value: '320' }, { label: '256 kbps', value: '256' }, { label: '192 kbps', value: '192' } ], mp4: [ { label: '1080p (Full HD)', value: '1080' }, { label: '720p (HD)', value: '720' }, { label: `480p (${t('quality_standard')})`, value: '480' } ] }; } // 1. Initial Load & Dependency Verification window.addEventListener('DOMContentLoaded', async () => { // Setup Custom Titlebar Controls document.getElementById('titlebar-minimize').addEventListener('click', () => { window.api.minimizeWindow(); }); document.getElementById('titlebar-close').addEventListener('click', () => { window.api.closeWindow(); }); // Setup Language Selector Change Handler languageSelect.addEventListener('change', async (e) => { await loadTranslations(e.target.value); }); // Select All Checkbox Handler selectAllCheckbox.addEventListener('change', () => { const checked = selectAllCheckbox.checked; playlistTracks.forEach(track => { track.checked = checked; }); renderTrackList(currentVideoData); }); // Bulk MP3 Button Handler bulkMp3Btn.addEventListener('click', () => { playlistTracks.forEach(track => { if (track.checked) { track.format = 'mp3'; track.quality = '320'; } }); const focusedTrack = playlistTracks.find(t => t.id === focusedTrackId); if (focusedTrack) syncBottomPanelWithTrack(focusedTrack); renderTrackList(currentVideoData); }); // Bulk MP4 Button Handler bulkMp4Btn.addEventListener('click', () => { playlistTracks.forEach(track => { if (track.checked) { track.format = 'mp4'; track.quality = '1080'; } }); const focusedTrack = playlistTracks.find(t => t.id === focusedTrackId); if (focusedTrack) syncBottomPanelWithTrack(focusedTrack); renderTrackList(currentVideoData); }); const speedSelect = document.getElementById('summary-speed-select'); if (speedSelect) { speedSelect.addEventListener('change', () => { updateSummaryStats(); }); } // Get and load persisted or default language const savedLang = localStorage.getItem('vibe_lang') || 'tr'; languageSelect.value = savedLang; await loadTranslations(savedLang); // Get and display default downloads folder path const defaultPath = await window.api.getDownloadsFolder(); outputPath.textContent = defaultPath; // Listen to dependency checking progress window.api.onDependencyStatus(({ message, progress }) => { let msg = message; if (message.includes('Downloading yt-dlp')) { const pctMatch = message.match(/(\d+)%/); msg = pctMatch ? `${t('btn_downloading')} yt-dlp... ${pctMatch[1]}%` : `${t('btn_downloading')} yt-dlp...`; } else if (message.includes('Downloading ffmpeg')) { const pctMatch = message.match(/(\d+)%/); msg = pctMatch ? `${t('btn_downloading')} ffmpeg... ${pctMatch[1]}%` : `${t('btn_downloading')} ffmpeg...`; } else if (message.includes('Extracting ffmpeg')) { msg = `${t('status_processing')} ffmpeg...`; } else if (message.includes('Dependencies ready')) { msg = t('status_completed'); } depMsg.textContent = msg; depProgress.style.width = `${progress}%`; depPercent.textContent = `${progress}%`; }); try { const result = await window.api.checkDependencies(); if (result.success) { // Transition out the loading overlay dependencyOverlay.style.opacity = '0'; setTimeout(() => { dependencyOverlay.classList.remove('active'); }, 400); } else { depTitle.textContent = t('badge_error'); depMsg.textContent = `${t('error_default')} ${result.error}`; depPercent.textContent = t('badge_error'); } } catch (err) { depTitle.textContent = t('badge_error'); depMsg.textContent = err.message; } }); // 2. Output Path Folder Changer changeFolderBtn.addEventListener('click', async () => { const newPath = await window.api.selectDirectory(); outputPath.textContent = newPath; }); // 3. Format Toggle Event Listeners formatMp3Btn.addEventListener('click', () => { if (currentFormat === 'mp3') return; currentFormat = 'mp3'; formatMp3Btn.classList.add('active'); formatMp4Btn.classList.remove('active'); currentQuality = '320'; // Reset to default best quality for MP3 renderQualityPills(); applyBottomSettingsToSelected(); }); formatMp4Btn.addEventListener('click', () => { if (currentFormat === 'mp4') return; currentFormat = 'mp4'; formatMp4Btn.classList.add('active'); formatMp3Btn.classList.remove('active'); currentQuality = '1080'; // Reset to default best quality for MP4 renderQualityPills(); applyBottomSettingsToSelected(); }); // 4. Quality Pills Renderer function renderQualityPills() { qualityPillsContainer.innerHTML = ''; const configs = getQualityConfigs(); const options = configs[currentFormat]; options.forEach(opt => { const pill = document.createElement('button'); pill.className = 'quality-pill'; if (opt.value === currentQuality) { pill.classList.add('active'); } pill.textContent = opt.label; pill.dataset.value = opt.value; pill.addEventListener('click', () => { document.querySelectorAll('.quality-pill').forEach(p => p.classList.remove('active')); pill.classList.add('active'); currentQuality = opt.value; applyBottomSettingsToSelected(); }); qualityPillsContainer.appendChild(pill); }); } function syncBottomPanelWithTrack(track) { currentFormat = track.format; currentQuality = track.quality; if (currentFormat === 'mp3') { formatMp3Btn.classList.add('active'); formatMp4Btn.classList.remove('active'); } else { formatMp4Btn.classList.add('active'); formatMp3Btn.classList.remove('active'); } renderQualityPills(); } function applyBottomSettingsToSelected() { if (!currentVideoData) return; const checkedTracks = playlistTracks.filter(t => t.checked); if (checkedTracks.length > 0) { checkedTracks.forEach(track => { track.format = currentFormat; track.quality = currentQuality; }); } else if (focusedTrackId) { const focusedTrack = playlistTracks.find(t => t.id === focusedTrackId); if (focusedTrack) { focusedTrack.format = currentFormat; focusedTrack.quality = currentQuality; } } renderTrackList(currentVideoData); } function updateSelectAllCheckboxState() { const checkedCount = playlistTracks.filter(t => t.checked).length; selectAllCheckbox.checked = checkedCount === playlistTracks.length; } // Stats Calculator Helpers function parseDurationToSeconds(durationStr) { if (!durationStr || durationStr === t('duration_unknown') || durationStr === 'Unknown') return 0; const parts = durationStr.split(':').map(Number); if (parts.some(isNaN)) return 0; if (parts.length === 3) { // h:mm:ss return parts[0] * 3600 + parts[1] * 60 + parts[2]; } else if (parts.length === 2) { // m:ss return parts[0] * 60 + parts[1]; } else if (parts.length === 1) { return parts[0]; } return 0; } function formatSecondsToReadable(totalSeconds) { if (!totalSeconds) return '0 ' + (t('sec_suffix_short') || 'sn'); const hrs = Math.floor(totalSeconds / 3600); const mins = Math.floor((totalSeconds % 3600) / 60); const secs = Math.floor(totalSeconds % 60); let result = []; if (hrs > 0) { result.push(`${hrs} ${t('hr_suffix_short') || 'sa'}`); } if (mins > 0 || hrs > 0) { result.push(`${mins} ${t('min_suffix_short') || 'dk'}`); } if (secs > 0 || (hrs === 0 && mins === 0)) { result.push(`${secs} ${t('sec_suffix_short') || 'sn'}`); } return result.join(' '); } function updateSummaryStats() { const summaryCard = document.getElementById('playlist-summary-card'); if (!summaryCard) return; const checkedTracks = playlistTracks.filter(t => t.checked); if (checkedTracks.length === 0) { document.getElementById('summary-total-count').textContent = '0'; document.getElementById('summary-total-duration').textContent = formatSecondsToReadable(0); document.getElementById('summary-est-size').textContent = '0 MB'; document.getElementById('summary-est-time').textContent = '~0 ' + (t('sec_suffix_short') || 'sn'); // Hide duration items when empty document.getElementById('summary-item-duration').style.display = 'none'; document.getElementById('summary-item-speed').style.display = 'none'; document.getElementById('summary-item-size').style.display = 'none'; document.getElementById('summary-item-time').style.display = 'none'; return; } // Calculate total count document.getElementById('summary-total-count').textContent = checkedTracks.length; // Check if any of the checked tracks has unknown duration const hasUnknownDuration = checkedTracks.some(track => { const secs = parseDurationToSeconds(track.duration); return secs <= 0 || !track.duration || track.duration.includes('Bilinmiyor') || track.duration === 'Unknown'; }); if (hasUnknownDuration) { // Hide duration-related items completely document.getElementById('summary-item-duration').style.display = 'none'; document.getElementById('summary-item-speed').style.display = 'none'; document.getElementById('summary-item-size').style.display = 'none'; document.getElementById('summary-item-time').style.display = 'none'; } else { // Show duration-related items document.getElementById('summary-item-duration').style.display = 'flex'; document.getElementById('summary-item-speed').style.display = 'flex'; document.getElementById('summary-item-size').style.display = 'flex'; document.getElementById('summary-item-time').style.display = 'flex'; // Calculate total duration let totalSeconds = 0; checkedTracks.forEach(track => { totalSeconds += parseDurationToSeconds(track.duration); }); document.getElementById('summary-total-duration').textContent = formatSecondsToReadable(totalSeconds); // Calculate estimated size in MB let totalSizeMB = 0; checkedTracks.forEach(track => { const duration = parseDurationToSeconds(track.duration); if (track.format === 'mp3') { const kbps = parseInt(track.quality) || 320; totalSizeMB += (duration * kbps) / 8388.608; } else { let bitrate = 1500; if (track.quality === '1080') bitrate = 3000; else if (track.quality === '480') bitrate = 750; totalSizeMB += (duration * bitrate) / 8388.608; } }); document.getElementById('summary-est-size').textContent = `${totalSizeMB.toFixed(1)} MB`; // Calculate estimated download time based on selected internet speed const speedSelect = document.getElementById('summary-speed-select'); const speedMbps = parseFloat(speedSelect.value) || 24; const sizeMegabits = totalSizeMB * 8; let estSeconds = sizeMegabits / speedMbps; // Realism factor: connection handshake / conversion overhead estSeconds += checkedTracks.length * 1.5; document.getElementById('summary-est-time').textContent = `~${formatSecondsToReadable(Math.round(estSeconds))}`; } } // 5. Back Button (Step 2 to Step 1 Transition) backBtn.addEventListener('click', () => { // Reset any error card errorCard.style.display = 'none'; // Fade out Step 2 and show Step 1 step2.classList.remove('active'); step1.classList.add('active'); const summaryCard = document.getElementById('playlist-summary-card'); if (summaryCard) { summaryCard.style.display = 'none'; } currentVideoData = null; playlistTracks = []; focusedTrackId = null; urlInput.focus(); }); // 6. Link Analysis Handler analyzeBtn.addEventListener('click', async () => { const url = urlInput.value.trim(); if (!url) return; // Reset error displays errorCard.style.display = 'none'; // Toggle button loading states analyzeBtn.disabled = true; const analyzeSpan = analyzeBtn.querySelector('span'); const analyzeIcon = analyzeBtn.querySelector('.btn-icon'); analyzeSpan.textContent = t('btn_analyzing'); analyzeIcon.style.display = 'inline-block'; try { const browser = useCookiesCheckbox.checked ? cookieBrowserSelect.value : null; const result = await window.api.getVideoInfo(url, browser); if (result.success) { analyzedUrl = url; currentVideoData = result.info; playlistTracks = []; focusedTrackId = null; // Render the fetched video details inside the scrollable list container renderTrackList(result.info); // Wizard transition: Step 1 -> Step 2 step1.classList.remove('active'); step2.classList.add('active'); } else { showError(result.error); } } catch (err) { showError(err.message); } finally { analyzeBtn.disabled = false; analyzeSpan.textContent = t('btn_analyze'); analyzeIcon.style.display = 'none'; } }); // 7. Render Track List Function function renderTrackList(info) { trackList.innerHTML = ''; if (info.isPlaylist) { isPlaylistMode = true; playlistHeader.style.display = 'flex'; } else { isPlaylistMode = false; playlistHeader.style.display = 'none'; } const summaryCard = document.getElementById('playlist-summary-card'); if (summaryCard) { summaryCard.style.display = 'flex'; } if (info.entries) { info.entries.forEach(entry => { const existing = playlistTracks.find(t => t.id === entry.id); if (!existing) { playlistTracks.push({ id: entry.id, title: entry.title, thumbnail: entry.thumbnail, duration: entry.duration, uploader: entry.uploader, url: entry.url, format: currentFormat, quality: currentQuality, status: 'ready', progress: 0, checked: true }); } }); } if (playlistTracks.length > 0 && !focusedTrackId) { focusedTrackId = playlistTracks[0].id; } const focusedTrack = playlistTracks.find(t => t.id === focusedTrackId); if (focusedTrack) { syncBottomPanelWithTrack(focusedTrack); } playlistTracks.forEach(track => { const itemEl = document.createElement('div'); itemEl.className = `track-item ${track.id === focusedTrackId ? 'focused' : ''}`; itemEl.dataset.id = track.id; const checkboxWrapper = document.createElement('div'); checkboxWrapper.className = 'track-checkbox-wrapper'; const checkbox = document.createElement('input'); checkbox.type = 'checkbox'; checkbox.className = 'track-checkbox'; checkbox.checked = track.checked; checkbox.addEventListener('change', (e) => { e.stopPropagation(); track.checked = checkbox.checked; updateSelectAllCheckboxState(); }); checkboxWrapper.appendChild(checkbox); itemEl.appendChild(checkboxWrapper); const thumbEl = document.createElement('div'); thumbEl.className = 'track-thumb'; thumbEl.innerHTML = ` Thumbnail `; itemEl.appendChild(thumbEl); const detailsEl = document.createElement('div'); detailsEl.className = 'track-details'; detailsEl.innerHTML = `

${track.title}

${track.uploader}

${t('duration_label')} ${track.duration || t('duration_unknown')} `; itemEl.appendChild(detailsEl); const formatSelectorEl = document.createElement('div'); formatSelectorEl.className = 'track-format-selector'; const formatBadgeBtn = document.createElement('button'); formatBadgeBtn.className = `format-badge-btn ${track.format}`; formatBadgeBtn.textContent = track.format.toUpperCase(); formatBadgeBtn.addEventListener('click', (e) => { e.stopPropagation(); track.format = track.format === 'mp3' ? 'mp4' : 'mp3'; track.quality = track.format === 'mp3' ? '320' : '1080'; if (track.id === focusedTrackId) { syncBottomPanelWithTrack(track); } renderTrackList(currentVideoData); }); formatSelectorEl.appendChild(formatBadgeBtn); itemEl.appendChild(formatSelectorEl); const statusEl = document.createElement('div'); statusEl.className = 'track-status'; let statusText = t('badge_ready'); let badgeClass = 'ready'; if (track.status === 'downloading') { statusText = track.progress > 0 ? `${t('badge_downloading')} %${track.progress}` : t('badge_downloading'); badgeClass = 'downloading'; } else if (track.status === 'completed') { statusText = t('badge_completed'); badgeClass = 'completed'; } else if (track.status === 'error') { statusText = t('badge_error'); badgeClass = 'error'; } else if (track.status === 'waiting') { statusText = t('status_connecting'); badgeClass = 'ready'; } const statusBadge = document.createElement('span'); statusBadge.id = `badge-${track.id}`; statusBadge.className = `status-badge ${badgeClass}`; if (track.status === 'completed') { statusBadge.style.borderColor = 'rgba(0, 255, 135, 0.3)'; statusBadge.style.color = '#00ff87'; statusBadge.style.background = 'rgba(0, 255, 135, 0.1)'; } else if (track.status === 'error') { statusBadge.style.borderColor = 'rgba(255, 77, 77, 0.3)'; statusBadge.style.color = '#ff4d4d'; statusBadge.style.background = 'rgba(255, 77, 77, 0.1)'; } statusBadge.textContent = statusText; statusEl.appendChild(statusBadge); itemEl.appendChild(statusEl); itemEl.addEventListener('click', () => { focusedTrackId = track.id; syncBottomPanelWithTrack(track); renderTrackList(currentVideoData); }); trackList.appendChild(itemEl); }); updateSummaryStats(); } // 8. Media Download Executor downloadBtn.addEventListener('click', async () => { if (!analyzedUrl || playlistTracks.length === 0) return; const selectedItems = playlistTracks.filter(t => t.checked); if (selectedItems.length === 0) { Swal.fire({ title: t('badge_error'), text: t('err_no_items_selected') || 'Lütfen indirmek için en az bir dosya seçin.', icon: 'warning', background: 'rgba(23, 20, 38, 0.95)', color: '#fff', confirmButtonColor: '#7000ff' }); return; } // Setup download progress panel UI states downloadProgressPanel.style.display = 'block'; downloadBtn.disabled = true; backBtn.disabled = true; downloadStatus.textContent = t('status_connecting'); downloadPercent.textContent = '0%'; downloadProgressBar.style.width = '0%'; downloadSubtext.textContent = t('subtext_wait'); // Initialize overall progress bar UI values overallPercent.textContent = '0%'; overallProgressBar.style.width = '0%'; if (currentLang === 'tr') { overallCountText.textContent = `Tamamlanan: 0 / ${selectedItems.length}`; overallRemainingText.textContent = `Kalan: ${selectedItems.length}`; } else if (currentLang === 'de') { overallCountText.textContent = `Abgeschlossen: 0 / ${selectedItems.length}`; overallRemainingText.textContent = `Verbleibend: ${selectedItems.length}`; } else if (currentLang === 'ru') { overallCountText.textContent = `Завершено: 0 / ${selectedItems.length}`; overallRemainingText.textContent = `Осталось: ${selectedItems.length}`; } else if (currentLang === 'ar') { overallCountText.textContent = `اكتمل: 0 / ${selectedItems.length}`; overallRemainingText.textContent = `المتبقي: ${selectedItems.length}`; } else { overallCountText.textContent = `Completed: 0 / ${selectedItems.length}`; overallRemainingText.textContent = `Remaining: ${selectedItems.length}`; } // Disable UI interactive components during download document.querySelectorAll('.track-checkbox, .format-badge-btn, .quality-pill, .format-btn').forEach(el => { el.disabled = true; el.style.pointerEvents = 'none'; }); // Mark selected items as waiting selectedItems.forEach(track => { track.status = 'waiting'; track.progress = 0; const badge = document.getElementById(`badge-${track.id}`); if (badge) { badge.textContent = t('status_connecting'); badge.className = 'status-badge ready'; } }); // Subscribe to progress events from the main process window.api.onDownloadProgress(({ itemId, index, total, percent, status, title, failed }) => { const track = playlistTracks.find(t => t.id === itemId); if (track) { if (percent === 100) { track.status = 'completed'; track.progress = 100; } else if (failed) { track.status = 'error'; } else { track.status = 'downloading'; track.progress = percent; } } // Dynamic row updates const badge = document.getElementById(`badge-${itemId}`); if (badge) { if (percent === 100) { badge.textContent = t('badge_completed'); badge.className = 'status-badge completed'; badge.style.borderColor = 'rgba(0, 255, 135, 0.3)'; badge.style.color = '#00ff87'; badge.style.background = 'rgba(0, 255, 135, 0.1)'; } else if (failed) { badge.textContent = t('badge_error'); badge.className = 'status-badge error'; badge.style.borderColor = 'rgba(255, 77, 77, 0.3)'; badge.style.color = '#ff4d4d'; badge.style.background = 'rgba(255, 77, 77, 0.1)'; } else { badge.textContent = `${t('badge_downloading')} %${percent}`; badge.className = 'status-badge downloading'; } } // Update main footer progress panel let stat = status; if (status.includes('İndiriliyor')) { stat = `${t('btn_downloading')}... %${percent}`; } else if (status.includes('İşleniyor') || status.includes('Dönüştürülüyor')) { stat = t('status_processing'); } downloadStatus.textContent = `[${index + 1}/${total}] ${title}`; downloadPercent.textContent = `${percent}%`; downloadProgressBar.style.width = `${percent}%`; downloadSubtext.textContent = `${t('swal_file_size')}: ${stat}`; // Overall progress, completed vs remaining items live calculator const totalSelected = playlistTracks.filter(t => t.checked).length; const completedCount = playlistTracks.filter(t => t.checked && t.status === 'completed').length; const failedCount = playlistTracks.filter(t => t.checked && t.status === 'error').length; const remainingCount = totalSelected - completedCount - failedCount; // Calculate smooth continuous overall progress percentage const overallVal = ((completedCount + (percent / 100)) / totalSelected) * 100; const overallRound = Math.min(100, Math.round(overallVal)); overallPercent.textContent = `${overallRound}%`; overallProgressBar.style.width = `${overallRound}%`; if (currentLang === 'tr') { overallCountText.textContent = `Tamamlanan: ${completedCount} / ${totalSelected}`; overallRemainingText.textContent = `Kalan: ${remainingCount}${failedCount > 0 ? ` · Hata: ${failedCount}` : ''}`; } else if (currentLang === 'de') { overallCountText.textContent = `Abgeschlossen: ${completedCount} / ${totalSelected}`; overallRemainingText.textContent = `Verbleibend: ${remainingCount}${failedCount > 0 ? ` · Fehler: ${failedCount}` : ''}`; } else if (currentLang === 'ru') { overallCountText.textContent = `Завершено: ${completedCount} / ${totalSelected}`; overallRemainingText.textContent = `Осталось: ${remainingCount}${failedCount > 0 ? ` · Ошибки: ${failedCount}` : ''}`; } else if (currentLang === 'ar') { overallCountText.textContent = `اكتمل: ${completedCount} / ${totalSelected}`; overallRemainingText.textContent = `المتبقي: ${remainingCount}${failedCount > 0 ? ` · خطأ: ${failedCount}` : ''}`; } else { overallCountText.textContent = `Completed: ${completedCount} / ${totalSelected}`; overallRemainingText.textContent = `Remaining: ${remainingCount}${failedCount > 0 ? ` · Error: ${failedCount}` : ''}`; } }); try { const browser = useCookiesCheckbox.checked ? cookieBrowserSelect.value : null; // Map selected items into a simplified API task schema const downloadTasks = selectedItems.map(t => ({ id: t.id, title: t.title, url: t.url, format: t.format, quality: t.quality })); const result = await window.api.downloadMedia(downloadTasks, browser); if (result.success) { downloadStatus.textContent = t('status_completed'); downloadPercent.textContent = '100%'; downloadProgressBar.style.width = '100%'; downloadSubtext.textContent = t('subtext_success'); // Success gradient transitions downloadProgressBar.style.background = 'linear-gradient(90deg, #00ff87 0%, #60efff 100%)'; downloadProgressBar.style.boxShadow = '0 0 15px rgba(0, 255, 135, 0.6)'; const displayTitle = isPlaylistMode ? `${currentVideoData.playlistTitle || 'Playlist'} (${result.stats.completed}/${result.stats.totalItems})` : selectedItems[0].title; const displayUploader = isPlaylistMode ? 'Playlist Queue' : (selectedItems[0].uploader || t('duration_unknown')); const displaySize = isPlaylistMode ? `${result.stats.completed} ${t('badge_completed').toLowerCase()}` : (result.stats.results[0]?.size || 'Bilinmiyor'); const localizedDuration = result.stats.duration .replace('saniye', t('sec_suffix')) .replace('dakika', t('min_suffix')); // Trigger premium SweetAlert2 modal Swal.fire({ title: t('swal_success_title'), html: `

${isPlaylistMode ? t('playlist_title_label') || 'Çalma Listesi:' : t('swal_file_name')} ${displayTitle}

${t('swal_platform_uploader')} ${displayUploader}

${isPlaylistMode ? t('downloaded_files_label') || 'İndirilen Dosyalar:' : t('swal_file_size')} ${displaySize}

${t('swal_duration')} ${localizedDuration}

${t('swal_output_dir')}
${result.stats.outputDir}

`, icon: 'success', background: 'rgba(23, 20, 38, 0.95)', color: '#fff', confirmButtonText: t('swal_btn_open_folder'), showCancelButton: true, cancelButtonText: t('swal_btn_new_download'), confirmButtonColor: '#7000ff', cancelButtonColor: 'rgba(255, 255, 255, 0.1)', backdrop: 'rgba(0, 0, 0, 0.6)' }).then((swalResult) => { if (swalResult.isConfirmed) { window.api.openPath(result.stats.outputDir); } else { backBtn.click(); urlInput.value = ''; downloadProgressPanel.style.display = 'none'; } }); } else { showError(result.error); downloadProgressPanel.style.display = 'none'; } } catch (err) { showError(err.message); downloadProgressPanel.style.display = 'none'; } finally { downloadBtn.disabled = false; backBtn.disabled = false; document.querySelectorAll('.track-checkbox, .format-badge-btn, .quality-pill, .format-btn').forEach(el => { el.disabled = false; el.style.pointerEvents = 'auto'; }); } }); // Helper: Show Error Alert inside Step 1 function showError(msg) { let friendlyMsg = msg; if (msg.includes('Could not copy') && msg.includes('cookie database')) { friendlyMsg = t('err_cookies_locked'); } else if (msg.includes('You need to log in to access this content')) { friendlyMsg = t('err_login_required'); } else if (msg.includes('Failed to decrypt with DPAPI')) { friendlyMsg = t('err_dpapi_fail'); } errorMessage.textContent = `${t('err_prefix')}${friendlyMsg}`; errorCard.style.display = 'flex'; }