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.
+
-