Spaces:
Running
Running
| <html lang="pt-BR"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
| <title>Gerador de Cursos Inteligente</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> | |
| .dropzone { | |
| border: 2px dashed #cbd5e0; | |
| transition: all 0.3s; | |
| } | |
| .dropzone.active { | |
| border-color: #4f46e5; | |
| background-color: #eef2ff; | |
| } | |
| .module-card { | |
| transition: all 0.2s; | |
| } | |
| .module-card:hover { | |
| transform: translateY(-2px); | |
| box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1); | |
| } | |
| .flip-card { | |
| perspective: 1000px; | |
| } | |
| .flip-card-inner { | |
| transition: transform 0.6s; | |
| transform-style: preserve-3d; | |
| } | |
| .flip-card.flipped .flip-card-inner { | |
| transform: rotateY(180deg); | |
| } | |
| .flip-card-front, .flip-card-back { | |
| backface-visibility: hidden; | |
| } | |
| .flip-card-back { | |
| transform: rotateY(180deg); | |
| } | |
| .fade-in { | |
| animation: fadeIn 0.5s; | |
| } | |
| .progress-bar { | |
| height: 6px; | |
| border-radius: 3px; | |
| transition: width 0.3s ease; | |
| } | |
| @keyframes fadeIn { | |
| from { opacity: 0; } | |
| to { opacity: 1; } | |
| } | |
| </style> | |
| </head> | |
| <body class="bg-gray-50 min-h-screen"> | |
| <div class="container mx-auto px-4 py-8"> | |
| <!-- Header --> | |
| <header class="mb-10 text-center"> | |
| <h1 class="text-4xl font-bold text-indigo-700 mb-2">Gerador de Cursos Inteligente</h1> | |
| <p class="text-gray-600 max-w-2xl mx-auto">Transforme seus materiais em cursos interativos com módulos, quizzes e flashcards em minutos!</p> | |
| </header> | |
| <!-- Main Content --> | |
| <div class="grid grid-cols-1 lg:grid-cols-3 gap-8"> | |
| <!-- Left Panel - Course Creation --> | |
| <div class="lg:col-span-2 space-y-8"> | |
| <!-- Course Info Section --> | |
| <div class="bg-white rounded-xl shadow-md p-6"> | |
| <h2 class="text-xl font-semibold text-gray-800 mb-4">Informações do Curso</h2> | |
| <div class="space-y-4"> | |
| <div> | |
| <label class="block text-sm font-medium text-gray-700 mb-1">Título do Curso</label> | |
| <input type="text" id="courseTitle" class="w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-indigo-500 focus:border-indigo-500" placeholder="Ex: Introdução à Programação"> | |
| </div> | |
| <div> | |
| <label class="block text-sm font-medium text-gray-700 mb-1">Descrição</label> | |
| <textarea id="courseDescription" rows="3" class="w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-indigo-500 focus:border-indigo-500" placeholder="Descreva o objetivo do curso..."></textarea> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- Content Input Section --> | |
| <div class="bg-white rounded-xl shadow-md p-6"> | |
| <h2 class="text-xl font-semibold text-gray-800 mb-4">Adicionar Conteúdo</h2> | |
| <div class="flex space-x-2 mb-4"> | |
| <button id="textTab" class="px-4 py-2 bg-indigo-600 text-white rounded-lg font-medium active-tab">Texto</button> | |
| <button id="pdfTab" class="px-4 py-2 bg-gray-200 text-gray-700 rounded-lg font-medium">PDF</button> | |
| <button id="txtTab" class="px-4 py-2 bg-gray-200 text-gray-700 rounded-lg font-medium">TXT</button> | |
| </div> | |
| <!-- Text Input --> | |
| <div id="textInput" class="content-section"> | |
| <textarea id="contentText" rows="8" class="w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-indigo-500 focus:border-indigo-500" placeholder="Cole seu texto aqui ou comece a digitar..."></textarea> | |
| <div class="mt-4 flex justify-end"> | |
| <button id="analyzeText" class="px-6 py-2 bg-indigo-600 text-white rounded-lg font-medium hover:bg-indigo-700 transition flex items-center"> | |
| <i class="fas fa-magic mr-2"></i> Gerar Módulos | |
| </button> | |
| </div> | |
| </div> | |
| <!-- File Upload --> | |
| <div id="fileInput" class="content-section hidden"> | |
| <div id="dropzone" class="dropzone rounded-xl p-8 text-center cursor-pointer"> | |
| <i class="fas fa-cloud-upload-alt text-4xl text-indigo-500 mb-3"></i> | |
| <p class="text-gray-600 mb-2">Arraste e solte seus arquivos aqui</p> | |
| <p class="text-sm text-gray-500 mb-4">ou</p> | |
| <label for="fileUpload" class="px-4 py-2 bg-indigo-600 text-white rounded-lg font-medium hover:bg-indigo-700 transition cursor-pointer"> | |
| Selecione os Arquivos | |
| </label> | |
| <input type="file" id="fileUpload" class="hidden" accept=".pdf,.txt"> | |
| </div> | |
| <div id="filePreview" class="mt-4 hidden"> | |
| <div class="flex items-center p-3 bg-gray-100 rounded-lg"> | |
| <i class="fas fa-file-alt text-indigo-500 mr-3 text-xl"></i> | |
| <div class="flex-1"> | |
| <p id="fileName" class="font-medium"></p> | |
| <p id="fileSize" class="text-sm text-gray-500"></p> | |
| </div> | |
| <button id="removeFile" class="text-red-500 hover:text-red-700"> | |
| <i class="fas fa-times"></i> | |
| </button> | |
| </div> | |
| <!-- Progress Bar --> | |
| <div id="uploadProgress" class="hidden mt-3"> | |
| <div class="flex justify-between text-sm text-gray-600 mb-1"> | |
| <span>Upload em progresso...</span> | |
| <span id="progressPercent">0%</span> | |
| </div> | |
| <div class="w-full bg-gray-200 rounded-full"> | |
| <div id="progressBar" class="progress-bar bg-indigo-600" style="width: 0%"></div> | |
| </div> | |
| </div> | |
| <div class="mt-4 flex justify-end"> | |
| <button id="analyzeFile" class="px-6 py-2 bg-indigo-600 text-white rounded-lg font-medium hover:bg-indigo-700 transition flex items-center"> | |
| <i class="fas fa-magic mr-2"></i> Processar Arquivo | |
| </button> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- Generated Modules --> | |
| <div id="modulesSection" class="hidden"> | |
| <div class="flex justify-between items-center mb-4"> | |
| <h2 class="text-xl font-semibold text-gray-800">Módulos Gerados</h2> | |
| <button id="addModule" class="px-4 py-2 bg-green-600 text-white rounded-lg font-medium hover:bg-green-700 transition flex items-center"> | |
| <i class="fas fa-plus mr-2"></i> Adicionar Módulo | |
| </button> | |
| </div> | |
| <div id="modulesContainer" class="space-y-4"> | |
| <!-- Modules will be added here dynamically --> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- Right Panel - Preview --> | |
| <div class="lg:col-span-1"> | |
| <div class="bg-white rounded-xl shadow-md p-6 sticky top-6"> | |
| <h2 class="text-xl font-semibold text-gray-800 mb-4">Pré-visualização do Curso</h2> | |
| <div id="coursePreview" class="space-y-4"> | |
| <div class="text-center py-10 text-gray-400"> | |
| <i class="fas fa-book-open text-4xl mb-3"></i> | |
| <p>Seu curso aparecerá aqui</p> | |
| </div> | |
| </div> | |
| <div class="mt-6 pt-4 border-t border-gray-200"> | |
| <button id="generateCourse" class="w-full py-3 bg-indigo-600 text-white rounded-lg font-medium hover:bg-indigo-700 transition flex items-center justify-center"> | |
| <i class="fas fa-download mr-2"></i> Exportar Curso | |
| </button> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- Module Modal --> | |
| <div id="moduleModal" class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center hidden z-50"> | |
| <div class="bg-white rounded-xl p-6 w-full max-w-2xl max-h-[90vh] overflow-y-auto"> | |
| <div class="flex justify-between items-center mb-4"> | |
| <h3 class="text-lg font-semibold">Editar Módulo</h3> | |
| <button id="closeModal" class="text-gray-500 hover:text-gray-700"> | |
| <i class="fas fa-times"></i> | |
| </button> | |
| </div> | |
| <div class="space-y-4"> | |
| <div> | |
| <label class="block text-sm font-medium text-gray-700 mb-1">Título do Módulo</label> | |
| <input type="text" id="moduleTitle" class="w-full px-4 py-2 border border-gray-300 rounded-lg"> | |
| </div> | |
| <div> | |
| <label class="block text-sm font-medium text-gray-700 mb-1">Conteúdo</label> | |
| <textarea id="moduleContent" rows="6" class="w-full px-4 py-2 border border-gray-300 rounded-lg"></textarea> | |
| </div> | |
| <div> | |
| <label class="block text-sm font-medium text-gray-700 mb-1">Imagem (URL)</label> | |
| <input type="text" id="moduleImage" class="w-full px-4 py-2 border border-gray-300 rounded-lg" placeholder="Cole a URL da imagem"> | |
| </div> | |
| <div class="pt-4"> | |
| <h4 class="font-medium mb-2">Adicionar Quiz</h4> | |
| <div id="quizQuestions" class="space-y-3"> | |
| <!-- Quiz questions will be added here --> | |
| </div> | |
| <button id="addQuestion" class="mt-2 px-4 py-2 bg-blue-100 text-blue-600 rounded-lg text-sm font-medium"> | |
| <i class="fas fa-plus mr-1"></i> Adicionar Pergunta | |
| </button> | |
| </div> | |
| <div class="pt-4"> | |
| <h4 class="font-medium mb-2">Adicionar Flashcards</h4> | |
| <div id="flashcardsContainer" class="space-y-3"> | |
| <!-- Flashcards will be added here --> | |
| </div> | |
| <button id="addFlashcard" class="mt-2 px-4 py-2 bg-purple-100 text-purple-600 rounded-lg text-sm font-medium"> | |
| <i class="fas fa-plus mr-1"></i> Adicionar Flashcard | |
| </button> | |
| </div> | |
| <div class="flex justify-end space-x-3 pt-4"> | |
| <button id="cancelModule" class="px-4 py-2 border border-gray-300 rounded-lg font-medium">Cancelar</button> | |
| <button id="saveModule" class="px-4 py-2 bg-indigo-600 text-white rounded-lg font-medium">Salvar</button> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| <script> | |
| // DOM Elements | |
| const textTab = document.getElementById('textTab'); | |
| const pdfTab = document.getElementById('pdfTab'); | |
| const txtTab = document.getElementById('txtTab'); | |
| const textInput = document.getElementById('textInput'); | |
| const fileInput = document.getElementById('fileInput'); | |
| const contentText = document.getElementById('contentText'); | |
| const analyzeText = document.getElementById('analyzeText'); | |
| const dropzone = document.getElementById('dropzone'); | |
| const fileUpload = document.getElementById('fileUpload'); | |
| const filePreview = document.getElementById('filePreview'); | |
| const fileName = document.getElementById('fileName'); | |
| const fileSize = document.getElementById('fileSize'); | |
| const removeFile = document.getElementById('removeFile'); | |
| const analyzeFile = document.getElementById('analyzeFile'); | |
| const modulesSection = document.getElementById('modulesSection'); | |
| const modulesContainer = document.getElementById('modulesContainer'); | |
| const addModule = document.getElementById('addModule'); | |
| const coursePreview = document.getElementById('coursePreview'); | |
| const generateCourse = document.getElementById('generateCourse'); | |
| const moduleModal = document.getElementById('moduleModal'); | |
| const closeModal = document.getElementById('closeModal'); | |
| const cancelModule = document.getElementById('cancelModule'); | |
| const saveModule = document.getElementById('saveModule'); | |
| const moduleTitle = document.getElementById('moduleTitle'); | |
| const moduleContent = document.getElementById('moduleContent'); | |
| const moduleImage = document.getElementById('moduleImage'); | |
| const quizQuestions = document.getElementById('quizQuestions'); | |
| const addQuestion = document.getElementById('addQuestion'); | |
| const flashcardsContainer = document.getElementById('flashcardsContainer'); | |
| const addFlashcard = document.getElementById('addFlashcard'); | |
| const uploadProgress = document.getElementById('uploadProgress'); | |
| const progressBar = document.getElementById('progressBar'); | |
| const progressPercent = document.getElementById('progressPercent'); | |
| // State | |
| let currentTab = 'text'; | |
| let currentFile = null; | |
| let modules = []; | |
| let currentModuleIndex = null; | |
| // Tab Switching | |
| textTab.addEventListener('click', () => switchTab('text')); | |
| pdfTab.addEventListener('click', () => switchTab('pdf')); | |
| txtTab.addEventListener('click', () => switchTab('txt')); | |
| function switchTab(tab) { | |
| currentTab = tab; | |
| // Update tab buttons | |
| textTab.classList.remove('bg-indigo-600', 'text-white'); | |
| textTab.classList.add('bg-gray-200', 'text-gray-700'); | |
| pdfTab.classList.remove('bg-indigo-600', 'text-white'); | |
| pdfTab.classList.add('bg-gray-200', 'text-gray-700'); | |
| txtTab.classList.remove('bg-indigo-600', 'text-white'); | |
| txtTab.classList.add('bg-gray-200', 'text-gray-700'); | |
| if (tab === 'text') { | |
| textTab.classList.remove('bg-gray-200', 'text-gray-700'); | |
| textTab.classList.add('bg-indigo-600', 'text-white'); | |
| textInput.classList.remove('hidden'); | |
| fileInput.classList.add('hidden'); | |
| } else { | |
| if (tab === 'pdf') { | |
| pdfTab.classList.remove('bg-gray-200', 'text-gray-700'); | |
| pdfTab.classList.add('bg-indigo-600', 'text-white'); | |
| fileUpload.setAttribute('accept', '.pdf'); | |
| } else { | |
| txtTab.classList.remove('bg-gray-200', 'text-gray-700'); | |
| txtTab.classList.add('bg-indigo-600', 'text-white'); | |
| fileUpload.setAttribute('accept', '.txt'); | |
| } | |
| textInput.classList.add('hidden'); | |
| fileInput.classList.remove('hidden'); | |
| } | |
| } | |
| // File Upload Handling | |
| dropzone.addEventListener('dragover', (e) => { | |
| e.preventDefault(); | |
| dropzone.classList.add('active'); | |
| }); | |
| dropzone.addEventListener('dragleave', () => { | |
| dropzone.classList.remove('active'); | |
| }); | |
| dropzone.addEventListener('drop', (e) => { | |
| e.preventDefault(); | |
| dropzone.classList.remove('active'); | |
| if (e.dataTransfer.files.length) { | |
| handleFileUpload(e.dataTransfer.files[0]); | |
| } | |
| }); | |
| fileUpload.addEventListener('change', (e) => { | |
| if (e.target.files.length) { | |
| handleFileUpload(e.target.files[0]); | |
| } | |
| }); | |
| function handleFileUpload(file) { | |
| // Validate file type based on current tab | |
| let isValid = false; | |
| if (currentTab === 'pdf') { | |
| // Check for PDF files by extension or MIME type | |
| isValid = file.name.toLowerCase().endsWith('.pdf') || | |
| file.type === 'application/pdf'; | |
| } else if (currentTab === 'txt') { | |
| // Check for TXT files by extension or MIME type | |
| isValid = file.name.toLowerCase().endsWith('.txt') || | |
| file.type === 'text/plain'; | |
| } | |
| if (!isValid) { | |
| alert(`Por favor, selecione um arquivo ${currentTab.toUpperCase()} válido.`); | |
| return; | |
| } | |
| // Show file preview | |
| currentFile = file; | |
| fileName.textContent = file.name; | |
| fileSize.textContent = formatFileSize(file.size); | |
| filePreview.classList.remove('hidden'); | |
| // Simulate file upload with progress | |
| simulateFileUpload(file); | |
| } | |
| function simulateFileUpload(file) { | |
| // Show progress bar | |
| uploadProgress.classList.remove('hidden'); | |
| analyzeFile.disabled = true; | |
| let progress = 0; | |
| const interval = setInterval(() => { | |
| progress += Math.random() * 10; | |
| if (progress > 100) progress = 100; | |
| // Update progress bar | |
| progressBar.style.width = `${progress}%`; | |
| progressPercent.textContent = `${Math.round(progress)}%`; | |
| if (progress === 100) { | |
| clearInterval(interval); | |
| setTimeout(() => { | |
| uploadProgress.classList.add('hidden'); | |
| analyzeFile.disabled = false; | |
| }, 500); | |
| } | |
| }, 200); | |
| } | |
| function formatFileSize(bytes) { | |
| if (bytes === 0) return '0 Bytes'; | |
| const k = 1024; | |
| const sizes = ['Bytes', 'KB', 'MB', 'GB']; | |
| const i = Math.floor(Math.log(bytes) / Math.log(k)); | |
| return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i]; | |
| } | |
| removeFile.addEventListener('click', () => { | |
| currentFile = null; | |
| fileUpload.value = ''; | |
| filePreview.classList.add('hidden'); | |
| uploadProgress.classList.add('hidden'); | |
| }); | |
| // Analyze Content | |
| analyzeText.addEventListener('click', () => { | |
| const text = contentText.value.trim(); | |
| if (!text) { | |
| alert('Por favor, insira algum texto para gerar os módulos.'); | |
| return; | |
| } | |
| // Simulate AI processing | |
| simulateProcessing(text); | |
| }); | |
| analyzeFile.addEventListener('click', () => { | |
| if (!currentFile) { | |
| alert('Por favor, faça upload de um arquivo primeiro.'); | |
| return; | |
| } | |
| // Simulate file processing | |
| simulateProcessing(`Conteúdo do arquivo ${currentFile.name}`); | |
| }); | |
| function simulateProcessing(content) { | |
| // Show loading state | |
| const button = currentTab === 'text' ? analyzeText : analyzeFile; | |
| button.innerHTML = '<i class="fas fa-spinner fa-spin mr-2"></i> Processando...'; | |
| button.disabled = true; | |
| // Simulate API call delay | |
| setTimeout(() => { | |
| // Generate sample modules from content | |
| generateSampleModules(content); | |
| // Reset button | |
| button.innerHTML = '<i class="fas fa-magic mr-2"></i> ' + (currentTab === 'text' ? 'Gerar Módulos' : 'Processar Arquivo'); | |
| button.disabled = false; | |
| // Show modules section | |
| modulesSection.classList.remove('hidden'); | |
| // Update preview | |
| updateCoursePreview(); | |
| }, 1500); | |
| } | |
| function generateSampleModules(content) { | |
| // Clear existing modules | |
| modules = []; | |
| // Split content into logical sections (simulating AI analysis) | |
| const sections = splitContentIntoSections(content); | |
| // Create modules for each section | |
| sections.forEach((section, index) => { | |
| const module = { | |
| id: Date.now() + index, | |
| title: section.title, | |
| content: section.content, | |
| image: getRandomImageUrl(section.keywords[0] || 'education'), | |
| quiz: generateQuizForSection(section), | |
| flashcards: generateFlashcardsForSection(section) | |
| }; | |
| modules.push(module); | |
| }); | |
| // If no sections were created (unlikely), create a single module | |
| if (modules.length === 0) { | |
| modules.push({ | |
| id: Date.now(), | |
| title: 'Módulo 1: Introdução', | |
| content: content, | |
| image: getRandomImageUrl('education'), | |
| quiz: [{ | |
| question: "Qual é o tópico principal deste módulo?", | |
| options: ["Opção A", "Opção B", "Opção C", "Opção D"], | |
| correctAnswer: 0 | |
| }], | |
| flashcards: [{ | |
| front: "Termo importante", | |
| back: "Definição do termo" | |
| }] | |
| }); | |
| } | |
| renderModules(); | |
| } | |
| function splitContentIntoSections(content) { | |
| // This is a simulation of how an AI might split content into modules | |
| // In a real app, this would be done by an AI service | |
| const paragraphs = content.split('\n\n').filter(p => p.trim().length > 0); | |
| // If content is short, treat it as a single module | |
| if (paragraphs.length <= 2) { | |
| return [{ | |
| title: "Módulo 1: Visão Geral", | |
| content: content, | |
| keywords: ["introdução", "visão geral"] | |
| }]; | |
| } | |
| // Group paragraphs into sections | |
| const sections = []; | |
| let currentSection = null; | |
| paragraphs.forEach(para => { | |
| // Check if this paragraph starts a new section | |
| const isHeading = para.split('\n')[0].length < 60 && | |
| (para.split('\n')[0].endsWith(':') || | |
| para.split('\n')[0].match(/^[A-Z][a-z]+/)); | |
| if (isHeading || !currentSection) { | |
| // Start a new section | |
| if (currentSection) { | |
| sections.push(currentSection); | |
| } | |
| const title = isHeading ? para.split('\n')[0] : `Módulo ${sections.length + 1}: ${para.substring(0, 30)}...`; | |
| const content = isHeading ? para.substring(para.indexOf('\n') + 1) : para; | |
| currentSection = { | |
| title: title, | |
| content: content, | |
| keywords: extractKeywords(para) | |
| }; | |
| } else { | |
| // Add to current section | |
| currentSection.content += '\n\n' + para; | |
| } | |
| }); | |
| // Add the last section | |
| if (currentSection) { | |
| sections.push(currentSection); | |
| } | |
| // Limit to 5 sections for demo purposes | |
| if (sections.length > 5) { | |
| return sections.slice(0, 5).map((section, i) => ({ | |
| ...section, | |
| title: `Módulo ${i + 1}: ${section.title.substring(0, 40)}...` | |
| })); | |
| } | |
| return sections; | |
| } | |
| function extractKeywords(text) { | |
| // Simple keyword extraction simulation | |
| const words = text.toLowerCase().split(/\s+/); | |
| const commonWords = ['o', 'a', 'os', 'as', 'de', 'do', 'da', 'dos', 'das', 'em', 'no', 'na', 'nos', 'nas', 'por', 'para', 'com', 'sem', 'que', 'é']; | |
| const keywords = words | |
| .filter(word => word.length > 3 && !commonWords.includes(word)) | |
| .slice(0, 3); | |
| return keywords.length > 0 ? keywords : ['aprendizado', 'conhecimento']; | |
| } | |
| function generateQuizForSection(section) { | |
| // Generate 1-3 quiz questions per section | |
| const questionCount = Math.min(3, Math.max(1, section.content.split('.').length / 5)); | |
| const questions = []; | |
| for (let i = 0; i < questionCount; i++) { | |
| questions.push({ | |
| question: `Pergunta ${i + 1} sobre "${section.title.substring(0, 20)}..."`, | |
| options: ["Opção A", "Opção B", "Opção C", "Opção D"], | |
| correctAnswer: Math.floor(Math.random() * 4) | |
| }); | |
| } | |
| return questions; | |
| } | |
| function generateFlashcardsForSection(section) { | |
| // Generate 2-5 flashcards per section | |
| const cardCount = Math.min(5, Math.max(2, section.keywords.length)); | |
| const flashcards = []; | |
| for (let i = 0; i < cardCount; i++) { | |
| const term = section.keywords[i] || `Termo ${i + 1}`; | |
| flashcards.push({ | |
| front: term.charAt(0).toUpperCase() + term.slice(1), | |
| back: `Definição de ${term}` | |
| }); | |
| } | |
| return flashcards; | |
| } | |
| function getRandomImageUrl(keyword) { | |
| return `https://source.unsplash.com/300x200/?${keyword},education`; | |
| } | |
| // Modules Management | |
| function renderModules() { | |
| modulesContainer.innerHTML = ''; | |
| modules.forEach((module, index) => { | |
| const moduleCard = document.createElement('div'); | |
| moduleCard.className = 'module-card bg-white rounded-xl shadow-md overflow-hidden'; | |
| moduleCard.innerHTML = ` | |
| <div class="p-5"> | |
| <div class="flex justify-between items-start"> | |
| <h3 class="font-semibold text-lg text-gray-800">${module.title}</h3> | |
| <div class="flex space-x-2"> | |
| <button class="edit-module px-3 py-1 bg-blue-100 text-blue-600 rounded-lg text-sm" data-index="${index}"> | |
| <i class="fas fa-edit"></i> | |
| </button> | |
| <button class="delete-module px-3 py-1 bg-red-100 text-red-600 rounded-lg text-sm" data-index="${index}"> | |
| <i class="fas fa-trash"></i> | |
| </button> | |
| </div> | |
| </div> | |
| <div class="mt-3 flex items-center text-sm text-gray-500"> | |
| <span class="mr-3"><i class="fas fa-image mr-1"></i> ${module.image ? 'Com imagem' : 'Sem imagem'}</span> | |
| <span class="mr-3"><i class="fas fa-question-circle mr-1"></i> ${module.quiz.length} perguntas</span> | |
| <span><i class="fas fa-layer-group mr-1"></i> ${module.flashcards.length} flashcards</span> | |
| </div> | |
| </div> | |
| `; | |
| modulesContainer.appendChild(moduleCard); | |
| }); | |
| // Add event listeners to edit and delete buttons | |
| document.querySelectorAll('.edit-module').forEach(btn => { | |
| btn.addEventListener('click', (e) => { | |
| const index = e.target.getAttribute('data-index'); | |
| openEditModuleModal(index); | |
| }); | |
| }); | |
| document.querySelectorAll('.delete-module').forEach(btn => { | |
| btn.addEventListener('click', (e) => { | |
| const index = e.target.getAttribute('data-index'); | |
| if (confirm('Tem certeza que deseja excluir este módulo?')) { | |
| modules.splice(index, 1); | |
| renderModules(); | |
| updateCoursePreview(); | |
| } | |
| }); | |
| }); | |
| } | |
| addModule.addEventListener('click', () => { | |
| currentModuleIndex = null; | |
| moduleTitle.value = ''; | |
| moduleContent.value = ''; | |
| moduleImage.value = ''; | |
| quizQuestions.innerHTML = ''; | |
| flashcardsContainer.innerHTML = ''; | |
| moduleModal.classList.remove('hidden'); | |
| }); | |
| function openEditModuleModal(index) { | |
| currentModuleIndex = index; | |
| const module = modules[index]; | |
| moduleTitle.value = module.title; | |
| moduleContent.value = module.content; | |
| moduleImage.value = module.image || ''; | |
| // Render quiz questions | |
| quizQuestions.innerHTML = ''; | |
| module.quiz.forEach((question, qIndex) => { | |
| addQuestionToModal(question, qIndex); | |
| }); | |
| // Render flashcards | |
| flashcardsContainer.innerHTML = ''; | |
| module.flashcards.forEach((card, cIndex) => { | |
| addFlashcardToModal(card, cIndex); | |
| }); | |
| moduleModal.classList.remove('hidden'); | |
| } | |
| // Quiz Question Management | |
| addQuestion.addEventListener('click', () => { | |
| addQuestionToModal({ | |
| question: '', | |
| options: ['', '', '', ''], | |
| correctAnswer: 0 | |
| }, quizQuestions.children.length); | |
| }); | |
| function addQuestionToModal(question, index) { | |
| const questionDiv = document.createElement('div'); | |
| questionDiv.className = 'bg-gray-50 p-3 rounded-lg'; | |
| questionDiv.innerHTML = ` | |
| <div class="flex justify-between items-center mb-2"> | |
| <label class="text-sm font-medium text-gray-700">Pergunta ${index + 1}</label> | |
| <button class="remove-question text-red-500 text-sm" data-index="${index}"> | |
| <i class="fas fa-times"></i> Remover | |
| </button> | |
| </div> | |
| <input type="text" class="w-full px-3 py-2 border border-gray-300 rounded-lg mb-2 question-text" | |
| value="${question.question}" placeholder="Digite a pergunta"> | |
| <div class="space-y-2 mb-3"> | |
| ${question.options.map((opt, optIndex) => ` | |
| <div class="flex items-center"> | |
| <input type="radio" name="correct-${index}" value="${optIndex}" | |
| class="mr-2 correct-answer" ${question.correctAnswer === optIndex ? 'checked' : ''}> | |
| <input type="text" class="flex-1 px-3 py-2 border border-gray-300 rounded-lg question-option" | |
| value="${opt}" placeholder="Opção ${optIndex + 1}"> | |
| </div> | |
| `).join('')} | |
| </div> | |
| `; | |
| quizQuestions.appendChild(questionDiv); | |
| // Add event listener to remove button | |
| questionDiv.querySelector('.remove-question').addEventListener('click', (e) => { | |
| e.target.closest('.bg-gray-50').remove(); | |
| // Renumber remaining questions | |
| const questions = quizQuestions.querySelectorAll('.bg-gray-50'); | |
| questions.forEach((q, i) => { | |
| q.querySelector('label').textContent = `Pergunta ${i + 1}`; | |
| }); | |
| }); | |
| } | |
| // Flashcard Management | |
| addFlashcard.addEventListener('click', () => { | |
| addFlashcardToModal({ | |
| front: '', | |
| back: '' | |
| }, flashcardsContainer.children.length); | |
| }); | |
| function addFlashcardToModal(card, index) { | |
| const cardDiv = document.createElement('div'); | |
| cardDiv.className = 'bg-gray-50 p-3 rounded-lg'; | |
| cardDiv.innerHTML = ` | |
| <div class="flex justify-between items-center mb-2"> | |
| <label class="text-sm font-medium text-gray-700">Flashcard ${index + 1}</label> | |
| <button class="remove-flashcard text-red-500 text-sm" data-index="${index}"> | |
| <i class="fas fa-times"></i> Remover | |
| </button> | |
| </div> | |
| <input type="text" class="w-full px-3 py-2 border border-gray-300 rounded-lg mb-2 flashcard-front" | |
| value="${card.front}" placeholder="Frente (termo)"> | |
| <input type="text" class="w-full px-3 py-2 border border-gray-300 rounded-lg flashcard-back" | |
| value="${card.back}" placeholder="Verso (definição)"> | |
| `; | |
| flashcardsContainer.appendChild(cardDiv); | |
| // Add event listener to remove button | |
| cardDiv.querySelector('.remove-flashcard').addEventListener('click', (e) => { | |
| e.target.closest('.bg-gray-50').remove(); | |
| // Renumber remaining flashcards | |
| const cards = flashcardsContainer.querySelectorAll('.bg-gray-50'); | |
| cards.forEach((c, i) => { | |
| c.querySelector('label').textContent = `Flashcard ${i + 1}`; | |
| }); | |
| }); | |
| } | |
| // Modal Actions | |
| closeModal.addEventListener('click', () => { | |
| moduleModal.classList.add('hidden'); | |
| }); | |
| cancelModule.addEventListener('click', () => { | |
| moduleModal.classList.add('hidden'); | |
| }); | |
| saveModule.addEventListener('click', () => { | |
| // Get module data from form | |
| const title = moduleTitle.value.trim(); | |
| const content = moduleContent.value.trim(); | |
| const image = moduleImage.value.trim(); | |
| if (!title || !content) { | |
| alert('Por favor, preencha pelo menos o título e o conteúdo do módulo.'); | |
| return; | |
| } | |
| // Get quiz questions | |
| const quiz = []; | |
| const questionDivs = quizQuestions.querySelectorAll('.bg-gray-50'); | |
| questionDivs.forEach(div => { | |
| const questionText = div.querySelector('.question-text').value.trim(); | |
| const options = Array.from(div.querySelectorAll('.question-option')).map(opt => opt.value.trim()); | |
| const correctAnswer = parseInt(div.querySelector('.correct-answer:checked').value); | |
| if (questionText && options.every(opt => opt)) { | |
| quiz.push({ | |
| question: questionText, | |
| options: options, | |
| correctAnswer: correctAnswer | |
| }); | |
| } | |
| }); | |
| // Get flashcards | |
| const flashcards = []; | |
| const flashcardDivs = flashcardsContainer.querySelectorAll('.bg-gray-50'); | |
| flashcardDivs.forEach(div => { | |
| const front = div.querySelector('.flashcard-front').value.trim(); | |
| const back = div.querySelector('.flashcard-back').value.trim(); | |
| if (front && back) { | |
| flashcards.push({ | |
| front: front, | |
| back: back | |
| }); | |
| } | |
| }); | |
| // Create or update module | |
| const moduleData = { | |
| id: currentModuleIndex !== null ? modules[currentModuleIndex].id : Date.now(), | |
| title: title, | |
| content: content, | |
| image: image || null, | |
| quiz: quiz, | |
| flashcards: flashcards | |
| }; | |
| if (currentModuleIndex !== null) { | |
| // Update existing module | |
| modules[currentModuleIndex] = moduleData; | |
| } else { | |
| // Add new module | |
| modules.push(moduleData); | |
| } | |
| // Update UI | |
| renderModules(); | |
| updateCoursePreview(); | |
| moduleModal.classList.add('hidden'); | |
| }); | |
| // Course Preview | |
| function updateCoursePreview() { | |
| const courseTitle = document.getElementById('courseTitle').value.trim() || 'Nome do Curso'; | |
| const courseDescription = document.getElementById('courseDescription').value.trim() || 'Descrição do curso será exibida aqui.'; | |
| coursePreview.innerHTML = ` | |
| <div class="text-center mb-6"> | |
| <h3 class="text-2xl font-bold text-gray-800">${courseTitle}</h3> | |
| <p class="text-gray-600 mt-2">${courseDescription}</p> | |
| </div> | |
| ${modules.length > 0 ? ` | |
| <div class="space-y-6"> | |
| ${modules.map((module, index) => ` | |
| <div class="border border-gray-200 rounded-xl overflow-hidden"> | |
| <div class="bg-gray-50 px-5 py-3 border-b border-gray-200"> | |
| <h4 class="font-semibold text-gray-800">${module.title}</h4> | |
| </div> | |
| <div class="p-5"> | |
| ${module.image ? ` | |
| <img src="${module.image}" alt="${module.title}" class="w-full h-48 object-cover rounded-lg mb-4"> | |
| ` : ''} | |
| <div class="prose max-w-none text-gray-700"> | |
| ${module.content.split('\n').map(p => `<p>${p}</p>`).join('')} | |
| </div> | |
| ${module.quiz.length > 0 ? ` | |
| <div class="mt-6 pt-4 border-t border-gray-200"> | |
| <h5 class="font-medium text-gray-800 mb-3">Quiz</h5> | |
| <div class="space-y-4"> | |
| ${module.quiz.map((q, qIndex) => ` | |
| <div class="quiz-question"> | |
| <p class="font-medium">${qIndex + 1}. ${q.question}</p> | |
| <div class="mt-2 space-y-2"> | |
| ${q.options.map((opt, optIndex) => ` | |
| <div class="flex items-center"> | |
| <input type="radio" id="quiz-${index}-${qIndex}-${optIndex}" | |
| name="quiz-${index}-${qIndex}" class="mr-2" ${q.correctAnswer === optIndex ? 'checked' : ''}> | |
| <label for="quiz-${index}-${qIndex}-${optIndex}" class="text-gray-700">${opt}</label> | |
| </div> | |
| `).join('')} | |
| </div> | |
| </div> | |
| `).join('')} | |
| </div> | |
| </div> | |
| ` : ''} | |
| ${module.flashcards.length > 0 ? ` | |
| <div class="mt-6 pt-4 border-t border-gray-200"> | |
| <h5 class="font-medium text-gray-800 mb-3">Flashcards</h5> | |
| <div class="grid grid-cols-1 sm:grid-cols-2 gap-3"> | |
| ${module.flashcards.map((card, cIndex) => ` | |
| <div class="flip-card" onclick="this.classList.toggle('flipped')"> | |
| <div class="flip-card-inner bg-white rounded-lg shadow-sm border border-gray-200 h-32"> | |
| <div class="flip-card-front flex items-center justify-center p-4 text-center"> | |
| <p class="font-medium">${card.front}</p> | |
| </div> | |
| <div class="flip-card-back flex items-center justify-center p-4 text-center bg-indigo-50"> | |
| <p>${card.back}</p> | |
| </div> | |
| </div> | |
| </div> | |
| `).join('')} | |
| </div> | |
| </div> | |
| ` : ''} | |
| </div> | |
| </div> | |
| `).join('')} | |
| </div> | |
| ` : ` | |
| <div class="text-center py-10 text-gray-400"> | |
| <i class="fas fa-book-open text-4xl mb-3"></i> | |
| <p>Adicione módulos para visualizar o curso</p> | |
| </div> | |
| `} | |
| `; | |
| // Add event listeners to flip cards | |
| document.querySelectorAll('.flip-card').forEach(card => { | |
| card.addEventListener('click', function() { | |
| this.classList.toggle('flipped'); | |
| }); | |
| }); | |
| } | |
| // Export Course | |
| generateCourse.addEventListener('click', () => { | |
| if (modules.length === 0) { | |
| alert('Por favor, adicione pelo menos um módulo ao curso antes de exportar.'); | |
| return; | |
| } | |
| // In a real app, this would generate a downloadable package | |
| alert('Funcionalidade de exportação seria implementada aqui!\nO curso seria empacotado para download.'); | |
| }); | |
| // Initialize | |
| document.getElementById('courseTitle').addEventListener('input', updateCoursePreview); | |
| document.getElementById('courseDescription').addEventListener('input', updateCoursePreview); | |
| </script> | |
| <p style="border-radius: 8px; text-align: center; font-size: 12px; color: #fff; margin-top: 16px;position: fixed; left: 8px; bottom: 8px; z-index: 10; background: rgba(0, 0, 0, 0.8); padding: 4px 8px;">Made with <img src="https://enzostvs-deepsite.hf.space/logo.svg" alt="DeepSite Logo" style="width: 16px; height: 16px; vertical-align: middle;display:inline-block;margin-right:3px;filter:brightness(0) invert(1);"><a href="https://enzostvs-deepsite.hf.space" style="color: #fff;text-decoration: underline;" target="_blank" >DeepSite</a> - 🧬 <a href="https://enzostvs-deepsite.hf.space?remix=zoeboy/criador-de-cursos2" style="color: #fff;text-decoration: underline;" target="_blank" >Remix</a></p></body> | |
| </html> |