From 3f2d2ec856e0c2c7d868c8b4e7f6bcc66c2e21e1 Mon Sep 17 00:00:00 2001 From: pjmalandrino Date: Thu, 22 May 2025 14:25:59 +0200 Subject: [PATCH] Update frontend to add tab organisation --- index.html | 220 ++++++++--------- static/app.js | 205 ++++++++-------- static/styles.css | 597 ++++++++++++++++++++-------------------------- 3 files changed, 476 insertions(+), 546 deletions(-) diff --git a/index.html b/index.html index cf389a6..cab63f2 100644 --- a/index.html +++ b/index.html @@ -3,136 +3,140 @@ - DocTags Analyzer and Visualizer + DocTags Tools - - - - -
-

DocTags Analyzer and Visualizer

+

DocTags Analyzer and Visualizer

-
-

Document Settings

+ +
+
- + -
+
-
-
-
+
-
-
-

- - - - - - - Step 1: Analyze PDF -

-

Extract DocTags structure from the selected PDF page

- -
-
+ +
+
1
+
+
2
+
+
3
+
-
-

- - - - - - Step 2: Generate Visualization -

-

Create visual representation of the extracted DocTags

- -
-
- -
-

- - - - - - Step 3: Extract Pictures -

-

Extract images from the document based on DocTags

- -
-
-
- - - - - - - -
- - +
+ + +
+

📄 Analyze PDF

+

Extract DocTags structure from the PDF page using AI-powered document analysis.

+ + + + + +
+ What this does: +
    +
  • Converts the PDF page to an image
  • +
  • Uses AI to identify document structure (headers, text, images, tables)
  • +
  • Generates DocTags with precise coordinate information
  • +
  • Saves the results to the results/ folder
  • +
+
+
+ +
+

🎨 Visualize DocTags

+

Create a visual representation of the extracted DocTags with bounding boxes around detected elements.

+ + + + + + + +
+ What this does: +
    +
  • Reads the DocTags file from Step 1
  • +
  • Overlays colored rectangles on the original PDF page
  • +
  • Each element type gets a different color (headers, text, images, etc.)
  • +
  • Saves the visualization as a PNG image
  • +
+
+
+ +
+

🖼️ Extract Pictures

+

Extract individual images from the document based on the detected picture regions.

+ + + + + +
+ What this does: +
    +
  • Identifies picture regions from the DocTags
  • +
  • Crops and extracts individual images
  • +
  • Saves each image as a separate PNG file
  • +
  • Creates an HTML index of all extracted images
  • +
