Session C (night2): the aggregator mounted — Explore hub, lens panels, one filter
- StreaksPanel renders THE LENS (interpreted read + tonight difficulty) + snapshot grade letters; streaks are FREE for every tier per the product definition (the picture is free, the read is paid) — only hot lists keep the free top-3 gate. - One stat selection now filters ALL layers: card props narrow together with streaks + hot lists (Slate activeStat → slateGameToCardData). - /explore = server SEO shell (daily-indexable: 'MLB Hit Streaks, Hot Hitters & Stat Leaders') + ExploreHub client: leaders + full streaks + hot lists, real-tier gated, defaults to the in-season sport. - Landing teaser fixed: it pointed at NBA (off-season → self-hid forever); now MLB top-3 through the lens — the anonymous-visitor hook actually shows. - Player dossier: ACTIVE STREAKS block (free, lens reads) via new ?player= filter on /api/streaks/:sport. - Schedule layer already the Slate base (mergeSlate) — verified, no change. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -2,7 +2,7 @@
|
||||
|
||||
import { useEffect, useState } from 'react';
|
||||
import { getHeadshotUrl, PLAYER_SILHOUETTE } from '@/lib/playerHeadshot';
|
||||
import { getVisibleCount, getHiddenCount, type Tier } from '@/lib/tierGate';
|
||||
import { type Tier } from '@/lib/tierGate';
|
||||
|
||||
/**
|
||||
* StreaksPanel (Session 23).
|
||||
@@ -25,6 +25,9 @@ interface Streak {
|
||||
category: string;
|
||||
currentStreak: number;
|
||||
description: string;
|
||||
// Session 60 (night2/C) — THE LENS + optional snapshot grade letter.
|
||||
lens?: { builtVs?: string[] | null; matchup?: string | null; difficulty?: string | null; read?: string | null };
|
||||
grade?: string | null;
|
||||
}
|
||||
|
||||
export interface StreaksPanelProps {
|
||||
@@ -56,10 +59,13 @@ export default function StreaksPanel({ sport, tier = 'free', stat = 'all', limit
|
||||
|
||||
if (!streaks || streaks.length === 0) return null;
|
||||
|
||||
const tierCount = getVisibleCount(tier, streaks.length);
|
||||
const cap = limit && limit > 0 ? Math.min(limit, tierCount) : tierCount;
|
||||
const visible = streaks.slice(0, cap);
|
||||
const hidden = limit ? streaks.length - visible.length : getHiddenCount(tier, streaks.length);
|
||||
// Session 60 (product def) — the PICTURE is free: every streak shows for
|
||||
// every tier. Only an explicit `limit` (landing teaser) caps the list.
|
||||
// The paid layer is the GRADE, not the data. tierGate still governs the
|
||||
// hot LISTS (top-3 free) — not streaks.
|
||||
void tier;
|
||||
const visible = limit && limit > 0 ? streaks.slice(0, limit) : streaks;
|
||||
const hidden = streaks.length - visible.length;
|
||||
|
||||
return (
|
||||
<section className="streaks-panel" style={{ margin: '16px 0' }}>
|
||||
@@ -76,16 +82,27 @@ export default function StreaksPanel({ sport, tier = 'free', stat = 'all', limit
|
||||
onError={(e) => { (e.target as HTMLImageElement).src = PLAYER_SILHOUETTE; }}
|
||||
/>
|
||||
<div style={{ flex: 1, minWidth: 0 }}>
|
||||
<div style={playerName}>{s.player}{s.team ? ` · ${s.team}` : ''}</div>
|
||||
<div style={streakDesc}>{s.description}</div>
|
||||
<div style={playerName}>
|
||||
{s.player}{s.team ? ` · ${s.team}` : ''}
|
||||
{/* Grade letter when the snapshot graded this player (the paid
|
||||
READ layer; the letter itself is public on the slate). */}
|
||||
{s.grade && <span className="mono" style={{ marginLeft: 8, fontSize: 10.5, fontWeight: 800, color: 'var(--g-a, #00D4A0)', border: '1px solid rgba(0,212,160,.35)', borderRadius: 4, padding: '1px 5px' }}>{s.grade}</span>}
|
||||
</div>
|
||||
{/* THE LENS — the interpreted read, never the raw streak alone. */}
|
||||
<div style={streakDesc}>{s.lens?.read || s.description}</div>
|
||||
{s.lens?.difficulty && (
|
||||
<div className="mono" style={{ fontSize: 10.5, marginTop: 2, color: s.lens.difficulty === 'step up' ? 'var(--amber, #FFB347)' : s.lens.difficulty === 'step down' ? 'var(--g-a, #00D4A0)' : 'var(--text-tertiary, #6A6A78)' }}>
|
||||
{s.lens.difficulty === 'step up' ? '▲ TOUGHER SPOT TONIGHT' : s.lens.difficulty === 'step down' ? '▼ SOFTER SPOT TONIGHT' : ''}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<span style={badgeStyle}>{s.currentStreak} G</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
{hidden > 0 && (
|
||||
<a href="/pricing" style={upsellStyle}>
|
||||
{hidden} more streak{hidden === 1 ? '' : 's'} — upgrade to see all →
|
||||
<a href="/explore" style={upsellStyle}>
|
||||
{hidden} more streak{hidden === 1 ? '' : 's'} on the board →
|
||||
</a>
|
||||
)}
|
||||
</section>
|
||||
|
||||
Reference in New Issue
Block a user