commit c9aa34905183eae6024cebe309cd249b84e9360e
parent 654d517dbc9ffa871891bb7e2ffecc99c2bb2dc3
Author: Pablo Murad <pablo@pablomurad.com>
Date: Sun, 25 Jan 2026 11:21:31 -0300
design chave
Diffstat:
3 files changed, 925 insertions(+), 343 deletions(-)
diff --git a/.gitignore b/.gitignore
@@ -53,3 +53,5 @@ Thumbs.db
# Logs
*.log
+
+.cursor/
+\ No newline at end of file
diff --git a/lazier/api/routes.py b/lazier/api/routes.py
@@ -502,35 +502,69 @@ def process_web_async(url: str, job_id: str, output_format: str, should_transcri
content = None
summary = None
+ transcription_path = None
summary_path = None
+ metadata = {}
# Verifica cache
cached = cache.get('web', url_hash) if cache else None
+ needs_processing = True
+
if cached:
- content = cached.get('content')
- summary = cached.get('summary')
- metadata = cached.get('metadata', {})
- if content and (not should_summarize or summary):
+ cached_content = cached.get('content')
+ cached_summary = cached.get('summary')
+ cached_metadata = cached.get('metadata', {})
+
+ # Usa dados do cache se disponíveis e necessários
+ if should_transcribe and cached_content:
+ content = cached_content
+ if not metadata:
+ metadata = cached_metadata
+ if should_summarize and cached_summary:
+ summary = cached_summary
+ if not metadata:
+ metadata = cached_metadata
+
+ # Verifica se tem tudo que precisa no cache
+ has_all_needed = True
+ if should_transcribe and not content:
+ has_all_needed = False
+ if should_summarize and not summary:
+ has_all_needed = False
+
+ if has_all_needed:
+ needs_processing = False
jobs[job_id]['progress'] = 100
broadcast_progress(job_id, 100, 'completed', 'Dados encontrados no cache')
- else:
- # Extrai conteúdo
- broadcast_progress(job_id, 30, 'processing', 'Extraindo conteúdo da página...')
- content_data = extract_web_content(url)
- content = content_data['content']
- metadata = {'title': content_data.get('title', 'Página Web'), 'webpage_url': url}
- jobs[job_id]['progress'] = 50
- broadcast_progress(job_id, 50, 'processing', 'Conteúdo extraído')
+
+ if needs_processing:
+ # Se precisa transcrever e não tem no cache, extrai conteúdo completo
+ if should_transcribe and not content:
+ broadcast_progress(job_id, 30, 'processing', 'Extraindo conteúdo da página...')
+ content_data = extract_web_content(url)
+ content = content_data['content']
+ metadata = {'title': content_data.get('title', 'Página Web'), 'webpage_url': url}
+ jobs[job_id]['progress'] = 50
+ broadcast_progress(job_id, 50, 'processing', 'Conteúdo extraído')
# Sumariza se solicitado
if should_summarize:
broadcast_progress(job_id, 60, 'processing', 'Gerando sumário...')
- summary = summarize_web_page(url)
+ # Se já tem conteúdo extraído, usa ele. Senão, summarize_web_page extrai internamente
+ if content:
+ summary = summarize_text(content, model='gpt-4o-mini', language='pt-BR')
+ else:
+ summary = summarize_web_page(url)
+ # Se não tinha conteúdo antes, pega metadados da extração interna
+ if not metadata:
+ content_data = extract_web_content(url)
+ metadata = {'title': content_data.get('title', 'Página Web'), 'webpage_url': url}
+
jobs[job_id]['progress'] = 80
broadcast_progress(job_id, 80, 'processing', 'Sumário concluído')
- # Gera arquivo só com sumário
- if summary:
+ # Gera arquivo só com sumário se apenas sumarizar
+ if summary and not should_transcribe:
summary_path = Path(get_lazier_filename(OUTPUT_DIR, output_format, "_summary"))
export(
transcription="",
@@ -540,8 +574,19 @@ def process_web_async(url: str, job_id: str, output_format: str, should_transcri
format_type=output_format
)
- # Salva cache
- if cache:
+ # Se apenas transcrever, gera arquivo só com transcrição
+ if should_transcribe and content and not should_summarize:
+ transcription_path = Path(get_lazier_filename(OUTPUT_DIR, output_format, "_transcription"))
+ export(
+ transcription=content,
+ summary=None,
+ metadata=metadata,
+ output_path=str(transcription_path),
+ format_type=output_format
+ )
+
+ # Salva cache apenas se extraiu conteúdo ou gerou sumário
+ if cache and (content or summary):
cache.set('web', url_hash, {
'content': content,
'summary': summary,
@@ -549,8 +594,19 @@ def process_web_async(url: str, job_id: str, output_format: str, should_transcri
'timestamp': datetime.now().isoformat(),
})
- # Para páginas web, sempre gera arquivo consolidado
- if content or summary:
+ # Gera arquivo consolidado apenas se ambos foram solicitados OU se apenas transcrever (sem arquivo separado)
+ should_generate_consolidated = False
+ if should_transcribe and should_summarize:
+ # Ambos solicitados: sempre gera consolidado
+ should_generate_consolidated = True
+ elif should_transcribe and not should_summarize:
+ # Apenas transcrever: gera consolidado se não tem arquivo separado
+ should_generate_consolidated = not transcription_path
+ elif should_summarize and not should_transcribe:
+ # Apenas sumarizar: NÃO gera consolidado (já tem arquivo separado)
+ should_generate_consolidated = False
+
+ if should_generate_consolidated:
broadcast_progress(job_id, 90, 'processing', 'Gerando arquivo de saída...')
output_path = Path(get_lazier_filename(OUTPUT_DIR, output_format))
@@ -564,10 +620,10 @@ def process_web_async(url: str, job_id: str, output_format: str, should_transcri
jobs[job_id]['result_path'] = str(output_path)
- # Armazena dados separados
- jobs[job_id]['transcription'] = content # Para web, conteúdo = transcrição
+ # Armazena dados separados - só armazena transcription se foi solicitado
+ jobs[job_id]['transcription'] = content if should_transcribe else None
jobs[job_id]['summary'] = summary
- jobs[job_id]['transcription_path'] = None # Web não tem transcrição separada
+ jobs[job_id]['transcription_path'] = str(transcription_path) if transcription_path else None
jobs[job_id]['summary_path'] = str(summary_path) if summary_path else None
jobs[job_id]['metadata'] = metadata
diff --git a/lazier/web/templates/index.html b/lazier/web/templates/index.html
@@ -5,522 +5,989 @@
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Lazier - Transcrição e Sumarização</title>
<link rel="icon" href="data:image/svg+xml,<svg xmlns=%22http://www.w3.org/2000/svg%22 viewBox=%220 0 100 100%22><text y=%22.9em%22 font-size=%2290%22>📢</text></svg>">
+ <link rel="preconnect" href="https://fonts.googleapis.com">
+ <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
+ <link href="https://fonts.googleapis.com/css2?family=Playfair+Display:wght@400;600;700&family=Work+Sans:wght@300;400;500;600;700&display=swap" rel="stylesheet">
<script src="https://cdn.jsdelivr.net/npm/marked/marked.min.js"></script>
<style>
+ /* ===== VARIÁVEIS CSS ===== */
+ :root {
+ --color-primary: #283593;
+ --color-primary-dark: #1a237e;
+ --color-primary-light: #3949ab;
+ --color-secondary: #ffb300;
+ --color-accent: #ffa000;
+ --color-accent-light: #ffc107;
+ --color-bg: #fafafa;
+ --color-surface: #ffffff;
+ --color-text: #263238;
+ --color-text-light: #546e7a;
+ --color-text-lighter: #78909c;
+ --color-border: #e0e0e0;
+ --color-border-light: #f5f5f5;
+ --font-display: 'Playfair Display', serif;
+ --font-body: 'Work Sans', sans-serif;
+ --spacing-xs: 4px;
+ --spacing-sm: 8px;
+ --spacing-md: 16px;
+ --spacing-lg: 24px;
+ --spacing-xl: 32px;
+ --spacing-2xl: 48px;
+ --spacing-3xl: 64px;
+ --border-radius-sm: 8px;
+ --border-radius-md: 12px;
+ --border-radius-lg: 16px;
+ --border-radius-xl: 24px;
+ --shadow-sm: 0 2px 4px rgba(0,0,0,0.08);
+ --shadow-md: 0 4px 12px rgba(0,0,0,0.12);
+ --shadow-lg: 0 8px 24px rgba(0,0,0,0.16);
+ --shadow-xl: 0 12px 40px rgba(0,0,0,0.2);
+ --transition-fast: 0.15s ease;
+ --transition-base: 0.3s ease;
+ --transition-slow: 0.5s ease;
+ }
+
+ /* ===== RESET E BASE ===== */
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
-
+
+ html {
+ scroll-behavior: smooth;
+ }
+
body {
- font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif;
- background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
+ font-family: var(--font-body);
+ background: linear-gradient(135deg, #1a237e 0%, #283593 50%, #3949ab 100%);
+ background-attachment: fixed;
min-height: 100vh;
- padding-top: 70px;
- padding-bottom: 60px;
- display: flex;
- flex-direction: column;
- }
-
- .container {
- flex: 1;
- display: flex;
- flex-direction: column;
- }
-
- .page {
- flex: 1;
+ color: var(--color-text);
+ line-height: 1.6;
+ padding-top: 80px;
+ padding-bottom: 0;
display: flex;
flex-direction: column;
+ position: relative;
+ overflow-x: hidden;
}
-
- /* Menu de Navegação */
+
+ /* Background texture overlay */
+ body::before {
+ content: '';
+ position: fixed;
+ top: 0;
+ left: 0;
+ width: 100%;
+ height: 100%;
+ background-image:
+ radial-gradient(circle at 20% 50%, rgba(255, 179, 0, 0.1) 0%, transparent 50%),
+ radial-gradient(circle at 80% 80%, rgba(255, 160, 0, 0.1) 0%, transparent 50%);
+ pointer-events: none;
+ z-index: 0;
+ }
+
+ /* ===== NAVBAR ===== */
.navbar {
position: fixed;
top: 0;
left: 0;
right: 0;
- background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
- padding: 15px 40px;
- box-shadow: 0 2px 10px rgba(0,0,0,0.1);
+ background: rgba(26, 35, 126, 0.85);
+ backdrop-filter: blur(20px);
+ -webkit-backdrop-filter: blur(20px);
+ padding: var(--spacing-md) var(--spacing-xl);
+ box-shadow: var(--shadow-md);
z-index: 1000;
display: flex;
justify-content: space-between;
align-items: center;
+ border-bottom: 1px solid rgba(255, 255, 255, 0.1);
+ transition: var(--transition-base);
}
-
+
.navbar-brand {
- color: white;
- font-size: 1.5em;
+ font-family: var(--font-display);
+ color: #ffffff;
+ font-size: clamp(1.5rem, 4vw, 2rem);
font-weight: 700;
text-decoration: none;
+ letter-spacing: -0.5px;
+ transition: var(--transition-base);
+ display: flex;
+ align-items: center;
+ gap: var(--spacing-sm);
}
-
+
+ .navbar-brand:hover {
+ transform: translateY(-2px);
+ text-shadow: 0 4px 12px rgba(255, 179, 0, 0.3);
+ }
+
.navbar-nav {
display: flex;
- gap: 30px;
+ gap: var(--spacing-xl);
list-style: none;
}
-
+
.navbar-nav a {
- color: white;
+ color: rgba(255, 255, 255, 0.9);
text-decoration: none;
font-weight: 500;
- padding: 8px 0;
- border-bottom: 2px solid transparent;
- transition: border-color 0.3s;
+ font-size: 1rem;
+ padding: var(--spacing-sm) 0;
+ position: relative;
+ transition: var(--transition-base);
+ }
+
+ .navbar-nav a::after {
+ content: '';
+ position: absolute;
+ bottom: 0;
+ left: 0;
+ width: 0;
+ height: 2px;
+ background: var(--color-secondary);
+ transition: var(--transition-base);
}
-
+
.navbar-nav a:hover,
.navbar-nav a.active {
- border-bottom-color: white;
+ color: #ffffff;
}
-
- /* Container Principal */
+
+ .navbar-nav a:hover::after,
+ .navbar-nav a.active::after {
+ width: 100%;
+ }
+
+ .mobile-menu-toggle {
+ display: none;
+ background: none;
+ border: none;
+ color: #ffffff;
+ font-size: 1.5rem;
+ cursor: pointer;
+ padding: var(--spacing-sm);
+ }
+
+ /* ===== CONTAINER ===== */
.container {
+ flex: 1;
max-width: 1200px;
margin: 0 auto;
- padding: 40px 20px;
+ padding: var(--spacing-2xl) var(--spacing-lg);
width: 100%;
+ position: relative;
+ z-index: 1;
}
-
+
.page {
display: none;
- background: white;
- border-radius: 20px;
- box-shadow: 0 20px 60px rgba(0,0,0,0.3);
- padding: 40px;
- min-height: calc(100vh - 130px);
+ background: var(--color-surface);
+ border-radius: var(--border-radius-xl);
+ box-shadow: var(--shadow-xl);
+ padding: var(--spacing-3xl);
+ min-height: calc(100vh - 200px);
+ animation: fadeIn 0.5s ease;
+ position: relative;
+ overflow: hidden;
}
-
+
+ .page::before {
+ content: '';
+ position: absolute;
+ top: 0;
+ left: 0;
+ right: 0;
+ height: 4px;
+ background: linear-gradient(90deg, var(--color-primary), var(--color-secondary), var(--color-accent));
+ }
+
.page.active {
display: flex;
flex-direction: column;
}
-
+
.page-content {
flex: 1;
overflow-y: auto;
}
-
+
+ @keyframes fadeIn {
+ from {
+ opacity: 0;
+ transform: translateY(20px);
+ }
+ to {
+ opacity: 1;
+ transform: translateY(0);
+ }
+ }
+
+ /* ===== TIPOGRAFIA ===== */
h1 {
- color: #333;
- margin-bottom: 10px;
- font-size: 2.5em;
+ font-family: var(--font-display);
+ color: var(--color-primary);
+ margin-bottom: var(--spacing-md);
+ font-size: clamp(2rem, 5vw, 3.5rem);
+ font-weight: 700;
+ line-height: 1.2;
+ letter-spacing: -1px;
+ }
+
+ h2 {
+ font-family: var(--font-display);
+ color: var(--color-primary);
+ font-size: clamp(1.5rem, 4vw, 2.5rem);
+ font-weight: 600;
+ margin-bottom: var(--spacing-md);
}
-
+
+ h3 {
+ font-family: var(--font-display);
+ color: var(--color-text);
+ font-size: clamp(1.25rem, 3vw, 1.75rem);
+ font-weight: 600;
+ margin-bottom: var(--spacing-md);
+ }
+
.subtitle {
- color: #666;
- margin-bottom: 30px;
+ color: var(--color-text-light);
+ margin-bottom: var(--spacing-2xl);
+ font-size: clamp(1rem, 2vw, 1.25rem);
+ font-weight: 400;
}
-
- /* Upload Area */
+
+ /* ===== UPLOAD AREA ===== */
.upload-area {
- border: 3px dashed #667eea;
- border-radius: 15px;
- padding: 60px 20px;
+ border: 3px dashed var(--color-border);
+ border-radius: var(--border-radius-lg);
+ padding: var(--spacing-3xl) var(--spacing-xl);
text-align: center;
- background: #f8f9ff;
- transition: all 0.3s;
+ background: linear-gradient(135deg, rgba(255, 179, 0, 0.03) 0%, rgba(255, 160, 0, 0.05) 100%);
+ transition: all var(--transition-base);
cursor: pointer;
- margin-bottom: 20px;
+ margin-bottom: var(--spacing-xl);
+ position: relative;
+ overflow: hidden;
}
-
+
+ .upload-area::before {
+ content: '';
+ position: absolute;
+ top: -50%;
+ left: -50%;
+ width: 200%;
+ height: 200%;
+ background: linear-gradient(45deg, transparent, rgba(255, 179, 0, 0.1), transparent);
+ transform: rotate(45deg);
+ transition: var(--transition-slow);
+ opacity: 0;
+ }
+
+ .upload-area:hover::before {
+ animation: shimmer 2s infinite;
+ opacity: 1;
+ }
+
+ @keyframes shimmer {
+ 0% { transform: translateX(-100%) translateY(-100%) rotate(45deg); }
+ 100% { transform: translateX(100%) translateY(100%) rotate(45deg); }
+ }
+
.upload-area:hover {
- border-color: #764ba2;
- background: #f0f2ff;
+ border-color: var(--color-secondary);
+ background: linear-gradient(135deg, rgba(255, 179, 0, 0.08) 0%, rgba(255, 160, 0, 0.12) 100%);
+ transform: translateY(-4px);
+ box-shadow: var(--shadow-lg);
}
-
+
.upload-area.dragover {
- border-color: #764ba2;
- background: #e8ebff;
+ border-color: var(--color-secondary);
+ background: linear-gradient(135deg, rgba(255, 179, 0, 0.15) 0%, rgba(255, 160, 0, 0.2) 100%);
transform: scale(1.02);
+ box-shadow: var(--shadow-xl);
}
-
+
.upload-icon {
- font-size: 4em;
- margin-bottom: 20px;
- }
-
+ font-size: clamp(3rem, 8vw, 5rem);
+ margin-bottom: var(--spacing-lg);
+ display: block;
+ animation: float 3s ease-in-out infinite;
+ }
+
+ @keyframes float {
+ 0%, 100% { transform: translateY(0); }
+ 50% { transform: translateY(-10px); }
+ }
+
+ .upload-area h3 {
+ font-size: clamp(1.125rem, 3vw, 1.5rem);
+ color: var(--color-text);
+ margin-bottom: var(--spacing-sm);
+ }
+
+ .upload-area p {
+ color: var(--color-text-light);
+ font-size: clamp(0.875rem, 2vw, 1rem);
+ }
+
.file-input {
display: none;
}
-
+
+ /* ===== URL INPUT ===== */
.url-input {
width: 100%;
- padding: 15px;
- border: 2px solid #ddd;
- border-radius: 10px;
- font-size: 16px;
- margin-bottom: 20px;
- }
-
- /* Opções de Processamento */
+ padding: var(--spacing-md) var(--spacing-lg);
+ border: 2px solid var(--color-border);
+ border-radius: var(--border-radius-md);
+ font-size: 1rem;
+ font-family: var(--font-body);
+ margin-bottom: var(--spacing-xl);
+ transition: all var(--transition-base);
+ background: var(--color-surface);
+ color: var(--color-text);
+ }
+
+ .url-input:focus {
+ outline: none;
+ border-color: var(--color-secondary);
+ box-shadow: 0 0 0 3px rgba(255, 179, 0, 0.1);
+ }
+
+ /* ===== PROCESSING OPTIONS ===== */
.processing-options {
- margin: 30px 0;
- padding: 20px;
- background: #f8f9ff;
- border-radius: 10px;
+ margin: var(--spacing-2xl) 0;
+ padding: var(--spacing-xl);
+ background: linear-gradient(135deg, rgba(40, 53, 147, 0.03) 0%, rgba(26, 35, 126, 0.05) 100%);
+ border-radius: var(--border-radius-lg);
+ border: 1px solid var(--color-border-light);
}
-
+
.processing-options h3 {
- margin-bottom: 15px;
- color: #333;
+ margin-bottom: var(--spacing-lg);
+ color: var(--color-primary);
}
-
+
.option-cards {
display: grid;
- grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
- gap: 15px;
- margin-top: 15px;
+ grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
+ gap: var(--spacing-lg);
+ margin-top: var(--spacing-lg);
}
-
+
.option-card {
- padding: 20px;
- border: 2px solid #ddd;
- border-radius: 10px;
+ padding: var(--spacing-xl);
+ border: 2px solid var(--color-border);
+ border-radius: var(--border-radius-md);
cursor: pointer;
- transition: all 0.3s;
+ transition: all var(--transition-base);
text-align: center;
- background: white;
+ background: var(--color-surface);
+ position: relative;
+ overflow: hidden;
}
-
+
+ .option-card::before {
+ content: '';
+ position: absolute;
+ top: 0;
+ left: 0;
+ right: 0;
+ height: 4px;
+ background: linear-gradient(90deg, var(--color-primary), var(--color-secondary));
+ transform: scaleX(0);
+ transition: var(--transition-base);
+ }
+
.option-card:hover {
- border-color: #667eea;
- transform: translateY(-2px);
- box-shadow: 0 4px 12px rgba(102, 126, 234, 0.2);
+ border-color: var(--color-secondary);
+ transform: translateY(-6px);
+ box-shadow: var(--shadow-lg);
}
-
+
+ .option-card:hover::before {
+ transform: scaleX(1);
+ }
+
.option-card.selected {
- border-color: #667eea;
- background: #f0f2ff;
+ border-color: var(--color-secondary);
+ background: linear-gradient(135deg, rgba(255, 179, 0, 0.08) 0%, rgba(255, 160, 0, 0.12) 100%);
+ box-shadow: var(--shadow-md);
}
-
+
+ .option-card.selected::before {
+ transform: scaleX(1);
+ }
+
.option-card input[type="radio"] {
- margin-right: 8px;
+ margin-right: var(--spacing-sm);
+ accent-color: var(--color-secondary);
}
-
+
.option-card label {
cursor: pointer;
font-weight: 600;
- color: #333;
+ color: var(--color-text);
display: flex;
align-items: center;
justify-content: center;
+ font-size: 1.125rem;
}
-
+
.option-card .description {
- margin-top: 8px;
- font-size: 0.9em;
- color: #666;
+ margin-top: var(--spacing-sm);
+ font-size: 0.9rem;
+ color: var(--color-text-light);
+ font-weight: 400;
}
-
- /* Opções Gerais */
+
+ /* ===== OPTIONS GRID ===== */
.options {
- margin-top: 30px;
+ margin-top: var(--spacing-2xl);
display: grid;
- grid-template-columns: 1fr 1fr;
- gap: 20px;
+ grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
+ gap: var(--spacing-xl);
}
-
+
.option-group {
display: flex;
flex-direction: column;
}
-
+
.option-group label {
- margin-bottom: 8px;
+ margin-bottom: var(--spacing-sm);
font-weight: 600;
- color: #333;
+ color: var(--color-text);
+ font-size: 0.95rem;
}
-
+
.option-group select {
- padding: 12px;
- border: 2px solid #ddd;
- border-radius: 8px;
- font-size: 14px;
+ padding: var(--spacing-md);
+ border: 2px solid var(--color-border);
+ border-radius: var(--border-radius-md);
+ font-size: 1rem;
+ font-family: var(--font-body);
+ background: var(--color-surface);
+ color: var(--color-text);
+ cursor: pointer;
+ transition: all var(--transition-base);
}
-
+
+ .option-group select:focus {
+ outline: none;
+ border-color: var(--color-secondary);
+ box-shadow: 0 0 0 3px rgba(255, 179, 0, 0.1);
+ }
+
+ /* ===== BUTTONS ===== */
.btn {
- background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
+ background: linear-gradient(135deg, var(--color-primary) 0%, var(--color-primary-light) 100%);
color: white;
border: none;
- padding: 15px 40px;
- border-radius: 10px;
- font-size: 16px;
+ padding: var(--spacing-md) var(--spacing-2xl);
+ border-radius: var(--border-radius-md);
+ font-size: 1.125rem;
font-weight: 600;
+ font-family: var(--font-body);
cursor: pointer;
- margin-top: 30px;
+ margin-top: var(--spacing-2xl);
width: 100%;
- transition: transform 0.2s;
- }
-
+ transition: all var(--transition-base);
+ position: relative;
+ overflow: hidden;
+ box-shadow: var(--shadow-md);
+ }
+
+ .btn::before {
+ content: '';
+ position: absolute;
+ top: 50%;
+ left: 50%;
+ width: 0;
+ height: 0;
+ border-radius: 50%;
+ background: rgba(255, 255, 255, 0.3);
+ transform: translate(-50%, -50%);
+ transition: width 0.6s, height 0.6s;
+ }
+
+ .btn:hover::before {
+ width: 300px;
+ height: 300px;
+ }
+
.btn:hover {
- transform: translateY(-2px);
+ transform: translateY(-3px);
+ box-shadow: var(--shadow-lg);
}
-
+
+ .btn:active {
+ transform: translateY(-1px);
+ box-shadow: var(--shadow-sm);
+ }
+
.btn:disabled {
opacity: 0.6;
cursor: not-allowed;
+ transform: none;
}
-
+
.btn-secondary {
- background: #6c757d;
- margin-top: 10px;
+ background: linear-gradient(135deg, var(--color-text-light) 0%, var(--color-text) 100%);
+ margin-top: var(--spacing-sm);
}
-
- .btn-secondary:hover {
- background: #5a6268;
+
+ .btn-small {
+ padding: var(--spacing-sm) var(--spacing-lg);
+ font-size: 0.95rem;
+ width: auto;
+ margin: 0;
}
-
- /* Jobs List */
+
+ /* ===== JOBS LIST ===== */
.jobs-list {
- margin-top: 40px;
+ margin-top: var(--spacing-2xl);
}
-
+
.job-card {
- background: #f8f9ff;
- border-radius: 10px;
- padding: 20px;
- margin-bottom: 15px;
- border-left: 4px solid #667eea;
+ background: var(--color-surface);
+ border-radius: var(--border-radius-md);
+ padding: var(--spacing-xl);
+ margin-bottom: var(--spacing-lg);
+ border-left: 4px solid var(--color-primary);
+ box-shadow: var(--shadow-sm);
+ transition: all var(--transition-base);
+ animation: slideIn 0.4s ease;
+ }
+
+ @keyframes slideIn {
+ from {
+ opacity: 0;
+ transform: translateX(-20px);
+ }
+ to {
+ opacity: 1;
+ transform: translateX(0);
+ }
}
-
+
+ .job-card:hover {
+ box-shadow: var(--shadow-md);
+ transform: translateX(4px);
+ }
+
.job-header {
display: flex;
justify-content: space-between;
align-items: center;
- margin-bottom: 10px;
+ margin-bottom: var(--spacing-md);
+ flex-wrap: wrap;
+ gap: var(--spacing-sm);
}
-
+
.job-title {
font-weight: 600;
- color: #333;
+ color: var(--color-text);
+ font-size: 1.125rem;
}
-
+
.job-status {
- padding: 5px 15px;
+ padding: var(--spacing-xs) var(--spacing-md);
border-radius: 20px;
- font-size: 12px;
+ font-size: 0.75rem;
font-weight: 600;
+ text-transform: uppercase;
+ letter-spacing: 0.5px;
}
-
- .status-pending { background: #ffc107; color: #333; }
- .status-processing { background: #17a2b8; color: white; }
- .status-completed { background: #28a745; color: white; }
- .status-failed { background: #dc3545; color: white; }
-
+
+ .status-pending {
+ background: rgba(255, 193, 7, 0.2);
+ color: #f57c00;
+ }
+ .status-processing {
+ background: rgba(33, 150, 243, 0.2);
+ color: #1976d2;
+ }
+ .status-completed {
+ background: rgba(76, 175, 80, 0.2);
+ color: #388e3c;
+ }
+ .status-failed {
+ background: rgba(244, 67, 54, 0.2);
+ color: #d32f2f;
+ }
+
.progress-bar {
width: 100%;
height: 8px;
- background: #e0e0e0;
+ background: var(--color-border-light);
border-radius: 4px;
overflow: hidden;
- margin: 10px 0;
+ margin: var(--spacing-md) 0;
+ position: relative;
}
-
+
.progress-fill {
height: 100%;
- background: linear-gradient(90deg, #667eea 0%, #764ba2 100%);
- transition: width 0.3s;
+ background: linear-gradient(90deg, var(--color-primary) 0%, var(--color-secondary) 50%, var(--color-accent) 100%);
+ background-size: 200% 100%;
+ transition: width var(--transition-base);
+ animation: progressShimmer 2s infinite;
+ border-radius: 4px;
}
-
+
+ @keyframes progressShimmer {
+ 0% { background-position: 200% 0; }
+ 100% { background-position: -200% 0; }
+ }
+
.job-actions {
- margin-top: 15px;
+ margin-top: var(--spacing-lg);
display: flex;
- gap: 10px;
+ gap: var(--spacing-sm);
flex-wrap: wrap;
}
-
- .btn-small {
- padding: 8px 16px;
- font-size: 14px;
- width: auto;
- margin: 0;
- }
-
+
+ /* ===== FILE LIST ===== */
.file-list {
- margin-top: 20px;
+ margin-top: var(--spacing-lg);
}
-
+
.file-item {
- background: white;
- padding: 10px;
- border-radius: 5px;
- margin-bottom: 5px;
+ background: var(--color-surface);
+ padding: var(--spacing-md);
+ border-radius: var(--border-radius-sm);
+ margin-bottom: var(--spacing-sm);
display: flex;
justify-content: space-between;
align-items: center;
+ border: 1px solid var(--color-border-light);
+ transition: var(--transition-base);
}
-
+
+ .file-item:hover {
+ background: rgba(255, 179, 0, 0.05);
+ border-color: var(--color-secondary);
+ }
+
.btn-remove {
- background: #dc3545;
+ background: #d32f2f;
color: white;
border: none;
- padding: 5px 10px;
- border-radius: 5px;
+ padding: var(--spacing-xs) var(--spacing-md);
+ border-radius: var(--border-radius-sm);
cursor: pointer;
- font-size: 14px;
- margin-left: 10px;
+ font-size: 0.875rem;
+ margin-left: var(--spacing-md);
+ transition: var(--transition-base);
+ font-weight: 600;
}
-
+
.btn-remove:hover {
- background: #c82333;
- }
-
- /* Footer */
- .footer {
- margin-top: auto;
- padding-top: 20px;
- padding-bottom: 20px;
- border-top: 1px solid #e0e0e0;
- text-align: center;
- color: #666;
- font-size: 14px;
+ background: #b71c1c;
+ transform: scale(1.05);
}
-
- /* Histórico */
+
+ /* ===== HISTORY FILTERS ===== */
.history-filters {
- margin-bottom: 20px;
+ margin-bottom: var(--spacing-xl);
display: flex;
- gap: 10px;
+ gap: var(--spacing-sm);
flex-wrap: wrap;
}
-
+
.filter-btn {
- padding: 8px 16px;
- border: 2px solid #ddd;
- background: white;
- border-radius: 5px;
+ padding: var(--spacing-sm) var(--spacing-lg);
+ border: 2px solid var(--color-border);
+ background: var(--color-surface);
+ border-radius: var(--border-radius-md);
cursor: pointer;
- transition: all 0.3s;
+ transition: all var(--transition-base);
+ font-family: var(--font-body);
+ font-weight: 500;
+ color: var(--color-text);
}
-
+
+ .filter-btn:hover {
+ border-color: var(--color-secondary);
+ transform: translateY(-2px);
+ box-shadow: var(--shadow-sm);
+ }
+
.filter-btn.active {
- border-color: #667eea;
- background: #f0f2ff;
- color: #667eea;
+ border-color: var(--color-secondary);
+ background: linear-gradient(135deg, rgba(255, 179, 0, 0.1) 0%, rgba(255, 160, 0, 0.15) 100%);
+ color: var(--color-primary);
+ font-weight: 600;
}
-
- /* Preview Content - Visualização Formatada */
+
+ /* ===== PREVIEW CONTENT ===== */
.preview-content {
- background: white;
- padding: 20px;
- border-radius: 8px;
- border: 1px solid #ddd;
+ background: var(--color-surface);
+ padding: var(--spacing-xl);
+ border-radius: var(--border-radius-md);
+ border: 1px solid var(--color-border);
max-height: 600px;
overflow-y: auto;
- margin-top: 15px;
- line-height: 1.6;
+ margin-top: var(--spacing-lg);
+ line-height: 1.8;
}
-
+
.preview-content h1, .preview-content h2, .preview-content h3, .preview-content h4 {
- margin-top: 20px;
- margin-bottom: 10px;
- color: #333;
+ margin-top: var(--spacing-xl);
+ margin-bottom: var(--spacing-md);
+ color: var(--color-primary);
+ font-family: var(--font-display);
}
-
+
.preview-content h1 {
- font-size: 1.8em;
- border-bottom: 2px solid #667eea;
- padding-bottom: 10px;
+ font-size: 1.875rem;
+ border-bottom: 3px solid var(--color-secondary);
+ padding-bottom: var(--spacing-md);
}
-
+
.preview-content h2 {
- font-size: 1.5em;
- color: #667eea;
+ font-size: 1.5rem;
+ color: var(--color-primary);
}
-
+
.preview-content h3 {
- font-size: 1.3em;
- }
-
- .preview-content h4 {
- font-size: 1.1em;
- margin-top: 15px;
+ font-size: 1.25rem;
}
-
+
.preview-content p {
- margin-bottom: 12px;
- line-height: 1.6;
+ margin-bottom: var(--spacing-md);
+ line-height: 1.8;
}
-
+
.preview-content pre {
- background: #f5f5f5;
- padding: 15px;
- border-radius: 4px;
+ background: rgba(40, 53, 147, 0.05);
+ padding: var(--spacing-lg);
+ border-radius: var(--border-radius-sm);
overflow-x: auto;
- border-left: 4px solid #667eea;
- margin: 15px 0;
+ border-left: 4px solid var(--color-secondary);
+ margin: var(--spacing-lg) 0;
}
-
+
.preview-content code {
- background: #f5f5f5;
+ background: rgba(40, 53, 147, 0.1);
padding: 2px 6px;
border-radius: 3px;
font-family: 'Courier New', monospace;
font-size: 0.9em;
}
-
+
.preview-content pre code {
background: transparent;
padding: 0;
}
-
+
.preview-content ul, .preview-content ol {
- margin: 10px 0;
- padding-left: 30px;
+ margin: var(--spacing-md) 0;
+ padding-left: var(--spacing-xl);
}
-
+
.preview-content li {
- margin-bottom: 5px;
+ margin-bottom: var(--spacing-xs);
}
-
+
.preview-content blockquote {
- border-left: 4px solid #667eea;
- padding-left: 15px;
- margin: 15px 0;
- color: #666;
+ border-left: 4px solid var(--color-secondary);
+ padding-left: var(--spacing-lg);
+ margin: var(--spacing-lg) 0;
+ color: var(--color-text-light);
font-style: italic;
}
-
+
.preview-content hr {
border: none;
- border-top: 2px solid #e0e0e0;
- margin: 20px 0;
+ border-top: 2px solid var(--color-border);
+ margin: var(--spacing-xl) 0;
}
-
+
.preview-content strong {
font-weight: 600;
- color: #333;
+ color: var(--color-text);
}
-
- .preview-content em {
- font-style: italic;
- }
-
+
.preview-content a {
- color: #667eea;
+ color: var(--color-primary);
text-decoration: none;
+ border-bottom: 1px solid transparent;
+ transition: var(--transition-base);
}
-
+
.preview-content a:hover {
- text-decoration: underline;
+ border-bottom-color: var(--color-primary);
}
-
+
.preview-text-plain {
white-space: pre-wrap;
word-wrap: break-word;
font-family: inherit;
}
+
+ /* ===== FOOTER ===== */
+ .main-footer {
+ background: linear-gradient(135deg, var(--color-primary-dark) 0%, var(--color-primary) 100%);
+ color: rgba(255, 255, 255, 0.9);
+ padding: var(--spacing-3xl) var(--spacing-xl);
+ margin-top: var(--spacing-3xl);
+ position: relative;
+ overflow: hidden;
+ }
+
+ .main-footer::before {
+ content: '';
+ position: absolute;
+ top: 0;
+ left: 0;
+ right: 0;
+ height: 1px;
+ background: linear-gradient(90deg, transparent, rgba(255, 179, 0, 0.5), transparent);
+ }
+
+ .footer-content {
+ max-width: 1200px;
+ margin: 0 auto;
+ display: grid;
+ grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
+ gap: var(--spacing-2xl);
+ }
+
+ .footer-section h4 {
+ font-family: var(--font-display);
+ color: var(--color-secondary);
+ font-size: 1.25rem;
+ margin-bottom: var(--spacing-md);
+ font-weight: 600;
+ }
+
+ .footer-section p,
+ .footer-section li {
+ color: rgba(255, 255, 255, 0.8);
+ font-size: 0.95rem;
+ line-height: 1.8;
+ margin-bottom: var(--spacing-xs);
+ }
+
+ .footer-section ul {
+ list-style: none;
+ padding: 0;
+ }
+
+ .footer-section a {
+ color: rgba(255, 255, 255, 0.9);
+ text-decoration: none;
+ transition: var(--transition-base);
+ display: inline-block;
+ }
+
+ .footer-section a:hover {
+ color: var(--color-secondary);
+ transform: translateX(4px);
+ }
+
+ .footer-bottom {
+ max-width: 1200px;
+ margin: var(--spacing-xl) auto 0;
+ padding-top: var(--spacing-xl);
+ border-top: 1px solid rgba(255, 255, 255, 0.1);
+ text-align: center;
+ color: rgba(255, 255, 255, 0.7);
+ font-size: 0.875rem;
+ }
+
+ /* ===== RESPONSIVIDADE ===== */
+ @media (max-width: 768px) {
+ .navbar {
+ padding: var(--spacing-md) var(--spacing-lg);
+ }
+
+ .navbar-nav {
+ position: fixed;
+ top: 70px;
+ left: 0;
+ right: 0;
+ background: rgba(26, 35, 126, 0.98);
+ backdrop-filter: blur(20px);
+ flex-direction: column;
+ padding: var(--spacing-lg);
+ gap: var(--spacing-md);
+ transform: translateX(-100%);
+ transition: var(--transition-base);
+ box-shadow: var(--shadow-lg);
+ }
+
+ .navbar-nav.active {
+ transform: translateX(0);
+ }
+
+ .mobile-menu-toggle {
+ display: block;
+ }
+
+ .container {
+ padding: var(--spacing-lg) var(--spacing-md);
+ }
+
+ .page {
+ padding: var(--spacing-xl) var(--spacing-lg);
+ border-radius: var(--border-radius-lg);
+ }
+
+ .option-cards {
+ grid-template-columns: 1fr;
+ }
+
+ .options {
+ grid-template-columns: 1fr;
+ }
+
+ .footer-content {
+ grid-template-columns: 1fr;
+ gap: var(--spacing-xl);
+ }
+
+ .job-header {
+ flex-direction: column;
+ align-items: flex-start;
+ }
+ }
+
+ @media (min-width: 769px) and (max-width: 1024px) {
+ .footer-content {
+ grid-template-columns: repeat(2, 1fr);
+ }
+ }
+
+ /* ===== ACESSIBILIDADE ===== */
+ *:focus-visible {
+ outline: 3px solid var(--color-secondary);
+ outline-offset: 2px;
+ }
+
+ @media (prefers-reduced-motion: reduce) {
+ *,
+ *::before,
+ *::after {
+ animation-duration: 0.01ms !important;
+ animation-iteration-count: 1 !important;
+ transition-duration: 0.01ms !important;
+ }
+ }
</style>
</head>
<body>
<!-- Menu de Navegação -->
<nav class="navbar">
<a href="#" class="navbar-brand" onclick="showPage('process'); return false;">🎧 Lazier</a>
- <ul class="navbar-nav">
+ <button class="mobile-menu-toggle" onclick="toggleMobileMenu()" aria-label="Toggle menu">☰</button>
+ <ul class="navbar-nav" id="navbarNav">
<li><a href="#" class="active" onclick="showPage('process'); return false;">Processar</a></li>
<li><a href="#" onclick="showPage('history'); return false;">Histórico</a></li>
<li><a href="#" onclick="showPage('downloads'); return false;">Downloads</a></li>
@@ -532,7 +999,7 @@
<div id="page-process" class="page active">
<div class="page-content">
<h1>🎧 Lazier</h1>
- <p class="subtitle">Transcrição e Sumarização de Áudios, Vídeos, Textos e PDFs</p>
+ <p class="subtitle">Transcrição e Sumarização de Áudios, Vídeos, Textos e PDFs usando OpenAI API</p>
<div class="upload-area" id="uploadArea">
<div class="upload-icon">📁</div>
@@ -594,10 +1061,6 @@
<div class="jobs-list" id="jobsList"></div>
</div>
-
- <footer class="footer">
- Desenvolvido por Pablo Murad - <span id="currentYear"></span>
- </footer>
</div>
<!-- Página: Histórico -->
@@ -615,10 +1078,6 @@
<div class="jobs-list" id="historyList"></div>
</div>
-
- <footer class="footer">
- Desenvolvido por Pablo Murad - <span id="currentYearHistory"></span>
- </footer>
</div>
<!-- Página: Downloads -->
@@ -629,12 +1088,61 @@
<div class="jobs-list" id="downloadsList"></div>
</div>
-
- <footer class="footer">
- Desenvolvido por Pablo Murad - <span id="currentYearDownloads"></span>
- </footer>
</div>
</div>
+
+ <!-- Footer Principal -->
+ <footer class="main-footer">
+ <div class="footer-content">
+ <div class="footer-section">
+ <h4>Sobre o Lazier</h4>
+ <p>Sistema CLI e WebGUI para transcrição e sumarização de áudios, vídeos, textos e PDFs usando OpenAI API.</p>
+ <p style="margin-top: var(--spacing-sm);"><strong>Versão:</strong> 0.01</p>
+ </div>
+
+ <div class="footer-section">
+ <h4>Desenvolvedor</h4>
+ <p><strong>Pablo Murad</strong></p>
+ <p><a href="mailto:pablomurad@pm.me">pablomurad@pm.me</a></p>
+ </div>
+
+ <div class="footer-section">
+ <h4>Funcionalidades</h4>
+ <ul>
+ <li>✓ Upload de arquivos (drag & drop)</li>
+ <li>✓ Processamento de URLs (YouTube, web)</li>
+ <li>✓ Progresso em tempo real</li>
+ <li>✓ Download em múltiplos formatos</li>
+ <li>✓ Histórico de processamentos</li>
+ <li>✓ Cache Redis (opcional)</li>
+ </ul>
+ </div>
+
+ <div class="footer-section">
+ <h4>Formatos Suportados</h4>
+ <ul>
+ <li><strong>Áudio:</strong> mp3, wav, m4a, aac, flac, ogg, opus, wma</li>
+ <li><strong>Vídeo:</strong> mp4, avi, mkv, mov, wmv, flv, webm</li>
+ <li><strong>Documentos:</strong> pdf, txt, md, html</li>
+ <li><strong>Online:</strong> YouTube, páginas web</li>
+ </ul>
+ </div>
+
+ <div class="footer-section">
+ <h4>Requisitos</h4>
+ <ul>
+ <li>Python 3.8+</li>
+ <li>Docker e Docker Compose</li>
+ <li>Chave da API OpenAI</li>
+ <li>Redis (opcional)</li>
+ </ul>
+ </div>
+ </div>
+
+ <div class="footer-bottom">
+ <p>Desenvolvido com ❤️ por Pablo Murad - <span id="currentYear"></span></p>
+ </div>
+ </footer>
<script>
// Estado da aplicação
@@ -647,18 +1155,41 @@
// Inicialização
document.addEventListener('DOMContentLoaded', () => {
document.getElementById('currentYear').textContent = new Date().getFullYear();
- document.getElementById('currentYearHistory').textContent = new Date().getFullYear();
- document.getElementById('currentYearDownloads').textContent = new Date().getFullYear();
loadHistory();
+
+ // Animações de entrada
+ const elements = document.querySelectorAll('.page-content > *');
+ elements.forEach((el, index) => {
+ el.style.opacity = '0';
+ el.style.transform = 'translateY(20px)';
+ setTimeout(() => {
+ el.style.transition = 'all 0.5s ease';
+ el.style.opacity = '1';
+ el.style.transform = 'translateY(0)';
+ }, index * 100);
+ });
});
+ // Toggle mobile menu
+ function toggleMobileMenu() {
+ const nav = document.getElementById('navbarNav');
+ nav.classList.toggle('active');
+ }
+
// Navegação
function showPage(page) {
document.querySelectorAll('.page').forEach(p => p.classList.remove('active'));
document.querySelectorAll('.navbar-nav a').forEach(a => a.classList.remove('active'));
document.getElementById(`page-${page}`).classList.add('active');
- document.querySelectorAll('.navbar-nav a')[['process', 'history', 'downloads'].indexOf(page)].classList.add('active');
+ const navLinks = document.querySelectorAll('.navbar-nav a');
+ const pageIndex = ['process', 'history', 'downloads'].indexOf(page);
+ if (pageIndex >= 0 && navLinks[pageIndex]) {
+ navLinks[pageIndex].classList.add('active');
+ }
+
+ // Fecha menu mobile se estiver aberto
+ document.getElementById('navbarNav').classList.remove('active');
currentPage = page;
@@ -717,12 +1248,11 @@
</div>
`).join('')}
</div>
- <p style="margin-top: 15px; color: #666;">Clique novamente para selecionar outros arquivos</p>
+ <p style="margin-top: var(--spacing-md); color: var(--color-text-light);">Clique novamente para selecionar outros arquivos</p>
`;
uploadArea.innerHTML = fileListHTML;
// Re-adiciona event listener após atualizar HTML
uploadArea.addEventListener('click', (e) => {
- // Não abrir se clicou no botão de remover
if (!e.target.classList.contains('btn-remove')) {
fileInput.click();
}
@@ -740,7 +1270,6 @@
function removeFile(index) {
selectedFiles.splice(index, 1);
updateFileList();
- // Limpa input para permitir re-seleção do mesmo arquivo
fileInput.value = '';
}
@@ -873,7 +1402,7 @@
resultEl.innerHTML = actionsHTML;
} else if (data.status === 'failed') {
- resultEl.innerHTML = `<p style="color:red; margin-top:10px;">Erro: ${data.error || 'Erro desconhecido'}</p>`;
+ resultEl.innerHTML = `<p style="color:#d32f2f; margin-top:var(--spacing-md);">Erro: ${data.error || 'Erro desconhecido'}</p>`;
}
}
@@ -882,20 +1411,17 @@
const response = await fetch(`/api/jobs/${jobId}/details`);
const data = await response.json();
- // Usa o formato retornado pelo endpoint
const format = data.format || 'docx';
let content = '<div class="preview-content">';
if (data.summary) {
content += '<h3>Sumário</h3>';
- // Renderiza Markdown se formato for md/markdown OU se conteúdo contém padrões Markdown
const shouldRenderMarkdown = (format === 'md' || format === 'markdown') || isMarkdownContent(data.summary);
if (shouldRenderMarkdown && typeof marked !== 'undefined') {
content += marked.parse(data.summary);
} else {
- // Formata texto plano preservando quebras de linha
content += '<div class="preview-text-plain">' + escapeHtml(data.summary) + '</div>';
}
content += '<hr>';
@@ -903,13 +1429,11 @@
if (data.transcription) {
content += '<h3>Transcrição</h3>';
- // Renderiza Markdown se formato for md/markdown OU se conteúdo contém padrões Markdown
const shouldRenderMarkdown = (format === 'md' || format === 'markdown') || isMarkdownContent(data.transcription);
if (shouldRenderMarkdown && typeof marked !== 'undefined') {
content += marked.parse(data.transcription);
} else {
- // Formata texto plano preservando quebras de linha
content += '<div class="preview-text-plain">' + escapeHtml(data.transcription) + '</div>';
}
}
@@ -931,18 +1455,17 @@
function isMarkdownContent(text) {
if (!text) return false;
- // Verifica padrões comuns de Markdown
const markdownPatterns = [
- /^\s*#{1,6}\s+.+$/m, // Títulos (# ## ###)
- /\*\*[^*]+\*\*/, // Negrito **texto**
- /\*[^*]+\*/, // Itálico *texto*
- /^\s*[-*+]\s+.+$/m, // Listas não ordenadas
- /^\s*\d+\.\s+.+$/m, // Listas ordenadas
- /\[.+\]\(.+\)/, // Links [texto](url)
- /```[\s\S]*?```/, // Blocos de código ```
- /`[^`]+`/, // Código inline `código`
- /^>\s+.+$/m, // Blockquotes >
- /^\|.+\|$/m, // Tabelas |
+ /^\s*#{1,6}\s+.+$/m,
+ /\*\*[^*]+\*\*/,
+ /\*[^*]+\*/,
+ /^\s*[-*+]\s+.+$/m,
+ /^\s*\d+\.\s+.+$/m,
+ /\[.+\]\(.+\)/,
+ /```[\s\S]*?```/,
+ /`[^`]+`/,
+ /^>\s+.+$/m,
+ /^\|.+\|$/m,
];
return markdownPatterns.some(pattern => pattern.test(text));
@@ -977,7 +1500,7 @@
}
if (filteredJobs.length === 0) {
- historyList.innerHTML = '<p style="text-align:center; color:#666; padding:40px;">Nenhum job encontrado</p>';
+ historyList.innerHTML = '<p style="text-align:center; color:var(--color-text-light); padding:var(--spacing-3xl);">Nenhum job encontrado</p>';
return;
}
@@ -1011,7 +1534,7 @@
const completedJobs = allJobs.filter(job => job.status === 'completed');
if (completedJobs.length === 0) {
- downloadsList.innerHTML = '<p style="text-align:center; color:#666; padding:40px;">Nenhum arquivo disponível para download</p>';
+ downloadsList.innerHTML = '<p style="text-align:center; color:var(--color-text-light); padding:var(--spacing-3xl);">Nenhum arquivo disponível para download</p>';
return;
}