32069863dc
- Backend: whitelist MLB stat_types in analyze.js + scan.js gates (mirrors python validation.py); fixes MLB scans 400ing. - Routes: /settings -> /profile, /report -> /blog redirect pages. - Profile: read tier from useAuth().tier (nav's source) to kill the Free-vs-DESK mismatch. - Fonts: self-host Inter/JetBrains Mono/IBM Plex Mono via next/font, drop the 503ing fonts.googleapis.com <link>; rewire literal font-family refs to vars. - Kept /settings/security (real MFA page) intact — NOT clobbered to a redirect. - +33 tests (1907 -> 1940), 149 suites; web build clean (exit 0). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
188 lines
5.7 KiB
TypeScript
188 lines
5.7 KiB
TypeScript
'use client';
|
|
|
|
// Session 16 — the floating demo card on the right side of the hero
|
|
// is now driven by /api/hero-prop. Live prop + grade renders with a
|
|
// glitch/blur overlay on the reasoning. Falls back to the static
|
|
// Jokic layout when no live odds are available.
|
|
import LiveHeroProp from './LiveHeroProp';
|
|
|
|
export default function Hero() {
|
|
return (
|
|
<section className="radial-glow diagonal-cut" style={{ position: 'relative', overflow: 'hidden' }}>
|
|
<div
|
|
style={{
|
|
maxWidth: 1200,
|
|
margin: '0 auto',
|
|
padding: '96px 24px 64px',
|
|
display: 'grid',
|
|
gap: 48,
|
|
alignItems: 'center',
|
|
}}
|
|
className="hero-grid"
|
|
>
|
|
<div className="animate-fade-up" style={{ maxWidth: 800 }}>
|
|
<span
|
|
className="mono"
|
|
style={{
|
|
display: 'inline-block',
|
|
padding: '4px 12px',
|
|
fontSize: 11,
|
|
fontWeight: 700,
|
|
letterSpacing: '0.10em',
|
|
borderRadius: 999,
|
|
background: 'var(--accent-glow)',
|
|
color: 'var(--grade-a)',
|
|
marginBottom: 24,
|
|
textTransform: 'uppercase',
|
|
}}
|
|
>
|
|
EVERY SPORT · EVERY PROP
|
|
</span>
|
|
<h1
|
|
className="text-balance"
|
|
style={{
|
|
fontSize: 'clamp(36px, 6vw, 64px)',
|
|
fontWeight: 700,
|
|
letterSpacing: '-0.03em',
|
|
lineHeight: 1.05,
|
|
marginBottom: 20,
|
|
}}
|
|
>
|
|
The books have every advantage.<br />
|
|
<span style={{ color: 'var(--grade-a)' }}>We built this to give it back.</span>
|
|
</h1>
|
|
<p
|
|
className="text-pretty"
|
|
style={{
|
|
fontSize: 18,
|
|
color: 'var(--text-secondary)',
|
|
lineHeight: 1.6,
|
|
marginBottom: 32,
|
|
maxWidth: 600,
|
|
}}
|
|
>
|
|
Grade your props across every sport with intelligence the books don't want you to have.
|
|
Forty-plus factors. Kill conditions. Alt-line ladders. The honest ledger.
|
|
</p>
|
|
<SportBadgeStrip />
|
|
|
|
<div style={{ display: 'flex', gap: 12, flexWrap: 'wrap', marginTop: 24 }}>
|
|
<a href="/signup" className="btn-primary" style={{ padding: '14px 28px', fontSize: 15 }}>
|
|
Get Started — Free
|
|
</a>
|
|
<a href="/ledger" className="btn-ghost" style={{ padding: '14px 28px', fontSize: 15 }}>
|
|
See the Ledger
|
|
</a>
|
|
</div>
|
|
<p style={{ fontSize: 13, color: 'var(--text-tertiary)', marginTop: 16 }}>
|
|
5 free reads every month. No credit card. Cancel anytime.
|
|
</p>
|
|
</div>
|
|
|
|
<LiveHeroProp />
|
|
</div>
|
|
<p
|
|
style={{
|
|
textAlign: 'center',
|
|
fontSize: 12,
|
|
color: 'var(--text-tertiary)',
|
|
padding: '0 24px 32px',
|
|
maxWidth: 600,
|
|
margin: '0 auto',
|
|
}}
|
|
>
|
|
VYNDR is an analytics tool, not a sportsbook. Gamble responsibly. 1-800-522-4700.
|
|
</p>
|
|
|
|
<style jsx>{`
|
|
@media (min-width: 960px) {
|
|
:global(.hero-grid) {
|
|
grid-template-columns: 1.4fr 1fr;
|
|
}
|
|
}
|
|
`}</style>
|
|
</section>
|
|
);
|
|
}
|
|
|
|
const SPORTS_DISPLAY = [
|
|
{ label: 'NBA', active: true, color: '#E94B3C' },
|
|
{ label: 'MLB', active: true, color: '#1E90FF' },
|
|
{ label: 'WNBA', active: true, color: '#F7944A' },
|
|
{ label: 'NFL', active: false, color: '#013369' },
|
|
{ label: 'NHL', active: false, color: '#A0A0B0' },
|
|
{ label: 'TENNIS', active: false, color: '#C5B358' },
|
|
{ label: 'MMA', active: false, color: '#D4AF37' },
|
|
{ label: 'BOXING', active: false, color: '#8B0000' },
|
|
{ label: 'GOLF', active: false, color: '#2E7D32' },
|
|
];
|
|
|
|
function SportBadgeStrip() {
|
|
return (
|
|
<div
|
|
role="list"
|
|
aria-label="Supported sports"
|
|
style={{
|
|
display: 'flex',
|
|
gap: 8,
|
|
flexWrap: 'wrap',
|
|
marginTop: 16,
|
|
marginBottom: 8,
|
|
maxWidth: 640,
|
|
}}
|
|
>
|
|
{SPORTS_DISPLAY.map((s) => {
|
|
const base: React.CSSProperties = {
|
|
fontFamily: 'var(--ibm-mono)',
|
|
fontSize: 11,
|
|
fontWeight: 700,
|
|
letterSpacing: '0.08em',
|
|
padding: '5px 11px',
|
|
borderRadius: 999,
|
|
textTransform: 'uppercase',
|
|
whiteSpace: 'nowrap',
|
|
};
|
|
return s.active ? (
|
|
<span
|
|
key={s.label}
|
|
role="listitem"
|
|
style={{
|
|
...base,
|
|
color: s.color,
|
|
background: `${s.color}1A`,
|
|
border: `1px solid ${s.color}66`,
|
|
boxShadow: `0 0 12px ${s.color}33`,
|
|
}}
|
|
>
|
|
{s.label}
|
|
</span>
|
|
) : (
|
|
<span
|
|
key={s.label}
|
|
role="listitem"
|
|
title="COMING THIS SUMMER"
|
|
aria-label={`${s.label} — coming this summer`}
|
|
style={{
|
|
...base,
|
|
color: 'var(--text-2)',
|
|
background: 'transparent',
|
|
border: '1px solid var(--border)',
|
|
}}
|
|
>
|
|
{s.label}
|
|
</span>
|
|
);
|
|
})}
|
|
</div>
|
|
);
|
|
}
|
|
|
|
// Session 16 — FloatingDemoCard / Stat / row removed. The hero card
|
|
// is now a live, graded prop fetched on mount; see LiveHeroProp.tsx.
|
|
// The static Jokic layout lives ONCE inside that component as the
|
|
// cold-start fallback when /api/hero-prop returns isStatic:true.
|
|
//
|
|
// GradePill (re-exported by GradeCard) is still imported at the top
|
|
// of this file because the section header uses it elsewhere; if a
|
|
// future cleanup confirms no other usage, that import can drop too.
|