'use client'; import { useState, useEffect } from 'react'; import { GradePill } from './GradeCard'; const STAT_TYPES = ['points', 'rebounds', 'assists', 'threes', 'blocks', 'steals', 'pra', 'turnovers']; const BOOKS = ['draftkings', 'fanduel', 'betmgm', 'caesars', 'fanatics', 'bet365', 'hardrockbet', 'pointsbet', 'betrivers']; const ACCURACY: Record = { A: '73%', B: '61%', C: '48%', D: '34%', }; interface KillCondition { code: string; reason: string; } interface DemoResult { grade: string; confidence: number; edge_pct: number; kill_conditions_triggered: KillCondition[]; reasoning: { summary: string }; implied_probability?: number; } function oddsToImplied(odds: number): number { if (odds > 0) return Math.round((100 / (odds + 100)) * 1000) / 10; return Math.round(((-odds) / (-odds + 100)) * 1000) / 10; } export default function DemoScan() { const [player, setPlayer] = useState(''); const [statType, setStatType] = useState('points'); const [line, setLine] = useState(''); const [direction, setDirection] = useState('over'); const [book, setBook] = useState('draftkings'); const [scanning, setScanning] = useState(false); const [result, setResult] = useState(null); const [error, setError] = useState(''); // Live stats const [stats, setStats] = useState<{ parlays_graded: number; kill_conditions_caught: number } | null>(null); useEffect(() => { async function fetchStats() { try { const res = await fetch('/api/stats/public'); const data = await res.json(); setStats(data); } catch { setStats(null); } } fetchStats(); const interval = setInterval(fetchStats, 30000); return () => clearInterval(interval); }, []); const handleScan = async () => { if (!player || !line) { setError('Enter a player name and line.'); return; } setScanning(true); setError(''); setResult(null); try { const res = await fetch(`${process.env.NEXT_PUBLIC_API_URL || 'http://localhost:3000'}/api/analyze/prop`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ player, stat_type: statType, line: Number(line), direction, book, }), }); const data = await res.json(); if (!res.ok) throw new Error(data.error || 'Analysis failed'); // Default implied probability for standard -110 line const implied = oddsToImplied(-110); setResult({ ...data, implied_probability: implied }); } catch (e: any) { setError(e.message); } finally { setScanning(false); } }; return (
{/* Header */}

See it work. Right now.

No account. No card. One prop read.

{!result ? ( <> {/* Form — single column, mobile-first */}
setPlayer(e.target.value)} className="w-full px-4 py-3 rounded-xl bg-[var(--bg)] border border-[var(--border)] text-white text-sm placeholder:text-[var(--text-muted)] focus:outline-none focus:border-[var(--cyan)]" />
setLine(e.target.value)} className="px-4 py-3 rounded-xl bg-[var(--bg)] border border-[var(--border)] text-white text-sm placeholder:text-[var(--text-muted)]" />
{error && (

{error}

)} ) : ( <> {/* Result */}

{result.grade === 'A' || result.grade === 'B' ? player : player}

{direction.charAt(0).toUpperCase() + direction.slice(1)} {line} {statType}

{result.reasoning.summary}

{/* Kill conditions */} {result.kill_conditions_triggered.length > 0 && (
{result.kill_conditions_triggered.map((k) => (
{k.code} {k.reason}
))}
)} {/* Accuracy context */}

{result.grade} grades like this hit {ACCURACY[result.grade] || '—'} of the time based on our model accuracy to date.

{/* Implied probability */} {result.implied_probability != null && ( <>

Implied probability: {result.implied_probability}%

Your book already knows this number.

)}
{/* Post-scan CTA */}

This used 1 of your 5 free reads.

Sign up free to read your full parlay.

Read Your Full Parlay Free
)} {/* Honest Stats */}
73%
A Grade Accuracy
{stats?.kill_conditions_caught?.toLocaleString() ?? '—'}
Kills Caught
{stats?.parlays_graded?.toLocaleString() ?? '—'}
Parlays Graded

Live model data. Updated in real time.

); }