| <!DOCTYPE html> |
| <html lang="pt-BR"> |
| <head> |
| <meta charset="UTF-8"> |
| <title>Gerador VSL Ultra</title> |
| |
| <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin> |
| <link rel="preconnect" href="https://fonts.googleapis.com"> |
| <link href="https://fonts.googleapis.com/css2?family=Open+Sans:wght@400;700&display=swap" rel="stylesheet"> |
| <style> |
| * { box-sizing: border-box; } |
| body { |
| margin: 0; padding: 0; |
| font-family: 'Open Sans', sans-serif; |
| background-color: #f9fafb; |
| color: #111; |
| } |
| header { |
| background: linear-gradient(135deg, #111827, #1f2937); |
| color: white; |
| padding: 25px; |
| font-size: 28px; |
| font-weight: bold; |
| text-align: center; |
| box-shadow: 0 4px 6px rgba(0,0,0,0.1); |
| } |
| .container { |
| max-width: 1200px; |
| margin: 40px auto; |
| padding: 40px; |
| background: white; |
| border-radius: 16px; |
| box-shadow: 0 10px 30px rgba(0,0,0,0.08); |
| } |
| .input-group { |
| margin-bottom: 25px; |
| transition: all 0.3s ease; |
| } |
| .input-group:hover { transform: translateY(-2px); } |
| label { |
| font-weight: 600; |
| display: block; |
| margin-bottom: 8px; |
| color: #374151; |
| } |
| input, button, audio { |
| width: 100%; |
| padding: 12px; |
| font-size: 16px; |
| border-radius: 8px; |
| border: 2px solid #e5e7eb; |
| transition: all 0.3s ease; |
| } |
| input:hover, input:focus { border-color: #2563eb; outline: none; } |
| button { |
| background: linear-gradient(135deg, #2563eb, #1d4ed8); |
| color: white; |
| border: none; |
| cursor: pointer; |
| margin-top: 10px; |
| font-weight: 600; |
| text-transform: uppercase; |
| letter-spacing: 0.5px; |
| transition: all 0.3s ease; |
| } |
| button:hover:not(:disabled) { |
| transform: translateY(-2px); |
| box-shadow: 0 4px 12px rgba(37, 99, 235, 0.2); |
| } |
| button:disabled { opacity: 0.5; cursor: not-allowed; } |
| .preview { |
| margin-top: 40px; |
| border: 2px dashed #cbd5e1; |
| border-radius: 12px; |
| padding: 60px 40px; |
| font-size: 42px; |
| font-family: 'Open Sans', sans-serif; |
| text-align: center; |
| white-space: pre-line; |
| min-height: 600px; |
| background: white; |
| display: flex; |
| align-items: center; |
| justify-content: center; |
| line-height: 1.4; |
| transition: all 0.3s ease; |
| position: relative; |
| } |
| |
| .preview p { max-width: 1000px; margin: 0 auto; animation: fadeIn 0.3s ease-in-out; } |
| |
| .preview.fullscreen { |
| position: fixed; |
| top: 0; |
| left: 0; |
| width: 100vw; |
| height: 100vh; |
| margin: 0; |
| padding: 40px; |
| border: none; |
| border-radius: 0; |
| background: white; |
| color: black; |
| font-size: 64px; |
| z-index: 9999; |
| display: flex; |
| align-items: center; |
| justify-content: center; |
| } |
| |
| .preview.fullscreen p { max-width: 1600px; line-height: 1.4; white-space: pre-line; } |
| |
| .preview.fullscreen.hd { width: 1920px; height: 1080px; transform-origin: top left; } |
| .preview.fullscreen.hd p { max-width: 1720px; } |
| |
| .bold-black { font-weight: 700; color: black; } |
| .bold-red { font-weight: 700; color: #dc2626; } |
| |
| .button-group { display: flex; gap: 15px; } |
| .button-group button { flex: 1; padding: 15px; } |
| |
| |
| @keyframes fadeIn { from { opacity: 0; } to { opacity: 1; } } |
| |
| |
| .status-bar { |
| display: flex; |
| gap: 20px; |
| margin-top: 20px; |
| padding: 15px; |
| background: #f8fafc; |
| border-radius: 8px; |
| font-size: 14px; |
| } |
| .status-item { display: flex; align-items: center; gap: 8px; } |
| .status-dot { width: 8px; height: 8px; border-radius: 50%; background: #cbd5e1; } |
| .status-dot.active { background: #22c55e; } |
| |
| |
| .slide-counter { |
| position: absolute; |
| top: 20px; |
| right: 20px; |
| background: rgba(0,0,0,0.7); |
| color: white; |
| padding: 8px 16px; |
| border-radius: 20px; |
| font-size: 14px; |
| font-weight: 600; |
| } |
| |
| .preview.fullscreen .slide-counter { display: none !important; } |
| </style> |
| </head> |
| <body> |
| <header>Gerador de VSL Ultra Profissional</header> |
| <div class="container"> |
| <div class="input-group"> |
| <label for="jsonFile">📄 Importar Transcrição (JSON):</label> |
| <input type="file" id="jsonFile" accept=".json" /> |
| </div> |
| <div class="input-group"> |
| <label for="audio">🎤 Importar Narração (Áudio):</label> |
| <input type="file" id="audio" accept="audio/*" /> |
| </div> |
| <div class="input-group"> |
| <audio controls id="player"></audio> |
| </div> |
| <div class="status-bar"> |
| <div class="status-item"> |
| <div id="transcriptionStatus" class="status-dot"></div> |
| <span>Transcrição</span> |
| </div> |
| <div class="status-item"> |
| <div id="audioStatus" class="status-dot"></div> |
| <span>Áudio</span> |
| </div> |
| <div class="status-item"> |
| <div id="syncStatus" class="status-dot"></div> |
| <span>Sincronização</span> |
| </div> |
| </div> |
| <div class="input-group"> |
| <div class="button-group"> |
| <button onclick="startSync()" id="startButton" disabled>▶️ Iniciar Apresentação</button> |
| <button onclick="toggleFullscreen()" id="fullscreenButton">🔲 Modo Tela Cheia</button> |
| <button onclick="toggle1080p()" id="hdButton">📺 Modo 1080p</button> |
| </div> |
| </div> |
| <div class="preview" id="slidePreview"> |
| <p>Carregue a transcrição e o áudio para começar...</p> |
| <div class="slide-counter" id="slideCounter" style="display: none;">1 / 1</div> |
| </div> |
| </div> |
|
|
| <script> |
| const player = document.getElementById("player"); |
| const preview = document.getElementById("slidePreview").querySelector("p"); |
| const slideCounter = document.getElementById("slideCounter"); |
| const transcriptionStatus = document.getElementById("transcriptionStatus"); |
| const audioStatus = document.getElementById("audioStatus"); |
| const syncStatus = document.getElementById("syncStatus"); |
| const startButton = document.getElementById("startButton"); |
| let slides = []; |
| let audioReady = false; |
| let isPlaying = false; |
| let hasStarted = false; |
| let currentSlideIndex = 0; |
| |
| |
| function escapeHtml(str) { |
| return String(str).replace(/[&<>"']/g, (m) => ({ |
| '&': '&', '<': '<', '>': '>', '"': '"', "'": ''' |
| })[m]); |
| } |
| |
| const importantWords = new Set([ |
| 'DOR','PESO','VERGONHA','TRISTEZA','SOFRIMENTO','CULPA','FRACASSO','LIMITE','BALANÇA','FOME','FRACA','TRANCAR','ROUBEI','FUGIR','TRISTE','COMPULSÃO','AUTOESTIMA','REALIDADE','ESCONDER','MENTIRA','DIETAS','EFEITO SANFONA','EXCESSO','RESULTADO','ESFORÇO','ENERGIA','MUDANÇA','ESCOLHA','RENASCIMENTO','ESPERANÇA','CONQUISTA','TRANSFORMAÇÃO','CORAGEM','SOLUÇÃO','FUNCIONA','EMAGRECER','ALIMENTAR','VIDA','SAÚDE','LIBERDADE','VERDADE','MILAGRE','EMOÇÃO','SUPORTE','JEJUM','PERSONAL','METABOLISMO','SAUDÁVEL','LEVE','SE OLHAR','VER','SORRIR','ORGULHO','MERECE','BRASILEIRA','REAL','CAMINHO','PASSO','PLANO','REFEIÇÕES','PRAIA','ESCONDERIJO','CONSCIÊNCIA','RESPOSTA','DESAFIO','COBRIR','RECOMEÇO','REJEIÇÃO','RECOMPENSA','DECISÃO','ACEITAÇÃO','VITÓRIA','SUPERAÇÃO','MOTIVAÇÃO','PROGRESSO','MUDOU MINHA VIDA','TRANSFORMOU MEU CORPO','NADA DAVA CERTO','DESISTIR','SOZINHA','ANSIEDADE','INSEGURANÇA','35 ANOS','9,3 QUILOS','20 QUILOS','2 QUILOS','55 CENTAVOS','53 CENTAVOS','R$55','R$197','PARABÉNS','CETOX','PERDER','QUEIMANDO','DELICIOSAS','EXTREMAMENTE','RESPONSABILIDADE','ATENÇÃO','URGENTE','INDISPONÍVEL','MANTER','PERDER','DESCOBRI','ÚNICA','ARMADURA','EFEITO','SANFONA','CERTEZA','GARANTIR','PESO','SEMPRE','FESTAS','VIAJANDO','CAÓTICAS','MÃOS','SEU','PARA','SEMPRE','COMER','ANIVERSÁRIOS','SABOTAR','TUDO','ATERRORIZAVA','NOITES','PROCESSO','EMAGRECIMENTO','ADIANTARIA','RECUPERAR','MESES','MULHERES','BRASILEIRAS','DIETA','PESQUISA','UNIVERSIDADE','SÃO','PAULO','BALANÇA','SUBIR','RESISTIR','MOMENTOS','CONTROLE','TPM','ESTRESSE','VIAGENS','ALMOÇOS','FAMÍLIA','RESPOSTA','DURA','MAIORIA','DIETAS','FRACASSA','AGORA','EMAGRECER','CORPO','LUTAR','NOVO','DIMINUI','HORMÔNIOS','SACIEDADE','AUMENTA','FOME','DERRUBA','ENERGIA','SABOTA','SILENCIOSAMENTE','NOME','HORMONAL','HISTÓRIA','MUDAR','PERSPECTIVA','CELEBRANDO','PRIMEIRA','CONQUISTA','HAVIA','PERDIDO','PREOCUPAR','ASSUSTADOR','ENGORDAR','MAIS','PESO','SENSAÇÃO','FRACASSO','ESPELHO','TRANSFORMAÇÃO','VERGONHA','31','12,3','QUILOS','18','97%' |
| ]); |
| |
| const veryImportantWords = new Set([ |
| 'NUNCA MAIS','DEFINITIVAMENTE','LIBERTAR','INSUPORTÁVEL','INSEGURANÇA','TRANSFORMAÇÃO','MUDAR TUDO','A VERDADE','É AGORA','MOMENTO','DECISÃO','A RESPOSTA','ME LIBERTAR','O COMEÇO','TUDO','NADA','MUDA','CULPA','MERECE','SIMPLES','ACESSÍVEL','HOJE','ÚLTIMA','AGORA','NOVA','LIBERDADE','CAMINHO CERTO','NUNCA MAIS VOU VIVER ASSIM','EU ROUBEI','GORDA','TRAVEI','GORDINHA','PAZ','SÓ QUEM SENTE SABE','NÃO AGUENTO MAIS','CHEGA','ACORDEI','EU DECIDI','FOI NAQUELE MOMENTO','SONHO','ALÉM','POSSÍVEL','REAL','ME VI NO ESPELHO','EMOCIONEI','MUDOU','VIREI','NUNCA','MAIS','PERDER','TUDO','ATERRORIZAVA','FRACASSA','REPROGRAMAR','ATERRORIZANTE','ENGANAR','MEMÓRIA','EXCLUSIVA','VIDA','ESTILO','DOERAM','DOR','SOCO','SEMPRE','CERTEZA','ABSOLUTA','CONTROLE','TOTAL','LIBERDADE','SEGURANÇA','CONFIANÇA','PAZ','TRANSFORMAÇÃO','BLINDADA','PROMESSA','CHANCE','ÚNICA','DECIDE','AGORA','HOJE','GARANTIA','GRATUITOS','INVESTIR','URGENTE','INDISPONÍVEL','RESPONSABILIDADE','EXTREMAMENTE','MANTER','SEU','ATENÇÃO','REALIDADE','FRACASSO','JUREI','OUTRA','PESSOA','ENGANAR','DESCOBRI','BÔNUS','GRATUITO','ECONOMIZANDO','PAGANDO','REALIDADES','DIFERENTES','INTELIGENTE','HESITAR','ARRISCAR','CONTADOR','EXPIRA','TEMPO','DEIXE','CLIQUE','ESPERO','LADO','DECISÃO','INTELIGENTE','BEM-VINDA','VIDA','NOVA','COMEÇA' |
| ]); |
| |
| function marcarPalavras(texto) { |
| return texto.split(/(\s+)/).map(token => { |
| const clean = token.normalize('NFD').replace(/\p{Diacritic}/gu, '').replace(/[.,!?;:\"\"\”\“]/g, '').toUpperCase(); |
| const safe = escapeHtml(token); |
| if (veryImportantWords.has(clean)) return `<span class='bold-red'>${safe}</span>`; |
| if (importantWords.has(clean)) return `<span class='bold-black'>${safe}</span>`; |
| return safe; |
| }).join(''); |
| } |
| |
| function updateSlideDisplay(slideIndex) { |
| if (slides.length === 0) return; |
| currentSlideIndex = Math.max(0, Math.min(slideIndex, slides.length - 1)); |
| preview.innerHTML = slides[currentSlideIndex].text; |
| slideCounter.textContent = `${currentSlideIndex + 1} / ${slides.length}`; |
| } |
| |
| function findCurrentSlideIndex(currentTime) { |
| let index = 0; |
| for (let i = 0; i < slides.length; i++) { |
| if (currentTime >= slides[i].start) { |
| index = i; |
| } else { break; } |
| } |
| return index; |
| } |
| |
| document.getElementById("jsonFile").addEventListener("change", function () { |
| const reader = new FileReader(); |
| reader.onload = function () { |
| try { |
| const data = JSON.parse(reader.result); |
| slides = []; |
| let bloco = [], lastStart = 0; |
| |
| for (let i = 0; i < data.words.length; i++) { |
| const w = data.words[i]; |
| const word = { start: w.start, end: w.end, word: escapeHtml(w.word) }; |
| if (bloco.length === 0) lastStart = word.start; |
| bloco.push(word); |
| const isLast = i === data.words.length - 1; |
| const nextWord = i < data.words.length - 1 ? data.words[i + 1] : null; |
| const longPause = nextWord && (nextWord.start - w.end > 0.7); |
| const isEnd = /[.!?]/.test(w.word); |
| const isPausaIntencional = longPause || isEnd; |
| const criarNovoSlide = isLast || (isPausaIntencional && bloco.length >= 1) || (!isPausaIntencional && bloco.length >= 12); |
| |
| if (criarNovoSlide) { |
| if (bloco.length === 1 && !isPausaIntencional && !isLast) continue; |
| const palavras = bloco.map(b => b.word); |
| if (palavras.length > 0) { |
| const first = palavras[0]; |
| palavras[0] = first.charAt(0).toUpperCase() + first.slice(1); |
| } |
| slides.push({ text: marcarPalavras(palavras.join(" ").trim()) + '...', start: lastStart }); |
| bloco = []; |
| } |
| } |
| if (slides.length > 0) { |
| updateSlideDisplay(0); |
| slideCounter.style.display = 'block'; |
| transcriptionStatus.classList.add("active"); |
| } |
| updateStartButton(); |
| } catch (error) { |
| alert("Erro ao processar o arquivo JSON. Verifique se o formato está correto."); |
| console.error(error); |
| } |
| } |
| reader.readAsText(this.files[0]); |
| }); |
| |
| document.getElementById("audio").addEventListener("change", function () { |
| player.src = URL.createObjectURL(this.files[0]); |
| audioReady = true; |
| audioStatus.classList.add("active"); |
| updateStartButton(); |
| }); |
| |
| function updateStartButton() { |
| const canStart = audioReady && slides.length > 0; |
| startButton.disabled = !canStart; |
| if (canStart && !syncStatus.classList.contains("active")) { |
| syncStatus.classList.add("active"); |
| } |
| } |
| |
| function toggleFullscreen() { |
| const previewEl = document.getElementById("slidePreview"); |
| previewEl.classList.toggle("fullscreen"); |
| previewEl.classList.remove("hd"); |
| if (document.fullscreenElement) { |
| document.exitFullscreen(); |
| } else { |
| previewEl.requestFullscreen(); |
| } |
| } |
| |
| function toggle1080p() { |
| const previewEl = document.getElementById("slidePreview"); |
| previewEl.classList.add("fullscreen"); |
| previewEl.classList.toggle("hd"); |
| if (!document.fullscreenElement) { |
| previewEl.requestFullscreen(); |
| } |
| } |
| |
| function startSync() { |
| if (!audioReady || slides.length === 0) { |
| alert("Por favor, importe o áudio e a transcrição primeiro."); |
| return; |
| } |
| |
| |
| if (hasStarted) { |
| if (player.paused) { player.play(); } else { player.pause(); } |
| return; |
| } |
| |
| |
| const countdown = 5; |
| let timeLeft = countdown; |
| preview.innerHTML = `Iniciando em ${timeLeft}...`; |
| startButton.disabled = true; |
| |
| const timer = setInterval(() => { |
| timeLeft--; |
| if (timeLeft > 0) { |
| preview.innerHTML = `Iniciando em ${timeLeft}...`; |
| } else { |
| clearInterval(timer); |
| player.currentTime = 0; |
| player.playbackRate = 1.08; |
| hasStarted = true; |
| player.play(); |
| } |
| }, 1000); |
| } |
| |
| function syncSlides() { |
| if (!isPlaying || slides.length === 0) return; |
| const newSlideIndex = findCurrentSlideIndex(player.currentTime); |
| if (newSlideIndex !== currentSlideIndex) { |
| updateSlideDisplay(newSlideIndex); |
| } |
| requestAnimationFrame(syncSlides); |
| } |
| |
| |
| player.addEventListener('play', function() { |
| if (slides.length > 0) { |
| isPlaying = true; |
| startButton.innerHTML = "⏸️ Pausar Apresentação"; |
| startButton.disabled = false; |
| syncSlides(); |
| } |
| }); |
| |
| player.addEventListener('pause', function() { |
| isPlaying = false; |
| startButton.innerHTML = "▶️ Continuar Apresentação"; |
| }); |
| |
| player.addEventListener('ended', function() { |
| isPlaying = false; |
| hasStarted = false; |
| startButton.innerHTML = "▶️ Iniciar Apresentação"; |
| }); |
| |
| |
| player.addEventListener('seeked', function() { |
| if (slides.length > 0) { |
| const newSlideIndex = findCurrentSlideIndex(player.currentTime); |
| updateSlideDisplay(newSlideIndex); |
| } |
| }); |
| |
| |
| document.addEventListener('keydown', function(e) { |
| if (e.key === 'Escape') { |
| const previewEl = document.getElementById("slidePreview"); |
| previewEl.classList.remove("fullscreen", "hd"); |
| if (document.fullscreenElement) { document.exitFullscreen(); } |
| } else if (e.key === ' ') { |
| |
| if (audioReady && slides.length > 0) { |
| e.preventDefault(); |
| if (!hasStarted) { startSync(); } else { if (player.paused) player.play(); else player.pause(); } |
| } |
| } else if (e.key === 'ArrowLeft' && slides.length > 0) { |
| e.preventDefault(); |
| if (player.currentTime > 1) { player.currentTime = Math.max(0, player.currentTime - 5); } |
| } else if (e.key === 'ArrowRight' && slides.length > 0) { |
| e.preventDefault(); |
| player.currentTime = Math.min(player.duration || player.currentTime + 5, player.currentTime + 5); |
| } |
| }, { capture: true }); |
| |
| |
| updateStartButton(); |
| </script> |
| </body> |
| </html> |
|
|