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(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 = `
+
+
+
${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; }
+}
+
+