diff --git a/public/app.js b/public/app.js index d569ec6..f7f5362 100644 --- a/public/app.js +++ b/public/app.js @@ -11,6 +11,72 @@ function escapeHtml(text) { .replace(/'/g, "'"); } +// Função auxiliar para exibir alerta customizado e elegante (substitui o alert nativo) +function showCustomAlert(title, message) { + return new Promise((resolve) => { + const overlay = document.createElement('div'); + overlay.className = 'custom-dialog-overlay'; + + overlay.innerHTML = ` +
+
+ 🔔 + ${escapeHtml(title)} +
+

${escapeHtml(message)}

+ +
+ `; + + const btn = overlay.querySelector('.btn-dialog-confirm'); + btn.addEventListener('click', () => { + overlay.remove(); + resolve(); + }); + + document.body.appendChild(overlay); + }); +} + +// Função auxiliar para exibir diálogo de confirmação elegante (substitui o confirm nativo) +function showCustomConfirm(title, message, isDanger = false) { + return new Promise((resolve) => { + const overlay = document.createElement('div'); + overlay.className = 'custom-dialog-overlay'; + + overlay.innerHTML = ` +
+
+ ${isDanger ? '⚠️' : '❓'} + ${escapeHtml(title)} +
+

${escapeHtml(message)}

+ +
+ `; + + const btnCancel = overlay.querySelector('.btn-dialog-cancel'); + const btnConfirm = overlay.querySelector(isDanger ? '.btn-dialog-danger' : '.btn-dialog-confirm'); + + btnCancel.addEventListener('click', () => { + overlay.remove(); + resolve(false); + }); + + btnConfirm.addEventListener('click', () => { + overlay.remove(); + resolve(true); + }); + + document.body.appendChild(overlay); + }); +} + // Função auxiliar para gerar nome de arquivo de download formatado como . function getDownloadFileName(type, ext) { const now = new Date(); @@ -3504,6 +3570,7 @@ const initApp = () => { const btnDeleteComicsProject = document.getElementById('btnDeleteComicsProject'); const comicsProjectTitleInput = document.getElementById('comicsProjectTitleInput'); const btnSaveComicsProject = document.getElementById('btnSaveComicsProject'); + const btnSaveNewComicsProject = document.getElementById('btnSaveNewComicsProject'); let currentComicsProjectId = null; let currentComicsCharDescEnglish = ''; @@ -3619,6 +3686,7 @@ const initApp = () => { if (comicsProjectSelect) comicsProjectSelect.value = ''; if (comicsProjectTitleInput) comicsProjectTitleInput.value = ''; if (btnDeleteComicsProject) btnDeleteComicsProject.style.display = 'none'; + if (btnSaveComicsProject) btnSaveComicsProject.style.display = 'none'; comicsTemaInput.value = ''; comicsResultArea.style.display = 'none'; @@ -3707,6 +3775,7 @@ const initApp = () => { comicsTemaInput.value = project.tema || ''; if (btnDeleteComicsProject) btnDeleteComicsProject.style.display = 'block'; + if (btnSaveComicsProject) btnSaveComicsProject.style.display = 'block'; // Carregar proporção selectedComicsRatio = project.proporcao || '16:9'; @@ -3829,34 +3898,40 @@ const initApp = () => { } catch (err) { console.error(err); - alert('Erro ao carregar projeto: ' + err.message); + await showCustomAlert('Erro', 'Erro ao carregar projeto: ' + err.message); } }; - const saveComicsProject = async () => { + const saveComicsProjectFlow = async (forceNew = false) => { const titulo = comicsProjectTitleInput ? comicsProjectTitleInput.value.trim() : ''; if (!titulo) { - alert('Por favor, digite um título para salvar seu projeto.'); + await showCustomAlert('Campo Obrigatório', 'Por favor, digite um título para salvar seu projeto.'); return; } const characters = []; document.querySelectorAll('.comic-char-row').forEach(row => { - const type = row.querySelector('.char-type-input').value.trim(); - const name = row.querySelector('.char-name-input').value.trim(); - const isAnimal = row.querySelector('span').textContent === '🐾'; - if (type || name) { - characters.push({ type, name, isAnimal }); + const typeInput = row.querySelector('.char-type-input'); + const nameInput = row.querySelector('.char-name-input'); + if (typeInput && nameInput) { + const type = typeInput.value.trim(); + const name = nameInput.value.trim(); + const isAnimal = row.querySelector('span').textContent === '🐾'; + if (type || name) { + characters.push({ type, name, isAnimal }); + } } }); - let cenario = comicsCenarioSelect.value; - if (cenario === 'custom') { + let cenario = comicsCenarioSelect ? comicsCenarioSelect.value : 'no parque de diversões colorido'; + if (cenario === 'custom' && comicsCenarioCustomInput) { cenario = comicsCenarioCustomInput.value.trim(); } + const targetId = forceNew ? null : currentComicsProjectId; + const body = { - id: currentComicsProjectId, + id: targetId, titulo, tema: comicsTemaInput.value.trim(), cenario, @@ -3885,18 +3960,28 @@ const initApp = () => { const data = await resp.json(); currentComicsProjectId = data.id; - alert('Projeto salvo com sucesso!'); + await showCustomAlert('Sucesso', 'Projeto salvo com sucesso!'); + if (btnDeleteComicsProject) btnDeleteComicsProject.style.display = 'block'; + if (btnSaveComicsProject) btnSaveComicsProject.style.display = 'block'; await loadComicsProjectsList(); } catch (err) { console.error(err); - alert('Erro ao salvar projeto: ' + err.message); + await showCustomAlert('Erro', 'Erro ao salvar projeto: ' + err.message); } }; + const saveComicsProject = () => saveComicsProjectFlow(false); + const saveNewComicsProject = () => saveComicsProjectFlow(true); + const deleteComicsProject = async () => { if (!currentComicsProjectId) return; - if (!confirm('Tem certeza de que deseja excluir este projeto permanentemente?')) return; + const confirmed = await showCustomConfirm( + 'Confirmar Exclusão', + 'Tem certeza de que deseja excluir este projeto permanentemente?', + true + ); + if (!confirmed) return; try { const resp = await fetch(`/api/comics/projects/${currentComicsProjectId}`, { @@ -3905,12 +3990,12 @@ const initApp = () => { if (!resp.ok) throw new Error('Falha ao excluir projeto'); - alert('Projeto excluído com sucesso!'); + await showCustomAlert('Excluído', 'Projeto excluído com sucesso!'); startNewComicsProject(); await loadComicsProjectsList(); } catch (err) { console.error(err); - alert('Erro ao excluir projeto: ' + err.message); + await showCustomAlert('Erro', 'Erro ao excluir projeto: ' + err.message); } }; @@ -3929,8 +4014,12 @@ const initApp = () => { } if (btnNewComicsProject) { - btnNewComicsProject.addEventListener('click', () => { - if (confirm('Deseja iniciar um novo projeto? Alterações não salvas serão perdidas.')) { + btnNewComicsProject.addEventListener('click', async () => { + const confirmed = await showCustomConfirm( + 'Novo Projeto', + 'Deseja iniciar um novo projeto? Alterações não salvas serão perdidas.' + ); + if (confirmed) { startNewComicsProject(); } }); @@ -3940,6 +4029,10 @@ const initApp = () => { btnSaveComicsProject.addEventListener('click', saveComicsProject); } + if (btnSaveNewComicsProject) { + btnSaveNewComicsProject.addEventListener('click', saveNewComicsProject); + } + if (btnDeleteComicsProject) { btnDeleteComicsProject.addEventListener('click', deleteComicsProject); } @@ -3986,7 +4079,7 @@ const initApp = () => { btnGenerateComics.addEventListener('click', async () => { const tema = comicsTemaInput.value.trim(); if (!tema) { - alert('Por favor, digite um tema para a história.'); + await showCustomAlert('Campo Requerido', 'Por favor, digite um tema para a história.'); return; } @@ -4001,7 +4094,7 @@ const initApp = () => { }); if (personagens.length === 0) { - alert('Adicione e preencha pelo menos um personagem principal.'); + await showCustomAlert('Personagens Faltando', 'Adicione e preencha pelo menos um personagem principal.'); return; } @@ -4161,7 +4254,7 @@ const initApp = () => { } catch (err) { console.error('Erro ao fabricar quadrinhos:', err); - alert('Erro ao fabricar quadrinhos: ' + err.message); + await showCustomAlert('Erro', 'Erro ao fabricar quadrinhos: ' + err.message); comicsGenerateLoader.style.display = 'none'; btnGenerateComics.disabled = false; } diff --git a/public/index.html b/public/index.html index cd43692..2b350e9 100644 --- a/public/index.html +++ b/public/index.html @@ -759,7 +759,8 @@
- + +
diff --git a/public/style.css b/public/style.css index 3524138..5cc5b74 100644 --- a/public/style.css +++ b/public/style.css @@ -3231,5 +3231,117 @@ code { box-shadow: 0 0 8px rgba(219, 39, 119, 0.2); } +/* ========================================================================== + CUSTOM MODAL DIALOGS (ALERT & CONFIRM) + ========================================================================== */ +.custom-dialog-overlay { + position: fixed; + top: 0; + left: 0; + width: 100vw; + height: 100vh; + background: rgba(0, 0, 0, 0.6); + backdrop-filter: blur(8px); + -webkit-backdrop-filter: blur(8px); + display: flex; + justify-content: center; + align-items: center; + z-index: 10000; + animation: dialogFadeIn 0.2s ease-out forwards; +} + +.custom-dialog-card { + background: var(--bg-primary, #121212); + border: 1px solid var(--border-light, #262626); + border-radius: 16px; + width: 400px; + max-width: 90%; + padding: 24px; + box-shadow: 0 10px 30px rgba(0, 0, 0, 0.5); + display: flex; + flex-direction: column; + gap: 16px; + transform: scale(0.9); + opacity: 0; + animation: dialogScaleUp 0.25s cubic-bezier(0.34, 1.56, 0.64, 1) forwards; +} + +.custom-dialog-header { + font-size: 1.1rem; + font-weight: 700; + color: var(--text-primary, #ffffff); + display: flex; + align-items: center; + gap: 8px; +} + +.custom-dialog-body { + font-size: 0.92rem; + color: var(--text-secondary, #a3a3a3); + line-height: 1.5; + margin: 0; +} + +.custom-dialog-footer { + display: flex; + justify-content: flex-end; + gap: 12px; + margin-top: 8px; +} + +.btn-dialog { + padding: 8px 18px; + font-size: 0.85rem; + font-weight: 600; + border-radius: 8px; + cursor: pointer; + border: 1px solid transparent; + transition: all 0.2s ease; +} + +.btn-dialog-cancel { + background: transparent; + border-color: var(--border-light, #262626); + color: var(--text-secondary, #a3a3a3); +} + +.btn-dialog-cancel:hover { + background: rgba(255, 255, 255, 0.05); + color: var(--text-primary, #ffffff); +} + +.btn-dialog-confirm { + background: linear-gradient(135deg, var(--brand-green, #10a37f) 0%, #0a5f49 100%); + color: white; + box-shadow: 0 4px 12px rgba(16, 163, 127, 0.25); +} + +.btn-dialog-confirm:hover { + opacity: 0.9; + transform: translateY(-1px); +} + +.btn-dialog-danger { + background: linear-gradient(135deg, #ef4444 0%, #b91c1c 100%); + color: white; + box-shadow: 0 4px 12px rgba(239, 68, 68, 0.25); +} + +.btn-dialog-danger:hover { + opacity: 0.9; + transform: translateY(-1px); +} + +@keyframes dialogFadeIn { + from { opacity: 0; } + to { opacity: 1; } +} + +@keyframes dialogScaleUp { + from { transform: scale(0.95); opacity: 0; } + to { transform: scale(1); opacity: 1; } +} + +