diff --git a/static/script.js b/static/script.js index a4d44dd..0160629 100644 --- a/static/script.js +++ b/static/script.js @@ -1,431 +1,296 @@ document.addEventListener('DOMContentLoaded', () => { const dom = { + // Form & Main form: document.getElementById('upload-form'), urlInput: document.getElementById('url'), - platformInput: document.getElementById('input-platform'), submitBtn: document.getElementById('submit-button'), + platformInput: document.getElementById('input-platform'), - badgeContainer: document.getElementById('platform-badge-container'), - detectedIcon: document.getElementById('detected-icon'), + // Detection UI + detectionArea: document.getElementById('detection-area'), detectedText: document.getElementById('detected-text'), + detectedIcon: document.getElementById('detected-icon'), urlIcon: document.getElementById('url-icon'), - + + // Options optionsContainer: document.getElementById('options-container'), ytOptions: document.getElementById('youtube-options'), codecSection: document.getElementById('codec-options-section'), - - ytFormatRadios: document.querySelectorAll('input[name="yt_format"]'), - qualityWrapper: document.getElementById('quality-wrapper'), + ytRadios: document.querySelectorAll('input[name="yt_format"]'), mp3Select: document.getElementById('mp3_bitrate'), mp4Select: document.getElementById('mp4_quality'), - codecSwitch: document.getElementById('codec-switch'), - codecPreference: document.getElementById('codec_preference'), - - progressContainer: document.getElementById('progress-container'), + codecPref: document.getElementById('codec_preference'), + + // Views + processView: document.getElementById('process-view'), + resultView: document.getElementById('result-view'), + formContainer: document.querySelector('.form-container'), + + // Progress & Logs + statusMsg: document.getElementById('status-message'), progressBar: document.getElementById('progress-bar'), - statusMessage: document.getElementById('status-message'), - logContent: document.getElementById('log-content'), // Für den letzten Log-Eintrag + logContent: document.getElementById('pseudo-log-content'), - resultContainer: document.getElementById('result-container'), + // Result resultUrl: document.getElementById('result-url'), - errorMessage: document.getElementById('error-message'), - - // History Elements - historyTableBody: document.querySelector('#history-table tbody'), - clearHistoryBtn: document.getElementById('clear-history-button'), - contextMenu: document.getElementById('context-menu'), + errorMsg: document.getElementById('error-message'), - // Pseudo Log Elements - pseudoLogArea: document.getElementById('pseudo-log-area'), - pseudoLogContent: document.getElementById('pseudo-log-content'), - terminalLines: document.querySelectorAll('.terminal-line') + // History + historyTableBody: document.querySelector('#history-table tbody'), + clearHistoryBtn: document.getElementById('clear-history-button') }; 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; const platforms = [ { name: 'SoundCloud', pattern: /soundcloud\.com/, icon: 'fa-soundcloud', color: '#ff5500' }, { name: 'YouTube', pattern: /(youtube\.com|youtu\.be)/, icon: 'fa-youtube', color: '#ff0000' }, - { name: 'TikTok', pattern: /tiktok\.com/, icon: 'fa-tiktok', color: '#000000' }, + { name: 'TikTok', pattern: /tiktok\.com/, icon: 'fa-tiktok', color: '#fe2c55' }, { name: 'Instagram', pattern: /instagram\.com/, icon: 'fa-instagram', color: '#E1306C' }, - { name: 'Twitter', pattern: /(twitter\.com|x\.com)/, icon: 'fa-x-twitter', color: '#000000' } + { name: 'Twitter', pattern: /(twitter\.com|x\.com)/, icon: 'fa-x-twitter', color: '#fff' } ]; - 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...", + const pseudoLogs = [ + "Connecting to media node...", + "Handshaking with API...", + "Resolving stream URL...", + "Allocating buffer...", + "Starting download stream...", + "Processing data chunks...", + "Verifying integrity...", + "Optimizing container...", + "Finalizing upload..." ]; - // --- Initialisierung --- function init() { - setupEventListeners(); - loadClientHistory(); - startPseudoLogging(); + setupEvents(); + loadHistory(); } - // --- Event Listeners --- - function setupEventListeners() { + function setupEvents() { dom.urlInput.addEventListener('input', handleUrlInput); - dom.form.addEventListener('submit', handleFormSubmit); - dom.ytFormatRadios.forEach(radio => radio.addEventListener('change', updateYtQualityVisibility)); + dom.form.addEventListener('submit', handleSubmit); + + dom.ytRadios.forEach(r => r.addEventListener('change', updateYtOptions)); if(dom.codecSwitch) { - dom.codecSwitch.addEventListener('change', (e) => { - dom.codecPreference.value = e.target.checked ? 'h264' : 'original'; + dom.codecSwitch.addEventListener('change', e => { + dom.codecPref.value = e.target.checked ? 'h264' : 'original'; }); } - if(dom.clearHistoryBtn) dom.clearHistoryBtn.addEventListener('click', clearClientHistory); - document.addEventListener('click', hideContextMenu); - if(dom.historyTableBody) { - dom.historyTableBody.addEventListener('contextmenu', handleHistoryRightClick); - dom.contextMenu.addEventListener('click', handleContextMenuClick); - } + + if(dom.clearHistoryBtn) dom.clearHistoryBtn.addEventListener('click', clearHistory); } - // --- URL Erkennung & Optionen --- + // --- Detection --- function handleUrlInput() { const url = dom.urlInput.value.trim(); const detected = platforms.find(p => p.pattern.test(url)); - + if (detected) { dom.platformInput.value = detected.name; - dom.detectedText.textContent = `${detected.name} DETECTED`; - dom.detectedIcon.className = `fas ${detected.icon}`; - dom.detectedIcon.style.color = detected.color; - dom.badgeContainer.style.opacity = '1'; - dom.urlIcon.className = `fas ${detected.icon}`; + dom.detectedText.textContent = detected.name + " erkannt"; + dom.detectedIcon.className = `fab ${detected.icon}`; + dom.detectionArea.style.opacity = '1'; + dom.detectionArea.style.transform = 'translateY(0)'; + + // Icon in Input + dom.urlIcon.className = `fab ${detected.icon}`; dom.urlIcon.style.color = detected.color; - showOptionsForPlatform(detected.name); + + showOptions(detected.name); } else { - dom.badgeContainer.style.opacity = '0'; - dom.urlIcon.className = 'fas fa-keyboard'; // Default CLI icon - dom.urlIcon.style.color = ''; - hideAllOptions(); + if(url.length === 0) { + dom.detectionArea.style.opacity = '0'; + dom.detectionArea.style.transform = 'translateY(-10px)'; + dom.urlIcon.className = 'fas fa-link'; + dom.urlIcon.style.color = ''; + } + // Keep options hidden if no match } } - function showOptionsForPlatform(platformName) { - dom.optionsContainer.classList.add('show'); + function showOptions(platform) { dom.ytOptions.classList.add('d-none'); dom.codecSection.classList.add('d-none'); - - if (platformName === 'YouTube') { + + if (platform === 'YouTube') { dom.ytOptions.classList.remove('d-none'); - updateYtQualityVisibility(); // Initial call - } else if (['TikTok', 'Instagram', 'Twitter'].includes(platformName)) { + updateYtOptions(); + } else if (['TikTok', 'Instagram', 'Twitter'].includes(platform)) { dom.codecSection.classList.remove('d-none'); - } else { - dom.optionsContainer.classList.remove('show'); } } - function hideAllOptions() { - dom.optionsContainer.classList.remove('show'); - setTimeout(() => { - dom.ytOptions.classList.add('d-none'); - dom.codecSection.classList.add('d-none'); - }, 500); - } - - function updateYtQualityVisibility() { - const format = document.querySelector('input[name="yt_format"]:checked')?.value; - if (!format) return; - + function updateYtOptions() { + const format = document.querySelector('input[name="yt_format"]:checked').value; if (format === 'mp3') { dom.mp3Select.classList.remove('d-none'); dom.mp4Select.classList.add('d-none'); - dom.codecSection.classList.add('d-none'); - } else { // mp4 + } else { dom.mp3Select.classList.add('d-none'); dom.mp4Select.classList.remove('d-none'); - dom.codecSection.classList.remove('d-none'); } } - // --- 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) { + // --- Processing --- + async function handleSubmit(e) { e.preventDefault(); - stopPseudoLogging(); // Stop the fake logs - - // 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 = "INITIALIZING..."; - dom.progressBar.style.width = "5%"; + + // UI Switch + dom.formContainer.classList.add('d-none'); + dom.processView.classList.remove('d-none'); + dom.resultView.classList.add('d-none'); + dom.errorMsg.classList.add('d-none'); + + startPseudoLogs(); const formData = new FormData(dom.form); - try { - const response = await fetch('/start_download', { method: 'POST', body: formData }); - const result = await response.json(); + const res = await fetch('/start_download', { method: 'POST', body: formData }); + const data = await res.json(); - if (response.ok && result.job_id) { - currentJobId = result.job_id; + if (res.ok && data.job_id) { + currentJobId = data.job_id; startPolling(); } else { - showError(result.error || "Server error during submission."); + throw new Error(data.error || "Start fehlgeschlagen"); } } catch (err) { - showError(`Connection Error: ${err.message}`); + showError(err.message); } } function startPolling() { pollingInterval = setInterval(async () => { try { - if (!currentJobId) { stopPolling(); return; } + if(!currentJobId) return; const res = await fetch(`/status?job_id=${currentJobId}`); - - if (res.status === 404) { stopPolling(); showError("Job not found (expired or invalid)."); return; } - if (!res.ok) { throw new Error(`HTTP error! status: ${res.status}`); } + if (res.status === 404) { showError("Job nicht gefunden"); return; } const status = await res.json(); - updateProgressUI(status); + + // Update UI + dom.progressBar.style.width = (status.progress || 0) + "%"; + if(status.message) dom.statusMsg.textContent = status.message.toUpperCase(); if (status.status === 'completed') { - stopPolling(); - showResult(status.result_url); + finishJob(status.result_url); } else if (status.status === 'error' || status.error) { - stopPolling(); - showError(status.error || status.message || "An unknown error occurred."); + showError(status.error || status.message); } - } catch (err) { - console.error("Polling Error:", err); - showError(`Polling failed: ${err.message}`); - stopPolling(); + } catch (e) { + console.error(e); } - }, 1500); + }, 1000); } - function stopPolling() { + function finishJob(url) { clearInterval(pollingInterval); - pollingInterval = null; - } - - function updateProgressUI(status) { - const pct = status.progress || 0; - dom.progressBar.style.width = `${pct}%`; + clearInterval(pseudoLogInterval); - // 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.processView.classList.add('d-none'); + dom.resultView.classList.remove('d-none'); dom.resultUrl.href = url; - dom.resultUrl.textContent = "DOWNLOAD LINK"; // Button text - saveToClientHistory(url); // Save to client-side history + + saveHistory(url); } function showError(msg) { - dom.progressContainer.classList.add('d-none'); - 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'; - } - - // --- Client-Side History --- - function getHistory() { - const history = localStorage.getItem('mediaDlHistory'); - if (!history) return []; - try { - 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() - }; + clearInterval(pollingInterval); + clearInterval(pseudoLogInterval); - // 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 + dom.processView.classList.add('d-none'); + dom.formContainer.classList.remove('d-none'); // Back to form + dom.errorMsg.textContent = msg; + dom.errorMsg.classList.remove('d-none'); } - function loadClientHistory() { - const history = getHistory(); - renderHistory(history); + // --- Pseudo Logs (CLI Effect) --- + function startPseudoLogs() { + dom.logContent.innerHTML = ''; + let index = 0; + + function addLine() { + if (index >= pseudoLogs.length) index = 0; // Loop or stop + const div = document.createElement('div'); + div.className = 'terminal-line'; + div.textContent = "> " + pseudoLogs[index]; + dom.logContent.appendChild(div); + + // Auto scroll + const win = document.querySelector('.terminal-window .terminal-body'); + win.scrollTop = win.scrollHeight; + + index++; + } + + addLine(); // First one immediate + pseudoLogInterval = setInterval(addLine, 2000); + } + + // --- Local History --- + function loadHistory() { + const raw = localStorage.getItem('mdl_history'); + if(!raw) { + dom.historyTableBody.innerHTML = 'Kein Verlauf vorhanden'; + return; + } + + let data = JSON.parse(raw); + // Clean expired (>7 days) + const now = Date.now(); + data = data.filter(i => (now - i.ts) < (7 * 24 * 60 * 60 * 1000)); + localStorage.setItem('mdl_history', JSON.stringify(data)); + + renderHistory(data); } function renderHistory(data) { - if (!dom.historyTableBody) return; dom.historyTableBody.innerHTML = ''; - 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 = ` - ${timeStr} - - ${item.title.substring(0, 25)}... - + const date = new Date(item.ts); + const time = date.toLocaleTimeString([], {hour:'2-digit', minute:'2-digit'}); + + const tr = document.createElement('tr'); + tr.innerHTML = ` + ${time} + ${item.platform} + ${item.title || 'Unbekannt'} + + + + + `; - dom.historyTableBody.appendChild(row); + dom.historyTableBody.appendChild(tr); }); } - - 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' }; + + function saveHistory(url) { + const pf = dom.platformInput.value; + const entry = { + url: url, + platform: pf, + title: pf + " Download", // Could be improved if backend sent title + ts: Date.now() + }; + + let data = JSON.parse(localStorage.getItem('mdl_history') || '[]'); + data.unshift(entry); + if(data.length > 20) data.pop(); + localStorage.setItem('mdl_history', JSON.stringify(data)); + loadHistory(); } - async function clearClientHistory() { - if(confirm("Clear ALL history? This cannot be undone.")) { - localStorage.removeItem('mediaDlHistory'); - renderHistory([]); // Clear the table + function clearHistory() { + if(confirm("Verlauf wirklich löschen?")) { + localStorage.removeItem('mdl_history'); + loadHistory(); } } - // --- Context Menu --- - let contextUrl = null; - function handleHistoryRightClick(e) { - const linkBtn = e.target.closest('a[data-url]'); - if(linkBtn) { - e.preventDefault(); - contextUrl = linkBtn.dataset.url; - dom.contextMenu.style.top = `${e.pageY}px`; - dom.contextMenu.style.left = `${e.pageX}px`; - dom.contextMenu.classList.remove('d-none'); - } - } - - function handleContextMenuClick(e) { - if(e.target.closest('[data-action="copy"]') && contextUrl) { - navigator.clipboard.writeText(contextUrl); - // 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(); }); \ No newline at end of file diff --git a/static/style.css b/static/style.css index 0346327..95d2d71 100644 --- a/static/style.css +++ b/static/style.css @@ -1,281 +1,296 @@ -/* Terminal/CLI Theme - Dark & Monospace */ - +/* --- Variables & Reset --- */ :root { - --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; + --bg-deep: #0f0c29; + --bg-mid: #302b63; + --bg-light: #24243e; + + --primary: #8b5cf6; /* Violet 500 */ + --primary-glow: #a78bfa; + --accent: #d8b4fe; + + --glass-bg: rgba(255, 255, 255, 0.05); + --glass-border: rgba(255, 255, 255, 0.1); + --glass-shadow: 0 8px 32px 0 rgba(0, 0, 0, 0.37); + + --font-ui: 'Outfit', sans-serif; + --font-mono: 'JetBrains Mono', monospace; } body { - 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 */ + font-family: var(--font-ui); + background-color: var(--bg-deep); + color: #fff; + overflow-x: hidden; } -/* Terminal Background Animation */ -.terminal-backdrop { +/* --- Animated Background --- */ +.bg-gradient-animate { position: fixed; - top: 0; - left: 0; - width: 100%; - height: 100%; - background: var(--bg-dark); - overflow: hidden; + top: 0; left: 0; width: 100%; height: 100%; + background: linear-gradient(-45deg, #0f0c29, #302b63, #24243e, #1a1a2e); + background-size: 400% 400%; + animation: gradientBG 15s ease infinite; + z-index: -2; +} + +@keyframes gradientBG { + 0% { background-position: 0% 50%; } + 50% { background-position: 100% 50%; } + 100% { background-position: 0% 50%; } +} + +.bg-glow { + position: fixed; + border-radius: 50%; + filter: blur(100px); + opacity: 0.4; z-index: -1; + animation: floatBlob 10s ease-in-out infinite; } -.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%); } +.bg-glow-1 { + top: -10%; left: -10%; width: 50vw; height: 50vw; + background: var(--primary); } -/* Main Container */ -.container { - z-index: 2; +.bg-glow-2 { + bottom: -10%; right: -10%; width: 40vw; height: 40vw; + background: #4c1d95; /* Darker Purple */ + animation-delay: -5s; } -/* CLI Card */ -.cli-card { - background: var(--card-bg); - border: 1px solid var(--card-border); - border-radius: 8px; - padding: 2rem 2.5rem; +@keyframes floatBlob { + 0%, 100% { transform: translate(0, 0); } + 50% { transform: translate(30px, -20px); } +} + +/* --- Typography & Helpers --- */ +.text-primary-light { color: var(--accent) !important; } +.tracking-wide { letter-spacing: 0.05em; } +.max-w-lg { max-width: 600px; } + +/* --- Hero Section --- */ +.hero-wrapper { + max-width: 900px; 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); + padding: 0 1rem; } -.logo-text { - background: linear-gradient(90deg, var(--accent-primary), var(--accent-secondary)); +.hero-title { + font-size: 4rem; + font-weight: 700; + background: linear-gradient(to right, #fff, var(--accent)); -webkit-background-clip: text; -webkit-text-fill-color: transparent; - text-shadow: 0 0 10px var(--shadow-color); + text-shadow: 0 0 30px rgba(139, 92, 246, 0.3); } -/* CLI Input Group */ -.cli-input-group { - background: var(--input-bg); - border: 1px solid var(--input-border); - border-radius: 6px; +@media (max-width: 768px) { + .hero-title { font-size: 2.5rem; } +} + +/* --- Glass Input --- */ +.glass-input-wrapper { + background: rgba(0, 0, 0, 0.3); + backdrop-filter: blur(10px); + border: 1px solid var(--glass-border); + border-radius: 16px; + transition: transform 0.3s, box-shadow 0.3s, border-color 0.3s; overflow: hidden; } -.cli-input-group .input-group-text { - background: transparent; - border: none; - color: var(--text-muted); +.glass-input-wrapper:focus-within { + transform: translateY(-2px); + box-shadow: 0 15px 40px rgba(139, 92, 246, 0.2); + border-color: var(--primary); } -.cli-input { - color: var(--text-color); - caret-color: var(--text-highlight); - border-radius: 0; /* Ensures no rounded corners inside */ +.form-control::placeholder { color: rgba(255, 255, 255, 0.3); } +.form-control:focus { box-shadow: none; } + +.btn-action { + background: var(--primary); + color: white; + border: none; + border-radius: 0; + transition: filter 0.2s; + min-width: 120px; } -.cli-input::placeholder { - color: var(--text-muted); - opacity: 0.7; +.btn-action:hover { + filter: brightness(1.2); + color: white; } -.cli-button { - background: var(--accent-primary); - border: none; - color: var(--bg-dark); - font-weight: bold; - text-transform: uppercase; +/* --- Detection & Options --- */ +.glass-badge { + background: rgba(255, 255, 255, 0.1); + backdrop-filter: blur(5px); + border: 1px solid var(--glass-border); + font-weight: 300; letter-spacing: 1px; - transition: background 0.2s ease, transform 0.2s ease; -} -.cli-button:hover { - background: var(--accent-secondary); - transform: translateX(2px); } -/* 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); +.options-drawer { + max-width: 600px; } -/* CLI Output & Options */ -.cli-output-line { - font-family: 'Consolas', monospace; - text-align: left; - color: var(--text-muted); - font-size: 0.9rem; -} -.cli-highlight { - color: var(--text-highlight); - animation: blink 1s infinite step-end; -} -@keyframes blink { - 0%, 100% { opacity: 1; } - 50% { opacity: 0; } +.option-group { + animation: slideDown 0.4s ease forwards; } -.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); } +@keyframes slideDown { + from { opacity: 0; transform: translateY(-10px); } 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; +/* Custom Switch Toggle (MP3/MP4) */ +.switch-toggle { + display: flex; + background: rgba(0,0,0,0.4); + border-radius: 8px; + position: relative; + padding: 4px; + width: fit-content; + border: 1px solid var(--glass-border); } - -/* 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; +.switch-toggle input { display: none; } +.switch-toggle label { + padding: 6px 16px; + cursor: pointer; + z-index: 2; + font-size: 0.9rem; + color: rgba(255,255,255,0.7); + transition: color 0.3s; } -.form-switch .form-check-input:checked { - background-color: var(--accent-primary); - border-color: var(--accent-secondary); -} - -/* Progress Bar */ -.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); -} - -/* 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); -} - -/* Context Menu */ -.cli-context-menu { - background: var(--card-bg); - border: 1px solid var(--card-border); +.toggle-bg { + position: absolute; + top: 4px; left: 4px; + height: calc(100% - 8px); + width: calc(50% - 4px); /* Approximation */ + background: var(--primary); border-radius: 6px; + transition: transform 0.3s cubic-bezier(0.4, 0.0, 0.2, 1); + z-index: 1; } -.cli-context-menu .list-group-item { - background: transparent; - color: var(--text-color); - border: none; - transition: background 0.2s ease; +#format-mp3:checked ~ .toggle-bg { transform: translateX(0); width: 63px; } +#format-mp4:checked ~ .toggle-bg { transform: translateX(100%) translateX(6px); width: 63px; } +#format-mp3:checked + label { color: white; } +#format-mp4:checked + label { color: white; } + +.glass-select { + background: rgba(0,0,0,0.4); + border: 1px solid var(--glass-border); + color: white; + border-radius: 8px; + font-size: 0.9rem; + cursor: pointer; } -.cli-context-menu .list-group-item:hover { - background: rgba(0, 255, 0, 0.1); +.glass-select:focus { + background: rgba(0,0,0,0.6); + color: white; + box-shadow: none; + border-color: var(--primary); } -/* General Animation */ -.fade-in-up { animation: fadeInUp 0.8s ease-out forwards; } -@keyframes fadeInUp { +.custom-switch:checked { + background-color: var(--primary); + border-color: var(--primary); +} + +/* --- Processing & Terminal --- */ +.glass-progress { + height: 6px; + background: rgba(255,255,255,0.1); + border-radius: 3px; + overflow: hidden; +} +.bg-primary-gradient { + background: linear-gradient(90deg, var(--primary), var(--accent)); + box-shadow: 0 0 10px var(--primary); + transition: width 0.3s ease; +} + +.terminal-window { + background: rgba(10, 10, 15, 0.85); /* Very dark */ + border: 1px solid rgba(255,255,255,0.1); + border-radius: 8px; + box-shadow: 0 10px 30px rgba(0,0,0,0.5); + height: 200px; + display: flex; + flex-direction: column; +} +.dot { width: 10px; height: 10px; border-radius: 50%; } +.terminal-body { + flex: 1; + overflow-y: auto; + font-family: var(--font-mono); + opacity: 0.9; + padding-top: 0.5rem; +} +.terminal-line { + margin-bottom: 4px; + animation: typeIn 0.2s ease forwards; +} +@keyframes typeIn { from { opacity: 0; transform: translateX(-5px); } to { opacity: 1; transform: translateX(0); } } + +/* --- Result View --- */ +.result-card { + background: rgba(255,255,255,0.05); + backdrop-filter: blur(15px); + border: 1px solid rgba(255,255,255,0.1); + box-shadow: 0 20px 50px rgba(0,0,0,0.5); +} +.btn-primary-glow { + background: var(--primary); + color: white; + border: none; + box-shadow: 0 0 20px rgba(139, 92, 246, 0.4); + transition: transform 0.2s, box-shadow 0.2s; +} +.btn-primary-glow:hover { + transform: translateY(-2px); + box-shadow: 0 0 30px rgba(139, 92, 246, 0.6); + color: white; +} + +/* --- History Modal --- */ +.glass-modal { + background: rgba(20, 20, 30, 0.85); + backdrop-filter: blur(15px); + border: 1px solid var(--glass-border); + box-shadow: 0 0 50px rgba(0,0,0,0.5); +} +.border-bottom-white-10 { border-bottom: 1px solid rgba(255,255,255,0.1); } +.border-top-white-10 { border-top: 1px solid rgba(255,255,255,0.1); } +#history-table a { color: var(--accent); text-decoration: none; } +#history-table a:hover { text-decoration: underline; } + +/* --- Utility Classes --- */ +.btn-glass-icon { + background: rgba(255,255,255,0.1); + color: white; + border: 1px solid transparent; + border-radius: 50%; + width: 45px; height: 45px; + transition: all 0.2s; +} +.btn-glass-icon:hover { + background: rgba(255,255,255,0.2); + color: white; + border-color: rgba(255,255,255,0.3); +} + +.fade-in { animation: fadeIn 0.8s ease forwards; opacity: 0; } +.delay-1 { animation-delay: 0.2s; } +.delay-2 { animation-delay: 0.4s; } + +@keyframes fadeIn { from { opacity: 0; transform: translateY(20px); } to { opacity: 1; transform: translateY(0); } } -.animate-options { animation: fadeInCli 0.5s ease-out forwards; } - -/* 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; } +.glass-alert { + background: rgba(220, 38, 38, 0.2); + border: 1px solid rgba(220, 38, 38, 0.5); + backdrop-filter: blur(5px); } \ No newline at end of file diff --git a/templates/index.html b/templates/index.html index cc379d8..1c91f48 100644 --- a/templates/index.html +++ b/templates/index.html @@ -2,172 +2,200 @@ - unknownMedien.dl - Smart Downloader + unknownMedien.dl + + - -
-
-
-
-
+ +
+
+
-
+ + + + +
- -
-

unknownMedien.dl

-

Der schnelle Weg zu deinen Medien.

+
+ + +

Media Downloader

+

+ Einfach Link einfügen. Wir übernehmen den Rest. +

-
- + +
+ + -
- - - -
+ +
+
+ +
+ + +
- -
- - - SCANNING... - -
- - -
-
+ +
-
-
YOUTUBE CONFIG
-
-
- -
- - - - + + + + SoundCloud erkannt + + + +
+ + +
+
+
+ +
+ + + + +
+
+
+
+ + +
-
- - - -
-
-
-
-
- -
H.264 (for max compatibility)
-
-
- - + +
+
+ Kompatibilität (H.264) +
+ + +
+
+ + + + +
+ + +
+ + +

INITIALISIERUNG...

+ + +
+
- -
-
+ +
+
+
+
+
+ worker@unknown-dl:~# process_task +
+
+ +
- - - - - - -
-
-
-
-
-
-
-
- +
+
+
+ +
+

Fertig!

+

Deine Datei steht bereit.

+ + + Herunterladen + + +
+ +
-

COMPLETE.

- - DOWNLOAD - -
- - -
- © 2025 unknownMedien.dl • Links expire in 7 days. -
- -
+