Files
vyndr/web/src/components/Hero.tsx
T
builtbykev 32069863dc Session 41: P0 audit fixes — MLB stat_types, broken routes, tier mismatch, self-hosted fonts (1940 tests)
- 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>
2026-06-17 21:55:40 -04:00

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&apos;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.