import { useEffect, useRef } from 'react' import anime from 'animejs' // Per-source display config const SOURCE = { ml: { icon: '🤖', title: 'AI Model', desc: 'Machine learning trained on 5 years of price history', plain: { BUY: 'Our AI sees patterns similar to when prices rose in the past', 'STRONG BUY': 'Our AI is strongly predicting upward movement', SELL: 'Our AI sees patterns similar to when prices dropped in the past', 'STRONG SELL':'Our AI is strongly predicting downward movement', HOLD: 'Our AI is not confident enough to call a direction yet', }, }, technical: { icon: '📊', title: 'Chart Analysis', desc: 'Price trends, momentum, moving averages & volume', plain: { BUY: 'Price is trending up with positive momentum', 'STRONG BUY': 'Strong uptrend — multiple chart indicators agree', SELL: 'Price is trending down — momentum looks negative', 'STRONG SELL':'Strong downtrend — charts are flashing warning signs', HOLD: 'Chart signals are mixed — no clear trend right now', }, }, fundamental: { icon: '🏢', title: 'Company Health', desc: 'P/E ratio, revenue growth, profit margins & debt', plain: { undervalued: 'The company looks like a potential bargain vs similar companies', overvalued: 'The stock may be expensive compared to what the company actually earns', 'fair value': 'The company is priced fairly for its size and earnings', unknown: 'Not enough financial data to form a clear view', }, }, sentiment: { icon: '📰', title: 'News & Mood', desc: 'Recent headlines and investor sentiment from the web', plain: { BULLISH: 'Recent news and investor mood are mostly positive', BEARISH: 'Recent news and investor mood are mostly negative', NEUTRAL: 'News is balanced — no strong positive or negative lean', }, }, } // Signal → colour function signalColor(score) { if (score > 0.2) return '#00e87a' if (score < -0.2) return '#ff4466' return '#ffcc44' } // Normalise signal text for display function formatSignal(signal) { return (signal || 'HOLD').toString().toUpperCase() } // ── Individual card ─────────────────────────────────────────────────────────── function SignalCard({ sourceKey, sourceData, index }) { const barRef = useRef(null) const cfg = SOURCE[sourceKey] || SOURCE.technical const score = sourceData.score ?? 0 const signal = sourceData.signal ?? 'HOLD' const weight = Math.round((sourceData.weight ?? 0) * 100) const color = signalColor(score) const barPct = Math.round((score + 1) * 50) // -1..+1 → 0..100% // Pick plain-English text const plainMap = cfg.plain const signalKey = signal.toString() const plainText = plainMap[signalKey] || plainMap[signalKey.toUpperCase()] || Object.values(plainMap)[1] || '' useEffect(() => { anime({ targets: barRef.current, width: ['0%', `${barPct}%`], delay: 700 + index * 120, duration: 1000, easing: 'easeOutExpo', }) }, []) return (
{cfg.desc}
{plainText}
{/* ML extras */} {sourceKey === 'ml' && (