| import { useState } from 'react'; |
| import { motion, AnimatePresence } from 'framer-motion'; |
| import { ChevronDown, Code2, BarChart3, Database, AlertOctagon } from 'lucide-react'; |
| import styles from './TechnicalMetadata.module.css'; |
|
|
| function Section({ icon: Icon, title, defaultOpen = false, children }) { |
| const [open, setOpen] = useState(defaultOpen); |
|
|
| return ( |
| <div className={styles.section}> |
| <button |
| className={styles.sectionToggle} |
| onClick={() => setOpen(!open)} |
| aria-expanded={open} |
| > |
| <div className={styles.sectionLeft}> |
| <div className={styles.sectionIcon}><Icon size={14} /></div> |
| <span>{title}</span> |
| </div> |
| <ChevronDown |
| size={15} |
| style={{ transform: open ? 'rotate(180deg)' : 'rotate(0deg)', transition: 'transform 0.2s ease', color: 'var(--text-muted)' }} |
| /> |
| </button> |
| |
| <AnimatePresence> |
| {open && ( |
| <motion.div |
| className={styles.sectionBody} |
| initial={{ height: 0, opacity: 0 }} |
| animate={{ height: 'auto', opacity: 1 }} |
| exit={{ height: 0, opacity: 0 }} |
| transition={{ duration: 0.2 }} |
| > |
| <div className={styles.sectionContent}>{children}</div> |
| </motion.div> |
| )} |
| </AnimatePresence> |
| </div> |
| ); |
| } |
|
|
| function MetaRow({ label, value, mono = false, highlight = false }) { |
| return ( |
| <div className={`${styles.metaRow} ${highlight ? styles.metaRowHighlight : ''}`}> |
| <span className={styles.metaLabel}>{label}</span> |
| <span className={`${styles.metaValue} ${mono ? 'font-mono' : ''}`}>{value}</span> |
| </div> |
| ); |
| } |
|
|
| export default function TechnicalMetadata({ result }) { |
| return ( |
| <div className={styles.wrapper}> |
| <div className={styles.header}> |
| <Code2 size={15} /> |
| <span>Technical Metadata</span> |
| <span className={styles.headerNote}>Generative fingerprints & frequency signals</span> |
| </div> |
| |
| <div className={styles.sections}> |
| {/* FFT Analysis */} |
| <Section icon={BarChart3} title="Frequency Analysis (FFT)" defaultOpen={true}> |
| <MetaRow label="Spectral Anomaly" value={result.fft.spectralAnomaly ? '⚠ Detected' : '✓ None'} highlight={result.fft.spectralAnomaly} /> |
| <MetaRow label="Peak Frequency" value={result.fft.peakFrequency} mono /> |
| <MetaRow label="Anomaly Bands" value={result.fft.anomalyBands.join(', ')} mono /> |
| <MetaRow label="DCT Coefficients" value={result.fft.dctCoefficients} /> |
| <MetaRow label="Noise Pattern" value={result.fft.noisePattern} /> |
| </Section> |
| |
| {/* Artifacts */} |
| <Section icon={AlertOctagon} title="Generative Fingerprints" defaultOpen={true}> |
| {result.artifacts.map((a) => ( |
| <div key={a.id} className={styles.artifactDetail}> |
| <div className={styles.artifactDetailHeader}> |
| <span className={`${styles.sev} ${styles['sev_' + a.severity]}`}>{a.severity.toUpperCase()}</span> |
| <span className={styles.artifactDetailType}>{a.type}</span> |
| </div> |
| <p className={styles.artifactDetailText}>{a.detail}</p> |
| </div> |
| ))} |
| </Section> |
| |
| {/* File metadata */} |
| <Section icon={Database} title="File Metadata"> |
| {Object.entries(result.metadata) |
| .filter(([key]) => key !== 'heatmapPreview') |
| .map(([key, val]) => ( |
| <MetaRow |
| key={key} |
| label={key.replace(/([A-Z])/g, ' $1').replace(/^./, s => s.toUpperCase())} |
| value={val} |
| mono |
| /> |
| ))} |
| </Section> |
| </div> |
| </div> |
| ); |
| } |
|
|