2026-04-28 20:32:34 +02:00
import React , { useState , useCallback , useEffect } from 'react' ;
import { useDropzone } from 'react-dropzone' ;
2026-05-20 03:26:54 -07:00
import { Upload , X , ShieldCheck , Target , TrendingUp , AlertTriangle , Loader2 , Zap , BrainCircuit , Camera , ScanLine , Bell , BellRing , Activity , LineChart , Crosshair } from 'lucide-react' ;
2026-04-28 20:32:34 +02:00
import { motion , AnimatePresence } from 'motion/react' ;
import { analyzeForexChart } from '../services/geminiService' ;
import { AnalysisResponse , SignalResult , SignalType } from '../types' ;
import { cn } from '../lib/utils' ;
2026-06-09 04:35:32 -07:00
import { toast } from 'sonner' ;
2026-04-28 20:32:34 +02:00
import { ref , uploadBytes , getDownloadURL } from 'firebase/storage' ;
2026-05-20 03:26:54 -07:00
import axios from 'axios' ;
2026-05-12 07:56:18 +02:00
import { compressImage , cleanupStorage } from '../lib/imageUtils' ;
2026-04-28 20:32:34 +02:00
import { storage , auth , db , handleFirestoreError , OperationType } from '../lib/firebase' ;
import { collection , addDoc , serverTimestamp , query , where , getDocs , Timestamp } from 'firebase/firestore' ;
2026-05-20 03:26:54 -07:00
import { fetchCurrentPrice } from '../services/marketData' ;
import { sendTelegramAlert } from '../services/notifications' ;
2026-06-09 04:35:32 -07:00
import { WatchlistPanel } from './WatchlistPanel' ;
2026-04-28 20:32:34 +02:00
2026-05-07 19:02:04 +02:00
export const AnalysisView : React.FC < { userData? : any , onGoToHistory ?: () => void } > = ({ userData , onGoToHistory }) => {
2026-04-28 20:32:34 +02:00
const [ file , setFile ] = useState < File | null >( null );
const [ preview , setPreview ] = useState < string | null >( null );
const [ isAnalyzing , setIsAnalyzing ] = useState ( false );
const [ result , setResult ] = useState < AnalysisResponse | null >( null );
const [ error , setError ] = useState < string | null >( null );
2026-05-03 12:02:27 +02:00
const [ mode , setMode ] = useState < 'Técnico' | 'Fundamental' | 'Híbrido' > ( 'Híbrido' );
2026-06-09 04:35:32 -07:00
const [ tradingStyle , setTradingStyle ] = useState < 'Automático' | 'Scalping' | 'Day Trading' | 'Swing' > ( 'Automático' );
2026-04-28 20:32:34 +02:00
const [ usageInfo , setUsageInfo ] = useState < { used : number , limit : number } > ({ used : 0 , limit : userData?.analysisLimit ?? 8 });
2026-05-12 07:56:18 +02:00
const [ alerts , setAlerts ] = useState < { type : string , price : string , targetValue : number }[] > ([]);
2026-05-20 03:26:54 -07:00
const [ customAlertPrices , setCustomAlertPrices ] = useState < Record < string , string >>({});
2026-06-09 04:35:32 -07:00
const [ alertOptions , setAlertOptions ] = useState ({ execution : true , expiration : true });
2026-05-20 03:26:54 -07:00
const [ marketPrice , setMarketPrice ] = useState < number | null >( null );
2026-04-28 20:32:34 +02:00
2026-05-11 16:57:29 +02:00
const now = Date . now ();
const isLifetime = userData ? . plan === 'lifetime' ;
const endsAt = userData ? . subscriptionEndsAt ;
const isExpired = !! ( ! isLifetime && endsAt && now > endsAt );
const daysRemaining = ! isLifetime && endsAt ? ( endsAt - now ) / ( 1000 * 60 * 60 * 24 ) : null ;
const isEndingSoon = daysRemaining !== null && Math . ceil ( daysRemaining ) <= 3 && Math . ceil ( daysRemaining ) > 0 ;
2026-05-12 07:56:18 +02:00
useEffect (() => {
if ( result && result . entry ) {
if ( 'Notification' in window && Notification . permission !== 'granted' ) {
Notification . requestPermission ();
}
} else {
2026-05-20 03:26:54 -07:00
setMarketPrice ( null );
2026-05-12 07:56:18 +02:00
setAlerts ([]);
}
}, [ result ]);
useEffect (() => {
2026-05-20 03:26:54 -07:00
if ( ! result || ! result . pair ) return ;
2026-05-12 07:56:18 +02:00
2026-05-20 03:26:54 -07:00
let isMounted = true ;
let currentPrice = marketPrice ;
const fetchPrice = async () => {
try {
const price = await fetchCurrentPrice ( result . pair );
if ( price !== null && isMounted ) {
setMarketPrice ( price );
currentPrice = price ;
checkAlerts ( price );
2026-05-12 07:56:18 +02:00
}
2026-05-20 03:26:54 -07:00
} catch ( e ) {
console . error ( "Error fetching market price" , e );
}
};
2026-05-12 07:56:18 +02:00
2026-05-20 03:26:54 -07:00
const checkAlerts = ( price : number ) => {
2026-05-12 07:56:18 +02:00
let triggeredIndex = - 1 ;
const triggeredAlert = alerts . find (( alert , index ) => {
2026-05-20 03:26:54 -07:00
// Check if price crossed the target since last check
// Fallback condition if currentPrice is null is just exact match but that's rare,
// usually we just check within a small margin
const isTriggered = Math . abs ( price - alert . targetValue ) < ( alert . targetValue * 0.0002 ) ||
( currentPrice && currentPrice < alert . targetValue && price >= alert . targetValue ) ||
( currentPrice && currentPrice > alert . targetValue && price <= alert . targetValue );
2026-05-12 07:56:18 +02:00
if ( isTriggered ) triggeredIndex = index ;
return isTriggered ;
});
if ( triggeredAlert && triggeredIndex !== - 1 ) {
2026-05-20 03:26:54 -07:00
const msg = `🚨 Alerta QuantScan: O ativo ${ result . pair } atingiu seu alvo de ${ triggeredAlert . type } ( ${ triggeredAlert . price } ).` ;
try {
const audio = new Audio ( 'https://assets.mixkit.co/active_storage/sfx/2869/2869-preview.mp3' );
audio . play (). catch ( e => console . error ( "Audio play failed:" , e ));
} catch ( e ) {
console . error ( e )
}
2026-05-12 07:56:18 +02:00
if ( 'Notification' in window && Notification . permission === 'granted' ) {
new Notification ( '🚨 Alerta QuantScan' , {
2026-05-20 03:26:54 -07:00
body : msg ,
2026-05-12 07:56:18 +02:00
icon : 'https://i.ibb.co/9BwbV3M/FXBROS-WORLD-3.png'
});
} else {
2026-05-20 03:26:54 -07:00
window . alert ( msg );
2026-05-12 07:56:18 +02:00
}
setAlerts ( prev => prev . filter (( _ , i ) => i !== triggeredIndex ));
}
2026-05-20 03:26:54 -07:00
};
2026-05-12 07:56:18 +02:00
2026-05-20 03:26:54 -07:00
fetchPrice ();
const interval = setInterval ( fetchPrice , 60000 ); // Poll every minute
2026-05-12 07:56:18 +02:00
2026-05-20 03:26:54 -07:00
return () => {
isMounted = false ;
clearInterval ( interval );
};
}, [ result , alerts ]);
2026-05-12 07:56:18 +02:00
2026-04-28 20:32:34 +02:00
useEffect (() => {
const fetchUsage = async () => {
if ( ! auth . currentUser ) return ;
const today = new Date ();
today . setHours ( 0 , 0 , 0 , 0 );
try {
const q = query (
collection ( db , 'signals' ),
where ( 'userId' , '==' , auth . currentUser . uid )
);
const snapshot = await getDocs ( q ). catch ( err => {
handleFirestoreError ( err , OperationType . LIST , 'signals' );
throw err ;
});
const todaySignals = snapshot . docs . filter (( doc ) => {
const data = doc . data ();
return data . timestamp >= today . getTime ();
});
setUsageInfo ({
used : todaySignals.length ,
limit : userData?.analysisLimit ?? 8
});
} catch ( err ) {
console . error ( "Error fetching usage stats" , err );
}
};
fetchUsage ();
}, [ result , userData ]);
const handleStartAnalysis = async () => {
2026-05-11 16:57:29 +02:00
if ( isExpired ) {
setError ( "Sua assinatura expirou. Por favor, contate o administrador para renovar o plano." );
return ;
}
2026-04-28 20:32:34 +02:00
if ( ! preview || ! file ) return ;
if ( usageInfo . used >= usageInfo . limit ) {
setError ( `Limite diário atingido ( ${ usageInfo . used } / ${ usageInfo . limit } ). Faça upgrade para análises ilimitadas.` );
return ;
}
setIsAnalyzing ( true );
setError ( null );
try {
let downloadUrl = null ;
if ( auth . currentUser ) {
2026-05-06 23:48:03 +02:00
const fileRef = ref ( storage , `scans/ ${ auth . currentUser . uid } / ${ Date . now () } _ ${ file . name } ` );
2026-04-28 20:32:34 +02:00
await uploadBytes ( fileRef , file );
downloadUrl = await getDownloadURL ( fileRef );
2026-05-12 07:56:18 +02:00
// Clean up old scans for this user to avoid storage limits (keep max 100)
cleanupStorage ( `scans/ ${ auth . currentUser . uid } ` , 100 );
2026-04-28 20:32:34 +02:00
}
const base64 = preview . split ( ',' )[ 1 ];
2026-06-09 04:35:32 -07:00
const analysis = await analyzeForexChart ( base64 , undefined , mode , userData ? . plan , tradingStyle );
2026-05-06 23:48:03 +02:00
analysis . score = Math . round ( analysis . score );
2026-04-28 20:32:34 +02:00
setResult ( analysis );
if ( auth . currentUser ) {
2026-05-16 10:49:05 +02:00
const sanitizedAnalysis = { ... analysis };
if ( sanitizedAnalysis . pair ) sanitizedAnalysis . pair = String ( sanitizedAnalysis . pair ). substring ( 0 , 100 );
if ( sanitizedAnalysis . timeframe ) sanitizedAnalysis . timeframe = String ( sanitizedAnalysis . timeframe ). substring ( 0 , 50 );
sanitizedAnalysis . score = Math . round ( Number ( sanitizedAnalysis . score ) || 0 );
2026-04-28 20:32:34 +02:00
await addDoc ( collection ( db , 'signals' ), {
2026-05-16 10:49:05 +02:00
... sanitizedAnalysis ,
2026-04-28 20:32:34 +02:00
screenshotUrl : downloadUrl ,
userId : auth.currentUser.uid ,
createdAt : serverTimestamp (),
timestamp : Date.now (),
result : SignalResult.PENDING ,
2026-05-16 10:49:05 +02:00
type : sanitizedAnalysis . decision || 'WAIT'
2026-04-28 20:32:34 +02:00
}). catch ( err => {
handleFirestoreError ( err , OperationType . CREATE , 'signals' );
throw err ;
});
2026-05-20 03:26:54 -07:00
2026-06-09 04:35:32 -07:00
toast . success ( `Scanner Concluído com ${ sanitizedAnalysis . score } % de Confiança no ${ sanitizedAnalysis . pair || 'Ativo' } !` , {
description : `Decisão de ${ sanitizedAnalysis . decision } `
});
2026-05-20 03:26:54 -07:00
if ( sanitizedAnalysis . decision !== 'WAIT' ) {
axios . post ( '/api/onesignal/notify' , {
title : 'Novo Sinal QuantScan IA 🚨' ,
message : ` ${ sanitizedAnalysis . pair } - ${ sanitizedAnalysis . decision } ( ${ sanitizedAnalysis . signalType || 'Scalping' } ). Confiança: ${ sanitizedAnalysis . score } %`
}). catch ( console . error );
sendTelegramAlert ( sanitizedAnalysis ). catch ( console . error );
}
2026-04-28 20:32:34 +02:00
}
} catch ( err : any ) {
2026-05-12 07:56:18 +02:00
console . error ( "Gemini Error:" , err );
2026-05-07 13:24:31 +02:00
let errorMessage = err . message || 'Falha ao analisar imagem. Verifique se o gráfico está claro.' ;
if ( errorMessage . includes ( '429' ) || errorMessage . includes ( 'RESOURCE_EXHAUSTED' ) || errorMessage . includes ( 'prepayment credits' )) {
errorMessage = 'Sua cota de uso da API do Google Gemini (IA) foi excedida ou os créditos acabaram. Por favor, acesse o painel do Google AI Studio (https://ai.studio) para verificar seu faturamento e recarregar os créditos.' ;
2026-05-12 07:56:18 +02:00
} else {
errorMessage = `Erro na API: ${ errorMessage } ` ;
2026-05-07 13:24:31 +02:00
}
setError ( errorMessage );
2026-06-09 04:35:32 -07:00
toast . error ( 'Erro na análise do Scanner' , { description : errorMessage });
2026-04-28 20:32:34 +02:00
} finally {
setIsAnalyzing ( false );
}
};
2026-05-12 07:56:18 +02:00
const onDrop = useCallback ( async ( acceptedFiles : File []) => {
2026-04-28 20:32:34 +02:00
const selectedFile = acceptedFiles [ 0 ];
2026-05-12 07:56:18 +02:00
// Comprimir a imagem antes de setar state para reduzir MS
const compressedFile = await compressImage ( selectedFile , 1200 , 0.7 );
setFile ( compressedFile );
2026-04-28 20:32:34 +02:00
const reader = new FileReader ();
reader . onload = () => {
setPreview ( reader . result as string );
};
2026-05-12 07:56:18 +02:00
reader . readAsDataURL ( compressedFile );
2026-04-28 20:32:34 +02:00
setError ( null );
}, []);
const { getRootProps , getInputProps , isDragActive } = useDropzone ({
onDrop ,
accept : { 'image/*' : [] },
multiple : false
} as any );
const reset = () => {
setFile ( null );
setPreview ( null );
setResult ( null );
setError ( null );
2026-05-12 07:56:18 +02:00
setAlerts ([]);
};
2026-05-20 03:26:54 -07:00
const handleCustomAlertPriceChange = ( type : string , value : string ) => {
setCustomAlertPrices ( prev => ({ ... prev , [ type ] : value }));
};
2026-05-12 07:56:18 +02:00
const toggleAlert = ( type : string , priceStr : string ) => {
const numPrice = parseFloat ( priceStr . replace ( /[^0-9.]/g , '' ));
if ( isNaN ( numPrice )) return ;
setAlerts ( prev => {
const exists = prev . some ( a => a . type === type );
if ( exists ) {
return prev . filter ( a => a . type !== type );
} else {
return [... prev , { type , price : priceStr , targetValue : numPrice }];
}
});
2026-04-28 20:32:34 +02:00
};
return (
< div className = "max-w-4xl mx-auto space-y-5" >
2026-05-11 16:57:29 +02:00
{ isExpired && (
< div className = "bg-brand-red/10 border border-brand-red p-4 rounded-xl flex items-start gap-3" >
< AlertTriangle className = "text-brand-red shrink-0 mt-0.5" size = { 20 } />
< div >
< h3 className = "text-brand-red font-black text-sm uppercase tracking-widest" > Assinatura Expirada </ h3 >
< p className = "text-red-200/70 text-xs mt-1" >
Sua assinatura expirou . Renove seu plano para continuar usando o scanner analisador .
</ p >
</ div >
</ div >
)}
{ isEndingSoon && (
< div className = "bg-orange-500/10 border border-orange-500 p-4 rounded-xl flex items-start gap-3" >
< AlertTriangle className = "text-orange-500 shrink-0 mt-0.5" size = { 20 } />
< div >
< h3 className = "text-orange-500 font-black text-sm uppercase tracking-widest" > Assinatura Expirando </ h3 >
< p className = "text-orange-200/70 text-xs mt-1" >
Sua assinatura expira em { Math . ceil ( daysRemaining )} dia ( s ). Renove agora para não perder o acesso ao scanner .
</ p >
</ div >
</ div >
)}
2026-04-28 20:32:34 +02:00
< header className = "space-y-1" >
< h1 className = "text-xl font-black italic tracking-tighter text-white flex items-center gap-2.5 uppercase" >
< div className = "w-7 h-7 rounded-lg border-2 border-brand-red flex items-center justify-center red-glow" >
< TrendingUp size = { 14 } className = "text-brand-red" />
</ div >
Novo Scan
</ h1 >
< div className = "flex items-center justify-between" >
< p className = "text-zinc-500 text-[10px] font-medium leading-none" > Envie um gráfico para análise de IA Pro .</ p >
< div className = "flex items-center gap-2" >
< div className = "h-1.5 w-24 bg-white/5 rounded-full overflow-hidden border border-white/5" >
< div
className = { cn (
"h-full transition-all duration-500" ,
usageInfo . used >= usageInfo . limit ? "bg-brand-red" : "bg-green-500"
)}
style = {{ width : usageInfo.limit === 999999 ? '100%' : ` ${ Math . min ( 100 , ( usageInfo . used / usageInfo . limit ) * 100 ) } %` }}
/>
</ div >
< span className = "text-[10px] font-black uppercase text-zinc-500" >
{ usageInfo . limit === 999999 ? ` ${ usageInfo . used } /∞ Scans` : ` ${ usageInfo . used } / ${ usageInfo . limit } Scans` }
</ span >
</ div >
</ div >
</ header >
2026-06-09 04:35:32 -07:00
< WatchlistPanel />
2026-04-28 20:32:34 +02:00
{ ! result ? (
2026-05-03 12:02:27 +02:00
< div className = "glass-card p-6 border-zinc-900 min-h-[300px] flex flex-col justify-center items-center group relative" >
{ isAnalyzing && (
< div className = "absolute inset-0 z-50 bg-brand-dark/95 backdrop-blur-sm flex flex-col items-center justify-center gap-6 rounded-xl" >
< div className = "relative w-40 h-40 flex items-center justify-center" >
< div className = "absolute inset-0 border-2 border-brand-red/30 animate-ping rounded-full" />
< div className = "absolute inset-4 border-2 border-brand-red/20 animate-pulse rounded-full" />
< img src = "https://i.ibb.co/9BwbV3M/FXBROS-WORLD-3.png" className = "w-16 h-16 animate-pulse" alt = "Logo" />
< motion.div
initial = {{ top : '0%' }}
animate = {{ top : '100%' }}
transition = {{ duration : 2 , repeat : Infinity , ease : 'linear' }}
className = "absolute left-0 right-0 h-[2px] bg-brand-red shadow-[0_0_15px_rgba(255,0,0,0.8)]"
/>
</ div >
< p className = "text-brand-red font-black text-lg italic tracking-tighter animate-pulse uppercase" > Processando Scan Institucional ...</ p >
</ div >
)}
2026-04-28 20:32:34 +02:00
{ ! preview ? (
< div className = "w-full flex flex-col gap-4 items-center justify-center" >
2026-06-09 04:35:32 -07:00
< div className = "flex flex-col gap-3 w-full max-w-sm mb-4" >
< div className = "flex flex-col justify-center gap-1.5 w-full tour-step-2" >
< span className = "text-[9px] uppercase tracking-widest text-zinc-500 font-bold text-center" > Tipo de Análise </ span >
< div className = "flex gap-2 w-full justify-center" >
{([ 'Técnico' , 'Fundamental' , 'Híbrido' ] as const ). map (( m ) => (
< button
key = { m }
onClick = {() => setMode ( m )}
className = { cn (
"px-4 py-1.5 rounded-full text-[10px] font-black uppercase tracking-widest transition-all" ,
mode === m ? "bg-brand-red text-white" : "bg-white/5 text-white/50 hover:bg-white/10"
)}
>
{ m }
</ button >
))}
</ div >
</ div >
< div className = "flex flex-col justify-center gap-1.5 w-full tour-step-3" >
< span className = "text-[9px] uppercase tracking-widest text-zinc-500 font-bold text-center" > Estilo de Trading </ span >
< div className = "flex gap-2 w-full justify-center flex-wrap" >
{([ 'Automático' , 'Scalping' , 'Day Trading' , 'Swing' ] as const ). map (( s ) => (
< button
key = { s }
onClick = {() => setTradingStyle ( s )}
className = { cn (
"px-4 py-1.5 rounded-full text-[10px] font-black uppercase tracking-widest transition-all" ,
tradingStyle === s ? "bg-brand-red text-white" : "bg-white/5 text-white/50 hover:bg-white/10"
)}
>
{ s }
</ button >
))}
</ div >
</ div >
2026-05-03 12:02:27 +02:00
</ div >
2026-04-28 20:32:34 +02:00
< div
{ ...getRootProps () }
className = { cn (
2026-06-09 04:35:32 -07:00
"w-full flex flex-col items-center justify-center py-10 rounded-xl cursor-pointer transition-all duration-300 border border-transparent tour-step-1" ,
2026-05-11 16:57:29 +02:00
isDragActive ? "bg-brand-red/5 scale-[0.98] border-brand-red/30" : "hover:bg-zinc-800/20" ,
isExpired ? "opacity-50 pointer-events-none" : ""
2026-04-28 20:32:34 +02:00
)}
>
2026-05-11 16:57:29 +02:00
< input { ...getInputProps () } disabled = { isExpired } />
2026-04-28 20:32:34 +02:00
< div className = "w-16 h-16 bg-brand-gray rounded-xl flex items-center justify-center mb-4 border border-white/5 group-hover:border-brand-red/30 transition-colors" >
< Upload size = { 24 } className = "text-zinc-600 group-hover:scale-110 transition-transform" />
</ div >
< p className = "text-base font-black uppercase italic tracking-tighter text-white mb-1 text-center px-4" > Fazer Upload ou Arrastar </ p >
< p className = "text-zinc-600 text-[10px] font-bold uppercase tracking-widest text-center px-4" > TradingView , MT4 / MT5 , cTrader </ p >
</ div >
< div className = "flex gap-3 w-full" >
2026-05-11 16:57:29 +02:00
< label className = { cn (
"flex-1 bg-brand-dark border-2 border-white/5 text-white py-3.5 rounded-xl font-black text-xs hover:border-brand-red/50 transition-all active:scale-95 flex items-center justify-center gap-2 cursor-pointer relative overflow-hidden group" ,
isExpired ? "opacity-50 pointer-events-none" : ""
)}>
2026-04-28 20:32:34 +02:00
< Camera size = { 16 } className = "text-zinc-400 group-hover:text-brand-red transition-colors" />
< span className = "whitespace-nowrap pt-0.5" > TIRAR FOTO </ span >
2026-05-11 16:57:29 +02:00
< input type = "file" accept = "image/*" capture = "environment" disabled = { isExpired } className = "opacity-0 absolute inset-0 cursor-pointer w-full h-full" onChange = {( e ) => {
2026-04-28 20:32:34 +02:00
const f = e . target . files ? .[ 0 ];
if ( f ) onDrop ([ f ]);
}} />
</ label >
< label className = "flex-1 bg-brand-red/10 border-2 border-brand-red/30 text-brand-red py-3.5 rounded-xl font-black text-xs hover:bg-brand-red/20 transition-all active:scale-95 flex items-center justify-center gap-2 cursor-pointer relative overflow-hidden group" >
< ScanLine size = { 16 } className = "group-hover:scale-110 transition-transform" />
< span className = "whitespace-nowrap pt-0.5" > SCAN AO VIVO </ span >
< input type = "file" accept = "image/*" capture = "environment" className = "opacity-0 absolute inset-0 cursor-pointer w-full h-full" onChange = {( e ) => {
const f = e . target . files ? .[ 0 ];
if ( f ) onDrop ([ f ]);
}} />
</ label >
</ div >
</ div >
) : (
< div className = "w-full space-y-4" >
< div className = "relative rounded-lg overflow-hidden border border-white/5 max-h-[400px]" >
< img src = { preview } alt = "Preview" className = "w-full h-full object-contain" />
< button
onClick = { reset }
className = "absolute top-3 right-3 p-1.5 bg-black/60 rounded-full text-white hover:bg-brand-red transition-colors"
>
< X size = { 16 } />
</ button >
</ div >
< div className = "flex gap-3" >
2026-05-07 13:38:56 +02:00
< button
onClick = { reset }
disabled = { isAnalyzing }
className = "w-1/3 bg-white/5 text-white py-3.5 rounded-xl font-black text-sm hover:bg-white/10 border border-white/10 transition-all active:scale-95 flex items-center justify-center gap-2 disabled:opacity-50 uppercase"
>
NOVO SCAN
</ button >
2026-04-28 20:32:34 +02:00
< button
onClick = { handleStartAnalysis }
disabled = { isAnalyzing }
className = "flex-1 bg-brand-red text-white py-3.5 rounded-xl font-black text-base hover:bg-brand-red/90 transition-all active:scale-95 flex items-center justify-center gap-2 disabled:opacity-50"
>
{ isAnalyzing ? (
<>
< Loader2 className = "animate-spin" size = { 20 } />
ANALISANDO ...
</>
) : (
'ANALISAR GRÁFICO'
)}
</ button >
</ div >
{ error && < p className = "text-brand-red text-center text-xs font-bold" >{ error }</ p >}
</ div >
)}
</ div >
) : (
< motion.div
initial = {{ opacity : 0 , y : 10 }}
animate = {{ opacity : 1 , y : 0 }}
className = "space-y-5"
>
< div className = "grid grid-cols-1 md:grid-cols-2 gap-4" >
{ /* Score & Signal Info */ }
< div className = "glass-card flex flex-col items-center justify-center text-center space-y-4" >
< div className = "relative w-36 h-36 flex items-center justify-center" >
< svg className = "w-full h-full" viewBox = "0 0 100 100" >
< circle
cx = "50" cy = "50" r = "45"
fill = "none"
stroke = "rgba(255,255,255,0.03)"
strokeWidth = "6"
/>
< circle
cx = "50" cy = "50" r = "45"
fill = "none"
stroke = "currentColor"
strokeWidth = "6"
strokeDasharray = { 283 }
strokeDashoffset = { 283 - ( 283 * result . score ) / 100 }
className = { cn (
"transition-all duration-1000 ease-out" ,
2026-05-03 22:17:02 +02:00
result . decision === SignalType . BUY ? "text-green-500" : ( result . decision === SignalType . SELL ? "text-brand-red" : "text-zinc-500" )
2026-04-28 20:32:34 +02:00
)}
strokeLinecap = "round"
transform = "rotate(-90 50 50)"
/>
</ svg >
< div className = "absolute inset-0 flex flex-col items-center justify-center" >
< span className = { cn (
"text-4xl font-black leading-none" ,
2026-05-03 22:17:02 +02:00
result . decision === SignalType . BUY ? "text-green-500" : ( result . decision === SignalType . SELL ? "text-brand-red red-text-glow" : "text-zinc-500" )
2026-04-28 20:32:34 +02:00
)}>
{ result . score } %
</ span >
</ div >
</ div >
< div >
< h3 className = { cn (
"text-sm font-black uppercase tracking-wider italic" ,
result . score >= 80
? ( result . decision === SignalType . BUY ? "text-green-500" : "text-brand-red red-text-glow" )
: result . score >= 60 ? "text-orange-500" : "text-zinc-500"
)}>
{ result . score >= 80 ? '🔥 ALTA PROBABILIDADE' : result . score >= 60 ? '⚖️ MÉDIA PROBABILIDADE' : '❌ EVITAR' }
</ h3 >
< p className = "text-zinc-500 mt-1 text-[10px] font-medium max-w-[200px]" >{ result . justification }</ p >
2026-05-03 12:02:27 +02:00
< p className = "text-brand-red mt-1 text-[10px] font-bold max-w-[200px] italic" > ⚠️ { result . alerta }</ p >
2026-04-28 20:32:34 +02:00
</ div >
< div className = "w-full pt-4 border-t border-white/5 flex gap-3" >
< div className = "flex-1 glass-card p-2" >
2026-05-03 12:02:27 +02:00
< span className = "block text-[8px] text-zinc-600 uppercase font-black mb-0.5" > MODO </ span >
< span className = "text-xs font-black" >{ result . mode }</ span >
2026-04-28 20:32:34 +02:00
</ div >
< div className = "flex-1 glass-card p-2" >
< span className = "block text-[8px] text-zinc-600 uppercase font-black mb-0.5" > TF </ span >
2026-05-03 12:02:27 +02:00
< span className = "text-xs font-black" >{ result . timeframe }</ span >
2026-04-28 20:32:34 +02:00
</ div >
</ div >
2026-05-16 10:49:05 +02:00
{( result . riskReward || result . riskLevel || result . duration || result . signalType ) && (
< div className = "w-full pt-4 border-t border-white/5 grid grid-cols-2 gap-3" >
{ result . signalType && (
< div className = "glass-card p-2" >
< span className = "block text-[8px] text-zinc-600 uppercase font-black mb-0.5" > ESTILO </ span >
< span className = "text-[10px] font-black tracking-widest" >{ result . signalType }</ span >
</ div >
)}
{ result . riskLevel && (
< div className = "glass-card p-2" >
< span className = "block text-[8px] text-zinc-600 uppercase font-black mb-0.5" > RISCO </ span >
< span className = { cn (
"text-[10px] font-black tracking-widest" ,
result . riskLevel . toUpperCase () === 'LOW' ? "text-green-500" :
result . riskLevel . toUpperCase () === 'HIGH' ? "text-brand-red" : "text-orange-500"
)}>{ result . riskLevel }</ span >
</ div >
)}
{ result . riskReward && (
< div className = "glass-card p-2" >
< span className = "block text-[8px] text-zinc-600 uppercase font-black mb-0.5" > RISCO / RETORNO </ span >
< span className = "text-[10px] font-black font-mono" >{ result . riskReward }</ span >
</ div >
)}
{ result . duration && (
< div className = "glass-card p-2" >
< span className = "block text-[8px] text-zinc-600 uppercase font-black mb-0.5" > DURAÇÃO </ span >
< span className = "text-[10px] font-black tracking-widest uppercase" >{ result . duration }</ span >
</ div >
)}
</ div >
)}
2026-04-28 20:32:34 +02:00
</ div >
{ /* Trading Decision */ }
< div className = "glass-card space-y-4" >
2026-05-03 22:17:02 +02:00
< div className = "text-center" >
< span className = "text-[10px] font-black text-zinc-500 uppercase tracking-widest bg-white/5 px-2 py-0.5 rounded" >
{ result . pair }
</ span >
</ div >
2026-04-28 20:32:34 +02:00
< div className = { cn (
"w-full py-5 rounded-lg flex flex-col items-center justify-center gap-1" ,
result . decision === SignalType . BUY ? "bg-green-500/10 text-green-500" :
result . decision === SignalType . SELL ? "bg-brand-red/10 text-brand-red" :
"bg-zinc-500/10 text-zinc-500"
)}>
< span className = "text-4xl font-black uppercase italic tracking-tighter" >
{ result . decision === SignalType . BUY ? 'COMPRAR' :
result . decision === SignalType . SELL ? 'VENDER' :
'AGUARDAR' }
</ span >
2026-05-03 12:02:27 +02:00
< span className = "text-[8px] uppercase font-black tracking-widest opacity-60" > Decisão QuantScan </ span >
2026-04-28 20:32:34 +02:00
</ div >
2026-05-20 03:26:54 -07:00
{ marketPrice !== null && (
2026-05-12 07:56:18 +02:00
< div className = "flex flex-col items-center justify-center p-2 mb-2 bg-black/40 rounded border border-white/5" >
2026-05-20 03:26:54 -07:00
< span className = "text-[9px] uppercase tracking-widest text-zinc-500 font-bold" > Preço de Mercado </ span >
< span className = "font-mono text-lg font-black text-white" >{ marketPrice . toFixed ( 5 )}</ span >
2026-05-12 07:56:18 +02:00
</ div >
)}
2026-04-28 20:32:34 +02:00
< div className = "space-y-2" >
< div className = "flex items-center justify-between p-3 glass-card bg-transparent border-white/5" >
< div className = "flex items-center gap-2" >
< Target size = { 14 } className = "text-zinc-600" />
< span className = "text-[9px] font-black text-zinc-500 uppercase" > ENTRADA </ span >
</ div >
2026-05-12 07:56:18 +02:00
< div className = "flex items-center gap-2" >
2026-05-20 03:26:54 -07:00
< input
className = "font-mono font-black text-sm bg-transparent border-b border-dashed border-white/20 text-right w-24 outline-none focus:border-white transition-colors text-white"
value = { customAlertPrices [ 'Entrada' ] ?? result . entry }
onChange = {( e ) => handleCustomAlertPriceChange ( 'Entrada' , e . target . value )}
/>
2026-05-12 07:56:18 +02:00
< button
2026-05-20 03:26:54 -07:00
onClick = {() => toggleAlert ( 'Entrada' , customAlertPrices [ 'Entrada' ] ?? result . entry )}
2026-05-12 07:56:18 +02:00
className = { cn (
"p-1.5 rounded transition-colors" ,
alerts . some ( a => a . type === 'Entrada' ) ? "text-yellow-500 bg-yellow-500/20" : "text-zinc-500 hover:text-yellow-500 hover:bg-white/5"
)}
>
{ alerts . some ( a => a . type === 'Entrada' ) ? < BellRing size = { 14 } /> : < Bell size = { 14 } />}
</ button >
</ div >
2026-04-28 20:32:34 +02:00
</ div >
< div className = "flex items-center justify-between p-3 glass-card bg-transparent border-white/5" >
< div className = "flex items-center gap-2" >
< AlertTriangle size = { 14 } className = "text-brand-red/50" />
< span className = "text-[9px] font-black text-zinc-500 uppercase" > STOP LOSS </ span >
</ div >
2026-05-12 07:56:18 +02:00
< div className = "flex items-center gap-2" >
2026-05-20 03:26:54 -07:00
< input
className = "font-mono font-black text-sm bg-transparent border-b border-dashed border-brand-red/30 text-right w-24 outline-none focus:border-brand-red transition-colors text-brand-red"
value = { customAlertPrices [ 'Stop Loss' ] ?? result . stopLoss }
onChange = {( e ) => handleCustomAlertPriceChange ( 'Stop Loss' , e . target . value )}
/>
2026-05-12 07:56:18 +02:00
< button
2026-05-20 03:26:54 -07:00
onClick = {() => toggleAlert ( 'Stop Loss' , customAlertPrices [ 'Stop Loss' ] ?? result . stopLoss )}
2026-05-12 07:56:18 +02:00
className = { cn (
"p-1.5 rounded transition-colors" ,
alerts . some ( a => a . type === 'Stop Loss' ) ? "text-yellow-500 bg-yellow-500/20" : "text-zinc-500 hover:text-yellow-500 hover:bg-white/5"
)}
>
{ alerts . some ( a => a . type === 'Stop Loss' ) ? < BellRing size = { 14 } /> : < Bell size = { 14 } />}
</ button >
</ div >
2026-04-28 20:32:34 +02:00
</ div >
< div className = "flex items-center justify-between p-3 glass-card bg-transparent border-white/5" >
< div className = "flex items-center gap-2" >
< ShieldCheck size = { 14 } className = "text-green-500/50" />
2026-05-16 10:49:05 +02:00
< span className = "text-[9px] font-black text-zinc-500 uppercase" > TAKE PROFIT 1 </ span >
2026-04-28 20:32:34 +02:00
</ div >
2026-05-12 07:56:18 +02:00
< div className = "flex items-center gap-2" >
2026-05-20 03:26:54 -07:00
< input
className = "font-mono font-black text-sm bg-transparent border-b border-dashed border-green-500/30 text-right w-24 outline-none focus:border-green-500 transition-colors text-green-500"
value = { customAlertPrices [ 'Take Profit 1' ] ?? result . takeProfit }
onChange = {( e ) => handleCustomAlertPriceChange ( 'Take Profit 1' , e . target . value )}
/>
2026-05-12 07:56:18 +02:00
< button
2026-05-20 03:26:54 -07:00
onClick = {() => toggleAlert ( 'Take Profit 1' , customAlertPrices [ 'Take Profit 1' ] ?? result . takeProfit )}
2026-05-12 07:56:18 +02:00
className = { cn (
"p-1.5 rounded transition-colors" ,
2026-05-16 10:49:05 +02:00
alerts . some ( a => a . type === 'Take Profit 1' ) ? "text-yellow-500 bg-yellow-500/20" : "text-zinc-500 hover:text-yellow-500 hover:bg-white/5"
2026-05-12 07:56:18 +02:00
)}
>
2026-05-16 10:49:05 +02:00
{ alerts . some ( a => a . type === 'Take Profit 1' ) ? < BellRing size = { 14 } /> : < Bell size = { 14 } />}
2026-05-12 07:56:18 +02:00
</ button >
</ div >
2026-04-28 20:32:34 +02:00
</ div >
2026-05-16 10:49:05 +02:00
{ result . takeProfit2 && (
< div className = "flex items-center justify-between p-3 glass-card bg-transparent border-white/5" >
< div className = "flex items-center gap-2" >
< ShieldCheck size = { 14 } className = "text-green-500/50" />
< span className = "text-[9px] font-black text-zinc-500 uppercase" > TAKE PROFIT 2 </ span >
</ div >
< div className = "flex items-center gap-2" >
2026-05-20 03:26:54 -07:00
< input
className = "font-mono font-black text-sm bg-transparent border-b border-dashed border-green-500/30 text-right w-24 outline-none focus:border-green-500 transition-colors text-green-500"
value = { customAlertPrices [ 'Take Profit 2' ] ?? result . takeProfit2 }
onChange = {( e ) => handleCustomAlertPriceChange ( 'Take Profit 2' , e . target . value )}
/>
2026-05-16 10:49:05 +02:00
< button
2026-05-20 03:26:54 -07:00
onClick = {() => toggleAlert ( 'Take Profit 2' , customAlertPrices [ 'Take Profit 2' ] ?? result . takeProfit2 ! )}
2026-05-16 10:49:05 +02:00
className = { cn (
"p-1.5 rounded transition-colors" ,
alerts . some ( a => a . type === 'Take Profit 2' ) ? "text-yellow-500 bg-yellow-500/20" : "text-zinc-500 hover:text-yellow-500 hover:bg-white/5"
)}
>
{ alerts . some ( a => a . type === 'Take Profit 2' ) ? < BellRing size = { 14 } /> : < Bell size = { 14 } />}
</ button >
</ div >
</ div >
)}
{ result . takeProfit3 && (
< div className = "flex items-center justify-between p-3 glass-card bg-transparent border-white/5" >
< div className = "flex items-center gap-2" >
< ShieldCheck size = { 14 } className = "text-green-500/50" />
< span className = "text-[9px] font-black text-zinc-500 uppercase" > TAKE PROFIT 3 </ span >
</ div >
< div className = "flex items-center gap-2" >
2026-05-20 03:26:54 -07:00
< input
className = "font-mono font-black text-sm bg-transparent border-b border-dashed border-green-500/30 text-right w-24 outline-none focus:border-green-500 transition-colors text-green-500"
value = { customAlertPrices [ 'Take Profit 3' ] ?? result . takeProfit3 }
onChange = {( e ) => handleCustomAlertPriceChange ( 'Take Profit 3' , e . target . value )}
/>
2026-05-16 10:49:05 +02:00
< button
2026-05-20 03:26:54 -07:00
onClick = {() => toggleAlert ( 'Take Profit 3' , customAlertPrices [ 'Take Profit 3' ] ?? result . takeProfit3 ! )}
2026-05-16 10:49:05 +02:00
className = { cn (
"p-1.5 rounded transition-colors" ,
alerts . some ( a => a . type === 'Take Profit 3' ) ? "text-yellow-500 bg-yellow-500/20" : "text-zinc-500 hover:text-yellow-500 hover:bg-white/5"
)}
>
{ alerts . some ( a => a . type === 'Take Profit 3' ) ? < BellRing size = { 14 } /> : < Bell size = { 14 } />}
</ button >
</ div >
</ div >
)}
2026-04-28 20:32:34 +02:00
</ div >
2026-06-09 04:35:32 -07:00
< div className = "pt-4 border-t border-white/5 space-y-3" >
< h4 className = "text-[10px] uppercase font-black tracking-widest text-zinc-400 mb-2" > Opções de Notificação </ h4 >
< div className = "flex items-center justify-between p-3 glass-card bg-transparent border-white/5" >
< div className = "flex items-center gap-2" >
< BellRing size = { 14 } className = "text-zinc-500" />
< span className = "text-[9px] font-black text-white uppercase" > Notificar Horário de Negociação </ span >
</ div >
< button
onClick = {() => setAlertOptions ( prev => ({ ... prev , execution : ! prev . execution }))}
className = { cn ( "w-8 h-4 rounded-full transition-colors relative" , alertOptions . execution ? "bg-brand-red" : "bg-white/10" )}
>
< div className = { cn ( "w-3 h-3 rounded-full bg-white absolute top-0.5 transition-all" , alertOptions . execution ? "right-0.5" : "left-0.5" )} />
</ button >
</ div >
< div className = "flex items-center justify-between p-3 glass-card bg-transparent border-white/5" >
< div className = "flex items-center gap-2" >
< AlertTriangle size = { 14 } className = "text-zinc-500" />
< span className = "text-[9px] font-black text-white uppercase" > Sinal Inválido ou Expirado </ span >
</ div >
< button
onClick = {() => setAlertOptions ( prev => ({ ... prev , expiration : ! prev . expiration }))}
className = { cn ( "w-8 h-4 rounded-full transition-colors relative" , alertOptions . expiration ? "bg-brand-red" : "bg-white/10" )}
>
< div className = { cn ( "w-3 h-3 rounded-full bg-white absolute top-0.5 transition-all" , alertOptions . expiration ? "right-0.5" : "left-0.5" )} />
</ button >
</ div >
</ div >
2026-04-28 20:32:34 +02:00
</ div >
</ div >
2026-05-20 03:26:54 -07:00
< div className = "grid grid-cols-1 md:grid-cols-2 gap-4 mt-6" >
{ result . multiTimeFrameAnalysis && (
< div className = "glass-card space-y-4 col-span-full" >
< h4 className = "text-[10px] font-black uppercase tracking-widest text-blue-500 flex items-center gap-2" >
< Activity size = { 14 } /> MULTI TIME FRAME ANALYSIS
</ h4 >
< p className = "text-xs text-zinc-300 leading-relaxed font-medium" >
{ result . multiTimeFrameAnalysis }
</ p >
</ div >
)}
2026-04-28 20:32:34 +02:00
< div className = "glass-card space-y-4" >
< h4 className = "text-[10px] font-black uppercase tracking-widest text-brand-red flex items-center gap-2" >
2026-05-03 12:02:27 +02:00
< BrainCircuit size = { 14 } /> ANÁLISE TÉCNICA ( SMC + LIQ )
2026-04-28 20:32:34 +02:00
</ h4 >
< p className = "text-xs text-zinc-400 leading-relaxed font-medium" >
2026-05-03 12:02:27 +02:00
{ result . tecnica }
2026-04-28 20:32:34 +02:00
</ p >
</ div >
2026-05-20 03:26:54 -07:00
2026-04-28 20:32:34 +02:00
< div className = "glass-card space-y-4" >
< h4 className = "text-[10px] font-black uppercase tracking-widest text-green-500 flex items-center gap-2" >
2026-05-03 12:02:27 +02:00
< TrendingUp size = { 14 } /> ANÁLISE FUNDAMENTAL
2026-04-28 20:32:34 +02:00
</ h4 >
< p className = "text-xs text-zinc-400 leading-relaxed font-medium" >
2026-05-03 12:02:27 +02:00
{ result . fundamental }
2026-04-28 20:32:34 +02:00
</ p >
</ div >
2026-05-20 03:26:54 -07:00
{ result . winrateLearning && (
< div className = "glass-card space-y-4" >
< h4 className = "text-[10px] font-black uppercase tracking-widest text-purple-500 flex items-center gap-2" >
< LineChart size = { 14 } /> WINRATE LEARNING AI
</ h4 >
< p className = "text-xs text-zinc-300 leading-relaxed font-medium" >
{ result . winrateLearning }
</ p >
</ div >
)}
{ result . trailingStop && (
< div className = "glass-card space-y-4" >
< h4 className = "text-[10px] font-black uppercase tracking-widest text-orange-500 flex items-center gap-2" >
< Crosshair size = { 14 } /> TRAILING STOP AI
</ h4 >
< p className = "text-xs text-zinc-300 leading-relaxed font-medium" >
{ result . trailingStop }
</ p >
</ div >
)}
2026-05-03 12:02:27 +02:00
< div className = "glass-card space-y-4 col-span-full" >
2026-04-28 20:32:34 +02:00
< h4 className = "text-[10px] font-black uppercase tracking-widest text-zinc-400 flex items-center gap-2" >
2026-05-03 12:02:27 +02:00
< Target size = { 14 } /> ANALISE GERAL
2026-04-28 20:32:34 +02:00
</ h4 >
2026-05-03 12:02:27 +02:00
< p className = "text-xs text-zinc-300 leading-relaxed font-medium" >
{ result . analiseGeral }
</ p >
2026-04-28 20:32:34 +02:00
</ div >
</ div >
{ /* Analysis Illustration */ }
< div className = "glass-card overflow-hidden !p-0 border-zinc-900/50 relative" >
< div className = "absolute top-4 left-4 z-20" >
< h3 className = "text-xs font-black flex items-center gap-2 uppercase italic tracking-wider text-white" >
< ShieldCheck size = { 14 } className = { cn ( result . decision === SignalType . BUY ? "text-green-500" : "text-brand-red" )} />
Mapeamento Técnico da IA
</ h3 >
</ div >
< div className = "aspect-[21/9] relative bg-zinc-950 flex items-center justify-center" >
< div className = "absolute inset-0 z-10 bg-gradient-to-t from-brand-dark via-brand-dark/20 to-transparent" />
< img
src = { preview || '' }
alt = "Technical Analysis Map"
className = "w-full h-full object-cover opacity-60 mix-blend-screen grayscale"
/>
{ /* UI Overlays to simulate AI analysis */ }
2026-06-09 04:35:32 -07:00
{ /* Predictive Trend Overlay */ }
< div className = "absolute inset-0 z-15 pointer-events-none p-4 w-full h-full" >
< svg width = "100%" height = "100%" viewBox = "0 0 100 100" preserveAspectRatio = "none" className = "overflow-visible" >
< defs >
< linearGradient id = "trendGradient" x1 = "0%" y1 = "0%" x2 = "100%" y2 = "0%" >
< stop offset = "0%" stopColor = { result . decision === SignalType . BUY ? "#22c55e" : result . decision === SignalType . SELL ? "#ef4444" : "#71717a" } stopOpacity = "0" />
< stop offset = "100%" stopColor = { result . decision === SignalType . BUY ? "#22c55e" : result . decision === SignalType . SELL ? "#ef4444" : "#71717a" } stopOpacity = "0.8" />
</ linearGradient >
</ defs >
{ result . decision === SignalType . BUY && (
< motion.path
initial = {{ pathLength : 0 , opacity : 0 }}
animate = {{ pathLength : 1 , opacity : 1 }}
transition = {{ duration : 1.5 , ease : "easeInOut" , delay : 0.5 }}
d = "M 10 90 Q 40 80, 50 50 T 90 20"
fill = "none"
stroke = "url(#trendGradient)"
strokeWidth = "1.5"
strokeDasharray = "4 2"
/>
)}
{ result . decision === SignalType . SELL && (
< motion.path
initial = {{ pathLength : 0 , opacity : 0 }}
animate = {{ pathLength : 1 , opacity : 1 }}
transition = {{ duration : 1.5 , ease : "easeInOut" , delay : 0.5 }}
d = "M 10 20 Q 40 30, 50 60 T 90 90"
fill = "none"
stroke = "url(#trendGradient)"
strokeWidth = "1.5"
strokeDasharray = "4 2"
/>
)}
{ result . decision !== SignalType . WAIT && (
<>
< motion.circle
initial = {{ scale : 0 , opacity : 0 }}
animate = {{ scale : 1 , opacity : 1 }}
transition = {{ duration : 0.5 , delay : 2 }}
cx = "90"
cy = { result . decision === SignalType . BUY ? "20" : result . decision === SignalType . SELL ? "90" : "50" }
r = "1.5"
fill = { result . decision === SignalType . BUY ? "#22c55e" : result . decision === SignalType . SELL ? "#ef4444" : "#71717a" }
/>
< motion.text
initial = {{ opacity : 0 }}
animate = {{ opacity : 1 }}
transition = {{ duration : 0.5 , delay : 2 }}
x = "93"
y = { result . decision === SignalType . BUY ? "21.5" : result . decision === SignalType . SELL ? "91.5" : "51.5" }
fontSize = "3"
fill = "white"
fontWeight = "bold"
>
{ result . score } % OVERLAY DE PREVISÃO
</ motion.text >
</>
)}
</ svg >
</ div >
2026-04-28 20:32:34 +02:00
< div className = "absolute inset-0 z-20 p-6 flex flex-col justify-end" >
< div className = "flex flex-wrap gap-2 mb-2" >
< span className = "px-2 py-0.5 bg-brand-red/20 border border-brand-red/30 rounded text-[8px] font-bold text-brand-red uppercase tracking-tighter backdrop-blur-sm" >
Fluxo Institucional Detectado
</ span >
< span className = "px-2 py-0.5 bg-white/5 border border-white/10 rounded text-[8px] font-bold text-zinc-400 uppercase tracking-tighter backdrop-blur-sm" >
HFT Signature
</ span >
</ div >
< div className = "flex items-center justify-between" >
< div >
< p className = "text-xl font-black italic uppercase tracking-tighter text-white" >
{ result . pair } / { result . timeframe }
</ p >
< p className = "text-[10px] font-mono text-zinc-500 uppercase tracking-widest" >
Ref : AIS - BETA - { Math . floor ( Math . random () * 10000 )}
</ p >
</ div >
< div className = "text-right" >
< p className = "text-[10px] font-black text-brand-red uppercase" > Confirmação IA </ p >
< p className = "text-2xl font-black italic text-white leading-none" >{ result . score } % </ p >
</ div >
</ div >
</ div >
{ /* Decorative Scanning Line */ }
< motion.div
initial = {{ top : '0%' }}
animate = {{ top : '100%' }}
transition = {{ duration : 3 , repeat : Infinity , ease : 'linear' }}
className = "absolute left-0 right-0 h-px bg-brand-red/50 z-30 shadow-[0_0_10px_rgba(255,0,0,0.5)]"
/>
</ div >
</ div >
2026-05-07 19:02:04 +02:00
< div className = "flex flex-col gap-2" >
< button
onClick = { reset }
className = "w-full py-3 text-zinc-600 hover:text-white font-black text-xs uppercase tracking-widest transition-colors"
>
NOVO SCAN INSTITUCIONAL
</ button >
{ onGoToHistory && (
< button
onClick = { onGoToHistory }
className = "w-full py-2 text-zinc-500 hover:text-white font-medium text-[10px] uppercase tracking-widest transition-colors"
>
Ver Histórico de Sinais
</ button >
)}
</ div >
2026-04-28 20:32:34 +02:00
</ motion.div >
)}
</ div >
);
};