anycoder-e75ff2e1 / index.html
faelfernandes's picture
Upload folder using huggingface_hub
f47aedc verified
<!DOCTYPE html>
<html lang="pt-BR">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>CriaVideo AI - Construtor de Anúncios 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">
<script src="https://cdnjs.cloudflare.com/ajax/libs/tone/14.8.49/Tone.js"></script>
<style>
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@300;400;600;700&family=Space+Grotesk:wght@500;700&display=swap');
:root {
--neon-blue: #00f3ff;
--neon-purple: #bc13fe;
--dark-bg: #0a0a0f;
--panel-bg: #13131f;
}
body {
font-family: 'Inter', sans-serif;
background-color: var(--dark-bg);
color: #e2e8f0;
overflow-x: hidden;
}
h1, h2, h3, .brand-font {
font-family: 'Space Grotesk', sans-serif;
}
/* Custom Scrollbar */
::-webkit-scrollbar {
width: 8px;
}
::-webkit-scrollbar-track {
background: var(--dark-bg);
}
::-webkit-scrollbar-thumb {
background: #334155;
border-radius: 4px;
}
::-webkit-scrollbar-thumb:hover {
background: #475569;
}
/* Animations */
@keyframes gradient-xy {
0%, 100% { background-position: 0% 50%; }
50% { background-position: 100% 50%; }
}
.animate-gradient {
background-size: 200% 200%;
animation: gradient-xy 6s ease infinite;
}
.glass-panel {
background: rgba(19, 19, 31, 0.7);
backdrop-filter: blur(12px);
border: 1px solid rgba(255, 255, 255, 0.08);
box-shadow: 0 4px 30px rgba(0, 0, 0, 0.3);
}
.step-active {
border-color: var(--neon-blue);
box-shadow: 0 0 15px rgba(0, 243, 255, 0.2);
}
.step-completed {
border-color: #10b981;
}
/* Loading Spinner */
.loader {
width: 48px;
height: 48px;
border: 5px solid #FFF;
border-bottom-color: var(--neon-purple);
border-radius: 50%;
display: inline-block;
box-sizing: border-box;
animation: rotation 1s linear infinite;
}
@keyframes rotation {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
.typing-cursor::after {
content: '|';
animation: blink 1s step-start infinite;
}
@keyframes blink {
50% { opacity: 0; }
}
/* Range Slider Styling */
input[type=range] {
-webkit-appearance: none;
background: transparent;
}
input[type=range]::-webkit-slider-thumb {
-webkit-appearance: none;
height: 16px;
width: 16px;
border-radius: 50%;
background: var(--neon-blue);
cursor: pointer;
margin-top: -6px;
}
input[type=range]::-webkit-slider-runnable-track {
width: 100%;
height: 4px;
cursor: pointer;
background: #334155;
border-radius: 2px;
}
</style>
</head>
<body class="min-h-screen flex flex-col">
<!-- Navbar -->
<nav class="border-b border-gray-800 bg-black/50 backdrop-blur-md sticky top-0 z-50">
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
<div class="flex items-center justify-between h-16">
<div class="flex items-center gap-3">
<div class="w-8 h-8 rounded bg-gradient-to-tr from-cyan-500 to-purple-600 flex items-center justify-center">
<i class="fa-solid fa-bolt text-white text-sm"></i>
</div>
<span class="text-xl font-bold tracking-tight text-white">Cria<span class="text-cyan-400">Video</span> AI</span>
</div>
<div class="flex items-center gap-4">
<button onclick="openSettings()" class="text-gray-400 hover:text-white transition-colors">
<i class="fa-solid fa-gear"></i> Configurações
</button>
<a href="https://huggingface.co/spaces/akhaliq/anycoder" target="_blank" class="text-xs bg-gray-800 hover:bg-gray-700 px-3 py-1.5 rounded-full text-gray-300 transition-colors border border-gray-700">
Built with <span class="text-cyan-400 font-semibold">anycoder</span>
</a>
</div>
</div>
</div>
</nav>
<!-- Main Content -->
<main class="flex-grow p-6 max-w-7xl mx-auto w-full grid grid-cols-1 lg:grid-cols-12 gap-8">
<!-- Left Sidebar: Steps -->
<div class="lg:col-span-3 space-y-4">
<div class="glass-panel rounded-xl p-6 sticky top-24">
<h3 class="text-sm font-semibold text-gray-400 uppercase tracking-wider mb-4">Progresso</h3>
<div class="space-y-4 relative">
<!-- Connecting Line -->
<div class="absolute left-3.5 top-2 bottom-2 w-0.5 bg-gray-800 -z-10"></div>
<!-- Step 1 -->
<div id="step-indicator-1" class="flex items-center gap-3 p-3 rounded-lg border border-transparent bg-gray-800/50 step-active transition-all">
<div class="w-7 h-7 rounded-full bg-cyan-500/20 text-cyan-400 flex items-center justify-center text-xs font-bold border border-cyan-500/50">1</div>
<div>
<p class="font-medium text-sm">Roteiro</p>
<p class="text-xs text-gray-500">Copy & Story</p>
</div>
</div>
<!-- Step 2 -->
<div id="step-indicator-2" class="flex items-center gap-3 p-3 rounded-lg border border-transparent opacity-50 transition-all">
<div class="w-7 h-7 rounded-full bg-gray-700 text-gray-400 flex items-center justify-center text-xs font-bold border border-gray-600">2</div>
<div>
<p class="font-medium text-sm">Narração</p>
<p class="text-xs text-gray-500">Voz & Áudio</p>
</div>
</div>
<!-- Step 3 -->
<div id="step-indicator-3" class="flex items-center gap-3 p-3 rounded-lg border border-transparent opacity-50 transition-all">
<div class="w-7 h-7 rounded-full bg-gray-700 text-gray-400 flex items-center justify-center text-xs font-bold border border-gray-600">3</div>
<div>
<p class="font-medium text-sm">Legendas</p>
<p class="text-xs text-gray-500">Transcrição</p>
</div>
</div>
<!-- Step 4 -->
<div id="step-indicator-4" class="flex items-center gap-3 p-3 rounded-lg border border-transparent opacity-50 transition-all">
<div class="w-7 h-7 rounded-full bg-gray-700 text-gray-400 flex items-center justify-center text-xs font-bold border border-gray-600">4</div>
<div>
<p class="font-medium text-sm">Cenas</p>
<p class="text-xs text-gray-500">Imagens AI</p>
</div>
</div>
<!-- Step 5 -->
<div id="step-indicator-5" class="flex items-center gap-3 p-3 rounded-lg border border-transparent opacity-50 transition-all">
<div class="w-7 h-7 rounded-full bg-gray-700 text-gray-400 flex items-center justify-center text-xs font-bold border border-gray-600">5</div>
<div>
<p class="font-medium text-sm">Render</p>
<p class="text-xs text-gray-500">Vídeo Final</p>
</div>
</div>
</div>
</div>
</div>
<!-- Center: Workspace -->
<div class="lg:col-span-9 space-y-6">
<!-- STEP 1: ROTEIRO -->
<div id="step-1" class="glass-panel rounded-xl p-8 border-t-4 border-cyan-500">
<div class="flex justify-between items-start mb-6">
<div>
<h2 class="text-2xl font-bold text-white mb-1">1. Criação do Roteiro</h2>
<p class="text-gray-400 text-sm">Defina o conceito ou deixe a IA criar para você.</p>
</div>
<div class="flex gap-2">
<button onclick="setScriptMode('manual')" id="btn-manual" class="px-4 py-2 rounded-lg bg-gray-700 text-white text-sm hover:bg-gray-600 transition">Manual</button>
<button onclick="setScriptMode('ai')" id="btn-ai" class="px-4 py-2 rounded-lg bg-cyan-600 text-white text-sm hover:bg-cyan-500 transition shadow-lg shadow-cyan-500/20">Gerar com IA</button>
</div>
</div>
<!-- AI Generation Inputs -->
<div id="ai-inputs" class="space-y-4 mb-6 animate-fade-in">
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
<div>
<label class="block text-xs font-medium text-gray-400 mb-1">Produto/Serviço</label>
<input type="text" id="product-input" placeholder="Ex: Curso de Marketing Digital" class="w-full bg-gray-900 border border-gray-700 rounded-lg px-4 py-2 text-white focus:border-cyan-500 focus:ring-1 focus:ring-cyan-500 outline-none transition">
</div>
<div>
<label class="block text-xs font-medium text-gray-400 mb-1">Público Alvo</label>
<input type="text" id="audience-input" placeholder="Ex: Empreendedores iniciantes" class="w-full bg-gray-900 border border-gray-700 rounded-lg px-4 py-2 text-white focus:border-cyan-500 focus:ring-1 focus:ring-cyan-500 outline-none transition">
</div>
</div>
<div>
<label class="block text-xs font-medium text-gray-400 mb-1">Tom de Voz</label>
<select id="tone-select" class="w-full bg-gray-900 border border-gray-700 rounded-lg px-4 py-2 text-white focus:border-cyan-500 outline-none">
<option value="profissional">Profissional & Sério</option>
<option value="entusiasmado">Entusiasmado & Energético</option>
<option value="dramatico">Storytelling Dramático</option>
<option value="vsl">VSL (Video Sales Letter) Direto</option>
</select>
</div>
<div class="flex items-center gap-4">
<div class="flex-1">
<label class="block text-xs font-medium text-gray-400 mb-1">Duração Estimada: <span id="duration-display" class="text-cyan-400">30s</span></label>
<input type="range" id="duration-slider" min="15" max="90" value="30" step="15" class="w-full h-2 bg-gray-700 rounded-lg appearance-none cursor-pointer">
</div>
</div>
<button onclick="generateScript()" class="w-full bg-gradient-to-r from-cyan-600 to-blue-600 hover:from-cyan-500 hover:to-blue-500 text-white font-bold py-3 rounded-lg transition-all transform hover:scale-[1.01] flex items-center justify-center gap-2">
<i class="fa-solid fa-wand-magic-sparkles"></i> Gerar Roteiro com IA
</button>
</div>
<!-- Editor -->
<div class="relative">
<label class="block text-xs font-medium text-gray-400 mb-1">Roteiro (Copy)</label>
<textarea id="script-editor" rows="8" class="w-full bg-gray-900 border border-gray-700 rounded-lg p-4 text-white font-mono text-sm focus:border-cyan-500 outline-none resize-none" placeholder="Cole seu roteiro aqui ou gere com IA..."></textarea>
<div class="absolute bottom-3 right-3 text-xs text-gray-500">
<span id="char-count">0</span> chars | ~<span id="word-time">0</span>s
</div>
</div>
<div class="mt-6 flex justify-end">
<button onclick="nextStep(2)" class="bg-white text-black font-bold py-2 px-6 rounded-lg hover:bg-gray-200 transition flex items-center gap-2">
Próximo: Narração <i class="fa-solid fa-arrow-right"></i>
</button>
</div>
</div>
<!-- STEP 2: NARRAÇÃO -->
<div id="step-2" class="glass-panel rounded-xl p-8 border-t-4 border-purple-500 hidden">
<h2 class="text-2xl font-bold text-white mb-6">2. Geração de Narração</h2>
<div class="grid grid-cols-1 md:grid-cols-3 gap-6 mb-8">
<div class="bg-gray-800/50 p-4 rounded-lg border border-gray-700 hover:border-purple-500 cursor-pointer transition group" onclick="selectVoiceProvider('elevenlabs')">
<div class="flex items-center justify-between mb-2">
<span class="font-bold text-purple-400">ElevenLabs</span>
<i class="fa-solid fa-check-circle text-gray-600 group-hover:text-purple-500"></i>
</div>
<p class="text-xs text-gray-400">Qualidade Ultra-Realista. Melhor para VSLs.</p>
</div>
<div class="bg-gray-800/50 p-4 rounded-lg border border-gray-700 hover:border-purple-500 cursor-pointer transition group" onclick="selectVoiceProvider('azure')">
<div class="flex items-center justify-between mb-2">
<span class="font-bold text-blue-400">Azure TTS</span>
<i class="fa-solid fa-check-circle text-gray-600 group-hover:text-blue-500"></i>
</div>
<p class="text-xs text-gray-400">Estável e rápido. Ótimo custo-benefício.</p>
</div>
<div class="bg-gray-800/50 p-4 rounded-lg border border-gray-700 hover:border-purple-500 cursor-pointer transition group" onclick="selectVoiceProvider('minimax')">
<div class="flex items-center justify-between mb-2">
<span class="font-bold text-green-400">MiniMax</span>
<i class="fa-solid fa-check-circle text-gray-600 group-hover:text-green-500"></i>
</div>
<p class="text-xs text-gray-400">Modelo novo com boa entonação.</p>
</div>
</div>
<div class="bg-gray-900 rounded-lg p-6 border border-gray-800">
<div class="flex items-center justify-between mb-4">
<h3 class="font-semibold text-white">Processamento de Blocos</h3>
<span class="text-xs bg-purple-900/50 text-purple-300 px-2 py-1 rounded border border-purple-700">Modo: Chunking Inteligente</span>
</div>
<div id="audio-progress-container" class="space-y-3">
<!-- Generated via JS -->
<div class="text-center py-8 text-gray-500">
<p>Aguardando início...</p>
</div>
</div>
<div class="mt-6 flex justify-center">
<button onclick="generateAudio()" id="btn-generate-audio" class="bg-purple-600 hover:bg-purple-500 text-white font-bold py-3 px-8 rounded-full shadow-lg shadow-purple-500/30 transition-all flex items-center gap-2">
<i class="fa-solid fa-microphone"></i> Sintetizar Voz
</button>
</div>
</div>
<div class="mt-6 flex justify-between">
<button onclick="prevStep(1)" class="text-gray-400 hover:text-white font-medium py-2 px-4">Voltar</button>
<button onclick="nextStep(3)" id="btn-next-3" class="bg-white text-black font-bold py-2 px-6 rounded-lg hover:bg-gray-200 transition opacity-50 cursor-not-allowed" disabled>
Próximo: Legendas <i class="fa-solid fa-arrow-right"></i>
</button>
</div>
</div>
<!-- STEP 3: LEGENDAS -->
<div id="step-3" class="glass-panel rounded-xl p-8 border-t-4 border-yellow-500 hidden">
<h2 class="text-2xl font-bold text-white mb-2">3. Transcrição & Legendas</h2>
<p class="text-gray-400 text-sm mb-6">Transcrevendo o áudio com precisão de palavra (Whisper).</p>
<div class="bg-black rounded-lg p-1 border border-gray-800 relative overflow-hidden h-64 flex items-center justify-center" id="waveform-container">
<canvas id="audio-visualizer" class="absolute inset-0 w-full h-full"></canvas>
<div id="transcribing-text" class="relative z-10 text-center hidden">
<div class="loader mb-4"></div>
<p class="text-yellow-400 font-mono animate-pulse">Analisando áudio...</p>
</div>
</div>
<div class="mt-4 bg-gray-900 rounded-lg p-4 border border-gray-800 h-48 overflow-y-auto font-mono text-sm text-gray-300" id="transcription-preview">
<!-- Preview of SRT/JSON format -->
<span class="text-gray-600 italic">A transcrição aparecerá aqui...</span>
</div>
<div class="mt-6 flex justify-between">
<button onclick="prevStep(2)" class="text-gray-400 hover:text-white font-medium py-2 px-4">Voltar</button>
<button onclick="transcribeAudio()" id="btn-transcribe" class="bg-yellow-600 hover:bg-yellow-500 text-white font-bold py-2 px-6 rounded-lg transition">
<i class="fa-solid fa-closed-captioning"></i> Gerar Legendas
</button>
<button onclick="nextStep(4)" id="btn-next-4" class="hidden bg-white text-black font-bold py-2 px-6 rounded-lg hover:bg-gray-200 transition">
Próximo: Cenas <i class="fa-solid fa-arrow-right"></i>
</button>
</div>
</div>
<!-- STEP 4: IMAGENS -->
<div id="step-4" class="glass-panel rounded-xl p-8 border-t-4 border-pink-500 hidden">
<div class="flex justify-between items-center mb-6">
<div>
<h2 class="text-2xl font-bold text-white">4. Construção de Cenas</h2>
<p class="text-gray-400 text-sm">Gerando imagens contextuais a cada 3 segundos.</p>
</div>
<div class="text-right">
<p class="text-xs text-gray-500 uppercase">Modelo Selecionado</p>
<p class="text-pink-400 font-bold">Nano Banana (SDXL)</p>
</div>
</div>
<div class="grid grid-cols-2 md:grid-cols-4 gap-4" id="scene-grid">
<!-- Scenes generated here -->
</div>
<div class="mt-8 p-4 bg-gray-800/50 rounded-lg border border-gray-700">
<h4 class="text-sm font-bold text-white mb-2"><i class="fa-solid fa-lightbulb text-yellow-400"></i> Contexto Visual Atual</h4>
<p class="text-xs text-gray-400">O sistema analisou o roteiro e determinou o tema: <span id="visual-theme" class="text-cyan-400 font-mono">[Tema não definido]</span>.</p>
</div>
<div class="mt-6 flex justify-between">
<button onclick="prevStep(3)" class="text-gray-400 hover:text-white font-medium py-2 px-4">Voltar</button>
<button onclick="nextStep(5)" class="bg-white text-black font-bold py-2 px-6 rounded-lg hover:bg-gray-200 transition">
Finalizar Vídeo <i class="fa-solid fa-film"></i>
</button>
</div>
</div>
<!-- STEP 5: RENDER -->
<div id="step-5" class="glass-panel rounded-xl p-8 border-t-4 border-green-500 hidden">
<h2 class="text-2xl font-bold text-white mb-6 text-center">5. Renderização Final</h2>
<div class="max-w-2xl mx-auto bg-black rounded-xl overflow-hidden border border-gray-800 shadow-2xl relative aspect-video group">
<!-- Video Player Simulation -->
<div id="video-player" class="w-full h-full flex items-center justify-center bg-gray-900 relative">
<img id="render-preview-img" src="" class="absolute inset-0 w-full h-full object-cover opacity-50 transition-opacity duration-300">
<div class="absolute inset-0 bg-gradient-to-t from-black/80 to-transparent"></div>
<!-- Playback Controls Overlay -->
<div class="absolute bottom-0 left-0 right-0 p-4 z-20">
<div class="w-full bg-gray-700 h-1 rounded-full mb-4 overflow-hidden">
<div id="progress-bar" class="bg-green-500 h-full w-0 transition-all duration-100"></div>
</div>
<div class="flex justify-between items-center text-white">
<div class="flex gap-4">
<button id="play-pause-btn" class="hover:text-green-400"><i class="fa-solid fa-play"></i></button>
<span class="text-xs font-mono mt-1">00:00 / <span id="total-duration">00:00</span></span>
</div>
<div class="flex gap-4">
<button class="hover:text-green-400"><i class="fa-solid fa-closed-captioning"></i></button>
<button class="hover:text-green-400"><i class="fa-solid fa-expand"></i></button>
</div>
</div>
</div>
<!-- Subtitle Overlay -->
<div class="absolute bottom-16 left-0 right-0 text-center px-8 z-10 pointer-events-none">
<p id="video-subtitle" class="text-white text-lg md:text-2xl font-bold drop-shadow-md stroke-black" style="text-shadow: 2px 2px 0 #000, -1px -1px 0 #000, 1px -1px 0 #000, -1px 1px 0 #000, 1px 1px 0 #000;">
Legendas aparecerão aqui
</p>
</div>
<!-- Center Play Button (Initial State) -->
<button id="center-play-btn" class="absolute z-30 w-16 h-16 bg-white/20 backdrop-blur rounded-full flex items-center justify-center hover:scale-110 transition text-white">
<i class="fa-solid fa-play text-2xl ml-1"></i>
</button>
</div>
</div>
<div class="mt-8 flex justify-center gap-4">
<button onclick="resetApp()" class="px-6 py-3 rounded-lg border border-gray-600 text-gray-300 hover:bg-gray-800 transition">Novo Projeto</button>
<button class="px-6 py-3 rounded-lg bg-green-600 hover:bg-green-500 text-white font-bold shadow-lg shadow-green-500/20 transition flex items-center gap-2">
<i class="fa-solid fa-download"></i> Baixar MP4 (Simulado)
</button>
</div>
</div>
</div>
</main>
<!-- Settings Modal -->
<div id="settings-modal" class="fixed inset-0 bg-black/80 backdrop-blur-sm z-[100] hidden flex items-center justify-center">
<div class="bg-gray-900 border border-gray-700 rounded-xl w-full max-w-lg p-6 shadow-2xl transform transition-all scale-100">
<div class="flex justify-between items-center mb-6">
<h3 class="text-xl font-bold text-white">Configurações de API</h3>
<button onclick="closeSettings()" class="text-gray-400 hover:text-white"><i class="fa-solid fa-xmark text-xl"></i></button>
</div>
<div class="space-y-4">
<div>
<label class="block text-sm font-medium text-gray-300 mb-1">OpenRouter API Key</label>
<input type="password" id="api-openrouter" class="w-full bg-black border border-gray-700 rounded px-3 py-2 text-white focus:border-cyan-500 outline-none" placeholder="sk-or-...">
<p class="text-xs text-gray-500 mt-1">Usado para geração de roteiros.</p>
</div>
<div class="border-t border-gray-800 my-4"></div>
<div>
<label class="block text-sm font-medium text-gray-300 mb-1">ElevenLabs API Key</label>
<input type="password" id="api-eleven" class="w-full bg-black border border-gray-700 rounded px-3 py-2 text-white focus:border-purple-500 outline-none">
</div>
<div>
<label class="block text-sm font-medium text-gray-300 mb-1">Azure Speech Key</label>
<input type="password" id="api-azure" class="w-full bg-black border border-gray-700 rounded px-3 py-2 text-white focus:border-blue-500 outline-none">
</div>
</div>
<div class="mt-8 flex justify-end">
<button onclick="saveSettings()" class="bg-cyan-600 hover:bg-cyan-500 text-white px-6 py-2 rounded-lg font-medium transition">Salvar Configurações</button>
</div>
</div>
</div>
<script>
// --- STATE MANAGEMENT ---
const state = {
step: 1,
script: "",
audioUrl: null,
transcription: [], // Array of { text, start, end }
scenes: [], // Array of image URLs
duration: 30,
isPlaying: false
};
// --- MOCK DATA & UTILS ---
const MOCK_IMAGES = [
"https://images.unsplash.com/photo-1557804506-669a67965ba0?w=800&q=80", // Business
"https://images.unsplash.com/photo-1516321318423-f06f85e504b3?w=800&q=80", // Laptop
"https://images.unsplash.com/photo-1556761175-5973dc0f32e7?w=800&q=80", // Meeting
"https://images.unsplash.com/photo-1551288049-bebda4e38f71?w=800&q=80", // Charts
"https://images.unsplash.com/photo-1507679799987-c73779587ccf?w=800&q=80", // Suit
"https://images.unsplash.com/photo-1531297424006-dbfa3e92ea1d?w=800&q=80" // Tech
];
// --- NAVIGATION ---
function nextStep(step) {
document.getElementById(`step-${state.step}`).classList.add('hidden');
document.getElementById(`step-indicator-${state.step}`).classList.remove('step-active');
document.getElementById(`step-indicator-${state.step}`).classList.add('step-completed');
state.step = step;
document.getElementById(`step-${state.step}`).classList.remove('hidden');
document.getElementById(`step-indicator-${state.step}`).classList.remove('opacity-50');
document.getElementById(`step-indicator-${state.step}`).classList.add('step-active');
// Scroll to top
window.scrollTo({ top: 0, behavior: 'smooth' });
}
function prevStep(step) {
document.getElementById(`step-${state.step}`).classList.add('hidden');
document.getElementById(`step-indicator-${state.step}`).classList.add('opacity-50');
document.getElementById(`step-indicator-${state.step}`).classList.remove('step-active');
state.step = step;
document.getElementById(`step-${state.step}`).classList.remove('hidden');
document.getElementById(`step-indicator-${state.step}`).classList.add('step-active');
}
// --- STEP 1: SCRIPT ---
function setScriptMode(mode) {
const btnManual = document.getElementById('btn-manual');
const btnAi = document.getElementById('btn-ai');
const aiInputs = document.getElementById('ai-inputs');
if (mode === 'manual') {
btnManual.classList.replace('bg-gray-700', 'bg-cyan-600');
btnAi.classList.replace('bg-cyan-600', 'bg-gray-700');
aiInputs.classList.add('hidden');
} else {
btnAi.classList.replace('bg-gray-700', 'bg-cyan-600');
btnManual.classList.replace('bg-cyan-600', 'bg-gray-700');
aiInputs.classList.remove('hidden');
}
}
// Character counting
document.getElementById('script-editor').addEventListener('input', (e) => {
const text = e.target.value;
document.getElementById('char-count').innerText = text.length;
// Rough estimate: 130 words per minute. Avg 5 chars per word. ~650 chars per min -> ~11 chars per sec
const seconds = Math.ceil(text.length / 11);
document.getElementById('word-time').innerText = seconds;
});
// Duration Slider
document.getElementById('duration-slider').addEventListener('input', (e) => {
document.getElementById('duration-display').innerText = e.target.value + 's';
state.duration = parseInt(e.target.value);
});
async function generateScript() {
const product = document.getElementById('product-input').value;
const audience = document.getElementById('audience-input').value;
const tone = document.getElementById('tone-select').value;
const btn = document.querySelector('#ai-inputs button');
if (!product || !audience) {
alert("Preencha o produto e público alvo.");
return;
}
const originalText = btn.innerHTML;
btn.innerHTML = `<i class="fa-solid fa-circle-notch fa-spin"></i> Gerando...`;
btn.disabled = true;
// Simulate API Call to OpenRouter
await new Promise(r => setTimeout(r, 2000));
// Mock Content Generation
const hooks = [
"Você está perdendo dinheiro todos os dias sem perceber.",
"Imagine acordar com vendas automáticas na sua conta.",
"O mercado mudou. E quem não adaptar, será engolido."
];
const bodies = [
`O ${product} não é apenas mais um curso. É um sistema validado por ${audience} que já faturaram milhões.`,
`Nós testamos essa estratégia com 500 ${audience}. O resultado? Um aumento de 300% em conversão.`,
`Pare de tentar adivinhar. Use dados. Use inteligência. Use o ${product}.`
];
const closes = [
"Clique no botão abaixo agora e transforme seu resultado.",
"As vagas estão acabando. Garanta a sua agora.",
"Seu futuro começa com uma decisão. Decida agora."
];
const generatedScript = `${hooks[Math.floor(Math.random()*hooks.length)]}\n\n${bodies[Math.floor(Math.random()*bodies.length)]}\n\n${closes[Math.floor(Math.random()*closes.length)]}`;
// Typewriter effect
const editor = document.getElementById('script-editor');
editor.value = "";
let i = 0;
const type = () => {
if (i < generatedScript.length) {
editor.value += generatedScript.charAt(i);
i++;
setTimeout(type, 20);
} else {
btn.innerHTML = originalText;
btn.disabled = false;
// Trigger input event to update counters
editor.dispatchEvent(new Event('input'));
}
};
type();
}
// --- STEP 2: AUDIO ---
function selectVoiceProvider(provider) {
// Visual selection logic
const cards = document.querySelectorAll('#step-2 .grid > div');
cards.forEach(c => c.classList.remove('border-purple-500', 'bg-gray-800'));
cards.forEach(c => c.classList.add('border-gray-700', 'bg-gray-800/50'));
// Find clicked (simplified for demo)
event.currentTarget.classList.remove('border-gray-700', 'bg-gray-800/50');
event.currentTarget.classList.add('border-purple-500', 'bg-gray-800');
}
async function generateAudio() {
const container = document.getElementById('audio-progress-container');
const btn = document.getElementById('btn-generate-audio');
const nextBtn = document.getElementById('btn-next-3');
btn.innerHTML = `<i class="fa-solid fa-circle-notch fa-spin"></i> Processando...`;
btn.disabled = true;
// Simulate chunking
container.innerHTML = '';
const chunks = 4;
for (let i = 1; i <= chunks; i++) {
const div = document.createElement('div');
div.className = "flex items-center gap-3 bg-gray-800 p-3 rounded border border-gray-700";
div.innerHTML = `
<div class="w-2 h-2 rounded-full bg-yellow-500 animate-pulse"></div>
<div class="flex-1">
<div class="flex justify-between text-xs mb-1">
<span class="text-gray-300">Bloco de Áudio ${i}/${chunks}</span>
<span class="text-gray-500">Processando...</span>
</div>
<div class="w-full bg-gray-700 h-1.5 rounded-full overflow-hidden">
<div class="bg-purple-500 h-full w-0 transition-all duration-1000" style="width: 0%" id="prog-${i}"></div>
</div>
</div>
`;
container.appendChild(div);
// Simulate processing time per chunk
await new Promise(r => setTimeout(r, 800));
document.getElementById(`prog-${i}`).style.width = '100%';
div.querySelector('.text-gray-500').innerText = "Concluído";
div.querySelector('.bg-yellow-500').classList.replace('bg-yellow-500', 'bg-green-500');
div.querySelector('.bg-yellow-500').classList.remove('animate-pulse');
}
// Finalize
btn.innerHTML = `<i class="fa-solid fa-check"></i> Áudio Gerado`;
btn.classList.replace('bg-purple-600', 'bg-green-600');
nextBtn.disabled = false;
nextBtn.classList.remove('opacity-50', 'cursor-not-allowed');
// Create a dummy audio URL (Tone.js synth for demo)
// In a real app, this would be the concatenated Blob URL
}
// --- STEP 3: TRANSCRIPTION ---
async function transcribeAudio() {
const btn = document.getElementById('btn-transcribe');
const nextBtn = document.getElementById('btn-next-4');
const loader = document.getElementById('transcribing-text');
const preview = document.getElementById('transcription-preview');
btn.classList.add('hidden');
loader.classList.remove('hidden');
loader.classList.add('flex', 'flex-col', 'items-center');
// Simulate Whisper API delay
await new Promise(r => setTimeout(r, 2500));
// Generate mock transcription data
const scriptText = document.getElementById('script-editor').value;
const words = scriptText.split(' ');
const wordDuration = 0.4; // seconds per word approx
let currentTime = 0;
let srtContent = "";
words.forEach((word, index) => {
const start = currentTime;
const end = currentTime + wordDuration + (Math.random() * 0.2);
state.transcription.push({
word: word,
start: start,
end: end
});
srtContent += `<div class="mb-1"><span class="text-yellow-500">${start.toFixed(2)}s</span> ${word}</div>`;
currentTime = end;
});
loader.classList.add('hidden');
loader.classList.remove('flex');
preview.innerHTML = srtContent;
nextBtn.classList.remove('hidden');
}
// --- STEP 4: IMAGES ---
// Pre-load step 4 visuals when entering
function initStep4() {
const grid = document.getElementById('scene-grid');
grid.innerHTML = '';
// Determine number of scenes based on duration (every 3 seconds)
const numScenes = Math.ceil(state.duration / 3);
const theme = document.getElementById('product-input').value || "Marketing Digital";
document.getElementById('visual-theme').innerText = theme;
for(let i=0; i<numScenes; i++) {
const div = document.createElement('div');
div.className = "relative group aspect-video bg-gray-800 rounded-lg overflow-hidden border border-gray-700";
div.innerHTML = `
<div class="absolute inset-0 flex items-center justify-center bg-gray-900 z-10" id="img-loader-${i}">
<i class="fa-solid fa-image text-gray-600 text-2xl animate-bounce"></i>
</div>
<img id="scene-img-${i}" src="" class="w-full h-full object-cover opacity-0 transition-opacity duration-500">
<div class="absolute bottom-0 left-0 right-0 bg-black/70 p-2 translate-y-full group-hover:translate-y-0 transition-transform">
<button class="text-xs text-white hover:text-cyan-400 w-full text-left"><i class="fa-solid fa-rotate"></i> Regenerar</button>
<button class="text-xs text-white hover:text-red-400 w-full text-left"><i class="fa-solid fa-trash"></i> Remover</button>
</div>
<div class="absolute top-2 right-2 bg-black/50 text-white text-xs px-2 py-1 rounded backdrop-blur">
00:${(i*3).toString().padStart(2,'0')}
</div>
`;
grid.appendChild(div);
// Simulate Image Gen Delay
setTimeout(() => {
const img = document.getElementById(`scene-img-${i}`);
const loader = document.getElementById(`img-loader-${i}`);
// Pick random mock image
img.src = MOCK_IMAGES[Math.floor(Math.random() * MOCK_IMAGES.length)];
img.onload = () => {
img.classList.remove('opacity-0');
loader.classList.add('hidden');
};
state.scenes.push(img.src);
}, 500 + (i * 800));
}
}
// Hook step 4 init
const observer = new MutationObserver((mutations) => {
mutations.forEach((mutation) => {
if (mutation.target.id === 'step-4' && !mutation.target.classList.contains('hidden')) {
initStep4();
}
});
});
observer.observe(document.getElementById('step-4'), { attributes: true, attributeFilter: ['class'] });
// --- STEP 5: PLAYER LOGIC ---
let playbackInterval;
// Hook step 5 init
const observer5 = new MutationObserver((mutations) => {
mutations.forEach((mutation) => {
if (mutation.target.id === 'step-5' && !mutation.target.classList.contains('hidden')) {
initPlayer();
}
});
});
observer5.observe(document.getElementById('step-5'), { attributes: true, attributeFilter: ['class'] });
function initPlayer() {
const playBtn = document.getElementById('center-play-btn');
const playPauseBtn = document.getElementById('play-pause-btn');
playBtn.onclick = togglePlay;
playPauseBtn.onclick = togglePlay;
// Set total duration
const mins = Math.floor(state.duration / 60);
const secs = state.duration % 60;
document.getElementById('total-duration').innerText = `${mins.toString().padStart(2,'0')}:${secs.toString().padStart(2,'0')}`;
// Set first image
if(state.scenes.length > 0) {
document.getElementById('render-preview-img').src = state.scenes[0];
}
}
function togglePlay() {
state.isPlaying = !state.isPlaying;
const icon = state.isPlaying ? '<i class="fa-solid fa-pause"></i>' : '<i class="fa-solid fa-play"></i>';
document.getElementById('play-pause-btn').innerHTML = icon;
document.getElementById('center-play-btn').style.opacity = state.isPlaying ? '0' : '1';
document.getElementById('center-play-btn').style.pointerEvents = state.isPlaying ? 'none' : 'auto';
if (state.isPlaying) {
let currentTime = 0;
const progressBar = document.getElementById('progress-bar');
const subtitleEl = document.getElementById('video-subtitle');
const imgEl = document.getElementById('render-preview-img');
playbackInterval = setInterval(() => {
currentTime += 0.1;
// Update Progress Bar
const pct = (currentTime / state.duration) * 100;
progressBar.style.width = `${pct}%`;
// Update Subtitles
// Find current word
const currentWordObj = state.transcription.find(t => t.start <= currentTime && t.end >= currentTime);
if (currentWordObj) {
subtitleEl.innerText = currentWordObj.word;
} else {
// Simple logic to show phrase if word precision fails (mock)
// In a real app, we map time to phrase
}