diff --git a/public/app.js b/public/app.js
index eefa56d..87a6648 100644
--- a/public/app.js
+++ b/public/app.js
@@ -7601,6 +7601,198 @@ document.addEventListener('DOMContentLoaded', () => {
let dragStartWidth, dragStartHeight;
let activeAssetTab = 'postit';
+ // --- ESTÚDIO DE CENAS EM CAMADAS ---
+ const btnDecomposeScene = document.getElementById('btnDecomposeScene');
+ const sceneDecomposePrompt = document.getElementById('sceneDecomposePrompt');
+ const sceneDecomposeLoader = document.getElementById('sceneDecomposeLoader');
+ const sceneDecomposeStatus = document.getElementById('sceneDecomposeStatus');
+ const sceneLayersList = document.getElementById('sceneLayersList');
+ const sceneLayersItems = document.getElementById('sceneLayersItems');
+
+ // Cria um item de camada de imagem direto no canvas (background ou sticker)
+ function addSceneLayerToCanvas(layerData) {
+ if (!canvasModeActive || !cartazPrintableArea) return;
+
+ const item = document.createElement('div');
+ item.className = 'canvas-item canvas-scene-layer';
+ item.setAttribute('data-layer-name', layerData.name || '');
+ item.setAttribute('data-rotation', '0');
+ item.style.position = 'absolute';
+ item.style.left = (layerData.left || 10) + '%';
+ item.style.top = (layerData.top || 10) + '%';
+ item.style.width = (layerData.width || 35) + '%';
+ item.style.height = (layerData.height || 35) + '%';
+ item.style.zIndex = layerData.isBackground ? '1' : String(10 + (layerData.zOffset || 0));
+ item.style.background = 'transparent';
+
+ if (layerData.isBackground) {
+ // Fundo: cobrindo 100% e não selecionável como item de controle
+ item.style.left = '0';
+ item.style.top = '0';
+ item.style.width = '100%';
+ item.style.height = '100%';
+ item.style.zIndex = '0';
+ item.style.pointerEvents = 'none'; // Por padrão, o fundo não captura eventos para facilitar a seleção dos elementos acima
+ item.innerHTML = ``;
+ item.title = '🖼️ Cenário de Fundo';
+ } else {
+ item.innerHTML = `
`;
+ item.title = '🎬 ' + (layerData.name || 'Elemento');
+ }
+
+ const handle = document.createElement('div');
+ handle.className = 'canvas-resize-handle';
+ item.appendChild(handle);
+
+ cartazPrintableArea.appendChild(item);
+
+ if (!layerData.isBackground) {
+ setupCanvasItemEvents(item);
+ // Permitir que o fundo seja selecionável após clicar nele diretamente
+ } else {
+ // para o fundo, adicionar capacidade de clicar para habilitar arrastar
+ item.style.pointerEvents = 'auto';
+ setupCanvasItemEvents(item);
+ }
+ return item;
+ }
+
+ // Renderiza a lista de camadas no painel lateral
+ function renderSceneLayers(layers) {
+ if (!sceneLayersItems) return;
+ sceneLayersItems.innerHTML = '';
+
+ const allLayers = [{ name: '🖼️ Cenário de Fundo', isBackground: true }, ...layers.map(l => ({ ...l }))];
+ allLayers.forEach((layer, index) => {
+ const chip = document.createElement('div');
+ chip.style.cssText = `display:flex; align-items:center; gap:8px; padding:5px 8px;
+ background:rgba(255,255,255,0.05); border:1px solid rgba(139,92,246,0.2);
+ border-radius:6px; font-size:0.78rem; color:var(--text-primary); cursor:pointer;`;
+ chip.innerHTML = `
+ ${layer.isBackground ? '🌄' : '✨'}
+ ${layer.name}
+ Camada ${index}
+ `;
+ chip.title = `${layer.isBackground ? 'Fundo da cena' : 'Elemento: ' + layer.name}`;
+ sceneLayersItems.appendChild(chip);
+ });
+ }
+
+ if (btnDecomposeScene) {
+ btnDecomposeScene.addEventListener('click', async () => {
+ const prompt = sceneDecomposePrompt ? sceneDecomposePrompt.value.trim() : '';
+ if (!prompt) {
+ showToast('Descreva a cena antes de gerar!', 'warning');
+ return;
+ }
+
+ // Garantir que o modo canvas está ativo
+ if (!canvasModeActive) {
+ if (cartazEditArea && cartazEditArea.style.display !== 'none') {
+ btnEditCartazToggle && btnEditCartazToggle.click();
+ }
+ enterCanvasMode();
+ }
+
+ // Mostrar loading
+ btnDecomposeScene.disabled = true;
+ btnDecomposeScene.style.opacity = '0.6';
+ if (sceneDecomposeLoader) {
+ sceneDecomposeLoader.style.display = 'flex';
+ }
+ if (sceneLayersList) sceneLayersList.style.display = 'none';
+
+ const statusSteps = [
+ 'Planejando a cena com IA...',
+ 'Identificando elementos e cenário...',
+ 'Gerando imagem de fundo...',
+ 'Gerando elementos individuais...',
+ 'Montando as camadas no canvas...'
+ ];
+ let stepIdx = 0;
+ const statusInterval = setInterval(() => {
+ if (sceneDecomposeStatus && stepIdx < statusSteps.length) {
+ sceneDecomposeStatus.textContent = statusSteps[stepIdx++];
+ }
+ }, 3500);
+
+ try {
+ const titulo = activeCartazData?.titulo || '';
+ const response = await fetch('/api/cartazes/decompose-scene', {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/json',
+ 'Authorization': `Bearer ${localStorage.getItem('token')}`
+ },
+ body: JSON.stringify({ tema: prompt, titulo })
+ });
+
+ clearInterval(statusInterval);
+
+ if (!response.ok) {
+ const errData = await response.json().catch(() => ({}));
+ throw new Error(errData.error || 'Erro na geração da cena.');
+ }
+
+ const data = await response.json();
+ if (!data.success) throw new Error('Resposta inválida do servidor.');
+
+ if (sceneDecomposeStatus) sceneDecomposeStatus.textContent = 'Montando as camadas no canvas...';
+
+ // Limpar canvas antes de montar (remover outros items de cena se houver)
+ const oldLayers = cartazPrintableArea.querySelectorAll('.canvas-scene-layer');
+ oldLayers.forEach(l => l.remove());
+
+ // Adicionar background
+ if (data.backgroundUrl) {
+ addSceneLayerToCanvas({ url: data.backgroundUrl, name: 'Cenário de Fundo', isBackground: true });
+ }
+
+ // Adicionar cada elemento com posição escalonada
+ if (Array.isArray(data.layers)) {
+ data.layers.forEach((layer, idx) => {
+ if (layer.url) {
+ const spread = [
+ { left: 10, top: 30 },
+ { left: 38, top: 35 },
+ { left: 62, top: 28 },
+ { left: 25, top: 55 }
+ ];
+ const pos = spread[idx % spread.length];
+ addSceneLayerToCanvas({
+ url: layer.url,
+ name: layer.name || `Elemento ${idx + 1}`,
+ isBackground: false,
+ left: pos.left,
+ top: pos.top,
+ width: 28,
+ height: 28,
+ zOffset: idx + 1
+ });
+ }
+ });
+
+ // Renderizar lista de camadas no painel
+ renderSceneLayers(data.layers);
+ if (sceneLayersList) sceneLayersList.style.display = 'flex';
+ }
+
+ showToast('🎬 Cena montada em camadas! Clique nos elementos para mover, girar e redimensionar.', 'success');
+
+ } catch (err) {
+ clearInterval(statusInterval);
+ console.error('[Decompose Scene]', err);
+ showToast('Erro ao criar a cena em camadas: ' + err.message, 'error');
+ } finally {
+ btnDecomposeScene.disabled = false;
+ btnDecomposeScene.style.opacity = '1';
+ if (sceneDecomposeLoader) sceneDecomposeLoader.style.display = 'none';
+ }
+ });
+ }
+
function enterCanvasMode() {
canvasModeActive = true;
if (btnCanvasModeToggle) {
diff --git a/public/index.html b/public/index.html
index c3b6002..340afd6 100644
--- a/public/index.html
+++ b/public/index.html
@@ -1330,7 +1330,7 @@