Spaces:
Sleeping
Sleeping
| import React, { useState, useEffect, useRef, useCallback } from 'react'; | |
| import { useInView } from 'react-intersection-observer'; | |
| const TERMINAL_LINES = [ | |
| { text: '$ psql -d neuralvault', color: 'var(--text3)' }, | |
| { text: 'psql (17.2)', color: 'var(--text3)' }, | |
| { text: '', color: '' }, | |
| { text: "INSERT INTO reviews (text, customer_id)", color: '#569CD6' }, | |
| { text: "VALUES ('Product stopped working after 1 week', 'cust_8821');", color: '#569CD6' }, | |
| { text: '', color: '' }, | |
| { text: '-- [TRIGGER] ai_sentiment_analyzer FIRES', color: 'var(--amber)' }, | |
| { text: 'DistilBERT inference ............. 0.031ms ✓', color: 'var(--green)' }, | |
| { text: 'sentiment_score: -0.89 → stored', color: 'var(--green)' }, | |
| { text: '-- [TRIGGER] ai_embedder FIRES', color: 'var(--amber)' }, | |
| { text: 'text-embedding-3-small ........... 0.44ms ✓', color: 'var(--green)' }, | |
| { text: 'embedding: [0.231, -0.445, 0.882, ...+1533 dims] → pgvector', color: 'var(--purple-l)' }, | |
| { text: '-- [TRIGGER] xgboost_fraud_scorer FIRES', color: 'var(--amber)' }, | |
| { text: 'XGBoost inference ................. 0.008ms ✓', color: 'var(--green)' }, | |
| { text: 'fraud_score: 0.14 → stored', color: 'var(--green)' }, | |
| { text: '', color: '' }, | |
| { text: 'COMMIT — 3 AI models, 1 transaction, 0.48ms total ✓', color: 'var(--green)' }, | |
| { text: 'INSERT 1', color: 'var(--text)' }, | |
| ]; | |
| function CountUp({ end, suffix = '', duration = 1500 }) { | |
| const [value, setValue] = useState(0); | |
| const { ref, inView } = useInView({ triggerOnce: true, threshold: 0.5 }); | |
| const hasAnimated = useRef(false); | |
| useEffect(() => { | |
| if (inView && !hasAnimated.current) { | |
| hasAnimated.current = true; | |
| const startTime = performance.now(); | |
| const animate = (currentTime) => { | |
| const elapsed = currentTime - startTime; | |
| const progress = Math.min(elapsed / duration, 1); | |
| const eased = 1 - Math.pow(1 - progress, 3); // ease-out cubic | |
| setValue(Math.round(eased * end)); | |
| if (progress < 1) requestAnimationFrame(animate); | |
| }; | |
| requestAnimationFrame(animate); | |
| } | |
| }, [inView, end, duration]); | |
| return <span ref={ref}>{value}{suffix}</span>; | |
| } | |
| export default function Hero({ onLaunchDemo, onViewBenchmarks }) { | |
| const [displayedLines, setDisplayedLines] = useState([]); | |
| const [currentCharIndex, setCurrentCharIndex] = useState(0); | |
| const [currentLineIndex, setCurrentLineIndex] = useState(0); | |
| const [isTyping, setIsTyping] = useState(true); | |
| const timeoutRef = useRef(null); | |
| const resetTerminal = useCallback(() => { | |
| setDisplayedLines([]); | |
| setCurrentLineIndex(0); | |
| setCurrentCharIndex(0); | |
| setIsTyping(true); | |
| }, []); | |
| useEffect(() => { | |
| if (!isTyping) return; | |
| if (currentLineIndex >= TERMINAL_LINES.length) { | |
| setIsTyping(false); | |
| timeoutRef.current = setTimeout(resetTerminal, 4000); | |
| return; | |
| } | |
| const line = TERMINAL_LINES[currentLineIndex]; | |
| if (line.text === '') { | |
| setDisplayedLines((prev) => [...prev, { text: '', color: '' }]); | |
| setCurrentLineIndex((i) => i + 1); | |
| setCurrentCharIndex(0); | |
| return; | |
| } | |
| if (currentCharIndex === 0) { | |
| setDisplayedLines((prev) => [...prev, { text: '', color: line.color }]); | |
| } | |
| if (currentCharIndex < line.text.length) { | |
| timeoutRef.current = setTimeout(() => { | |
| setDisplayedLines((prev) => { | |
| const updated = [...prev]; | |
| const last = updated[updated.length - 1]; | |
| updated[updated.length - 1] = { ...last, text: line.text.substring(0, currentCharIndex + 1) }; | |
| return updated; | |
| }); | |
| setCurrentCharIndex((c) => c + 1); | |
| }, 18); | |
| } else { | |
| setCurrentLineIndex((i) => i + 1); | |
| setCurrentCharIndex(0); | |
| } | |
| return () => { if (timeoutRef.current) clearTimeout(timeoutRef.current); }; | |
| }, [currentLineIndex, currentCharIndex, isTyping, resetTerminal]); | |
| const stats = [ | |
| { number: 50, suffix: 'M+', label: 'vectors, sub-100ms p99', sub: 'pgvector + pgvectorscale benchmark' }, | |
| { number: 28, suffix: '×', label: 'lower p95 vs Pinecone', sub: 'at 99% recall, 75% lower cost' }, | |
| { number: 0.48, suffix: 'ms', label: '3 AI models per INSERT', sub: 'DistilBERT + embedding + XGBoost', isDecimal: true }, | |
| ]; | |
| return ( | |
| <div className="nv-section" data-testid="hero-section" style={{ paddingTop: 100, paddingBottom: 80 }}> | |
| <div className="nv-container" style={{ textAlign: 'center' }}> | |
| {/* Eyebrow badge */} | |
| <div style={{ marginBottom: 20 }}> | |
| <span className="nv-badge nv-badge-teal">Production ML Infrastructure</span> | |
| </div> | |
| {/* Headline */} | |
| <h1 style={{ fontFamily: 'var(--font-heading)', fontWeight: 700, fontSize: 'clamp(36px, 6vw, 64px)', lineHeight: 1.05, color: 'var(--text)', marginBottom: 20 }}> | |
| The Database That Thinks. | |
| </h1> | |
| {/* Subheadline */} | |
| <p style={{ fontFamily: 'var(--font-body)', fontSize: 18, color: 'var(--text2)', maxWidth: 560, margin: '0 auto 40px', lineHeight: 1.7 }}> | |
| PostgreSQL 17 + pgvector + AI triggers + LangGraph. | |
| One system. No separate vector DB. No separate graph DB. | |
| No separate search index. Real benchmark data. Real failure analysis. | |
| </p> | |
| {/* Animated Terminal */} | |
| <div className="nv-terminal" data-testid="hero-terminal" style={{ maxWidth: 620, margin: '0 auto 40px' }}> | |
| <div className="nv-terminal-header"> | |
| <div style={{ display: 'flex', gap: 6 }}> | |
| <div className="nv-terminal-dot" style={{ background: 'var(--red)' }} /> | |
| <div className="nv-terminal-dot" style={{ background: 'var(--amber)' }} /> | |
| <div className="nv-terminal-dot" style={{ background: 'var(--green)' }} /> | |
| </div> | |
| <span style={{ fontSize: 11, color: 'var(--text3)', fontFamily: 'var(--font-mono)' }}>neuralvault — zsh — 80×24</span> | |
| </div> | |
| <div className="nv-terminal-body" style={{ minHeight: 340 }}> | |
| {displayedLines.map((line, i) => ( | |
| <div key={i} style={{ color: line.color || 'var(--text)', minHeight: line.text === '' ? 18 : 'auto' }}> | |
| {line.text} | |
| </div> | |
| ))} | |
| {isTyping && <span className="nv-cursor" />} | |
| </div> | |
| </div> | |
| {/* Stat cards */} | |
| <div style={{ display: 'flex', justifyContent: 'center', gap: 16, marginBottom: 40, flexWrap: 'wrap' }}> | |
| {stats.map((stat, i) => ( | |
| <div key={i} className="nv-card" style={{ padding: '20px 32px', textAlign: 'center', minWidth: 180 }}> | |
| <div style={{ fontFamily: 'var(--font-heading)', fontWeight: 700, fontSize: 32, color: 'var(--purple-l)', marginBottom: 4 }}> | |
| {stat.isDecimal ? ( | |
| <span>0.48ms</span> | |
| ) : ( | |
| <CountUp end={stat.number} suffix={stat.suffix} /> | |
| )} | |
| </div> | |
| <div style={{ fontSize: 14, color: 'var(--text)', marginBottom: 2 }}>{stat.label}</div> | |
| <div style={{ fontSize: 12, color: 'var(--text3)' }}>{stat.sub}</div> | |
| </div> | |
| ))} | |
| </div> | |
| {/* CTAs */} | |
| <div style={{ display: 'flex', justifyContent: 'center', gap: 12 }}> | |
| <button className="nv-btn-primary" onClick={onLaunchDemo} data-testid="hero-launch-demo"> | |
| Launch Live Demo → | |
| </button> | |
| <button className="nv-btn-ghost" onClick={onViewBenchmarks} data-testid="hero-view-benchmarks"> | |
| View Benchmarks ↓ | |
| </button> | |
| </div> | |
| </div> | |
| </div> | |
| ); | |
| } | |