import { useEffect, useRef } from 'react'; import type { AnalysisResult } from '../types'; interface ResultSectionProps { result: AnalysisResult; onReset: () => void; } function fmtVal(v: unknown) { if (v === null || v === undefined) return '—'; return String(v); } // Neumorphic card style — dark purple variant const neuCard: React.CSSProperties = { borderRadius: 30, background: '#120a24', boxShadow: '15px 15px 30px #0a0618, -15px -15px 30px #1a0e30', }; const neuCardAccent = (color: string): React.CSSProperties => ({ borderRadius: 30, background: '#120a24', boxShadow: `15px 15px 30px #0a0618, -15px -15px 30px #1a0e30, 0 0 0 1px ${color}22`, }); export default function ResultSection({ result, onReset }: ResultSectionProps) { const isFake = result.result === 'FAKE'; const pct = result.confidence; const accentColor = isFake ? '#ff3355' : '#a855f7'; const accentGlow = isFake ? 'rgba(255,51,85,0.4)' : 'rgba(168,85,247,0.4)'; const timelineRef = useRef(null); useEffect(() => { const container = timelineRef.current; if (!container) return; container.innerHTML = ''; const frames = result.frame_timeline ?? []; if (!frames.length) { container.innerHTML = 'No per-frame data available'; return; } const W = container.clientWidth || 600; const H = 120; const PAD = { top: 12, right: 16, bottom: 24, left: 32 }; const chartW = W - PAD.left - PAD.right; const chartH = H - PAD.top - PAD.bottom; const scores = frames.map(f => f.fake_pct / 100); const n = scores.length; // x/y helpers const xOf = (i: number) => PAD.left + (i / Math.max(n - 1, 1)) * chartW; const yOf = (v: number) => PAD.top + (1 - v) * chartH; // Smooth catmull-rom path const smooth = (pts: [number, number][]) => { if (pts.length < 2) return ''; let d = `M ${pts[0][0]},${pts[0][1]}`; for (let i = 0; i < pts.length - 1; i++) { const p0 = pts[Math.max(i - 1, 0)]; const p1 = pts[i]; const p2 = pts[i + 1]; const p3 = pts[Math.min(i + 2, pts.length - 1)]; const cp1x = p1[0] + (p2[0] - p0[0]) / 6; const cp1y = p1[1] + (p2[1] - p0[1]) / 6; const cp2x = p2[0] - (p3[0] - p1[0]) / 6; const cp2y = p2[1] - (p3[1] - p1[1]) / 6; d += ` C ${cp1x},${cp1y} ${cp2x},${cp2y} ${p2[0]},${p2[1]}`; } return d; }; const pts: [number, number][] = scores.map((v, i) => [xOf(i), yOf(v)]); const linePath = smooth(pts); const areaPath = linePath + ` L ${xOf(n - 1)},${yOf(0)} L ${xOf(0)},${yOf(0)} Z`; const gradId = `tl-grad-${Date.now()}`; const fakeColor = isFake ? '#ff3355' : '#a855f7'; const fakeColorMid = isFake ? 'rgba(255,51,85,0.35)' : 'rgba(168,85,247,0.35)'; const fakeColorEnd = isFake ? 'rgba(255,51,85,0.0)' : 'rgba(168,85,247,0.0)'; const svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg'); svg.setAttribute('width', '100%'); svg.setAttribute('height', String(H)); svg.setAttribute('viewBox', `0 0 ${W} ${H}`); svg.style.overflow = 'visible'; // Defs: gradient + glow filter svg.innerHTML = ` ${[0, 0.25, 0.5, 0.75, 1].map(v => ` ${Math.round(v * 100)}% `).join('')} 50% ${scores.map((v, i) => { const x = xOf(i); const y = yOf(v); const hot = v > 0.5; return ` `; }).join('')} ${scores.map((_, i) => { if (i % Math.max(1, Math.floor(n / 8)) !== 0 && i !== n - 1) return ''; return `F${i + 1}`; }).join('')} `; container.appendChild(svg); }, [result, isFake]); const metaItems = [ ['Frames Analyzed', fmtVal(result.metadata?.frames_analyzed)], ['Duration', result.metadata?.video_duration_sec ? result.metadata.video_duration_sec + 's' : '—'], ['FPS', fmtVal(result.metadata?.video_fps)], ['Resolution', fmtVal(result.metadata?.resolution)], ['Processing Time', result.processing_time_sec ? result.processing_time_sec + 's' : '—'], ]; const audioLabel: Record = { HUMAN_VOICE: 'Human Voice', AI_VOICE: 'AI Voice', AV_MISMATCH: 'AV Mismatch', NO_AUDIO: 'No Audio', }; return (
{/* ── Verdict card ── */}
{/* Badge */}
{isFake ? '⚠' : '✓'} {isFake ? 'DEEPFAKE' : 'AUTHENTIC'}
{/* Confidence */}
Confidence Score {pct}%
{/* Bar track — inset neumorphic */}
{/* Risk badge */}
= 65 ? '#ff3355' : pct >= 35 ? '#f59e0b' : '#a855f7') : '#a855f7', background: '#120a24', boxShadow: `4px 4px 10px #0a0618, -4px -4px 10px #1a0e30`, border: `1px solid ${ isFake ? (pct >= 65 ? 'rgba(255,51,85,0.3)' : pct >= 35 ? 'rgba(245,158,11,0.3)' : 'rgba(168,85,247,0.3)') : 'rgba(168,85,247,0.3)' }`, }}> {isFake ? (pct >= 65 ? 'CRITICAL RISK' : pct >= 35 ? 'MEDIUM RISK' : 'LOW RISK') : (pct >= 65 ? 'VERIFIED AUTHENTIC' : 'LIKELY AUTHENTIC') }
{result.audio?.available && (
{result.audio.result === 'HUMAN_VOICE' ? 'mic' : result.audio.result === 'AI_VOICE' ? 'smart_toy' : 'warning'} Audio: {audioLabel[result.audio.result] ?? result.audio.result} ({result.audio.confidence?.toFixed(1)}% conf)
)} {result.metadata_check?.c2pa_detected && (
verified C2PA metadata detected — AI-generated content signature found {result.metadata_check.tool_detected && ( ({result.metadata_check.tool_detected}) )}
)}
{/* ── Insights + Metadata row ── */}
{/* Insights */}

analytics Analysis Insights

{(result.details ?? ['Analysis completed.']).map((txt, i) => (
{txt}
))}
{/* Metadata */}

info Video Metadata

{metaItems.map(([k, v]) => (
{k} {v}
))} {result.cached && (
cached Result served from cache
)}
{/* ── Frame timeline ── */}

timeline Frame-by-Frame Analysis

{/* ── Action button ── */}
); }