|
|
<!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> |
|
|
|