diff --git a/static/script.js b/static/script.js index aaf2774..a4d44dd 100644 --- a/static/script.js +++ b/static/script.js @@ -1,51 +1,55 @@ document.addEventListener('DOMContentLoaded', () => { - // --- Selektoren --- const dom = { form: document.getElementById('upload-form'), urlInput: document.getElementById('url'), platformInput: document.getElementById('input-platform'), submitBtn: document.getElementById('submit-button'), - // Detection Feedback badgeContainer: document.getElementById('platform-badge-container'), detectedIcon: document.getElementById('detected-icon'), detectedText: document.getElementById('detected-text'), urlIcon: document.getElementById('url-icon'), - // Options optionsContainer: document.getElementById('options-container'), ytOptions: document.getElementById('youtube-options'), codecSection: document.getElementById('codec-options-section'), - // YouTube Specifics ytFormatRadios: document.querySelectorAll('input[name="yt_format"]'), qualityWrapper: document.getElementById('quality-wrapper'), mp3Select: document.getElementById('mp3_bitrate'), mp4Select: document.getElementById('mp4_quality'), - // Codec Switch codecSwitch: document.getElementById('codec-switch'), codecPreference: document.getElementById('codec_preference'), - // Progress & Result progressContainer: document.getElementById('progress-container'), progressBar: document.getElementById('progress-bar'), statusMessage: document.getElementById('status-message'), - logContent: document.getElementById('log-content'), + logContent: document.getElementById('log-content'), // Für den letzten Log-Eintrag + resultContainer: document.getElementById('result-container'), resultUrl: document.getElementById('result-url'), errorMessage: document.getElementById('error-message'), - // History + // History Elements historyTableBody: document.querySelector('#history-table tbody'), clearHistoryBtn: document.getElementById('clear-history-button'), - contextMenu: document.getElementById('context-menu') + contextMenu: document.getElementById('context-menu'), + + // Pseudo Log Elements + pseudoLogArea: document.getElementById('pseudo-log-area'), + pseudoLogContent: document.getElementById('pseudo-log-content'), + terminalLines: document.querySelectorAll('.terminal-line') }; let currentJobId = null; let pollingInterval = null; + let pseudoLogInterval = null; + let pseudoLogLines = []; + const LOG_ENTRY_DELAY = 80; // Millisekunden zwischen Buchstaben + const PSEUDO_LOG_MAX_LINES = 10; + const HISTORY_EXPIRY_DAYS = 7; - // --- Plattform Definitionen (Regex & Farben) --- const platforms = [ { name: 'SoundCloud', pattern: /soundcloud\.com/, icon: 'fa-soundcloud', color: '#ff5500' }, { name: 'YouTube', pattern: /(youtube\.com|youtu\.be)/, icon: 'fa-youtube', color: '#ff0000' }, @@ -54,33 +58,41 @@ document.addEventListener('DOMContentLoaded', () => { { name: 'Twitter', pattern: /(twitter\.com|x\.com)/, icon: 'fa-x-twitter', color: '#000000' } ]; - // --- Init --- + const pseudoLogCommands = [ + "Initializing environment...", + "Connecting to media servers...", + "Authenticating with unknownMedien.dl API...", + "Scanning URL for media info...", + "Detecting stream type...", + "Negotiating download protocol...", + "Allocating buffer space...", + "Pre-compiling conversion modules...", + "Establishing S3 connection...", + "Generating secure download token...", + "Preparing download stream...", + "Initiating download sequence...", + "Syncing metadata...", + "Finalizing file handles...", + ]; + + // --- Initialisierung --- function init() { setupEventListeners(); - loadHistory(); + loadClientHistory(); + startPseudoLogging(); } + // --- Event Listeners --- function setupEventListeners() { - // Auto-Detect bei Eingabe dom.urlInput.addEventListener('input', handleUrlInput); - - // Form Submit dom.form.addEventListener('submit', handleFormSubmit); - - // YouTube Format Toggle (MP3/MP4) - dom.ytFormatRadios.forEach(radio => { - radio.addEventListener('change', updateYtQualityVisibility); - }); - - // Codec Switch Toggle + dom.ytFormatRadios.forEach(radio => radio.addEventListener('change', updateYtQualityVisibility)); if(dom.codecSwitch) { dom.codecSwitch.addEventListener('change', (e) => { dom.codecPreference.value = e.target.checked ? 'h264' : 'original'; }); } - - // History - if(dom.clearHistoryBtn) dom.clearHistoryBtn.addEventListener('click', clearHistory); + if(dom.clearHistoryBtn) dom.clearHistoryBtn.addEventListener('click', clearClientHistory); document.addEventListener('click', hideContextMenu); if(dom.historyTableBody) { dom.historyTableBody.addEventListener('contextmenu', handleHistoryRightClick); @@ -88,29 +100,23 @@ document.addEventListener('DOMContentLoaded', () => { } } - // --- Logik: URL Erkennung --- + // --- URL Erkennung & Optionen --- function handleUrlInput() { const url = dom.urlInput.value.trim(); const detected = platforms.find(p => p.pattern.test(url)); if (detected) { - // UI Feedback dom.platformInput.value = detected.name; - dom.detectedText.textContent = `${detected.name} erkannt`; - dom.detectedIcon.className = `fab ${detected.icon}`; + dom.detectedText.textContent = `${detected.name} DETECTED`; + dom.detectedIcon.className = `fas ${detected.icon}`; dom.detectedIcon.style.color = detected.color; dom.badgeContainer.style.opacity = '1'; - - // Icon im Input Feld färben - dom.urlIcon.className = `fab ${detected.icon}`; + dom.urlIcon.className = `fas ${detected.icon}`; dom.urlIcon.style.color = detected.color; - - // Optionen anzeigen showOptionsForPlatform(detected.name); } else { - // Reset wenn unbekannt oder leer dom.badgeContainer.style.opacity = '0'; - dom.urlIcon.className = 'fas fa-link text-muted'; + dom.urlIcon.className = 'fas fa-keyboard'; // Default CLI icon dom.urlIcon.style.color = ''; hideAllOptions(); } @@ -118,17 +124,15 @@ document.addEventListener('DOMContentLoaded', () => { function showOptionsForPlatform(platformName) { dom.optionsContainer.classList.add('show'); - - // Reset specific sections dom.ytOptions.classList.add('d-none'); dom.codecSection.classList.add('d-none'); if (platformName === 'YouTube') { dom.ytOptions.classList.remove('d-none'); + updateYtQualityVisibility(); // Initial call } else if (['TikTok', 'Instagram', 'Twitter'].includes(platformName)) { dom.codecSection.classList.remove('d-none'); } else { - // SoundCloud braucht keine extra Optionen in diesem Design (Default ist MP3 Best) dom.optionsContainer.classList.remove('show'); } } @@ -138,38 +142,85 @@ document.addEventListener('DOMContentLoaded', () => { setTimeout(() => { dom.ytOptions.classList.add('d-none'); dom.codecSection.classList.add('d-none'); - }, 500); // Warten bis Animation fertig + }, 500); } function updateYtQualityVisibility() { - const format = document.querySelector('input[name="yt_format"]:checked').value; + const format = document.querySelector('input[name="yt_format"]:checked')?.value; + if (!format) return; + if (format === 'mp3') { dom.mp3Select.classList.remove('d-none'); dom.mp4Select.classList.add('d-none'); dom.codecSection.classList.add('d-none'); - } else { + } else { // mp4 dom.mp3Select.classList.add('d-none'); dom.mp4Select.classList.remove('d-none'); dom.codecSection.classList.remove('d-none'); } } - // --- Logik: Submit & Polling --- + // --- Pseudo Logging Animation --- + function startPseudoLogging() { + let currentLine = 0; + let charIndex = 0; + let commandIndex = 0; + + function displayNextLogLine() { + if (pseudoLogInterval) clearInterval(pseudoLogInterval); + if (commandIndex >= pseudoLogCommands.length) { + pseudoLogInterval = setTimeout(startPseudoLogging, 15000); // Restart after delay + return; + } + + const command = pseudoLogCommands[commandIndex]; + const lineElement = document.createElement('div'); + lineElement.classList.add('cli-output-line'); + lineElement.style.opacity = '0'; // Start hidden + dom.pseudoLogContent.appendChild(lineElement); + + charIndex = 0; + function typeWriter() { + if (charIndex < command.length) { + lineElement.textContent += command.charAt(charIndex); + charIndex++; + setTimeout(typeWriter, LOG_ENTRY_DELAY); + } else { + // Fade in effect for the completed line + let fadeEffect = setInterval(() => { + if (parseFloat(lineElement.style.opacity) < 1) { + lineElement.style.opacity = (parseFloat(lineElement.style.opacity) + 0.1).toString(); + } else { + clearInterval(fadeEffect); + // Add new line after a short pause + commandIndex++; + setTimeout(displayNextLogLine, 300); + } + }, 50); + + // Scroll to bottom + dom.pseudoLogArea.scrollTop = dom.pseudoLogArea.scrollHeight; + } + } + typeWriter(); + } + + // Start the logging process + dom.pseudoLogArea.style.opacity = '1'; // Make log area visible + displayNextLogLine(); + } + + // --- Form Submit & Progress --- async function handleFormSubmit(e) { e.preventDefault(); - - // Validierung - if(dom.badgeContainer.style.opacity === '0' && dom.urlInput.value.length > 0) { - // Fallback für unbekannte URLs -> Standard SoundCloud probieren oder Warnen - dom.platformInput.value = "SoundCloud"; - } + stopPseudoLogging(); // Stop the fake logs - // UI Transition + // Transition to Progress State dom.form.classList.add('d-none'); dom.progressContainer.classList.remove('d-none'); dom.resultContainer.classList.add('d-none'); dom.errorMessage.classList.add('d-none'); - dom.logContent.textContent = "Verbindung wird hergestellt..."; + dom.logContent.textContent = "INITIALIZING..."; dom.progressBar.style.width = "5%"; const formData = new FormData(dom.form); @@ -182,18 +233,21 @@ document.addEventListener('DOMContentLoaded', () => { currentJobId = result.job_id; startPolling(); } else { - showError(result.error || "Serverfehler beim Starten."); + showError(result.error || "Server error during submission."); } } catch (err) { - showError(`Verbindungsfehler: ${err.message}`); + showError(`Connection Error: ${err.message}`); } } function startPolling() { pollingInterval = setInterval(async () => { try { + if (!currentJobId) { stopPolling(); return; } const res = await fetch(`/status?job_id=${currentJobId}`); - if (res.status === 404) { stopPolling(); showError("Job verloren gegangen."); return; } + + if (res.status === 404) { stopPolling(); showError("Job not found (expired or invalid)."); return; } + if (!res.ok) { throw new Error(`HTTP error! status: ${res.status}`); } const status = await res.json(); updateProgressUI(status); @@ -203,88 +257,136 @@ document.addEventListener('DOMContentLoaded', () => { showResult(status.result_url); } else if (status.status === 'error' || status.error) { stopPolling(); - showError(status.error || status.message); + showError(status.error || status.message || "An unknown error occurred."); } } catch (err) { - console.error(err); + console.error("Polling Error:", err); + showError(`Polling failed: ${err.message}`); + stopPolling(); } }, 1500); } function stopPolling() { clearInterval(pollingInterval); + pollingInterval = null; } function updateProgressUI(status) { const pct = status.progress || 0; dom.progressBar.style.width = `${pct}%`; - if(status.message) dom.statusMessage.textContent = status.message; - + // Display the latest log message if(status.logs && status.logs.length > 0) { dom.logContent.textContent = status.logs[status.logs.length - 1]; + } else { + dom.logContent.textContent = status.message || "..."; + } + + if(status.message) { + dom.statusMessage.textContent = status.message.toUpperCase(); } } + // --- Result & Error Display --- function showResult(url) { dom.progressContainer.classList.add('d-none'); dom.resultContainer.classList.remove('d-none'); dom.resultUrl.href = url; - loadHistory(); // Verlauf aktualisieren + dom.resultUrl.textContent = "DOWNLOAD LINK"; // Button text + saveToClientHistory(url); // Save to client-side history } function showError(msg) { dom.progressContainer.classList.add('d-none'); - dom.form.classList.remove('d-none'); // Form wieder zeigen - dom.errorMessage.textContent = msg; + dom.form.classList.remove('d-none'); // Show form again for retry + dom.errorMessage.textContent = msg.toUpperCase(); dom.errorMessage.classList.remove('d-none'); + dom.submitBtn.innerHTML = ' RETRY'; } - // --- History Logic (Minimal) --- - async function loadHistory() { - if(!dom.historyTableBody) return; + // --- Client-Side History --- + function getHistory() { + const history = localStorage.getItem('mediaDlHistory'); + if (!history) return []; try { - const res = await fetch('/history'); - const data = await res.json(); - renderHistory(data); - } catch(e) { console.error("History Error", e); } + const parsed = JSON.parse(history); + // Filter out expired entries + const now = new Date(); + return parsed.filter(item => { + const expiryDate = new Date(item.expiry); + return expiryDate > now; + }); + } catch (e) { + console.error("Error parsing history:", e); + return []; + } + } + + function saveToClientHistory(url, title = "Unknown Title", platform = "Unknown") { + const history = getHistory(); + const expiryDate = new Date(); + expiryDate.setDate(expiryDate.getDate() + HISTORY_EXPIRY_DAYS); + + const newItem = { + url: url, + title: title, + platform: platform, + timestamp: new Date().toISOString(), + expiry: expiryDate.toISOString() + }; + + // Add new item and limit history size (e.g., last 50) + history.unshift(newItem); + if (history.length > 50) history.pop(); + + localStorage.setItem('mediaDlHistory', JSON.stringify(history)); + renderHistory(history); // Update UI immediately + } + + function loadClientHistory() { + const history = getHistory(); + renderHistory(history); } function renderHistory(data) { + if (!dom.historyTableBody) return; dom.historyTableBody.innerHTML = ''; - if(!data || !data.length) { - dom.historyTableBody.innerHTML = 'Kein Verlauf vorhanden.'; + if (!data || data.length === 0) { + dom.historyTableBody.innerHTML = 'NO HISTORY YET.'; return; } data.forEach(item => { const row = document.createElement('tr'); + const itemDate = new Date(item.timestamp); + const expiryDate = new Date(item.expiry); + const timeStr = `${itemDate.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' })}`; + const platformIcon = getPlatformIcon(item.platform); + row.innerHTML = ` - ${item.timestamp.split(' ')[1]} - - ${item.title} - + ${timeStr} + + ${item.title.substring(0, 25)}... + `; dom.historyTableBody.appendChild(row); }); } - - function getIconForPlatform(p) { - if(p === 'SoundCloud') return 'soundcloud text-warning'; - if(p === 'YouTube') return 'youtube text-danger'; - if(p === 'TikTok') return 'tiktok'; - if(p === 'Instagram') return 'instagram text-danger'; - if(p === 'Twitter') return 'x-twitter'; - return 'question'; + + function getPlatformIcon(p) { + const platformData = platforms.find(pl => pl.name === p); + if (platformData) return { class: `fas ${platformData.icon}`, color: platformData.color }; + return { class: `fas fa-question-circle`, color: '#808080' }; } - async function clearHistory() { - if(confirm("Verlauf wirklich löschen?")) { - await fetch('/clear_history', {method: 'POST'}); - loadHistory(); + async function clearClientHistory() { + if(confirm("Clear ALL history? This cannot be undone.")) { + localStorage.removeItem('mediaDlHistory'); + renderHistory([]); // Clear the table } } - // Context Menu Logic + // --- Context Menu --- let contextUrl = null; function handleHistoryRightClick(e) { const linkBtn = e.target.closest('a[data-url]'); @@ -300,12 +402,29 @@ document.addEventListener('DOMContentLoaded', () => { function handleContextMenuClick(e) { if(e.target.closest('[data-action="copy"]') && contextUrl) { navigator.clipboard.writeText(contextUrl); - dom.contextMenu.classList.add('d-none'); + // Temporary notification for copy action + const notification = document.createElement('div'); + notification.textContent = 'Link Copied!'; + notification.style.cssText = `position: fixed; top: 50%; left: 50%; transform: translate(-50%, -50%); background: #39ff14; color: #0a0a0a; padding: 10px 20px; border-radius: 5px; z-index: 10000; font-weight: bold; opacity: 0; transition: opacity 0.5s; font-family: 'Consolas', monospace;`; + document.body.appendChild(notification); + setTimeout(() => notification.style.opacity = '1', 10); + setTimeout(() => { + notification.style.opacity = '0'; + setTimeout(() => notification.remove(), 500); + }, 1500); } + hideContextMenu(); } - function hideContextMenu() { if(dom.contextMenu) dom.contextMenu.classList.add('d-none'); + contextUrl = null; + } + + // --- Stop Pseudo Logging --- + function stopPseudoLogging() { + if (pseudoLogInterval) clearInterval(pseudoLogInterval); + // Keep logs visible until progress starts + // Optionally hide it after transition: setTimeout(() => dom.pseudoLogArea.style.display = 'none', 800); } init(); diff --git a/static/style.css b/static/style.css index 9ce719c..0346327 100644 --- a/static/style.css +++ b/static/style.css @@ -1,180 +1,281 @@ -/* Modern Reset & Base */ +/* Terminal/CLI Theme - Dark & Monospace */ + :root { - --primary-color: #6366f1; /* Indigo */ - --secondary-color: #a855f7; /* Purple */ - --bg-dark: #0f172a; - --text-main: #1e293b; + --bg-dark: #0a0a0a; + --text-color: #00ff00; /* Classic Green */ + --text-muted: #008000; /* Darker Green */ + --text-highlight: #39ff14; /* Bright Green */ + --accent-primary: #00ff00; + --accent-secondary: #39ff14; + --input-bg: rgba(0, 255, 0, 0.05); + --input-border: rgba(0, 255, 0, 0.2); + --card-bg: rgba(10, 10, 10, 0.7); + --card-border: rgba(0, 255, 0, 0.1); + --shadow-color: rgba(0, 255, 0, 0.2); + --progress-bg: rgba(0, 255, 0, 0.15); + --progress-bar: linear-gradient(90deg, #00ff00, #39ff14); + --error-bg: rgba(255, 0, 0, 0.1); + --error-border: rgba(255, 0, 0, 0.3); + --success-color: #39ff14; } body { - font-family: 'Inter', sans-serif; - background-color: #f1f5f9; - color: var(--text-main); - overflow-x: hidden; + background-color: var(--bg-dark); + color: var(--text-color); + font-family: 'Consolas', 'Monaco', 'Andale Mono', 'Ubuntu Mono', monospace; + line-height: 1.5; + overflow-x: hidden; /* Verhindert horizontale Scrollbalken */ } -/* Animated Background Shapes */ -.bg-shape { +/* Terminal Background Animation */ +.terminal-backdrop { position: fixed; - border-radius: 50%; - filter: blur(80px); - z-index: -1; - opacity: 0.6; - animation: float 10s infinite ease-in-out; -} - -.shape-1 { - top: -10%; - left: -10%; - width: 600px; - height: 600px; - background: linear-gradient(to right, var(--primary-color), var(--secondary-color)); -} - -.shape-2 { - bottom: -10%; - right: -10%; - width: 500px; - height: 500px; - background: linear-gradient(to left, #3b82f6, #06b6d4); - animation-delay: -5s; -} - -@keyframes float { - 0%, 100% { transform: translate(0, 0); } - 50% { transform: translate(30px, 20px); } -} - -/* Hero Card */ -.hero-card { - background: rgba(255, 255, 255, 0.85); - backdrop-filter: blur(20px); - -webkit-backdrop-filter: blur(20px); - border: 1px solid rgba(255, 255, 255, 0.5); - border-radius: 24px; - padding: 3rem; + top: 0; + left: 0; width: 100%; - max-width: 650px; - box-shadow: 0 20px 40px -10px rgba(0, 0, 0, 0.1); + height: 100%; + background: var(--bg-dark); + overflow: hidden; + z-index: -1; } -.text-gradient { - background: linear-gradient(135deg, var(--primary-color), var(--secondary-color)); +.terminal-line { + position: absolute; + background: linear-gradient(to right, rgba(0, 255, 0, 0.02), rgba(57, 255, 20, 0.01)); + animation: scanline 15s linear infinite; +} +.terminal-line:nth-child(1) { top: 10%; width: 80%; left: 10%; animation-duration: 12s; } +.terminal-line:nth-child(2) { top: 50%; width: 60%; left: 20%; animation-duration: 18s; animation-delay: -6s; } +.terminal-line:nth-child(3) { top: 80%; width: 70%; left: 15%; animation-duration: 14s; animation-delay: -4s; } + +@keyframes scanline { + 0% { opacity: 0; transform: translateX(-100%); } + 50% { opacity: 0.5; } + 100% { opacity: 0; transform: translateX(100%); } +} + +/* Main Container */ +.container { + z-index: 2; +} + +/* CLI Card */ +.cli-card { + background: var(--card-bg); + border: 1px solid var(--card-border); + border-radius: 8px; + padding: 2rem 2.5rem; + width: 100%; + max-width: 700px; + box-shadow: 0 0 15px var(--shadow-color); + transition: transform 0.2s ease, box-shadow 0.2s ease; +} +.cli-card:hover { + transform: translateY(-5px); + box-shadow: 0 10px 30px var(--shadow-color); +} + +.logo-text { + background: linear-gradient(90deg, var(--accent-primary), var(--accent-secondary)); -webkit-background-clip: text; -webkit-text-fill-color: transparent; + text-shadow: 0 0 10px var(--shadow-color); } -/* Input Styling */ -.main-input-group { - border-radius: 16px; +/* CLI Input Group */ +.cli-input-group { + background: var(--input-bg); + border: 1px solid var(--input-border); + border-radius: 6px; overflow: hidden; - transition: transform 0.2s, box-shadow 0.2s; } -.main-input-group:focus-within { - transform: translateY(-2px); - box-shadow: 0 10px 25px -5px rgba(99, 102, 241, 0.3) !important; -} - -.main-input-group input { - font-size: 1.1rem; - background: white; -} -.main-input-group input:focus { - box-shadow: none; - background: white; -} - -#submit-button { - background: linear-gradient(135deg, var(--primary-color), var(--secondary-color)); +.cli-input-group .input-group-text { + background: transparent; border: none; - transition: filter 0.2s; -} -#submit-button:hover { - filter: brightness(110%); + color: var(--text-muted); } -/* Options Animation */ -.options-wrapper { - overflow: hidden; - max-height: 0; - opacity: 0; - transition: max-height 0.5s ease-in-out, opacity 0.4s ease-in-out; +.cli-input { + color: var(--text-color); + caret-color: var(--text-highlight); + border-radius: 0; /* Ensures no rounded corners inside */ +} +.cli-input::placeholder { + color: var(--text-muted); + opacity: 0.7; } -.options-wrapper.show { - max-height: 500px; /* Groß genug */ - opacity: 1; +.cli-button { + background: var(--accent-primary); + border: none; + color: var(--bg-dark); + font-weight: bold; + text-transform: uppercase; + letter-spacing: 1px; + transition: background 0.2s ease, transform 0.2s ease; +} +.cli-button:hover { + background: var(--accent-secondary); + transform: translateX(2px); } -.animate-options { - animation: fadeIn 0.5s ease; +/* CLI Badge */ +.cli-badge { + background: rgba(0, 255, 0, 0.1); + border: 1px solid var(--input-border); + color: var(--text-highlight); +} +.cli-icon { + color: var(--text-highlight); } -/* Glass Button */ -.btn-white-glass { - background: rgba(255, 255, 255, 0.5); - border: 1px solid rgba(255, 255, 255, 0.2); - backdrop-filter: blur(5px); - color: var(--text-main); - transition: all 0.2s; +/* CLI Output & Options */ +.cli-output-line { + font-family: 'Consolas', monospace; + text-align: left; + color: var(--text-muted); + font-size: 0.9rem; } -.btn-white-glass:hover { - background: white; - transform: translateY(-2px); - box-shadow: 0 5px 15px rgba(0,0,0,0.05); +.cli-highlight { + color: var(--text-highlight); + animation: blink 1s infinite step-end; +} +@keyframes blink { + 0%, 100% { opacity: 1; } + 50% { opacity: 0; } +} + +.cli-output-box { + background: rgba(10, 20, 10, 0.5); + border: 1px solid var(--card-border); + padding: 1rem; + border-radius: 4px; + text-align: left; + overflow: hidden; /* Important for smooth fade-in */ +} +.cli-output-box pre { + margin: 0; + white-space: pre-wrap; + word-wrap: break-word; +} + +.cli-card-inner { + background: rgba(20, 40, 20, 0.4); + border-radius: 6px; +} + +.cli-section { + animation: fadeInUpCli 0.5s ease-out; +} +@keyframes fadeInUpCli { + from { opacity: 0; transform: translateY(15px); } + to { opacity: 1; transform: translateY(0); } +} + +/* Options Styling */ +.form-label.small { font-size: 0.8rem; font-weight: bold; color: var(--text-muted); } +.text-success { color: var(--accent-secondary) !important; } +.btn-outline-success { border-color: var(--accent-primary); color: var(--accent-primary); } +.btn-outline-success:hover { background-color: var(--accent-primary); color: var(--bg-dark); } +.btn-check:checked + .btn-outline-success { background-color: var(--accent-primary); border-color: var(--accent-primary); color: var(--bg-dark); } + +.cli-select { + background: var(--input-bg); + border: 1px solid var(--input-border); + color: var(--text-color); + border-radius: 4px; +} + +/* Switch Button */ +.form-switch .form-check-input { + background-color: var(--text-muted); + border-color: var(--input-border); + transition: background-color 0.2s, border-color 0.2s; +} +.form-switch .form-check-input:checked { + background-color: var(--accent-primary); + border-color: var(--accent-secondary); } /* Progress Bar */ -.bg-gradient-primary { - background: linear-gradient(90deg, var(--primary-color), #06b6d4); - background-size: 200% 100%; - animation: gradientMove 2s linear infinite; +.cli-progress { + height: 10px; + border-radius: 5px; + background: var(--progress-bg); + border: 1px solid var(--input-border); +} +.cli-progress-bar { + background: var(--progress-bar); + border-radius: 5px; + transition: width 0.3s ease-out; + box-shadow: 0 0 5px var(--shadow-color); } -@keyframes gradientMove { - 0% { background-position: 100% 0; } - 100% { background-position: -100% 0; } +/* Alerts & Modals */ +.cli-alert { + background: var(--error-bg); + border: 1px solid var(--error-border); + color: var(--text-color); + border-radius: 4px; +} +.cli-modal-content { + background-color: var(--bg-dark); + border: 1px solid var(--card-border); + border-radius: 12px; +} +.cli-modal-header, .cli-modal-footer { + background-color: rgba(15, 15, 15, 0.8); + border: none; +} +.cli-table { + background-color: transparent; +} +.cli-table th { + background-color: rgba(10, 20, 10, 0.6); + color: var(--text-highlight); + border: none !important; + font-size: 0.8rem; +} +.cli-table td { + border-top: 1px solid var(--card-border); + color: var(--text-muted); +} +.cli-table tbody tr:hover td { + background-color: rgba(0, 255, 0, 0.1) !important; +} +.cli-table td a { + color: var(--accent-secondary); } -.animate-pulse { - animation: pulse 1.5s infinite; +/* Context Menu */ +.cli-context-menu { + background: var(--card-bg); + border: 1px solid var(--card-border); + border-radius: 6px; } -@keyframes pulse { - 0%, 100% { opacity: 1; } - 50% { opacity: 0.5; } -} - -/* Helpers */ -.hover-lift:hover { - transform: translateY(-3px); -} -.fade-in-up { - animation: fadeInUp 0.8s ease-out; +.cli-context-menu .list-group-item { + background: transparent; + color: var(--text-color); + border: none; + transition: background 0.2s ease; +} +.cli-context-menu .list-group-item:hover { + background: rgba(0, 255, 0, 0.1); } +/* General Animation */ +.fade-in-up { animation: fadeInUp 0.8s ease-out forwards; } @keyframes fadeInUp { from { opacity: 0; transform: translateY(20px); } to { opacity: 1; transform: translateY(0); } } -.extra-small { - font-size: 0.75rem; -} +.animate-options { animation: fadeInCli 0.5s ease-out forwards; } -/* Context Menu */ -.context-menu { - z-index: 9999; - min-width: 150px; - border-radius: 8px; - overflow: hidden; - border: none; -} -.context-menu .list-group-item { - cursor: pointer; - font-size: 0.9rem; - border: none; -} -.context-menu .list-group-item:hover { - background-color: #f3f4f6; +/* Responsive adjustments */ +@media (max-width: 768px) { + .cli-card { padding: 1.5rem; } + .logo-text { font-size: 2.5rem; } + .input-group-lg .form-control, .input-group-lg .form-select, .input-group-lg .input-group-text { font-size: 1rem; } } \ No newline at end of file diff --git a/templates/index.html b/templates/index.html index b3c92b1..cc379d8 100644 --- a/templates/index.html +++ b/templates/index.html @@ -2,95 +2,83 @@ - medien.mrunk.de - Smart Downloader + unknownMedien.dl - Smart Downloader - - - -
-
+ +
+
+
+
+
-
+
- -
-

Media Downloader

-

Füge einfach deinen Link ein. Wir erledigen den Rest.

+ +
+

unknownMedien.dl

+

Der schnelle Weg zu deinen Medien.

- - -
- - - - -
- -
- - - Warte auf Link... + +
+ + + SCANNING...
- -
-
+ +
+
- -
-
YouTube Einstellungen
+
+
YOUTUBE CONFIG
- +
- - + - +
- - - + - - +
- -
+
- -
Konvertiert zu H.264 (für WhatsApp/alte Geräte).
+ +
H.264 (for max compatibility)
@@ -98,22 +86,25 @@
-
- - + +
+
+
+ + - +
-
Starte...
-
-
+
+
+
-
Details...
+
@@ -121,25 +112,20 @@
-

Fertig!

- - Datei herunterladen +

COMPLETE.

+
+ DOWNLOAD
- -
- -
- © 2025 MrUnknownDE • Links gültig für 7 Tage -
+ +
+ © 2025 unknownMedien.dl • Links expire in 7 days.
@@ -147,20 +133,20 @@