const { useState, useRef, useEffect } = React; // ═══════════════════════════════════════════════════════════════════ // API KEYS — paste your keys here when running in browser/artifact // On vozdomeu.com.br these are stored safely in the server .env // ═══════════════════════════════════════════════════════════════════ // Keys are ONLY used in browser/artifact mode (non-production). // On vozdomeu.com.br all calls go through the PHP proxy — no keys needed client-side. const ANTHROPIC_KEY = "PASTE_ANTHROPIC_KEY_HERE"; const ELEVENLABS_KEY = "PASTE_ELEVENLABS_KEY_HERE"; const DID_KEY = "PASTE_DID_KEY_HERE"; // Detect environment — use Flask proxy on production, direct API in browser const IS_PRODUCTION = typeof window !== "undefined" && (window.location.hostname === "vozdomeu.com.br" || window.location.hostname === "www.vozdomeu.com.br" || window.location.hostname === "localhost"); const API_BASE = IS_PRODUCTION ? "/api" : null; // ═══════════════════════════════════════════════════════════════════ // ELEVENLABS VOICE LIBRARY — loaded dynamically from your account // ═══════════════════════════════════════════════════════════════════ const VOICE_LIBRARY = [ { id:"pNInz6obpgDQGcFmaJgB", name:"Adam", desc:"Grave e sereno", gender:"M" }, { id:"ErXwobaYiN019PkySvjV", name:"Antoni", desc:"Suave e carinhoso", gender:"M" }, { id:"VR6AewLTigWG4xSOukaG", name:"Arnold", desc:"Forte e marcante", gender:"M" }, { id:"TxGEqnHWrfWFTfGW9XjX", name:"Josh", desc:"Jovem e acolhedor", gender:"M" }, { id:"21m00Tcm4TlvDq8ikWAM", name:"Rachel", desc:"Suave e maternal", gender:"F" }, { id:"AZnzlk1XvdvUeBnXmlld", name:"Domi", desc:"Firme e presente", gender:"F" }, { id:"EXAVITQu4vr4xnSDxMaL", name:"Bella", desc:"Doce e amorosa", gender:"F" }, { id:"MF3mGyEYCl7XYWbV9V6O", name:"Elli", desc:"Alegre e animada", gender:"F" }, ]; // Fetch real voices from ElevenLabs account async function fetchElevenLabsVoices() { try { if (API_BASE) { // Production — use Flask proxy const r = await fetch(`${API_BASE}/voices`); if (!r.ok) return VOICE_LIBRARY; const d = await r.json(); if (!d.voices?.length) return VOICE_LIBRARY; return d.voices.map(v=>({ id: v.voice_id, name: v.name, desc: v.labels?.description || v.labels?.accent || v.category || "", gender: v.labels?.gender==="female" ? "F" : "M", })); } else { // Browser — direct call (may fail with CORS) if (!ELEVENLABS_KEY || ELEVENLABS_KEY.includes("PASTE")) return VOICE_LIBRARY; const r = await fetch("https://api.elevenlabs.io/v1/voices", { headers:{"xi-api-key":ELEVENLABS_KEY}, }); if (!r.ok) return VOICE_LIBRARY; const d = await r.json(); if (!d.voices?.length) return VOICE_LIBRARY; return d.voices.map(v=>({ id: v.voice_id, name: v.name, desc: v.labels?.description || v.labels?.accent || v.category || "", gender: v.labels?.gender==="female" ? "F" : "M", })); } } catch { return VOICE_LIBRARY; } } // ═══════════════════════════════════════════════════════════════════ // DATA // ═══════════════════════════════════════════════════════════════════ const NAV = [ { id:"home", emoji:"🕯️", label:"Início" }, { id:"memorial", emoji:"💫", label:"Memorial" }, { id:"prece", emoji:"🙏", label:"Prece" }, { id:"oracao", emoji:"✝️", label:"Oração" }, { id:"oracles", emoji:"🔮", label:"Oráculos" }, ]; const STAR_DATA = Array.from({length:60},(_,i)=>({ id:i, x:Math.random()*100, y:Math.random()*100, r:Math.random()*1.2+0.3, delay:Math.random()*6, dur:3+Math.random()*4, })); const INTENTIONS = [ {id:"passe", emoji:"🤲", label:"Passe Espiritual"}, {id:"desencarne", emoji:"🕯️", label:"Ente Desencarnado"}, {id:"reforma", emoji:"🌱", label:"Reforma Íntima"}, {id:"saude", emoji:"✨", label:"Cura e Saúde"}, {id:"familia", emoji:"👨‍👩‍👧", label:"Harmonia Familiar"}, {id:"gratidao", emoji:"🙏", label:"Gratidão"}, {id:"mediunidade",emoji:"🔮", label:"Mediunidade"}, {id:"luz", emoji:"💫", label:"Luz para Espíritos"}, ]; const ORACLE_SERVICES = [ {id:"prayer", emoji:"🙏", label:"Prece Personalizada", cost:5, burn:0.05, desc:"Prece Kardecista com doutrina embarcada"}, {id:"memorial", emoji:"💫", label:"Memorial Completo", cost:10, burn:0.10, desc:"Prece + mensagem canalizada"}, {id:"counsel", emoji:"💬", label:"Orientação Espiritual", cost:2, burn:0.02, desc:"Sessão com orientador erudito"}, {id:"voice", emoji:"🎙️", label:"Voz do Meu", cost:25, burn:0.25, desc:"Voz clonada + vídeo animado"}, {id:"evangelho", emoji:"📖", label:"Evangelho no Lar", cost:15, burn:0.15, desc:"Assinatura mensal semanal"}, {id:"batch", emoji:"⚡", label:"Lote de 10 Preces", cost:40, burn:0.40, desc:"Para centros ou integradores"}, ]; // ═══════════════════════════════════════════════════════════════════ // CRISIS DETECTION // ═══════════════════════════════════════════════════════════════════ const CRISIS_KEYWORDS = [ "suicid","me matar","quero morrer","não quero viver","acabar com tudo", "me juntar","encontrar ela","encontrar ele","encontrar minha mãe","encontrar meu pai", "não tem mais sentido","sem razão de viver","me jogar","tomar remédio demais", "me machucar","acabar com minha vida","não aguento mais","preferir morrer", "ir para o outro lado","ir junto","deixar esse mundo", ]; function detectCrisis(text) { const lower = text.toLowerCase(); return CRISIS_KEYWORDS.some(kw => lower.includes(kw)); } const CRISIS_RESPONSE = `Percebi que você está carregando uma dor muito grande agora. 💙 Antes de qualquer coisa — você não está sozinho(a). O CVV (Centro de Valorização da Vida) atende 24 horas, todos os dias, de forma gratuita e sigilosa: 📞 188 (ligar) 💬 cvv.org.br (chat online) Quem amamos nunca quer que soframos. Eles querem que continuemos aqui, com os que ainda nos amam neste plano. Por favor, fale com alguém agora. 🙏`; const css = ` @import url('https://fonts.googleapis.com/css2?family=Cormorant+Garamond:ital,wght@0,300;0,400;0,600;1,300;1,400&family=Cinzel:wght@400;500&display=swap'); @keyframes twinkle { 0%,100%{opacity:0.05} 50%{opacity:0.5} } @keyframes flicker { 0%,100%{opacity:1} 50%{opacity:0.6} } @keyframes fadeUp { from{opacity:0;transform:translateY(16px)} to{opacity:1;transform:translateY(0)} } @keyframes spin { from{transform:rotate(0deg)} to{transform:rotate(360deg)} } @keyframes glow { 0%,100%{box-shadow:0 0 20px rgba(200,150,80,0.15)} 50%{box-shadow:0 0 40px rgba(200,150,80,0.35)} } @keyframes revealWord { from{opacity:0;transform:translateY(3px)} to{opacity:1;transform:translateY(0)} } * { box-sizing:border-box; margin:0; padding:0; } body { background:#0a0705; } input,textarea,button,select { font-family:'Cormorant Garamond',Georgia,serif; } input::placeholder,textarea::placeholder { color:rgba(180,150,100,0.25); } ::-webkit-scrollbar { width:4px; } ::-webkit-scrollbar-track { background:transparent; } ::-webkit-scrollbar-thumb { background:rgba(180,140,80,0.2); border-radius:2px; } `; // ─── theme colors ─────────────────────────────────────────────── const C = { bg: "#0a0705", bgCard: "rgba(255,240,200,0.03)", bgCard2: "rgba(200,150,80,0.06)", border: "rgba(200,150,80,0.14)", border2: "rgba(200,150,80,0.28)", gold: "#c89640", goldSoft:"#e8c878", goldDim: "rgba(200,150,80,0.5)", text: "#e8dcc8", textDim: "rgba(220,200,160,0.5)", textFaint:"rgba(200,170,120,0.35)", green: "#4a9960", greenBg: "rgba(74,153,96,0.08)", greenBorder:"rgba(74,153,96,0.25)", }; // ═══════════════════════════════════════════════════════════════════ // API HELPERS // ═══════════════════════════════════════════════════════════════════ async function claudeAI(system, user) { if (API_BASE) { // Production — use Flask proxy (no keys in browser) const r = await fetch(`${API_BASE}/claude`, { method:"POST", headers:{"Content-Type":"application/json"}, body:JSON.stringify({system, user}), }); if (!r.ok) { const e=await r.json().catch(()=>({})); throw new Error(e.error || `Erro ${r.status}: ${r.statusText}`); } const d = await r.json(); const t = d?.content?.[0]?.text || d?.text; if (!t) throw new Error("Resposta vazia"); return t; } else { // Browser/artifact — direct call (requires key pasted at top) if (!ANTHROPIC_KEY || ANTHROPIC_KEY.includes("PASTE")) { throw new Error("Chave Anthropic não configurada. Cole sua API key no topo do arquivo."); } const r = await fetch("https://api.anthropic.com/v1/messages", { method:"POST", headers:{ "Content-Type":"application/json", "x-api-key": ANTHROPIC_KEY, "anthropic-version":"2023-06-01", "anthropic-dangerous-direct-browser-access":"true", }, body:JSON.stringify({ model:"claude-sonnet-4-6", max_tokens:1000, system, messages:[{role:"user",content:user}], }), }); if (!r.ok) { const e=await r.json().catch(()=>({})); throw new Error(`Erro ${r.status}: ${e?.error?.message||r.statusText}`); } const d = await r.json(); const t = d?.content?.[0]?.text; if (!t) throw new Error("Resposta vazia"); return t; } } async function elevenLabsTTS(text, voiceId) { if (API_BASE) { // Production — use Flask proxy (no CORS issues) const r = await fetch(`${API_BASE}/tts`, { method:"POST", headers:{"Content-Type":"application/json"}, body:JSON.stringify({text, voice_id:voiceId}), }); if (!r.ok) { const e = await r.json().catch(()=>({})); throw new Error(e.error || r.statusText); } const blob = await r.blob(); if (!blob || blob.size===0) throw new Error("Áudio vazio"); return URL.createObjectURL(new Blob([blob],{type:"audio/mpeg"})); } else { // Browser — direct call if (!ELEVENLABS_KEY || ELEVENLABS_KEY.includes("PASTE")) { throw new Error("Chave ElevenLabs não configurada."); } const r = await fetch(`https://api.elevenlabs.io/v1/text-to-speech/${voiceId}`, { method:"POST", headers:{ "Content-Type":"application/json", "xi-api-key": ELEVENLABS_KEY, "Accept": "audio/mpeg", }, body:JSON.stringify({ text, model_id:"eleven_multilingual_v2", voice_settings:{stability:0.75,similarity_boost:0.85,style:0.3,use_speaker_boost:true}, }), }); if (!r.ok) { const errText = await r.text().catch(()=>""); throw new Error(`ElevenLabs ${r.status}: ${errText||r.statusText}`); } const blob = await r.blob(); if (!blob || blob.size===0) throw new Error("ElevenLabs retornou áudio vazio"); return URL.createObjectURL(new Blob([blob],{type:"audio/mpeg"})); } } async function didTalkingPhoto(imageUrl, audioUrl) { if (!DID_KEY || DID_KEY.includes("PASTE")) { throw new Error("Chave D-ID não configurada."); } // Create talk const create = await fetch("https://api.d-id.com/talks", { method:"POST", headers:{"Content-Type":"application/json","Authorization":DID_KEY}, body:JSON.stringify({ source_url: imageUrl, script: { type:"audio", audio_url: audioUrl }, config: { fluent:true, pad_audio:0.0 }, }), }); if (!create.ok) throw new Error("D-ID create: " + create.statusText); const { id } = await create.json(); // Poll for result for (let i=0; i<20; i++) { await new Promise(r=>setTimeout(r,3000)); const poll = await fetch(`https://api.d-id.com/talks/${id}`, { headers:{"Authorization":DID_KEY}, }); const data = await poll.json(); if (data.status === "done") return data.result_url; if (data.status === "error") throw new Error("D-ID erro: " + data.error?.message); } throw new Error("D-ID timeout — tente novamente"); } // ═══════════════════════════════════════════════════════════════════ // SHARE CARD // ═══════════════════════════════════════════════════════════════════ async function generateShareImage(text, name, relation) { const canvas = document.createElement("canvas"); canvas.width = 800; canvas.height = 1000; const ctx = canvas.getContext("2d"); // warm dark background const bg = ctx.createRadialGradient(400,300,100,400,500,700); bg.addColorStop(0,"#1a1208"); bg.addColorStop(1,"#050302"); ctx.fillStyle=bg; ctx.fillRect(0,0,800,1000); // candle glow top const glow = ctx.createRadialGradient(400,80,10,400,80,200); glow.addColorStop(0,"rgba(220,160,60,0.15)"); glow.addColorStop(1,"transparent"); ctx.fillStyle=glow; ctx.fillRect(0,0,800,400); // stars for(let i=0;i<60;i++){ ctx.beginPath(); ctx.arc(Math.random()*800,Math.random()*1000,Math.random()*1+0.3,0,Math.PI*2); ctx.fillStyle=`rgba(220,180,100,${Math.random()*0.25+0.05})`; ctx.fill(); } // candle emoji top ctx.font="36px serif"; ctx.textAlign="center"; ctx.fillText("🕯️",400,65); // brand ctx.fillStyle=C.gold; ctx.font="500 16px 'Cinzel',Georgia,serif"; ctx.letterSpacing="4px"; ctx.fillText("VOZ DO MEU",400,100); // name if(name){ ctx.fillStyle=C.goldSoft; ctx.font="italic 22px 'Cormorant Garamond',Georgia,serif"; ctx.fillText(name,400,132); } if(relation){ ctx.fillStyle=C.textFaint; ctx.font="14px 'Cormorant Garamond',Georgia,serif"; ctx.fillText(relation,400,154); } // divider ctx.strokeStyle="rgba(200,150,80,0.2)"; ctx.lineWidth=1; ctx.beginPath(); ctx.moveTo(120,172); ctx.lineTo(680,172); ctx.stroke(); // ornament ctx.fillStyle="rgba(200,150,80,0.3)"; ctx.font="13px serif"; ctx.fillText("✦ ✦ ✦",400,196); // text wrap ctx.fillStyle="#ddd0b8"; ctx.font="17px 'Cormorant Garamond',Georgia,serif"; const words=text.split(" "); let line=""; let y=228; const maxW=560; const lh=28; for(let i=0;imaxW&&i>0){ ctx.fillText(line.trim(),400,y); line=words[i]+" "; y+=lh; } else line=test; } ctx.fillText(line.trim(),400,y); y+=lh+16; // bottom ornament ctx.fillStyle="rgba(200,150,80,0.25)"; ctx.font="13px serif"; ctx.fillText("✦ ✦ ✦",400,y+10); // divider ctx.strokeStyle="rgba(200,150,80,0.15)"; ctx.beginPath(); ctx.moveTo(120,y+32); ctx.lineTo(680,y+32); ctx.stroke(); // tagline ctx.fillStyle="rgba(200,160,80,0.4)"; ctx.font="italic 13px 'Cormorant Garamond',Georgia,serif"; ctx.fillText("A voz de quem você ama, para sempre.",400,y+56); // domain ctx.fillStyle="rgba(160,120,60,0.3)"; ctx.font="11px 'Cinzel',Georgia,serif"; ctx.fillText("vozdomeu.com.br",400,970); return canvas.toDataURL("image/png"); } async function shareWhatsApp(text, name, relation) { try { const img = await generateShareImage(text,name,relation); if(navigator.share&&navigator.canShare){ const blob=await(await fetch(img)).blob(); const file=new File([blob],"vozdomeu.png",{type:"image/png"}); if(navigator.canShare({files:[file]})){ await navigator.share({files:[file],title:"Voz do Meu",text:text.substring(0,100)+"...\n\nvozdomeu.com.br"}); return; } } } catch(e){} const t=encodeURIComponent(`🕯️ VOZ DO MEU 🕯️\n${name?name+"\n":""}\n${text}\n\n✦ A voz de quem você ama, para sempre ✦\nvozdomeu.com.br`); window.open(`https://wa.me/?text=${t}`,"_blank"); } // ═══════════════════════════════════════════════════════════════════ // REUSABLE COMPONENTS // ═══════════════════════════════════════════════════════════════════ function Btn({onClick,children,disabled,variant="default",style={}}) { const variants = { default: {background:C.bgCard,border:`1px solid ${C.border}`,color:C.textDim}, primary: {background:`linear-gradient(135deg,#3a2808,#5a3c10)`,border:`1px solid ${C.border2}`,color:C.goldSoft}, ghost: {background:"transparent",border:`1px solid ${C.border}`,color:C.textFaint}, whatsapp:{background:C.greenBg,border:`1px solid ${C.greenBorder}`,color:C.green}, }; return ( ); } function Field({label,value,set,ph,multi,rows=2}) { const base={width:"100%",background:C.bgCard,border:`1px solid ${C.border}`,borderRadius:10,padding:"11px 13px",color:C.text,fontSize:15,outline:"none",display:"block"}; return (
{multi ?