Feature: Fabrica de Quadrinhos profissional com geracao paralela, modo apresentacao fullscreen, storyboard interativo, exportacao ZIP e adaptacao mobile

This commit is contained in:
2026-08-31 11:05:03 +00:00
parent 665b9abe63
commit 36f72f56dc
3 changed files with 686 additions and 96 deletions
+391 -54
View File
@@ -4442,6 +4442,8 @@ Se algum dado não for mencionado, deixe a string vazia ou false para boolean.`
comicsCharListContainer.appendChild(row); comicsCharListContainer.appendChild(row);
}; };
const btnImportTurmaChars = document.getElementById('btnImportTurmaChars');
if (btnAddHumanChar) { if (btnAddHumanChar) {
btnAddHumanChar.addEventListener('click', () => { btnAddHumanChar.addEventListener('click', () => {
addCharacterRow('', '', false); addCharacterRow('', '', false);
@@ -4454,6 +4456,37 @@ Se algum dado não for mencionado, deixe a string vazia ou false para boolean.`
}); });
} }
if (btnImportTurmaChars) {
btnImportTurmaChars.addEventListener('click', async () => {
try {
btnImportTurmaChars.disabled = true;
btnImportTurmaChars.textContent = '⏳ Carregando...';
const res = await fetch('/api/alunos');
if (!res.ok) throw new Error('Falha ao obter lista de alunos');
const alunos = await res.json();
if (!alunos || alunos.length === 0) {
await showCustomAlert('Nenhum Aluno', 'Cadastre alunos na seção "Minha Turma" para importá-los automaticamente.');
return;
}
// Adiciona os alunos como personagens (até 4 para manter a história focada)
const selecionados = alunos.slice(0, 4);
comicsCharListContainer.innerHTML = '';
selecionados.forEach(aluno => {
const generoProvavel = aluno.nome.trim().endsWith('a') ? 'Menina' : 'Menino';
addCharacterRow(generoProvavel, aluno.nome.trim(), false);
});
await showCustomAlert('Turma Importada!', `${selecionados.length} alunos foram adicionados como personagens da história.`);
} catch (err) {
console.error('Erro ao importar alunos:', err);
await showCustomAlert('Erro', 'Não foi possível carregar a lista de alunos: ' + err.message);
} finally {
btnImportTurmaChars.disabled = false;
btnImportTurmaChars.textContent = '+ 🏫 Importar da Turma';
}
});
}
// Exibir/ocultar cenário personalizado // Exibir/ocultar cenário personalizado
if (comicsCenarioSelect) { if (comicsCenarioSelect) {
comicsCenarioSelect.addEventListener('change', () => { comicsCenarioSelect.addEventListener('change', () => {
@@ -4945,18 +4978,14 @@ Se algum dado não for mencionado, deixe a string vazia ou false para boolean.`
currentComicsCharDescEnglish = scriptData.character_description; currentComicsCharDescEnglish = scriptData.character_description;
} }
// 2. Gerar cada quadrinho sequencialmente // 2. Gerar quadrinhos em PARALELO com tracking de progresso em tempo real
if (genMode !== 'extend') { if (genMode !== 'extend') {
generatedPanelsData = []; generatedPanelsData = [];
} }
const totalPanels = scriptData.panels.length; const totalPanels = scriptData.panels.length;
let completedCount = 0;
for (let i = 0; i < totalPanels; i++) { const panelPromises = scriptData.panels.map(async (panel, idx) => {
const panel = scriptData.panels[i];
const progressPercent = 15 + Math.floor((i / totalPanels) * 80);
comicsLoaderProgress.style.width = `${progressPercent}%`;
comicsLoaderText.textContent = `Ilustrando quadrinho ${i + 1} de ${totalPanels}...`;
// Injetamos a proporção e estilo de forma reforçada no prompt // Injetamos a proporção e estilo de forma reforçada no prompt
const fullPrompt = `${panel.image_prompt}, high resolution children book illustration, cute Pixar style, ${selectedComicsRatio === '16:9' ? '16:9 aspect ratio' : '4:3 aspect ratio'}`; const fullPrompt = `${panel.image_prompt}, high resolution children book illustration, cute Pixar style, ${selectedComicsRatio === '16:9' ? '16:9 aspect ratio' : '4:3 aspect ratio'}`;
@@ -4968,65 +4997,43 @@ Se algum dado não for mencionado, deixe a string vazia ou false para boolean.`
if (!frameResp.ok) { if (!frameResp.ok) {
const err = await frameResp.json(); const err = await frameResp.json();
throw new Error(`Erro no quadrinho ${i + 1}: ${err.error}`); throw new Error(`Erro no quadrinho ${panel.panel_number}: ${err.error}`);
} }
const frameData = await frameResp.json(); const frameData = await frameResp.json();
generatedPanelsData.push({ completedCount++;
const progressPercent = 15 + Math.floor((completedCount / totalPanels) * 80);
if (comicsLoaderProgress) comicsLoaderProgress.style.width = `${progressPercent}%`;
if (comicsLoaderText) comicsLoaderText.textContent = `⚡ Ilustrando em paralelo: ${completedCount}/${totalPanels} quadros concluídos...`;
return {
panel_number: panel.panel_number, panel_number: panel.panel_number,
imageUrl: frameData.imageUrl, imageUrl: frameData.imageUrl,
dialogue: panel.dialogue || '', dialogue: panel.dialogue || '',
image_prompt: panel.image_prompt || '' image_prompt: panel.image_prompt || ''
};
}); });
const newPanels = await Promise.all(panelPromises);
// Ordena por panel_number para garantir a sequência correta
newPanels.sort((a, b) => a.panel_number - b.panel_number);
if (genMode === 'extend') {
generatedPanelsData = generatedPanelsData.concat(newPanels);
} else {
generatedPanelsData = newPanels;
} }
// Renderizar quadrinhos na tela (todos, incluindo os anteriores caso tenha sido extensão) // Atualizar cabeçalho e metadados
comicsResultTitle.textContent = scriptData.title || comicsResultTitle.textContent || 'Fábrica de Quadrinhos Pedagog'; comicsResultTitle.textContent = scriptData.title || comicsResultTitle.textContent || 'Fábrica de Quadrinhos Pedagog';
comicsGridContainer.innerHTML = ''; const metaPanels = document.getElementById('comicsMetaPanels');
if (metaPanels) metaPanels.textContent = `🖼️ ${generatedPanelsData.length} Quadros`;
const metaRatio = document.getElementById('comicsMetaRatio');
if (metaRatio) metaRatio.textContent = `📐 ${selectedComicsRatio} ${selectedComicsRatio === '16:9' ? 'HD' : 'Retrô'}`;
generatedPanelsData.forEach((panel, index) => { // Renderizar Storyboard Interativo
const panelCard = document.createElement('div'); renderComicsStoryboard();
panelCard.className = 'comic-panel-card';
const imgContainer = document.createElement('div');
imgContainer.className = `comic-panel-image-container ratio-${selectedComicsRatio.replace(':', '-')}`;
const img = document.createElement('img');
img.className = 'comic-panel-image';
img.src = panel.imageUrl || panel.image_url;
img.alt = `Quadro ${panel.panel_number}`;
imgContainer.appendChild(img);
// Selo do número
const badge = document.createElement('div');
badge.className = 'comic-panel-number-badge';
badge.textContent = panel.panel_number;
imgContainer.appendChild(badge);
panelCard.appendChild(imgContainer);
const caption = document.createElement('div');
caption.className = 'comic-caption-text';
caption.style.cssText = 'padding: 10px 14px; background: var(--bg-primary); border-top: 1px solid var(--border-light);';
const textarea = document.createElement('textarea');
textarea.className = 'comic-caption-input';
textarea.value = panel.dialogue || '';
textarea.placeholder = 'Digite a fala ou legenda do quadrinho...';
textarea.style.cssText = 'width: 100%; border: 1px solid var(--border-light); background: var(--bg-secondary); color: var(--text-primary); border-radius: 6px; padding: 8px; font-family: inherit; font-size: 0.85rem; resize: vertical; box-sizing: border-box; min-height: 50px;';
textarea.addEventListener('input', (e) => {
panel.dialogue = e.target.value;
if (generatedPanelsData[index]) {
generatedPanelsData[index].dialogue = e.target.value;
}
});
caption.appendChild(textarea);
panelCard.appendChild(caption);
comicsGridContainer.appendChild(panelCard);
});
// Se for uma nova história derivada de um projeto antigo, limpamos o ID para virar um novo projeto independente ao salvar // Se for uma nova história derivada de um projeto antigo, limpamos o ID para virar um novo projeto independente ao salvar
if (genMode === 'new_story' && currentComicsProjectId) { if (genMode === 'new_story' && currentComicsProjectId) {
@@ -5053,6 +5060,336 @@ Se algum dado não for mencionado, deixe a string vazia ou false para boolean.`
}); });
} }
// --- RENDERIZADOR DO STORYBOARD PROFISSIONAL DOS QUADRINHOS ---
const renderComicsStoryboard = () => {
if (!comicsGridContainer) return;
comicsGridContainer.innerHTML = '';
generatedPanelsData.forEach((panel, index) => {
const panelCard = document.createElement('div');
panelCard.className = 'comic-panel-card';
panelCard.dataset.index = index;
const imgContainer = document.createElement('div');
imgContainer.className = `comic-panel-image-container ratio-${selectedComicsRatio.replace(':', '-')}`;
const img = document.createElement('img');
img.className = 'comic-panel-image';
img.src = panel.imageUrl || panel.image_url;
img.alt = `Quadro ${panel.panel_number}`;
img.loading = 'lazy';
imgContainer.appendChild(img);
// Selo de número do quadrinho
const badge = document.createElement('div');
badge.className = 'comic-panel-number-badge';
badge.textContent = panel.panel_number;
imgContainer.appendChild(badge);
// Barra de Ações Rápidas por Quadro (Mobile & Desktop)
const actionsOverlay = document.createElement('div');
actionsOverlay.className = 'comic-panel-overlay-actions';
// Botão 🔍 Zoom
const btnZoom = document.createElement('button');
btnZoom.type = 'button';
btnZoom.className = 'btn-comic-action';
btnZoom.title = 'Ampliar imagem';
btnZoom.innerHTML = '🔍';
btnZoom.addEventListener('click', (e) => {
e.stopPropagation();
openComicsPresentation(index);
});
actionsOverlay.appendChild(btnZoom);
// Botão 🔄 Regenerar Imagem deste Quadro
const btnRegen = document.createElement('button');
btnRegen.type = 'button';
btnRegen.className = 'btn-comic-action';
btnRegen.title = 'Regenerar apenas este quadrinho';
btnRegen.innerHTML = '🔄';
btnRegen.addEventListener('click', async (e) => {
e.stopPropagation();
await regenerateSinglePanel(index, panelCard, img, btnRegen);
});
actionsOverlay.appendChild(btnRegen);
// Botão ✏️ Ver/Editar Prompt
const btnPromptToggle = document.createElement('button');
btnPromptToggle.type = 'button';
btnPromptToggle.className = 'btn-comic-action';
btnPromptToggle.title = 'Editar prompt visual';
btnPromptToggle.innerHTML = '✏️';
actionsOverlay.appendChild(btnPromptToggle);
// Botão 💾 Baixar Imagem Individual
const btnDownloadImg = document.createElement('button');
btnDownloadImg.type = 'button';
btnDownloadImg.className = 'btn-comic-action';
btnDownloadImg.title = 'Baixar esta imagem PNG';
btnDownloadImg.innerHTML = '💾';
btnDownloadImg.addEventListener('click', (e) => {
e.stopPropagation();
const a = document.createElement('a');
a.href = panel.imageUrl || panel.image_url;
a.download = `quadrinho_${panel.panel_number}.png`;
document.body.appendChild(a);
a.click();
a.remove();
});
actionsOverlay.appendChild(btnDownloadImg);
imgContainer.appendChild(actionsOverlay);
panelCard.appendChild(imgContainer);
// Área de Legenda / Diálogo
const caption = document.createElement('div');
caption.className = 'comic-caption-text';
const textarea = document.createElement('textarea');
textarea.className = 'comic-caption-input';
textarea.value = panel.dialogue || '';
textarea.placeholder = 'Digite a fala ou legenda do quadrinho...';
textarea.style.cssText = 'width: 100%; border: 1px solid var(--border-light); background: var(--bg-secondary); color: var(--text-primary); border-radius: 6px; padding: 8px; font-family: inherit; font-size: 0.85rem; resize: vertical; box-sizing: border-box; min-height: 48px;';
textarea.addEventListener('input', (e) => {
panel.dialogue = e.target.value;
if (generatedPanelsData[index]) {
generatedPanelsData[index].dialogue = e.target.value;
}
});
caption.appendChild(textarea);
// Mini-Editor de Prompt Visual (Oculto por padrão, ativado no botão ✏️)
const promptBox = document.createElement('div');
promptBox.className = 'comic-prompt-details';
const promptInput = document.createElement('textarea');
promptInput.style.cssText = 'width: 100%; font-size: 0.72rem; padding: 4px; background: var(--bg-primary); color: var(--text-primary); border: 1px solid var(--border-light); border-radius: 4px; resize: vertical; min-height: 40px; box-sizing: border-box;';
promptInput.value = panel.image_prompt || '';
promptInput.addEventListener('input', (e) => {
panel.image_prompt = e.target.value;
});
promptBox.appendChild(document.createTextNode('Prompt Visual: '));
promptBox.appendChild(promptInput);
caption.appendChild(promptBox);
btnPromptToggle.addEventListener('click', (e) => {
e.stopPropagation();
promptBox.style.display = promptBox.style.display === 'block' ? 'none' : 'block';
});
panelCard.appendChild(caption);
comicsGridContainer.appendChild(panelCard);
});
};
// Regenerar um único painel
const regenerateSinglePanel = async (index, panelCard, imgElement, btnRegen) => {
const panel = generatedPanelsData[index];
if (!panel) return;
try {
btnRegen.disabled = true;
btnRegen.textContent = '⏳';
imgElement.style.opacity = '0.4';
const promptToUse = panel.image_prompt || 'cute children illustration';
const fullPrompt = `${promptToUse}, high resolution children book illustration, cute Pixar style, ${selectedComicsRatio === '16:9' ? '16:9 aspect ratio' : '4:3 aspect ratio'}`;
const resp = await fetch('/api/comics/generate-frame', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ prompt: fullPrompt })
});
if (!resp.ok) {
const err = await resp.json();
throw new Error(err.error || 'Erro na regeneração');
}
const data = await resp.json();
panel.imageUrl = data.imageUrl;
imgElement.src = data.imageUrl + '?v=' + Date.now();
imgElement.style.opacity = '1';
} catch (err) {
console.error('Erro ao regenerar painel:', err);
await showCustomAlert('Erro', 'Não foi possível regenerar o quadrinho: ' + err.message);
imgElement.style.opacity = '1';
} finally {
btnRegen.disabled = false;
btnRegen.innerHTML = '🔄';
}
};
// --- EXPORTAR TODAS AS IMAGENS EM ZIP ---
const btnExportComicsZIP = document.getElementById('btnExportComicsZIP');
if (btnExportComicsZIP) {
btnExportComicsZIP.addEventListener('click', async () => {
if (!generatedPanelsData || generatedPanelsData.length === 0) {
await showCustomAlert('Aviso', 'Nenhum quadrinho gerado para exportar.');
return;
}
try {
btnExportComicsZIP.disabled = true;
btnExportComicsZIP.textContent = '⏳ Criando ZIP...';
if (typeof JSZip === 'undefined') {
throw new Error('Biblioteca JSZip não carregada.');
}
const zip = new JSZip();
const folder = zip.folder('quadrinhos');
const title = (comicsResultTitle?.textContent || 'Historia_Quadrinhos').replace(/[^a-zA-Z0-9_-]/g, '_');
let roteiroTxt = `HISTÓRIA EM QUADRINHOS: ${comicsResultTitle?.textContent || 'Fábrica de Quadrinhos'}\n`;
roteiroTxt += `Criado com PedagogIA em ${new Date().toLocaleDateString('pt-BR')}\n\n`;
for (let i = 0; i < generatedPanelsData.length; i++) {
const panel = generatedPanelsData[i];
const imgUrl = panel.imageUrl || panel.image_url;
roteiroTxt += `[QUADRO ${panel.panel_number}]\nFala/Legenda: ${panel.dialogue || '(Sem fala)'}\nPrompt Visual: ${panel.image_prompt || ''}\n\n`;
try {
const resp = await fetch(imgUrl);
const blob = await resp.blob();
folder.file(`quadrinho_${panel.panel_number}.jpg`, blob);
} catch (fetchErr) {
console.warn(`Erro ao incluir imagem ${panel.panel_number} no ZIP:`, fetchErr);
}
}
folder.file('roteiro.txt', roteiroTxt);
const content = await zip.generateAsync({ type: 'blob' });
const downloadUrl = URL.createObjectURL(content);
const a = document.createElement('a');
a.href = downloadUrl;
a.download = `${title}_pedagog.zip`;
document.body.appendChild(a);
a.click();
a.remove();
URL.revokeObjectURL(downloadUrl);
} catch (err) {
console.error('Erro ao gerar ZIP:', err);
await showCustomAlert('Erro', 'Não foi possível gerar o ZIP: ' + err.message);
} finally {
btnExportComicsZIP.disabled = false;
btnExportComicsZIP.innerHTML = '<span>📦 Baixar Todas as Imagens (ZIP)</span>';
}
});
}
// --- MODO APRESENTAÇÃO EM TELA CHEIA (LEITURA INFANTIL / MOBILE) ---
const comicsPresModal = document.getElementById('comicsPresentationModal');
const comicsPresTitle = document.getElementById('comicsPresTitle');
const comicsPresImage = document.getElementById('comicsPresImage');
const comicsPresCaption = document.getElementById('comicsPresCaption');
const comicsPresCounter = document.getElementById('comicsPresCounter');
const btnPresPrev = document.getElementById('btnPresPrev');
const btnPresNext = document.getElementById('btnPresNext');
const btnCloseComicsPresModal = document.getElementById('btnCloseComicsPresModal');
const btnComicsPresentationMode = document.getElementById('btnComicsPresentationMode');
let presCurrentIndex = 0;
const openComicsPresentation = (index = 0) => {
if (!generatedPanelsData || generatedPanelsData.length === 0) return;
presCurrentIndex = Math.max(0, Math.min(index, generatedPanelsData.length - 1));
if (comicsPresModal) comicsPresModal.style.display = 'flex';
updateComicsPresentationView();
};
const closeComicsPresentation = () => {
if (comicsPresModal) comicsPresModal.style.display = 'none';
};
const updateComicsPresentationView = () => {
if (!generatedPanelsData || generatedPanelsData.length === 0) return;
const panel = generatedPanelsData[presCurrentIndex];
if (!panel) return;
if (comicsPresTitle) comicsPresTitle.textContent = comicsResultTitle?.textContent || 'Fábrica de Quadrinhos';
if (comicsPresCounter) comicsPresCounter.textContent = `${presCurrentIndex + 1} / ${generatedPanelsData.length}`;
if (comicsPresImage) comicsPresImage.src = panel.imageUrl || panel.image_url;
if (comicsPresCaption) comicsPresCaption.textContent = panel.dialogue || '(História silenciosa)';
if (btnPresPrev) btnPresPrev.disabled = presCurrentIndex === 0;
if (btnPresNext) btnPresNext.disabled = presCurrentIndex === generatedPanelsData.length - 1;
};
if (btnComicsPresentationMode) {
btnComicsPresentationMode.addEventListener('click', () => {
openComicsPresentation(0);
});
}
if (btnPresPrev) {
btnPresPrev.addEventListener('click', () => {
if (presCurrentIndex > 0) {
presCurrentIndex--;
updateComicsPresentationView();
}
});
}
if (btnPresNext) {
btnPresNext.addEventListener('click', () => {
if (presCurrentIndex < generatedPanelsData.length - 1) {
presCurrentIndex++;
updateComicsPresentationView();
}
});
}
if (btnCloseComicsPresModal) {
btnCloseComicsPresModal.addEventListener('click', closeComicsPresentation);
}
// Teclado (setas) para o modo apresentação
window.addEventListener('keydown', (e) => {
if (comicsPresModal && comicsPresModal.style.display === 'flex') {
if (e.key === 'ArrowRight' || e.key === 'Space') {
if (presCurrentIndex < generatedPanelsData.length - 1) {
presCurrentIndex++;
updateComicsPresentationView();
}
} else if (e.key === 'ArrowLeft') {
if (presCurrentIndex > 0) {
presCurrentIndex--;
updateComicsPresentationView();
}
} else if (e.key === 'Escape') {
closeComicsPresentation();
}
}
});
// Touch Swipe para smartphones no modo apresentação
let touchStartX = 0;
if (comicsPresModal) {
comicsPresModal.addEventListener('touchstart', (e) => {
touchStartX = e.changedTouches[0].screenX;
}, { passive: true });
comicsPresModal.addEventListener('touchend', (e) => {
const touchEndX = e.changedTouches[0].screenX;
const diff = touchEndX - touchStartX;
if (Math.abs(diff) > 50) {
if (diff < 0 && presCurrentIndex < generatedPanelsData.length - 1) {
// Swipe para a esquerda -> Próximo
presCurrentIndex++;
updateComicsPresentationView();
} else if (diff > 0 && presCurrentIndex > 0) {
// Swipe para a direita -> Anterior
presCurrentIndex--;
updateComicsPresentationView();
}
}
}, { passive: true });
}
// Exportar PDF Retrato (1 por página) // Exportar PDF Retrato (1 por página)
if (btnExportComicsPDFPortrait) { if (btnExportComicsPDFPortrait) {
btnExportComicsPDFPortrait.addEventListener('click', async () => { btnExportComicsPDFPortrait.addEventListener('click', async () => {
+77 -22
View File
@@ -1400,9 +1400,10 @@
<!-- PERSONAGENS --> <!-- PERSONAGENS -->
<div class="settings-group"> <div class="settings-group">
<label style="color: var(--text-secondary); font-size: 0.82rem; font-weight: 600; text-transform: uppercase; letter-spacing: 0.5px; display: flex; justify-content: space-between; align-items: center;"> <label style="color: var(--text-secondary); font-size: 0.82rem; font-weight: 600; text-transform: uppercase; letter-spacing: 0.5px; display: flex; justify-content: space-between; align-items: center; flex-wrap: wrap; gap: 6px;">
<span>5. Personagens da História</span> <span>5. Personagens da História</span>
<div style="display: flex; gap: 8px;"> <div style="display: flex; gap: 6px; flex-wrap: wrap;">
<button type="button" id="btnImportTurmaChars" class="btn-music-option" style="padding: 3px 8px; font-size: 0.72rem; border-color: #3b82f6; color: #60a5fa; background: rgba(59, 130, 246, 0.08); margin: 0;" title="Carregar crianças da turma atual como personagens">+ 🏫 Importar da Turma</button>
<button type="button" id="btnAddHumanChar" class="btn-music-option" style="padding: 3px 8px; font-size: 0.72rem; border-color: var(--brand-pink); color: var(--brand-pink); background: rgba(219, 39, 119, 0.05); margin: 0;">+ 🧒 Humano</button> <button type="button" id="btnAddHumanChar" class="btn-music-option" style="padding: 3px 8px; font-size: 0.72rem; border-color: var(--brand-pink); color: var(--brand-pink); background: rgba(219, 39, 119, 0.05); margin: 0;">+ 🧒 Humano</button>
<button type="button" id="btnAddAnimalChar" class="btn-music-option" style="padding: 3px 8px; font-size: 0.72rem; border-color: var(--brand-pink); color: var(--brand-pink); background: rgba(219, 39, 119, 0.05); margin: 0;">+ 🐾 Animal</button> <button type="button" id="btnAddAnimalChar" class="btn-music-option" style="padding: 3px 8px; font-size: 0.72rem; border-color: var(--brand-pink); color: var(--brand-pink); background: rgba(219, 39, 119, 0.05); margin: 0;">+ 🐾 Animal</button>
</div> </div>
@@ -1432,45 +1433,66 @@
<!-- BOTÃO DE AÇÃO --> <!-- BOTÃO DE AÇÃO -->
<button id="btnGenerateComics" style="width: 100%; padding: 12px; background: var(--brand-pink, #db2777); color: white; border: none; border-radius: 10px; font-weight: 700; font-size: 1rem; cursor: pointer; display: flex; align-items: center; justify-content: center; gap: 8px; box-shadow: 0 4px 12px rgba(219, 39, 119, 0.3); transition: all 0.2s;"> <button id="btnGenerateComics" style="width: 100%; padding: 12px; background: var(--brand-pink, #db2777); color: white; border: none; border-radius: 10px; font-weight: 700; font-size: 1rem; cursor: pointer; display: flex; align-items: center; justify-content: center; gap: 8px; box-shadow: 0 4px 12px rgba(219, 39, 119, 0.3); transition: all 0.2s;">
<span>🎨 Criar Quadrinhos Pedagógicos</span> <span>🎨 Criar Quadrinhos Pedagógicos (Geração Paralela ⚡)</span>
</button> </button>
<!-- LOADING DE QUADRINHOS --> <!-- LOADING DE QUADRINHOS -->
<div id="comicsGenerateLoader" style="display: none; flex-direction: column; align-items: center; justify-content: center; gap: 12px; padding: 30px; background: var(--bg-secondary); border-radius: 12px; border: 1px dashed var(--border-light);"> <div id="comicsGenerateLoader" style="display: none; flex-direction: column; align-items: center; justify-content: center; gap: 12px; padding: 30px; background: var(--bg-secondary); border-radius: 12px; border: 1px dashed var(--border-light);">
<div class="media-spinner" style="border-top-color: var(--brand-pink, #db2777);"></div> <div class="media-spinner" style="border-top-color: var(--brand-pink, #db2777);"></div>
<p id="comicsLoaderText" style="color: var(--text-primary); font-weight: 600; font-size: 0.95rem; margin: 0; text-align: center;">Compondo a história pedagógica...</p> <p id="comicsLoaderText" style="color: var(--text-primary); font-weight: 600; font-size: 0.95rem; margin: 0; text-align: center;">Compondo a história pedagógica...</p>
<div style="width: 100%; max-width: 250px; height: 6px; background: var(--border-light); border-radius: 3px; overflow: hidden;"> <div style="width: 100%; max-width: 320px; height: 8px; background: var(--border-light); border-radius: 4px; overflow: hidden;">
<div id="comicsLoaderProgress" style="width: 0%; height: 100%; background: var(--brand-pink, #db2777); transition: width 0.4s ease;"></div> <div id="comicsLoaderProgress" style="width: 0%; height: 100%; background: var(--brand-pink, #db2777); transition: width 0.3s ease;"></div>
</div>
<span id="comicsLoaderSubtext" style="font-size: 0.78rem; color: var(--text-muted);">Processando quadros em paralelo de alta performance...</span>
</div>
<!-- RESULTADOS DA HISTÓRIA (STORYBOARD STUDIO) -->
<div id="comicsResultArea" style="display: none; flex-direction: column; gap: 18px;">
<!-- BARRA DE CABEÇALHO DO STORYBOARD -->
<div style="display: flex; flex-direction: column; gap: 10px; background: var(--bg-secondary); padding: 14px 16px; border-radius: 10px; border: 1px solid var(--border-light);">
<div style="display: flex; justify-content: space-between; align-items: center; flex-wrap: wrap; gap: 10px;">
<h4 id="comicsResultTitle" style="font-family: 'Outfit', sans-serif; font-size: 1.2rem; color: var(--text-primary); margin: 0; font-weight: 700;">Título da História</h4>
<div style="display: flex; gap: 8px; flex-wrap: wrap;">
<button type="button" id="btnComicsPresentationMode" class="btn-music-option" style="border-color: #8b5cf6; color: #a78bfa; background: rgba(139, 92, 246, 0.1); font-weight: 600; display: flex; align-items: center; gap: 6px; padding: 6px 14px;">
<span>🎬 Apresentar em Tela Cheia</span>
</button>
<button type="button" id="btnExportComicsZIP" class="btn-music-option" style="border-color: #3b82f6; color: #60a5fa; background: rgba(59, 130, 246, 0.1); font-weight: 600; display: flex; align-items: center; gap: 6px; padding: 6px 14px;">
<span>📦 Baixar Todas as Imagens (ZIP)</span>
</button>
</div> </div>
</div> </div>
<!-- RESULTADOS DA HISTÓRIA --> <div id="comicsResultMeta" style="font-size: 0.78rem; color: var(--text-muted); display: flex; gap: 14px; flex-wrap: wrap; border-top: 1px dashed var(--border-light); padding-top: 8px;">
<div id="comicsResultArea" style="display: none; flex-direction: column; gap: 20px;"> <span>🤖 Criado por: <strong>PedagogIA</strong></span>
<span id="comicsMetaPanels">🖼️ 5 Quadros</span>
<h4 id="comicsResultTitle" style="font-family: 'Outfit', sans-serif; font-size: 1.15rem; color: var(--text-primary); margin: 0; text-align: center; font-weight: 700;">Título da História</h4> <span id="comicsMetaRatio">📐 16:9 HD</span>
<span>✨ Dica: Você pode editar o texto dos balões ou regenerar qualquer quadrinho individualmente abaixo!</span>
<!-- CONTÊINER DOS QUADRINHOS --> </div>
<div id="comicsGridContainer" style="display: grid; grid-template-columns: repeat(auto-fit, minmax(280px, 1fr)); gap: 16px;">
<!-- Gerado dinamicamente: quadrinhos com as imagens e balões de fala correspondentes -->
</div> </div>
<!-- OPÇÕES DE EXPORTAÇÃO --> <!-- CONTÊINER DOS QUADRINHOS EM STORYBOARD -->
<div id="comicsGridContainer" style="display: grid; grid-template-columns: repeat(auto-fit, minmax(290px, 1fr)); gap: 18px;">
<!-- Gerado dinamicamente: cards de quadrinhos interativos -->
</div>
<!-- OPÇÕES DE EXPORTAÇÃO E VÍDEO -->
<div style="background: var(--bg-secondary); padding: 16px; border-radius: 12px; border: 1px solid var(--border-light); display: flex; flex-direction: column; gap: 14px;"> <div style="background: var(--bg-secondary); padding: 16px; border-radius: 12px; border: 1px solid var(--border-light); display: flex; flex-direction: column; gap: 14px;">
<h5 style="margin: 0; font-size: 0.9rem; font-weight: 700; text-transform: uppercase; color: var(--text-secondary); letter-spacing: 0.5px;">📥 Opções de Exportação</h5> <h5 style="margin: 0; font-size: 0.9rem; font-weight: 700; text-transform: uppercase; color: var(--text-secondary); letter-spacing: 0.5px;">📥 Mais Formatos de Exportação</h5>
<!-- DOWNLOAD DE PDF --> <!-- DOWNLOAD DE PDF -->
<div style="display: flex; flex-direction: column; gap: 6px;"> <div style="display: flex; flex-direction: column; gap: 6px;">
<span style="font-size: 0.8rem; font-weight: 600; color: var(--text-primary);">Salvar como Documento PDF:</span> <span style="font-size: 0.8rem; font-weight: 600; color: var(--text-primary);">Documento PDF para Impressão ou Registro Pedagógico:</span>
<div style="display: grid; grid-template-columns: repeat(2, 1fr); gap: 10px;"> <div style="display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: 10px;">
<button id="btnExportComicsPDFPortrait" class="btn-music-option" style="border-color: #10a37f; color: #10a37f; background: rgba(16, 163, 127, 0.05);">📄 PDF (1 por página - Retrato)</button> <button id="btnExportComicsPDFPortrait" class="btn-music-option" style="border-color: #10a37f; color: #10a37f; background: rgba(16, 163, 127, 0.05); font-weight: 600;">📄 PDF (1 por página - Retrato A4)</button>
<button id="btnExportComicsPDFLandscape" class="btn-music-option" style="border-color: #10a37f; color: #10a37f; background: rgba(16, 163, 127, 0.05);">📄 PDF (2 por página - Paisagem)</button> <button id="btnExportComicsPDFLandscape" class="btn-music-option" style="border-color: #10a37f; color: #10a37f; background: rgba(16, 163, 127, 0.05); font-weight: 600;">📄 PDF (2 por página - Álbum Paisagem)</button>
</div> </div>
</div> </div>
<!-- APRESENTAÇÃO EM VÍDEO --> <!-- APRESENTAÇÃO EM VÍDEO -->
<div style="display: flex; flex-direction: column; gap: 8px; border-top: 1px solid var(--border-light); padding-top: 14px;"> <div style="display: flex; flex-direction: column; gap: 8px; border-top: 1px solid var(--border-light); padding-top: 14px;">
<span style="font-size: 0.8rem; font-weight: 600; color: var(--text-primary);">Criar Apresentação de Vídeo Animada:</span> <span style="font-size: 0.8rem; font-weight: 600; color: var(--text-primary);">Criar Apresentação de Vídeo Animada com Música:</span>
<div style="display: grid; grid-template-columns: 1fr 1fr; gap: 10px;"> <div style="display: grid; grid-template-columns: 1fr 1fr; gap: 10px;">
<div class="settings-group" style="gap: 4px;"> <div class="settings-group" style="gap: 4px;">
@@ -1492,7 +1514,7 @@
</div> </div>
<button id="btnCompileComicsVideo" style="padding: 10px; background: var(--brand-pink, #db2777); color: white; border: none; border-radius: 8px; font-weight: 600; font-size: 0.88rem; cursor: pointer; display: flex; align-items: center; justify-content: center; gap: 6px; margin-top: 6px; transition: all 0.2s;"> <button id="btnCompileComicsVideo" style="padding: 10px; background: var(--brand-pink, #db2777); color: white; border: none; border-radius: 8px; font-weight: 600; font-size: 0.88rem; cursor: pointer; display: flex; align-items: center; justify-content: center; gap: 6px; margin-top: 6px; transition: all 0.2s;">
<span>🎬 Gerar Apresentação de Vídeo (MP4)</span> <span>🎬 Compilar Apresentação de Vídeo (MP4)</span>
</button> </button>
<!-- Spinner do Vídeo --> <!-- Spinner do Vídeo -->
@@ -2036,7 +2058,40 @@
</div> </div>
</div> </div>
<!-- Bibliotecas externas para renderizar markdown e blocos de código --> <!-- Modal: Apresentação em Tela Cheia dos Quadrinhos (Modo Leitura Infantil / Lousa / Mobile) -->
<div id="comicsPresentationModal" class="comics-presentation-modal" style="display: none;">
<div class="comics-pres-header">
<div style="display: flex; align-items: center; gap: 10px;">
<span style="font-size: 1.3rem;">📖</span>
<h3 id="comicsPresTitle" style="margin: 0; font-family: 'Outfit', sans-serif; font-size: 1.1rem; color: #fff;">História em Quadrinhos</h3>
</div>
<div style="display: flex; align-items: center; gap: 12px;">
<span id="comicsPresCounter" style="background: rgba(255,255,255,0.15); padding: 4px 12px; border-radius: 20px; font-size: 0.85rem; font-weight: 700; color: #fef08a;">1 / 5</span>
<button type="button" id="btnCloseComicsPresModal" style="background: rgba(255,255,255,0.1); border: 1px solid rgba(255,255,255,0.2); color: white; width: 36px; height: 36px; border-radius: 50%; cursor: pointer; font-size: 1.2rem; display: flex; align-items: center; justify-content: center; transition: all 0.2s;"></button>
</div>
</div>
<div class="comics-pres-stage">
<div class="comics-pres-card">
<img id="comicsPresImage" class="comics-pres-img" src="" alt="Quadrinho em Apresentação">
<div id="comicsPresCaption" class="comics-pres-caption">
Falas do quadrinho aparecerão aqui...
</div>
</div>
</div>
<div class="comics-pres-nav">
<button type="button" id="btnPresPrev" class="btn-pres-nav">
<span>⬅️ Anterior</span>
</button>
<button type="button" id="btnPresNext" class="btn-pres-nav" style="background: var(--brand-pink, #db2777); font-weight: 700;">
<span>Próximo ➡️</span>
</button>
</div>
</div>
<!-- Bibliotecas externas para renderizar markdown, PDFs e compactação ZIP -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/jszip/3.10.1/jszip.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jspdf/2.5.1/jspdf.umd.min.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/jspdf/2.5.1/jspdf.umd.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/marked/12.0.1/marked.min.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/marked/12.0.1/marked.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/highlight.min.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/highlight.min.js"></script>
+219 -21
View File
@@ -3358,22 +3358,22 @@ code {
} }
/* ========================================================================== /* ==========================================================================
FÁBRICA DE QUADRINHOS FÁBRICA DE QUADRINHOS — STORYBOARD STUDIO & PRESENTATION MODE
========================================================================== */ ========================================================================== */
.comic-panel-card { .comic-panel-card {
position: relative; position: relative;
border: 3px solid #1e293b; border: 3px solid #1e293b;
background: var(--bg-secondary); background: var(--bg-secondary);
box-shadow: 4px 4px 0px #1e293b; box-shadow: 4px 4px 0px #1e293b;
border-radius: 8px; border-radius: 10px;
overflow: hidden; overflow: hidden;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
transition: transform 0.2s, box-shadow 0.2s; transition: transform 0.2s cubic-bezier(0.4, 0, 0.2, 1), box-shadow 0.2s;
} }
.comic-panel-card:hover { .comic-panel-card:hover {
transform: translate(-2px, -2px); transform: translateY(-3px);
box-shadow: 6px 6px 0px #1e293b; box-shadow: 6px 8px 0px #1e293b;
} }
.comic-panel-image-container { .comic-panel-image-container {
@@ -3396,51 +3396,103 @@ code {
width: 100%; width: 100%;
height: 100%; height: 100%;
object-fit: cover; object-fit: cover;
transition: transform 0.3s ease;
display: block;
}
.comic-panel-card:hover .comic-panel-image {
transform: scale(1.02);
}
.comic-panel-overlay-actions {
position: absolute;
top: 8px;
left: 8px;
display: flex;
gap: 6px;
z-index: 15;
opacity: 0.9;
transition: opacity 0.2s;
}
.comic-panel-card:hover .comic-panel-overlay-actions {
opacity: 1;
}
.btn-comic-action {
background: rgba(15, 23, 42, 0.85);
color: white;
border: 1px solid rgba(255, 255, 255, 0.2);
backdrop-filter: blur(4px);
border-radius: 6px;
padding: 4px 7px;
font-size: 0.75rem;
cursor: pointer;
display: flex;
align-items: center;
gap: 4px;
transition: background 0.2s, transform 0.1s;
}
.btn-comic-action:hover {
background: var(--brand-pink, #db2777);
transform: scale(1.05);
} }
.comic-speech-bubble { .comic-speech-bubble {
position: absolute; position: absolute;
top: 8px; bottom: 10px;
left: 8px; left: 10px;
background: white; right: 10px;
background: rgba(255, 255, 255, 0.95);
border: 2px solid #1e293b; border: 2px solid #1e293b;
border-radius: 10px; border-radius: 8px;
padding: 4px 8px; padding: 6px 10px;
font-size: 0.72rem; font-size: 0.78rem;
font-weight: 700; font-weight: 700;
color: #0f172a; color: #0f172a;
max-width: 65%; box-shadow: 2px 2px 0px rgba(30, 41, 59, 0.2);
box-shadow: 2px 2px 0px rgba(30, 41, 59, 0.15);
z-index: 10; z-index: 10;
pointer-events: none;
} }
.comic-panel-number-badge { .comic-panel-number-badge {
position: absolute; position: absolute;
top: 8px; top: 8px;
right: 8px; right: 8px;
background: #fef08a; /* Amarelo claro */ background: #fef08a; /* Amarelo clássico gibi */
color: #1e293b; color: #1e293b;
font-weight: 800; font-weight: 800;
font-size: 0.8rem; font-size: 0.82rem;
width: 20px; padding: 2px 8px;
height: 20px; border-radius: 12px;
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
border: 2px solid #1e293b; border: 2px solid #1e293b;
border-radius: 50%;
z-index: 12; z-index: 12;
box-shadow: 1px 1px 0px #1e293b; box-shadow: 2px 2px 0px #1e293b;
} }
.comic-caption-text { .comic-caption-text {
padding: 10px 14px; padding: 10px 12px;
background: var(--bg-primary); background: var(--bg-primary);
color: var(--text-primary); color: var(--text-primary);
font-size: 0.82rem; font-size: 0.82rem;
line-height: 1.4; line-height: 1.4;
border-top: 1px solid var(--border-light); border-top: 1px solid var(--border-light);
font-weight: 500; font-weight: 500;
display: flex;
flex-direction: column;
gap: 6px;
}
.comic-prompt-details {
display: none;
font-size: 0.72rem;
color: var(--text-muted);
background: var(--bg-secondary);
padding: 6px 8px;
border-radius: 6px;
border: 1px dashed var(--border-light);
margin-top: 4px;
} }
.btn-comics-qty.active, .btn-comics-bubbles.active, .btn-comics-ratio.active, .btn-comics-char.active { .btn-comics-qty.active, .btn-comics-bubbles.active, .btn-comics-ratio.active, .btn-comics-char.active {
@@ -3448,7 +3500,113 @@ code {
border-color: #db2777; border-color: #db2777;
color: #f472b6; color: #f472b6;
font-weight: 600; font-weight: 600;
box-shadow: 0 0 8px rgba(219, 39, 119, 0.2); }
/* ==========================================================================
MODO APRESENTAÇÃO / LEITURA (FULLSCREEN STORYBOARD)
========================================================================== */
.comics-presentation-modal {
position: fixed;
inset: 0;
z-index: 10000;
background: rgba(10, 10, 15, 0.95);
backdrop-filter: blur(10px);
display: flex;
flex-direction: column;
align-items: center;
justify-content: space-between;
padding: 20px;
box-sizing: border-box;
}
.comics-pres-header {
width: 100%;
max-width: 1000px;
display: flex;
justify-content: space-between;
align-items: center;
color: white;
}
.comics-pres-stage {
flex: 1;
display: flex;
align-items: center;
justify-content: center;
width: 100%;
max-width: 960px;
position: relative;
margin: 10px 0;
}
.comics-pres-card {
max-width: 100%;
max-height: 70vh;
display: flex;
flex-direction: column;
align-items: center;
background: #0f172a;
border: 4px solid #334155;
border-radius: 14px;
overflow: hidden;
box-shadow: 0 20px 40px rgba(0, 0, 0, 0.8);
animation: comicFadeIn 0.3s ease;
}
.comics-pres-img {
max-width: 100%;
max-height: 55vh;
object-fit: contain;
display: block;
}
.comics-pres-caption {
width: 100%;
background: #ffffff;
color: #0f172a;
padding: 16px 24px;
box-sizing: border-box;
font-size: 1.15rem;
font-weight: 600;
text-align: center;
border-top: 3px solid #1e293b;
font-family: 'Fredoka', 'Outfit', sans-serif;
letter-spacing: 0.3px;
}
.comics-pres-nav {
display: flex;
align-items: center;
gap: 16px;
margin-top: 10px;
}
.btn-pres-nav {
background: rgba(255, 255, 255, 0.12);
color: white;
border: 1px solid rgba(255, 255, 255, 0.25);
padding: 10px 20px;
border-radius: 30px;
font-size: 1rem;
font-weight: 600;
cursor: pointer;
display: flex;
align-items: center;
gap: 8px;
transition: all 0.2s;
}
.btn-pres-nav:hover:not(:disabled) {
background: var(--brand-pink, #db2777);
transform: scale(1.05);
}
.btn-pres-nav:disabled {
opacity: 0.3;
cursor: not-allowed;
}
@keyframes comicFadeIn {
from { opacity: 0; transform: scale(0.97); }
to { opacity: 1; transform: scale(1); }
} }
/* ========================================================================== /* ==========================================================================
@@ -3635,6 +3793,46 @@ code {
overflow-y: auto; overflow-y: auto;
padding: 8px 4px; padding: 8px 4px;
} }
/* Otimização Mobile para a Fábrica de Quadrinhos */
#comicsGridContainer {
grid-template-columns: 1fr !important;
gap: 14px !important;
}
.comic-panel-overlay-actions {
opacity: 1 !important;
gap: 4px;
}
.btn-comic-action {
padding: 6px 9px;
font-size: 0.82rem;
min-height: 32px;
}
.comics-presentation-modal {
padding: 12px 10px !important;
}
.comics-pres-card {
max-height: 75vh !important;
border-width: 2px !important;
}
.comics-pres-img {
max-height: 46vh !important;
}
.comics-pres-caption {
font-size: 0.98rem !important;
padding: 12px 14px !important;
}
.btn-pres-nav {
padding: 8px 16px !important;
font-size: 0.9rem !important;
}
} }
/* Estilos de progresso da IA (Chain of Thought/Etapas) */ /* Estilos de progresso da IA (Chain of Thought/Etapas) */