+
+
+
+ + + + + + + + +
+ +
diff --git a/static/app.js b/static/app.js index 84d08ed..fd94935 100644 --- a/static/app.js +++ b/static/app.js @@ -2,47 +2,47 @@ const activeTasks = {}; let pollingInterval = null; -// Helper functions for UI -const UI = { - showLoader: (elementId) => { - const el = document.getElementById(elementId); - el.innerHTML = '
Processing...'; - }, - showSuccess: (elementId, message = 'Completed') => { - const el = document.getElementById(elementId); - el.innerHTML = `✓ ${message}`; - }, - showError: (elementId, message = 'Failed') => { - const el = document.getElementById(elementId); - el.innerHTML = `✗ ${message}`; - }, - showOutput: (content) => { - const outputDiv = document.getElementById('output'); - outputDiv.textContent = content; - outputDiv.classList.remove('hidden'); - // Scroll to show output - outputDiv.scrollIntoView({ behavior: 'smooth', block: 'nearest' }); - }, - showImage: (src) => { - const img = document.getElementById('result-image'); - const container = document.getElementById('image-container'); - img.src = src; - container.classList.remove('hidden'); - // Scroll to show image - container.scrollIntoView({ behavior: 'smooth', block: 'nearest' }); - }, - enableButton: (buttonId) => { - document.getElementById(buttonId).disabled = false; - }, - disableButton: (buttonId) => { - document.getElementById(buttonId).disabled = true; +// Tab switching functionality +function switchTab(tabName) { + // Hide all tab contents + const tabContents = document.querySelectorAll('.tab-content'); + tabContents.forEach(content => content.classList.remove('active')); + + // Remove active class from all tab buttons + const tabButtons = document.querySelectorAll('.tab-button'); + tabButtons.forEach(button => button.classList.remove('active')); + + // Show selected tab content + document.getElementById(tabName + '-tab').classList.add('active'); + + // Add active class to selected tab button + event.target.classList.add('active'); +} + +// Update progress indicator +function updateProgress(completedSteps) { + for (let i = 1; i <= 3; i++) { + const stepIndicator = document.getElementById(`step-${i}`); + const connector = document.getElementById(`connector-${i}`); + + if (i <= completedSteps) { + stepIndicator.classList.add('completed'); + stepIndicator.classList.remove('active'); + if (connector) connector.classList.add('completed'); + } else if (i === completedSteps + 1) { + stepIndicator.classList.add('active'); + stepIndicator.classList.remove('completed'); + } else { + stepIndicator.classList.remove('completed', 'active'); + if (connector) connector.classList.remove('completed'); + } } -}; +} // Load PDF files on page load window.addEventListener('DOMContentLoaded', function() { const pdfStatus = document.getElementById('pdf-load-status'); - pdfStatus.innerHTML = '
Loading PDF files...'; + pdfStatus.innerHTML = '
Loading PDF files...'; fetch('/pdf-files') .then(response => { @@ -54,7 +54,7 @@ window.addEventListener('DOMContentLoaded', function() { .then(data => { const select = document.getElementById('pdf_file'); if (data.length === 0) { - UI.showError('pdf-load-status', 'No PDF files found in the current directory'); + pdfStatus.innerHTML = 'No PDF files found in the current directory'; return; } @@ -64,11 +64,11 @@ window.addEventListener('DOMContentLoaded', function() { option.textContent = file; select.appendChild(option); }); - UI.showSuccess('pdf-load-status', `Loaded ${data.length} PDF files`); + pdfStatus.innerHTML = 'Loaded ' + data.length + ' PDF files'; }) .catch(error => { console.error('Error loading PDFs:', error); - UI.showError('pdf-load-status', `Error: ${error.message}`); + pdfStatus.innerHTML = 'Error: ' + error.message + ''; }); // Run an environment check on startup @@ -81,59 +81,55 @@ function checkEnvironment() { const envDetails = document.getElementById('env-details'); envDiv.classList.remove('hidden'); - envDetails.innerHTML = '
Checking environment...'; + envDetails.innerHTML = '
Checking environment...'; fetch('/check-environment') .then(response => response.json()) .then(data => { - let html = '
'; + let html = '
    '; // Current working directory - html += `

    Working directory: ${data.cwd}

    `; + html += '
  • Working directory: ' + data.cwd + '
  • '; // Python version - html += `

    Python version: ${data.python_version}

    `; + html += '
  • Python version: ' + data.python_version + '
  • '; // Required scripts check if (data.missing_scripts.length === 0) { - html += '

    All required scripts found

    '; + html += '
  • ✓ All required scripts found
  • '; } else { - html += `

    Missing scripts: ${data.missing_scripts.join(', ')}

    `; + html += '
  • ✗ Missing scripts: ' + data.missing_scripts.join(', ') + '
  • '; } // PDF files check if (data.pdf_files.length > 0) { - html += `

    Found ${data.pdf_files.length} PDF files

    `; - html += '
    PDF Files
      '; - data.pdf_files.forEach(file => { - html += `
    • ${file}
    • `; - }); - html += '
    '; + html += '
  • ✓ Found ' + data.pdf_files.length + ' PDF files: ' + data.pdf_files.join(', ') + '
  • '; } else { - html += '

    No PDF files found in the working directory

    '; + html += '
  • ✗ No PDF files found in the working directory
  • '; } // Results directory check if (data.results_dir_exists) { - html += '

    Results directory exists

    '; + html += '
  • ✓ Results directory exists
  • '; if (data.results_dir_writable) { - html += '

    Results directory is writable

    '; + html += '
  • ✓ Results directory is writable
  • '; } else { - html += '

    Results directory is not writable

    '; + html += '
  • ✗ Results directory is not writable
  • '; } } else { - html += '

    Results directory does not exist

    '; + html += '
  • ✗ Results directory does not exist
  • '; } - html += '
'; + html += ''; - // List of all files for debugging (collapsible) - html += `
All files in directory (${data.files.length} files)
${data.files.join('\n')}
`; + // List of all files for debugging + html += '
All files in directory (' + data.files.length + ' files)
' +
+                data.files.join('\n') + '
'; envDetails.innerHTML = html; }) .catch(error => { - envDetails.innerHTML = `
Error checking environment: ${error.message}
`; + envDetails.innerHTML = '
Error checking environment: ' + error.message + '
'; }); } @@ -142,7 +138,9 @@ function manuallyRunScript() { const command = prompt("Enter command to run (e.g., 'python analyzer.py --image document.pdf --page 1')"); if (!command) return; - UI.showOutput(`Running command: ${command}\nPlease wait...`); + const outputDiv = document.getElementById('output'); + outputDiv.textContent = 'Running command: ' + command + '\nPlease wait...'; + outputDiv.classList.remove('hidden'); // Create form data const formData = new FormData(); @@ -155,12 +153,13 @@ function manuallyRunScript() { }) .then(response => response.json()) .then(data => { - UI.showOutput( - `Command: ${command}\n\n${data.success ? 'Success!\n\n' : 'Failed!\n\n'}${data.output || ''}${data.error ? '\n\nError: ' + data.error : ''}` - ); + outputDiv.textContent = 'Command: ' + command + '\n\n' + + (data.success ? 'Success!\n\n' : 'Failed!\n\n') + + (data.output || '') + + (data.error ? '\n\nError: ' + data.error : ''); }) .catch(error => { - UI.showOutput(`Error running command: ${error.message}`); + outputDiv.textContent = 'Error running command: ' + error.message; }); } @@ -200,24 +199,32 @@ function pollTasks() { if (data.done) { if (data.success) { // Task completed successfully - UI.showSuccess(`${taskInfo.type}-status`, 'Completed'); + statusElement.innerHTML = '✓ Completed successfully!'; + statusElement.classList.remove('hidden'); // Enable button - UI.enableButton(`${taskInfo.type}-btn`); + document.getElementById(`${taskInfo.type}-btn`).disabled = false; // Display output - UI.showOutput(data.output); + const outputDiv = document.getElementById('output'); + outputDiv.textContent = data.output; + outputDiv.classList.remove('hidden'); // Show image if available for visualizer if (taskInfo.type === 'visualizer' && data.image_file) { - UI.showImage(`/${data.image_file}?t=${new Date().getTime()}`); + document.getElementById('result-image').src = '/' + data.image_file + '?t=' + new Date().getTime(); + document.getElementById('image-container').classList.remove('hidden'); } - // Enable next step button if applicable + // Enable next step button and update progress if (taskInfo.type === 'analyzer') { - UI.enableButton('visualizer-btn'); + document.getElementById('visualizer-btn').disabled = false; + updateProgress(1); } else if (taskInfo.type === 'visualizer') { - UI.enableButton('extractor-btn'); + document.getElementById('extractor-btn').disabled = false; + updateProgress(2); + } else if (taskInfo.type === 'extractor') { + updateProgress(3); } // Remove from active tasks @@ -225,11 +232,14 @@ function pollTasks() { checkAndStopPolling(); } else { // Task failed - UI.showError(`${taskInfo.type}-status`, data.error || 'Unknown error'); - UI.enableButton(`${taskInfo.type}-btn`); + statusElement.innerHTML = '✗ Failed: ' + (data.error || 'Unknown error') + ''; + statusElement.classList.remove('hidden'); + document.getElementById(`${taskInfo.type}-btn`).disabled = false; // Display error output - UI.showOutput(`Error: ${data.error || 'Unknown error'}`); + const outputDiv = document.getElementById('output'); + outputDiv.textContent = 'Error: ' + (data.error || 'Unknown error'); + outputDiv.classList.remove('hidden'); // Remove from active tasks delete activeTasks[taskId]; @@ -237,17 +247,12 @@ function pollTasks() { } } else { // Still running - statusElement.innerHTML = '
Processing...'; + statusElement.innerHTML = '
Running...'; + statusElement.classList.remove('hidden'); } }) .catch(error => { console.error('Error checking task status:', error); - UI.showError(`${taskInfo.type}-status`, 'Failed to check status'); - UI.enableButton(`${taskInfo.type}-btn`); - - // Remove from active tasks to prevent endless error messages - delete activeTasks[taskId]; - checkAndStopPolling(); }); } } @@ -263,7 +268,8 @@ function runScript(script) { } // Disable button - UI.disableButton(`${script}-btn`); + const button = document.getElementById(`${script}-btn`); + button.disabled = true; // Create form data const formData = new FormData(); @@ -272,7 +278,9 @@ function runScript(script) { formData.append('adjust', adjust); // Show running status - UI.showLoader(`${script}-status`); + const statusElement = document.getElementById(`${script}-status`); + statusElement.innerHTML = '
Starting...'; + statusElement.classList.remove('hidden'); // Clear previous output document.getElementById('output').classList.add('hidden'); @@ -288,13 +296,14 @@ function runScript(script) { case 'analyzer': endpoint = '/run-analyzer'; // Disable next step buttons - UI.disableButton('visualizer-btn'); - UI.disableButton('extractor-btn'); + document.getElementById('visualizer-btn').disabled = true; + document.getElementById('extractor-btn').disabled = true; + updateProgress(0); break; case 'visualizer': endpoint = '/run-visualizer'; // Disable next step button - UI.disableButton('extractor-btn'); + document.getElementById('extractor-btn').disabled = true; break; case 'extractor': endpoint = '/run-extractor'; @@ -324,25 +333,31 @@ function runScript(script) { startPolling(); // Update status - UI.showLoader(`${script}-status`); + statusElement.innerHTML = '
Running...'; // Show initial output - UI.showOutput(data.message || 'Task started, please wait...'); + const outputDiv = document.getElementById('output'); + outputDiv.textContent = data.message || 'Task started, please wait...'; + outputDiv.classList.remove('hidden'); } else { // Failed to start task - UI.showError(`${script}-status`, 'Failed to start task'); - UI.enableButton(`${script}-btn`); + statusElement.innerHTML = '✗ Failed to start task'; + button.disabled = false; // Show error - UI.showOutput(`Error: ${data.error || 'Failed to start task'}`); + const outputDiv = document.getElementById('output'); + outputDiv.textContent = 'Error: ' + (data.error || 'Failed to start task'); + outputDiv.classList.remove('hidden'); } }) .catch(error => { console.error('Error starting task:', error); - UI.showError(`${script}-status`, error.message); - UI.enableButton(`${script}-btn`); + statusElement.innerHTML = '✗ ' + error.message + ''; + button.disabled = false; // Show error - UI.showOutput(`Error: ${error.message}`); + const outputDiv = document.getElementById('output'); + outputDiv.textContent = 'Error: ' + error.message; + outputDiv.classList.remove('hidden'); }); } \ No newline at end of file diff --git a/static/styles.css b/static/styles.css index c0c38bb..7092a04 100644 --- a/static/styles.css +++ b/static/styles.css @@ -1,431 +1,342 @@ -/* Modern stylesheet for DocTags Analyzer and Visualizer */ - -:root { - /* Color palette */ - --bg-color: #f8f1ee; - --primary-color: #195162; - --secondary-color: #618794; - --light-color: #e6eaf1; - --mint: #b9e9da; - --amber: #ffdb96; - --pale-rose: #ffc9c9; - --white: #ffffff; - --error: #e74c3c; - --success: #2ecc71; - --warning: #f39c12; - --shadow: rgba(25, 81, 98, 0.1); - - /* Typography */ - --font-sans: 'Inter', system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif; - --font-mono: 'Fira Code', 'SF Mono', Monaco, Consolas, 'Liberation Mono', 'Courier New', monospace; -} - -/* Base styles */ body { - font-family: var(--font-sans); - background-color: var(--bg-color); - color: var(--primary-color); - line-height: 1.6; - margin: 0; - padding: 0; -} - -#app-container { - max-width: 1200px; + font-family: Arial, sans-serif; + max-width: 900px; margin: 0 auto; - padding: 2rem; -} - -h1, h2, h3, h4, h5, h6 { - color: var(--primary-color); - margin-top: 0; - font-weight: 600; + padding: 20px; + background-color: #f5f5f5; } h1 { - font-size: 2.25rem; - margin-bottom: 1.5rem; - position: relative; - display: inline-block; + color: #333; + text-align: center; + margin-bottom: 30px; } -h1::after { - content: ''; - position: absolute; - width: 50%; - height: 4px; - background-color: var(--secondary-color); - bottom: -8px; - left: 0; - border-radius: 2px; -} - -h3 { - font-size: 1.35rem; - color: var(--secondary-color); - margin-bottom: 1rem; -} - -/* Card Component */ -.card { - background-color: var(--white); - border-radius: 12px; - box-shadow: 0 8px 16px var(--shadow); - padding: 1.5rem; - margin-bottom: 1.5rem; - transition: transform 0.2s ease, box-shadow 0.2s ease; -} - -.card:hover { - transform: translateY(-4px); - box-shadow: 0 10px 20px var(--shadow); -} - -/* Form Elements */ +/* Common form styles */ .form-group { - margin-bottom: 1.5rem; + margin-bottom: 15px; } label { display: block; - margin-bottom: 0.5rem; - font-weight: 500; - color: var(--primary-color); + margin-bottom: 5px; + font-weight: bold; } -select, input[type="number"], input[type="text"] { +select, input { + padding: 8px; width: 100%; - padding: 12px 16px; - border: 2px solid var(--light-color); - border-radius: 8px; - background-color: var(--white); - font-size: 1rem; - color: var(--primary-color); - transition: border-color 0.2s ease, box-shadow 0.2s ease; - -webkit-appearance: none; - appearance: none; -} - -select { - background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' viewBox='0 0 24 24' fill='none' stroke='%23618794' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M6 9l6 6 6-6'/%3E%3C/svg%3E"); - background-repeat: no-repeat; - background-position: right 16px center; - padding-right: 48px; -} - -select:focus, input:focus { - outline: none; - border-color: var(--secondary-color); - box-shadow: 0 0 0 3px rgba(97, 135, 148, 0.25); -} - -.checkbox-container { - display: flex; - align-items: center; - position: relative; - cursor: pointer; - user-select: none; -} - -.checkbox-container input { - position: absolute; - opacity: 0; - cursor: pointer; - height: 0; - width: 0; -} - -.checkmark { - height: 24px; - width: 24px; - background-color: var(--white); - border: 2px solid var(--light-color); + max-width: 300px; + border: 1px solid #ddd; border-radius: 4px; - margin-right: 10px; - display: flex; - align-items: center; - justify-content: center; - transition: all 0.2s ease; } -.checkbox-container:hover input ~ .checkmark { - background-color: var(--light-color); -} - -.checkbox-container input:checked ~ .checkmark { - background-color: var(--secondary-color); - border-color: var(--secondary-color); -} - -.checkmark:after { - content: ""; - display: none; - width: 5px; - height: 10px; - border: solid white; - border-width: 0 2px 2px 0; - transform: rotate(45deg); -} - -.checkbox-container input:checked ~ .checkmark:after { - display: block; -} - -/* Buttons */ button { - background-color: var(--secondary-color); + padding: 12px 20px; + background-color: #4CAF50; color: white; border: none; - border-radius: 8px; - padding: 12px 24px; - font-size: 1rem; - font-weight: 500; + border-radius: 4px; cursor: pointer; - transition: background-color 0.2s ease, transform 0.1s ease; - display: inline-flex; - align-items: center; - justify-content: center; + margin-right: 10px; + margin-bottom: 10px; + font-size: 14px; + transition: background-color 0.3s; } -button:hover:not(:disabled) { - background-color: var(--primary-color); - transform: translateY(-2px); -} - -button:active:not(:disabled) { - transform: translateY(0); +button:hover { + background-color: #45a049; } button:disabled { - background-color: var(--light-color); - color: var(--secondary-color); + background-color: #cccccc; cursor: not-allowed; - opacity: 0.7; } -button svg { - margin-right: 8px; +.output { + margin-top: 20px; + padding: 15px; + border: 1px solid #ddd; + border-radius: 4px; + background-color: #fff; + white-space: pre-wrap; + max-height: 300px; + overflow-y: auto; + font-family: monospace; + font-size: 12px; } -/* Status indicators */ -.status { - margin-top: 1rem; - font-weight: 500; +.hidden { + display: none; +} + +img { + max-width: 100%; + margin-top: 10px; + border: 1px solid #ddd; + border-radius: 4px; +} + +/* Tab styles */ +.tabs-container { + background-color: white; + border-radius: 8px; + box-shadow: 0 2px 10px rgba(0,0,0,0.1); + overflow: hidden; +} + +.tab-buttons { display: flex; - align-items: center; + background-color: #f8f9fa; + border-bottom: 1px solid #ddd; +} + +.tab-button { + flex: 1; + padding: 15px 20px; + background-color: transparent; + border: none; + cursor: pointer; + font-size: 16px; + font-weight: 500; + color: #666; + transition: all 0.3s; + border-bottom: 3px solid transparent; +} + +.tab-button:hover { + background-color: #e9ecef; + color: #333; +} + +.tab-button.active { + background-color: white; + color: #4CAF50; + border-bottom-color: #4CAF50; +} + +.tab-content { + display: none; + padding: 30px; + background-color: white; +} + +.tab-content.active { + display: block; +} + +.tab-content h3 { + color: #333; + margin-top: 0; + margin-bottom: 10px; +} + +.tab-content p { + color: #666; + margin-bottom: 20px; +} + +/* Tab description styles */ +.tab-description { + margin-top: 20px; + color: #666; + font-size: 14px; +} + +.tab-description ul { + margin-top: 10px; +} + +.tab-description li { + margin-bottom: 5px; +} + +.tab-description code { + background-color: #f1f3f4; + padding: 2px 4px; + border-radius: 3px; + font-size: 13px; +} + +/* Status styles */ +.status { + font-weight: bold; + margin-top: 15px; + padding: 10px; + border-radius: 4px; + background-color: #f8f9fa; } .error { - color: var(--error); + color: #d32f2f; + background-color: #ffebee; } .success { - color: var(--success); + color: #43a047; + background-color: #e8f5e8; } .working { - color: var(--secondary-color); + color: #1976d2; + background-color: #e3f2fd; } .loader { - display: inline-block; - width: 20px; - height: 20px; - margin-right: 10px; - border: 3px solid var(--light-color); + border: 3px solid #f3f3f3; + border-top: 3px solid #3498db; border-radius: 50%; - border-top-color: var(--secondary-color); - animation: spin 1s ease-in-out infinite; + width: 16px; + height: 16px; + animation: spin 1s linear infinite; + display: inline-block; + vertical-align: middle; + margin-right: 8px; } @keyframes spin { - to { transform: rotate(360deg); } + 0% { transform: rotate(0deg); } + 100% { transform: rotate(360deg); } } -/* Pipeline Container */ -.pipeline-container { - display: grid; - grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); - gap: 1.5rem; - margin: 2rem 0; +/* Settings panel */ +.settings-panel { + background-color: #fff; + padding: 20px; + border-radius: 8px; + margin-bottom: 20px; + box-shadow: 0 2px 5px rgba(0,0,0,0.1); } -/* Step Cards */ -.step-card { - background-color: var(--white); - border-radius: 12px; - border-left: 5px solid var(--mint); - box-shadow: 0 4px 12px var(--shadow); - padding: 1.5rem; - transition: transform 0.2s ease, box-shadow 0.2s ease; +.settings-row { display: flex; - flex-direction: column; + gap: 20px; + align-items: end; + flex-wrap: wrap; } -.step-card:nth-child(2) { - border-left-color: var(--amber); +.settings-row .form-group { + flex: 1; + min-width: 200px; } -.step-card:nth-child(3) { - border-left-color: var(--pale-rose); -} - -.step-card:hover { - transform: translateY(-4px); - box-shadow: 0 8px 16px var(--shadow); -} - -.step-card h3 { - margin-top: 0; - display: flex; - align-items: center; -} - -.step-card h3 svg { - margin-right: 10px; -} - -.step-card p { - color: var(--secondary-color); - margin-bottom: 1.5rem; - flex-grow: 1; -} - -/* Environment Check Panel */ +/* Environment check styles */ #environment-check { - background-color: var(--white); - border-radius: 12px; - box-shadow: 0 4px 12px var(--shadow); - padding: 1.5rem; - margin-top: 2rem; + margin-top: 20px; + padding: 15px; + background-color: #fff3cd; + border: 1px solid #ffeeba; + border-radius: 4px; } .env-success { - color: var(--success); - display: flex; - align-items: center; + color: #155724; } .env-error { - color: var(--error); - display: flex; - align-items: center; + color: #721c24; } -.env-success::before, .env-error::before { - content: ''; - display: inline-block; - width: 16px; - height: 16px; - margin-right: 8px; - border-radius: 50%; -} - -.env-success::before { - background-color: var(--success); -} - -.env-error::before { - background-color: var(--error); -} - -/* Output Container */ -#output { - background-color: var(--white); - border-radius: 12px; - padding: 1.5rem; - white-space: pre-wrap; - font-family: var(--font-mono); - font-size: 0.9rem; - line-height: 1.6; - color: var(--primary-color); - overflow-x: auto; - box-shadow: 0 4px 12px var(--shadow); - max-height: 300px; - overflow-y: auto; -} - -/* Image Container */ -#image-container { - background-color: var(--white); - border-radius: 12px; - padding: 1.5rem; - box-shadow: 0 4px 12px var(--shadow); - margin-top: 2rem; -} - -#image-container h2 { - margin-top: 0; - margin-bottom: 1rem; - display: flex; - align-items: center; -} - -#image-container h2::before { - content: ''; - display: inline-block; - width: 24px; - height: 24px; - margin-right: 10px; - background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='%23618794' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Crect x='3' y='3' width='18' height='18' rx='2' ry='2'/%3E%3Ccircle cx='8.5' cy='8.5' r='1.5'/%3E%3Cpolyline points='21 15 16 10 5 21'/%3E%3C/svg%3E"); - background-repeat: no-repeat; -} - -#result-image { - max-width: 100%; - border-radius: 8px; - box-shadow: 0 2px 8px var(--shadow); -} - -/* Debug Section */ .debug-section { - margin-top: 3rem; - padding-top: 1.5rem; - border-top: 1px dashed var(--light-color); + margin-top: 30px; + border-top: 1px solid #ddd; + padding-top: 20px; + text-align: center; +} + +.debug-section button { + background-color: #6c757d; + margin: 0 5px; +} + +.debug-section button:hover { + background-color: #5a6268; +} + +/* Image container styles */ +#image-container { + margin-top: 20px; + text-align: center; +} + +#image-container h4 { + color: #333; + margin-bottom: 15px; +} + +/* Progress indicator */ +.progress-indicator { display: flex; - gap: 1rem; + justify-content: center; + align-items: center; + margin: 20px 0; } -/* Utility Classes */ -.hidden { - display: none !important; +.step-indicator { + width: 30px; + height: 30px; + border-radius: 50%; + background-color: #ddd; + color: white; + display: flex; + align-items: center; + justify-content: center; + margin: 0 10px; + font-weight: bold; + transition: background-color 0.3s; } -.mt-1 { - margin-top: 0.5rem; +.step-indicator.completed { + background-color: #4CAF50; } -.mt-2 { - margin-top: 1rem; +.step-indicator.active { + background-color: #2196F3; } -.mt-4 { - margin-top: 2rem; +.step-connector { + width: 50px; + height: 2px; + background-color: #ddd; + transition: background-color 0.3s; } -.mb-1 { - margin-bottom: 0.5rem; +.step-connector.completed { + background-color: #4CAF50; } -.mb-2 { - margin-bottom: 1rem; -} - -.mb-4 { - margin-bottom: 2rem; -} - -/* Responsive adjustments */ +/* Responsive design */ @media (max-width: 768px) { - #app-container { - padding: 1rem; + body { + padding: 10px; } - .pipeline-container { - grid-template-columns: 1fr; + .tab-buttons { + flex-direction: column; } - button { - width: 100%; + .tab-button { + text-align: center; + border-bottom: 1px solid #ddd; + border-right: none; + } + + .tab-button.active { + border-bottom: 1px solid #4CAF50; + border-left: 3px solid #4CAF50; + } + + .settings-row { + flex-direction: column; + gap: 10px; + } + + .progress-indicator { + flex-wrap: wrap; + gap: 10px; + } + + .step-connector { + display: none; } } \ No newline at end of file