/* ============================================================ NeuralScout — Processing Timeline (How It Works) 5 steps, horizontal, with active-step animation ============================================================ */ function Timeline() { const [active, setActive] = React.useState(0); React.useEffect(() => { const id = setInterval(() => setActive(a => (a + 1) % 5), 3400); return () => clearInterval(id); }, []); const steps = [ { n: '01', title: 'RTSP feed', sub: 'Paste your stream URL — we connect in seconds.', render: () => }, { n: '02', title: 'Draw zones of interest', sub: 'Sketch polygons on any frame: lines, regions, counters.', render: () => }, { n: '03', title: 'Vision AI processes Stream', sub: 'Streams are processed at 30 fps with sub-100ms latency with 95% accuracy.', render: () => }, { n: '04', title: 'See it on the dashboard', sub: 'Charts, filters, alerts — grouped by camera, zone, time.', render: () => }, { n: '05', title: 'Ask the VLM', sub: 'Type a question. The model applies it to every frame.', render: () => }, ]; return (
{/* Connecting line */}
{steps.map((s, i) => (
{s.n}
))}
{steps.map((s, i) => (
{s.render()}
STEP_{s.n}

{s.title}

{s.sub}

))}
); } /* ---- step vizes ---- */ function StepRTSP({ active }) { const now = useTick(); const t = (now % 2200) / 2200; const text = 'rtsp://10.0.1.44:554/stream1'; const chars = active ? Math.floor(t * text.length * 1.3) : text.length; return (
CAMERA URL
{text.slice(0, chars)} {active && chars < text.length && }
● CONNECTED 30 FPS · 1080p
); } function StepZones({ active }) { const now = useTick(); const t = active ? (now % 3000) / 3000 : 1; const pts1 = [[15, 45], [65, 38], [72, 82], [18, 88]]; const pts2 = [[70, 20], [130, 25], [135, 62], [75, 60]]; const drawLen1 = Math.min(1, t * 1.4); const drawLen2 = active ? Math.max(0, Math.min(1, (t - 0.35) * 1.4)) : 1; const makePath = (pts, frac) => { if (frac <= 0) return ''; const n = pts.length; const total = n; const stop = frac * total; let d = `M ${pts[0][0]} ${pts[0][1]}`; for (let i = 1; i <= stop && i <= n; i++) { const idx = i % n; d += ` L ${pts[idx][0]} ${pts[idx][1]}`; } return d; }; return (
{/* mock feed */}
= 0.99 ? 'var(--ns-cyan-soft)' : 'none'} strokeDasharray="3 2" /> = 0.99 ? 'var(--ns-amber-soft)' : 'none'} strokeDasharray="3 2" /> {pts1.map(([x, y], i) => )} {pts2.map(([x, y], i) => )} {/* cursor */} {active && drawLen1 < 1 && ( )}
); } function StepQuery({ active }) { const now = useTick(); const q = 'flag anyone not wearing a hi-vis vest'; const t = active ? (now % 4000) / 4000 : 1; const chars = active ? Math.floor(t * q.length * 1.6) : q.length; return (
VLM · PROMPT
› {q.slice(0, chars)} {active && chars < q.length && }
{chars >= q.length && (
3 MATCHES active now
)}
); } function StepStream({ active }) { const now = useTick(); const t = (now % 1800) / 1800; return (
{/* Source */}
FEED
{/* Packets flowing */}
{active && [0, 0.33, 0.66].map((offset, i) => { const p = ((t + offset) % 1); return (
{Math.floor(p * 30)}ms
); })}
{/* Server */}
{active &&
}
SERVER
); } function StepDash({ active }) { const now = useTick(); const t = (now % 2500) / 2500; const bars = [0.3, 0.45, 0.6, 0.75, 0.85, 0.95, 0.7]; return (
TODAY
1,842
DWELL
3m 42s
{bars.map((b, i) => (
))}
); } const tlStyles = { wrap: { display: 'flex', flexDirection: 'column', gap: 32 }, track: { position: 'relative', display: 'grid', gridTemplateColumns: 'repeat(5, 1fr)', padding: '0 20px', }, line: { position: 'absolute', top: 14, left: 'calc(10% + 14px)', right: 'calc(10% + 14px)', height: 2, background: 'var(--ns-line)', borderRadius: 99, overflow: 'hidden', }, lineFill: { height: '100%', background: 'var(--ns-accent)', transition: 'width 0.6s var(--ns-ease)', boxShadow: '0 0 12px var(--ns-accent)', }, step: { display: 'flex', justifyContent: 'center', position: 'relative', zIndex: 1 }, dot: { width: 30, height: 30, borderRadius: 99, border: '2px solid var(--ns-line-2)', background: 'var(--ns-bg-3)', display: 'flex', alignItems: 'center', justifyContent: 'center', transition: 'all 0.4s var(--ns-ease)', }, grid: { display: 'grid', gridTemplateColumns: 'repeat(3, 1fr)', gap: 14 }, card: { border: '1px solid var(--ns-line)', background: 'var(--ns-bg-1)', borderRadius: 'var(--ns-r-3)', overflow: 'hidden', transition: 'all 0.4s var(--ns-ease)', }, cardViz: { aspectRatio: '16/7', borderBottom: '1px solid var(--ns-line)', overflow: 'hidden', position: 'relative', }, cardTitle: { fontSize: 17, margin: '2px 0 4px', fontWeight: 500, letterSpacing: '-0.01em', }, cardSub: { fontSize: 13, color: 'var(--ns-fg-dim)', margin: 0, lineHeight: 1.5, textWrap: 'pretty', }, }; window.Timeline = Timeline;