| <!DOCTYPE html> |
| <html lang="pt-BR"> |
| <head> |
| <meta charset="UTF-8"> |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> |
| <title>SiteBuilder Pro - Gerador de Sites</title> |
| <script src="https://cdn.tailwindcss.com"></script> |
| <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css"> |
| <style> |
| .resizable { |
| resize: both; |
| overflow: auto; |
| min-width: 300px; |
| min-height: 200px; |
| } |
| .component-placeholder { |
| border: 2px dashed #ccc; |
| padding: 20px; |
| text-align: center; |
| margin: 10px 0; |
| border-radius: 8px; |
| background-color: #f9f9f9; |
| } |
| .component-selected { |
| border: 2px solid #3b82f6; |
| box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.3); |
| } |
| #previewFrame { |
| width: 100%; |
| height: 100%; |
| border: none; |
| background: white; |
| } |
| .sidebar { |
| transition: all 0.3s ease; |
| } |
| .sidebar-collapsed { |
| width: 60px !important; |
| } |
| .sidebar-collapsed .sidebar-text { |
| display: none; |
| } |
| .sidebar-collapsed .fa-lg { |
| font-size: 1.5rem !important; |
| } |
| .draggable-component { |
| cursor: grab; |
| user-select: none; |
| } |
| .draggable-component:active { |
| cursor: grabbing; |
| } |
| </style> |
| </head> |
| <body class="bg-gray-100 h-screen flex flex-col"> |
| |
| <nav class="bg-blue-600 text-white p-4 shadow-md flex justify-between items-center"> |
| <div class="flex items-center space-x-2"> |
| <i class="fas fa-code fa-lg"></i> |
| <h1 class="text-xl font-bold">SiteBuilder Pro</h1> |
| </div> |
| <div class="flex space-x-4"> |
| <button id="exportBtn" class="bg-white text-blue-600 px-4 py-2 rounded-md hover:bg-blue-50 transition"> |
| <i class="fas fa-download mr-2"></i>Exportar |
| </button> |
| <button id="saveBtn" class="bg-blue-700 px-4 py-2 rounded-md hover:bg-blue-800 transition"> |
| <i class="fas fa-save mr-2"></i>Salvar |
| </button> |
| </div> |
| </nav> |
|
|
| <div class="flex flex-1 overflow-hidden"> |
| |
| <div id="componentsSidebar" class="sidebar bg-gray-800 text-white w-64 flex flex-col transition-all duration-300"> |
| <div class="p-4 border-b border-gray-700 flex justify-between items-center"> |
| <h2 class="text-lg font-semibold sidebar-text">Componentes</h2> |
| <button id="toggleSidebar" class="text-gray-400 hover:text-white"> |
| <i class="fas fa-chevron-left fa-lg"></i> |
| </button> |
| </div> |
| <div class="p-4 overflow-y-auto flex-1"> |
| <div class="mb-6"> |
| <h3 class="text-sm uppercase text-gray-400 mb-2 sidebar-text">Básicos</h3> |
| <div class="space-y-2"> |
| <div class="draggable-component bg-gray-700 p-3 rounded-md cursor-move hover:bg-gray-600 transition flex items-center" |
| draggable="true" data-type="header"> |
| <i class="fas fa-heading mr-3 sidebar-text"></i> |
| <span class="sidebar-text">Cabeçalho</span> |
| </div> |
| <div class="draggable-component bg-gray-700 p-3 rounded-md cursor-move hover:bg-gray-600 transition flex items-center" |
| draggable="true" data-type="paragraph"> |
| <i class="fas fa-paragraph mr-3 sidebar-text"></i> |
| <span class="sidebar-text">Parágrafo</span> |
| </div> |
| <div class="draggable-component bg-gray-700 p-3 rounded-md cursor-move hover:bg-gray-600 transition flex items-center" |
| draggable="true" data-type="button"> |
| <i class="fas fa-square mr-3 sidebar-text"></i> |
| <span class="sidebar-text">Botão</span> |
| </div> |
| </div> |
| </div> |
|
|
| <div class="mb-6"> |
| <h3 class="text-sm uppercase text-gray-400 mb-2 sidebar-text">Mídia</h3> |
| <div class="space-y-2"> |
| <div class="draggable-component bg-gray-700 p-3 rounded-md cursor-move hover:bg-gray-600 transition flex items-center" |
| draggable="true" data-type="image"> |
| <i class="fas fa-image mr-3 sidebar-text"></i> |
| <span class="sidebar-text">Imagem</span> |
| </div> |
| <div class="draggable-component bg-gray-700 p-3 rounded-md cursor-move hover:bg-gray-600 transition flex items-center" |
| draggable="true" data-type="video"> |
| <i class="fas fa-video mr-3 sidebar-text"></i> |
| <span class="sidebar-text">Vídeo</span> |
| </div> |
| </div> |
| </div> |
|
|
| <div class="mb-6"> |
| <h3 class="text-sm uppercase text-gray-400 mb-2 sidebar-text">Layout</h3> |
| <div class="space-y-2"> |
| <div class="draggable-component bg-gray-700 p-3 rounded-md cursor-move hover:bg-gray-600 transition flex items-center" |
| draggable="true" data-type="section"> |
| <i class="fas fa-square-full mr-3 sidebar-text"></i> |
| <span class="sidebar-text">Seção</span> |
| </div> |
| <div class="draggable-component bg-gray-700 p-3 rounded-md cursor-move hover:bg-gray-600 transition flex items-center" |
| draggable="true" data-type="columns"> |
| <i class="fas fa-columns mr-3 sidebar-text"></i> |
| <span class="sidebar-text">Colunas</span> |
| </div> |
| </div> |
| </div> |
| </div> |
| </div> |
|
|
| |
| <div class="flex-1 flex flex-col bg-white"> |
| <div class="border-b p-4 flex space-x-4"> |
| <div class="flex items-center space-x-2"> |
| <i class="fas fa-desktop text-gray-600"></i> |
| <span>Desktop</span> |
| </div> |
| <div class="flex items-center space-x-2"> |
| <i class="fas fa-tablet-alt text-gray-400"></i> |
| <span>Tablet</span> |
| </div> |
| <div class="flex items-center space-x-2"> |
| <i class="fas fa-mobile-alt text-gray-400"></i> |
| <span>Mobile</span> |
| </div> |
| </div> |
| <div id="editorArea" class="flex-1 overflow-auto p-8 bg-gray-100"> |
| <div id="canvas" class="bg-white shadow-lg mx-auto w-full max-w-4xl min-h-screen"> |
| |
| <div class="component-placeholder" id="dropZone"> |
| Arraste componentes aqui para começar |
| </div> |
| </div> |
| </div> |
| </div> |
|
|
| |
| <div class="w-64 bg-white border-l border-gray-200 p-4 overflow-y-auto"> |
| <h2 class="text-lg font-semibold mb-4">Propriedades</h2> |
| <div id="propertyPanel"> |
| <div class="text-gray-500 italic">Selecione um componente para editar suas propriedades</div> |
| </div> |
| </div> |
| </div> |
|
|
| |
| <div id="exportModal" class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center hidden"> |
| <div class="bg-white rounded-lg p-6 w-full max-w-2xl"> |
| <div class="flex justify-between items-center mb-4"> |
| <h3 class="text-xl font-bold">Exportar Site</h3> |
| <button id="closeExportModal" class="text-gray-500 hover:text-gray-700"> |
| <i class="fas fa-times"></i> |
| </button> |
| </div> |
| <div class="mb-4"> |
| <label class="block text-gray-700 mb-2">Nome do Site</label> |
| <input type="text" id="siteName" class="w-full p-2 border rounded-md" placeholder="Meu Site Incrível"> |
| </div> |
| <div class="mb-6"> |
| <label class="block text-gray-700 mb-2">Formato</label> |
| <div class="flex space-x-4"> |
| <label class="flex items-center"> |
| <input type="radio" name="exportFormat" value="html" checked class="mr-2"> |
| <span>HTML Completo</span> |
| </label> |
| <label class="flex items-center"> |
| <input type="radio" name="exportFormat" value="zip" class="mr-2"> |
| <span>Arquivo ZIP</span> |
| </label> |
| </div> |
| </div> |
| <div class="flex justify-end space-x-3"> |
| <button id="cancelExport" class="px-4 py-2 border rounded-md hover:bg-gray-100">Cancelar</button> |
| <button id="confirmExport" class="px-4 py-2 bg-blue-600 text-white rounded-md hover:bg-blue-700">Exportar</button> |
| </div> |
| </div> |
| </div> |
|
|
| <script> |
| document.addEventListener('DOMContentLoaded', function() { |
| // Variáveis globais |
| let selectedComponent = null; |
| let components = []; |
| let nextId = 1; |
| |
| // Elementos DOM |
| const canvas = document.getElementById('canvas'); |
| const dropZone = document.getElementById('dropZone'); |
| const propertyPanel = document.getElementById('propertyPanel'); |
| const componentsSidebar = document.getElementById('componentsSidebar'); |
| const toggleSidebar = document.getElementById('toggleSidebar'); |
| const exportBtn = document.getElementById('exportBtn'); |
| const exportModal = document.getElementById('exportModal'); |
| const closeExportModal = document.getElementById('closeExportModal'); |
| const cancelExport = document.getElementById('cancelExport'); |
| const confirmExport = document.getElementById('confirmExport'); |
| |
| // Event listeners para componentes arrastáveis |
| document.querySelectorAll('.draggable-component').forEach(component => { |
| component.addEventListener('dragstart', function(e) { |
| e.dataTransfer.setData('text/plain', this.dataset.type); |
| }); |
| }); |
| |
| // Event listeners para a área de drop |
| canvas.addEventListener('dragover', function(e) { |
| e.preventDefault(); |
| this.classList.add('bg-blue-50'); |
| }); |
| |
| canvas.addEventListener('dragleave', function() { |
| this.classList.remove('bg-blue-50'); |
| }); |
| |
| canvas.addEventListener('drop', function(e) { |
| e.preventDefault(); |
| this.classList.remove('bg-blue-50'); |
| |
| const componentType = e.dataTransfer.getData('text/plain'); |
| if (componentType) { |
| addComponent(componentType, e.clientX, e.clientY); |
| } |
| }); |
| |
| // Função para adicionar um novo componente |
| function addComponent(type, x, y) { |
| const id = 'component-' + nextId++; |
| let componentHtml = ''; |
| let defaultProps = {}; |
| |
| // Criar HTML baseado no tipo de componente |
| switch(type) { |
| case 'header': |
| componentHtml = ` |
| <h1 class="text-4xl font-bold mb-4" contenteditable="true">Título Principal</h1> |
| `; |
| defaultProps = { |
| text: 'Título Principal', |
| size: '4xl', |
| color: '#000000', |
| alignment: 'left' |
| }; |
| break; |
| case 'paragraph': |
| componentHtml = ` |
| <p class="text-gray-700 mb-4" contenteditable="true">Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nullam in dui mauris.</p> |
| `; |
| defaultProps = { |
| text: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nullam in dui mauris.', |
| color: '#374151', |
| alignment: 'left' |
| }; |
| break; |
| case 'button': |
| componentHtml = ` |
| <button class="bg-blue-600 text-white px-6 py-2 rounded-md hover:bg-blue-700 transition">Botão</button> |
| `; |
| defaultProps = { |
| text: 'Botão', |
| bgColor: '#2563eb', |
| textColor: '#ffffff', |
| size: 'medium', |
| rounded: 'md' |
| }; |
| break; |
| case 'image': |
| componentHtml = ` |
| <img src="https://via.placeholder.com/800x400" alt="Imagem de exemplo" class="w-full h-auto"> |
| `; |
| defaultProps = { |
| src: 'https://via.placeholder.com/800x400', |
| alt: 'Imagem de exemplo', |
| width: '100%', |
| alignment: 'center' |
| }; |
| break; |
| case 'section': |
| componentHtml = ` |
| <div class="p-8 bg-gray-100 rounded-lg"> |
| <div class="component-placeholder">Adicione componentes dentro desta seção</div> |
| </div> |
| `; |
| defaultProps = { |
| bgColor: '#f3f4f6', |
| padding: '8', |
| rounded: 'lg' |
| }; |
| break; |
| default: |
| return; |
| } |
| |
| // Criar elemento do componente |
| const componentElement = document.createElement('div'); |
| componentElement.className = 'component mb-6 p-4 relative'; |
| componentElement.id = id; |
| componentElement.innerHTML = componentHtml; |
| componentElement.dataset.type = type; |
| |
| // Adicionar menu de contexto |
| const contextMenu = document.createElement('div'); |
| contextMenu.className = 'absolute top-0 right-0 bg-white shadow-md rounded-md hidden'; |
| contextMenu.innerHTML = ` |
| <button class="component-delete px-3 py-1 text-red-500 hover:bg-red-50 w-full text-left"> |
| <i class="fas fa-trash mr-2"></i>Excluir |
| </button> |
| <button class="component-duplicate px-3 py-1 text-blue-500 hover:bg-blue-50 w-full text-left"> |
| <i class="fas fa-copy mr-2"></i>Duplicar |
| </button> |
| `; |
| componentElement.appendChild(contextMenu); |
| |
| // Mostrar menu de contexto ao clicar com o botão direito |
| componentElement.addEventListener('contextmenu', function(e) { |
| e.preventDefault(); |
| document.querySelectorAll('.component-context-menu').forEach(menu => { |
| menu.classList.add('hidden'); |
| }); |
| contextMenu.classList.remove('hidden'); |
| |
| // Posicionar o menu próximo ao cursor |
| const rect = this.getBoundingClientRect(); |
| contextMenu.style.top = (e.clientY - rect.top) + 'px'; |
| contextMenu.style.left = (e.clientX - rect.left) + 'px'; |
| }); |
| |
| // Esconder menu quando clicar em outro lugar |
| document.addEventListener('click', function() { |
| contextMenu.classList.add('hidden'); |
| }); |
| |
| // Event listeners para os botões do menu de contexto |
| contextMenu.querySelector('.component-delete').addEventListener('click', function() { |
| componentElement.remove(); |
| components = components.filter(c => c.id !== id); |
| if (selectedComponent === id) { |
| selectedComponent = null; |
| updatePropertyPanel(); |
| } |
| }); |
| |
| contextMenu.querySelector('.component-duplicate').addEventListener('click', function() { |
| addComponent(type, x, y); |
| }); |
| |
| // Selecionar componente ao clicar |
| componentElement.addEventListener('click', function(e) { |
| if (e.target.closest('.component-delete, .component-duplicate')) return; |
| |
| // Desselecionar todos os componentes |
| document.querySelectorAll('.component').forEach(c => { |
| c.classList.remove('component-selected'); |
| }); |
| |
| // Selecionar este componente |
| this.classList.add('component-selected'); |
| selectedComponent = id; |
| updatePropertyPanel(); |
| }); |
| |
| // Adicionar ao canvas (removendo o placeholder se for o primeiro componente) |
| if (dropZone && canvas.children.length === 1) { |
| canvas.removeChild(dropZone); |
| } |
| canvas.appendChild(componentElement); |
| |
| // Adicionar aos componentes |
| components.push({ |
| id: id, |
| type: type, |
| props: defaultProps, |
| html: componentElement.innerHTML |
| }); |
| } |
| |
| // Atualizar painel de propriedades |
| function updatePropertyPanel() { |
| if (!selectedComponent) { |
| propertyPanel.innerHTML = '<div class="text-gray-500 italic">Selecione um componente para editar suas propriedades</div>'; |
| return; |
| } |
| |
| const component = components.find(c => c.id === selectedComponent); |
| if (!component) return; |
| |
| let propertiesHtml = ''; |
| |
| switch(component.type) { |
| case 'header': |
| propertiesHtml = ` |
| <div class="mb-4"> |
| <label class="block text-gray-700 mb-1">Texto</label> |
| <input type="text" class="w-full p-2 border rounded-md component-prop" |
| data-prop="text" value="${component.props.text}"> |
| </div> |
| <div class="mb-4"> |
| <label class="block text-gray-700 mb-1">Tamanho</label> |
| <select class="w-full p-2 border rounded-md component-prop" data-prop="size"> |
| <option value="xs" ${component.props.size === 'xs' ? 'selected' : ''}>Pequeno</option> |
| <option value="sm" ${component.props.size === 'sm' ? 'selected' : ''}>Médio Pequeno</option> |
| <option value="base" ${component.props.size === 'base' ? 'selected' : ''}>Normal</option> |
| <option value="lg" ${component.props.size === 'lg' ? 'selected' : ''}>Médio Grande</option> |
| <option value="xl" ${component.props.size === 'xl' ? 'selected' : ''}>Grande</option> |
| <option value="2xl" ${component.props.size === '2xl' ? 'selected' : ''}>Extra Grande</option> |
| <option value="3xl" ${component.props.size === '3xl' ? 'selected' : ''}>3XL</option> |
| <option value="4xl" ${component.props.size === '4xl' ? 'selected' : ''}>4XL</option> |
| <option value="5xl" ${component.props.size === '5xl' ? 'selected' : ''}>5XL</option> |
| </select> |
| </div> |
| <div class="mb-4"> |
| <label class="block text-gray-700 mb-1">Cor do Texto</label> |
| <input type="color" class="w-full p-1 border rounded-md component-prop" |
| data-prop="color" value="${component.props.color}"> |
| </div> |
| <div class="mb-4"> |
| <label class="block text-gray-700 mb-1">Alinhamento</label> |
| <div class="flex space-x-2"> |
| <button class="p-2 border rounded-md ${component.props.alignment === 'left' ? 'bg-blue-100 border-blue-300' : ''}" |
| data-prop="alignment" data-value="left"> |
| <i class="fas fa-align-left"></i> |
| </button> |
| <button class="p-2 border rounded-md ${component.props.alignment === 'center' ? 'bg-blue-100 border-blue-300' : ''}" |
| data-prop="alignment" data-value="center"> |
| <i class="fas fa-align-center"></i> |
| </button> |
| <button class="p-2 border rounded-md ${component.props.alignment === 'right' ? 'bg-blue-100 border-blue-300' : ''}" |
| data-prop="alignment" data-value="right"> |
| <i class="fas fa-align-right"></i> |
| </button> |
| </div> |
| </div> |
| `; |
| break; |
| case 'button': |
| propertiesHtml = ` |
| <div class="mb-4"> |
| <label class="block text-gray-700 mb-1">Texto</label> |
| <input type="text" class="w-full p-2 border rounded-md component-prop" |
| data-prop="text" value="${component.props.text}"> |
| </div> |
| <div class="mb-4"> |
| <label class="block text-gray-700 mb-1">Cor de Fundo</label> |
| <input type="color" class="w-full p-1 border rounded-md component-prop" |
| data-prop="bgColor" value="${component.props.bgColor}"> |
| </div> |
| <div class="mb-4"> |
| <label class="block text-gray-700 mb-1">Cor do Texto</label> |
| <input type="color" class="w-full p-1 border rounded-md component-prop" |
| data-prop="textColor" value="${component.props.textColor}"> |
| </div> |
| <div class="mb-4"> |
| <label class="block text-gray-700 mb-1">Tamanho</label> |
| <select class="w-full p-2 border rounded-md component-prop" data-prop="size"> |
| <option value="small" ${component.props.size === 'small' ? 'selected' : ''}>Pequeno</option> |
| <option value="medium" ${component.props.size === 'medium' ? 'selected' : ''}>Médio</option> |
| <option value="large" ${component.props.size === 'large' ? 'selected' : ''}>Grande</option> |
| </select> |
| </div> |
| <div class="mb-4"> |
| <label class="block text-gray-700 mb-1">Borda Arredondada</label> |
| <select class="w-full p-2 border rounded-md component-prop" data-prop="rounded"> |
| <option value="none" ${component.props.rounded === 'none' ? 'selected' : ''}>Nenhuma</option> |
| <option value="sm" ${component.props.rounded === 'sm' ? 'selected' : ''}>Pouco</option> |
| <option value="md" ${component.props.rounded === 'md' ? 'selected' : ''}>Médio</option> |
| <option value="lg" ${component.props.rounded === 'lg' ? 'selected' : ''}>Grande</option> |
| <option value="full" ${component.props.rounded === 'full' ? 'selected' : ''}>Completo</option> |
| </select> |
| </div> |
| `; |
| break; |
| case 'image': |
| propertiesHtml = ` |
| <div class="mb-4"> |
| <label class="block text-gray-700 mb-1">URL da Imagem</label> |
| <input type="text" class="w-full p-2 border rounded-md component-prop" |
| data-prop="src" value="${component.props.src}"> |
| </div> |
| <div class="mb-4"> |
| <label class="block text-gray-700 mb-1">Texto Alternativo</label> |
| <input type="text" class="w-full p-2 border rounded-md component-prop" |
| data-prop="alt" value="${component.props.alt}"> |
| </div> |
| <div class="mb-4"> |
| <label class="block text-gray-700 mb-1">Largura</label> |
| <select class="w-full p-2 border rounded-md component-prop" data-prop="width"> |
| <option value="25%" ${component.props.width === '25%' ? 'selected' : ''}>25%</option> |
| <option value="50%" ${component.props.width === '50%' ? 'selected' : ''}>50%</option> |
| <option value="75%" ${component.props.width === '75%' ? 'selected' : ''}>75%</option> |
| <option value="100%" ${component.props.width === '100%' ? 'selected' : ''}>100%</option> |
| </select> |
| </div> |
| <div class="mb-4"> |
| <label class="block text-gray-700 mb-1">Alinhamento</label> |
| <div class="flex space-x-2"> |
| <button class="p-2 border rounded-md ${component.props.alignment === 'left' ? 'bg-blue-100 border-blue-300' : ''}" |
| data-prop="alignment" data-value="left"> |
| <i class="fas fa-align-left"></i> |
| </button> |
| <button class="p-2 border rounded-md ${component.props.alignment === 'center' ? 'bg-blue-100 border-blue-300' : ''}" |
| data-prop="alignment" data-value="center"> |
| <i class="fas fa-align-center"></i> |
| </button> |
| <button class="p-2 border rounded-md ${component.props.alignment === 'right' ? 'bg-blue-100 border-blue-300' : ''}" |
| data-prop="alignment" data-value="right"> |
| <i class="fas fa-align-right"></i> |
| </button> |
| </div> |
| </div> |
| `; |
| break; |
| default: |
| propertiesHtml = `<div class="text-gray-500 italic">Nenhuma propriedade editável para este componente</div>`; |
| } |
| |
| propertyPanel.innerHTML = ` |
| <div class="mb-4 pb-2 border-b"> |
| <h3 class="font-medium capitalize">${component.type}</h3> |
| </div> |
| ${propertiesHtml} |
| `; |
| |
| // Adicionar event listeners para as propriedades |
| propertyPanel.querySelectorAll('.component-prop').forEach(input => { |
| input.addEventListener('change', function() { |
| const prop = this.dataset.prop; |
| const value = this.type === 'checkbox' ? this.checked : this.value; |
| |
| // Atualizar propriedade no objeto do componente |
| const component = components.find(c => c.id === selectedComponent); |
| if (component) { |
| component.props[prop] = value; |
| updateComponent(component); |
| } |
| }); |
| }); |
| |
| // Event listeners para botões de alinhamento |
| propertyPanel.querySelectorAll('button[data-prop |
| </html> |