'use client' import { useState, useRef, useCallback, useEffect, DragEvent } from 'react' import { useRouter } from 'next/navigation' // Determine API_BASE: if the baked-in env var is defined, use it. // Otherwise, in the browser, default to empty string (same-origin). const getApiBase = () => { if (process.env.NEXT_PUBLIC_API_URL) return process.env.NEXT_PUBLIC_API_URL; if (typeof window !== 'undefined') return ''; // Production same-origin return 'http://localhost:8000'; // SSR fallback }; const API_BASE = getApiBase(); const VOC_CLASSES = [ { name: 'aeroplane', color: '#87CEEB' }, { name: 'bicycle', color: '#FFA500' }, { name: 'bird', color: '#FFD700' }, { name: 'boat', color: '#00BFFF' }, { name: 'bottle', color: '#9400D3' }, { name: 'bus', color: '#FF1493' }, { name: 'car', color: '#DC143C' }, { name: 'cat', color: '#FF8C00' }, { name: 'chair', color: '#8B4513' }, { name: 'cow', color: '#D4A017' }, { name: 'diningtable', color: '#D2691E' },{ name: 'dog', color: '#BA55D3' }, { name: 'horse', color: '#FF69B4' }, { name: 'motorbike', color: '#22c55e' }, { name: 'person', color: '#FF4500' }, { name: 'potted plant', color: '#228B22' }, { name: 'sheep', color: '#B8A40A' }, { name: 'sofa', color: '#00CED1' }, { name: 'train', color: '#3b82f6' }, { name: 'tv/monitor', color: '#0D9488' }, ] const STEPS = [ { num: '01', title: 'Upload', desc: 'Drag & drop or select your video file' }, { num: '02', title: 'Process', desc: 'SegVision Engine segments every frame with high precision' }, { num: '03', title: 'Download', desc: 'Get H.264 side-by-side comparison MP4' }, ] const FEATURES = [ { icon: '🎯', title: '21 Object Classes', desc: 'Identifies people, cars, animals, furniture & more using our Neural Engine.', tag: 'SegVision' }, { icon: '⚑', title: 'GPU Accelerated', desc: 'CUDA-powered inference for real-time frame-by-frame segmentation.', tag: 'PyTorch' }, { icon: '🎬', title: 'Side-by-Side Output', desc: 'Original and segmented frames combined into one comparison video.', tag: 'H.264 MP4' }, { icon: 'πŸ“‘', title: 'Live Progress', desc: 'Real-time WebSocket updates showing segmentation progress as it runs.', tag: 'WebSocket' }, ] const formatBytes = (b: number) => b < 1024*1024 ? `${(b/1024).toFixed(1)} KB` : `${(b/(1024*1024)).toFixed(1)} MB` /* ── Scroll animation hook ──────────────────────────────────────────────────── */ function useScrollReveal() { useEffect(() => { const targets = document.querySelectorAll('.scroll-hidden, .scroll-left, .scroll-right, .scroll-scale') const observer = new IntersectionObserver( (entries) => entries.forEach(e => { if (e.isIntersecting) { e.target.classList.add('scroll-visible') observer.unobserve(e.target) } }), { threshold: 0.12 } ) targets.forEach(t => observer.observe(t)) return () => observer.disconnect() }, []) } export default function HomePage() { const router = useRouter() const fileInputRef = useRef(null) const [dragging, setDragging] = useState(false) const [file, setFile] = useState(null) const [preview, setPreview] = useState(null) const [uploading, setUploading] = useState(false) const [error, setError] = useState(null) useScrollReveal() const validate = (f: File) => { if (!f.name.match(/\.(mp4|mov|avi|webm|mkv)$/i)) return 'Only MP4, MOV, AVI, WebM, MKV supported.' if (f.size > 200 * 1024 * 1024) return 'File too large. Max 200 MB.' return null } const selectFile = useCallback((f: File) => { const err = validate(f); if (err) { setError(err); return } setError(null); setFile(f); setPreview(URL.createObjectURL(f)) }, []) const onDrop = useCallback((e: DragEvent) => { e.preventDefault(); setDragging(false) const f = e.dataTransfer.files[0]; if (f) selectFile(f) }, [selectFile]) const handleUpload = async () => { if (!file) return setUploading(true); setError(null) try { const form = new FormData(); form.append('file', file) const endpoint = API_BASE ? `${API_BASE}/api/upload` : 'api/upload' const res = await fetch(endpoint, { method: 'POST', body: form }) if (!res.ok) { const d = await res.json(); throw new Error(d.detail ?? 'Upload failed') } const data = await res.json() router.push(`/processing?id=${data.job_id}`) } catch (e: any) { setError(e.message ?? 'Upload failed. Is the backend running?') setUploading(false) } } return (
{/* ── Hero ───────────────────────────────────────────────────────────────── */}
{/* Badge β€” animates immediately */}
Proprietary Neural Engine Β· Neural Core v1.0 Β· 21+ Recognition Classes
{/* Headline */}

{'Video'.split('').map((c,i) => ( {c === ' ' ? '\u00a0' : c} ))}
Segmentation

Upload any video and watch SegVision identify, colour, and label every object in real-time β€” delivered as a stunning side-by-side comparison.

{/* CTA scroll hint */}
{/* ── How it Works ──────────────────────────────────────────────────────── */}

How it works

{STEPS.map((step, i) => (
{step.num}

{step.title}

{step.desc}

))}
{/* ── Upload Card ───────────────────────────────────────────────────────── */}

Upload your video

{/* Moving border card β€” clean white */}
{!file ? (
{ e.preventDefault(); setDragging(true) }} onDragLeave={() => setDragging(false)} onDrop={onDrop} onClick={() => fileInputRef.current?.click()} > {/* Upload icon */}

{dragging ? 'Drop to upload' : 'Drop video here'}

or click to browse Β· Max 200 MB

{['MP4', 'MOV', 'AVI', 'WebM', 'MKV'].map(f => ( {f} ))}
) : (

{file.name}

{formatBytes(file.size)}

)} { const f = e.target.files?.[0]; if (f) selectFile(f) }} /> {error && (
{error}
)}
{/* ── Feature Cards ─────────────────────────────────────────────────────── */}

Features

PyTorch Β· FastAPI Β· Next.js
{FEATURES.map((f, i) => (
{f.icon}
{f.tag}

{f.title}

{f.desc}

))}
{/* ── Class Palette ─────────────────────────────────────────────────────── */}

Detectable Objects

{VOC_CLASSES.length} classes
{VOC_CLASSES.map((c) => ( {c.name} ))}
) }