M1b screen 01: the flat EDGE BOARD — Design's mobile board IA
The one genuinely-new mobile screen. Design's mobile BOARD is a FLAT edge-ranked list (all graded props across every game on one list, sorted by edge) — not the desktop's game-grouped cards. Implemented to the drawing with REAL snapshot data: - slateAdapter.flattenToEdgeBoard(cards) — pure transform of the assembled GameCardData[] (grade→game join already done) into ranked rows, edge desc. STRICT null edge sorts LAST (never 0-coerced to the top — Data Semantics Rule). Threaded edge_pct through buildPlayerStripsFromProps (was dropped). 6 unit tests. - MobileEdgeBoard component — Design's exact screen-01 rows: rank (green #1), player + prop, matchup sub-line with TeamChips + live-dot, tier grade chip, and the edge% as the one bold mono hero (green +, red −). Ranked opacity ramp (1 → .55) + green inset border on the top reads. Breadth strip EDGES/AVG CLV/ GAMES — CLV honest '—' (per-slate CLV isn't computed; never fabricated). - Slate: <768px renders the flat board, ≥768px keeps game cards (same data, toggled by width). Ungraded slate still shows game cards on phones (no blank). Built to Design's screen-01 drawing, VISUALLY UNVERIFIED at 390px — the core mobile screen, top of the master-audit list. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -7,7 +7,8 @@ import { useRouter } from 'next/navigation';
|
||||
import VyndrGameCard, { type GameCardData } from '@/components/vyndr/GameCard';
|
||||
import type { SlateSport, GameLines, GameStreak } from '@/components/GameCard';
|
||||
import { PropRowProp, Tier } from '@/components/PropRow';
|
||||
import { isRelevantGame, detectBestLines, indexGrades, indexDeltas, buildPlayerStripsFromProps, formatGameTime, buildPitcherMap, pitchersForGameTeams, gradeKey } from '@/lib/slateAdapter';
|
||||
import { isRelevantGame, detectBestLines, indexGrades, indexDeltas, buildPlayerStripsFromProps, formatGameTime, buildPitcherMap, pitchersForGameTeams, gradeKey, flattenToEdgeBoard, edgeBoardBreadth } from '@/lib/slateAdapter';
|
||||
import MobileEdgeBoard from '@/components/vyndr/MobileEdgeBoard';
|
||||
// Wave 4A (Step 3) — OUTLOOK MODE: the never-empty grid. When there are no
|
||||
// live games (and it's not a fetch failure) the grid shows REAL data —
|
||||
// yesterday's proven receipts + tomorrow's date-pinned schedule.
|
||||
@@ -1181,16 +1182,37 @@ export default function Slate({ initialTab = 'all', tier = 'free', preferredBook
|
||||
graded prop has a real ≥2-book median to compare the model against. */}
|
||||
{dateOffset === 0 && <MarketBreadth items={breadthItems} />}
|
||||
|
||||
<div style={{ display: 'grid', gap: 16 }}>
|
||||
{orderedGames.map((g, i) => (
|
||||
<VyndrGameCard
|
||||
key={`${g.sport}-${g.homeTeam}-${g.awayTeam}-${i}`}
|
||||
game={slateGameToCardData(g, gradeIndex, deltaIndex, pitcherMap, activeStat, viability, liveIndex)}
|
||||
preferredBooks={preferredBooks}
|
||||
onOpen={() => router.push('/scan')}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
{(() => {
|
||||
// Build each game's card once, then present two ways (Rev 3, screen 01):
|
||||
// MOBILE = Design's FLAT edge-ranked board (all props, one list, sorted
|
||||
// by edge); DESKTOP = the game-grouped cards. Same data, different IA —
|
||||
// toggled by width in globals.css (.edge-board-mobile / -desktop).
|
||||
const cards = orderedGames.map((g) => slateGameToCardData(g, gradeIndex, deltaIndex, pitcherMap, activeStat, viability, liveIndex));
|
||||
const edgeRows = flattenToEdgeBoard(cards);
|
||||
const breadth = edgeBoardBreadth(edgeRows, cards);
|
||||
const hasBoard = edgeRows.length > 0;
|
||||
return (
|
||||
<>
|
||||
{hasBoard && (
|
||||
<div className="edge-board-mobile" style={{ background: 'var(--bg-1)', border: '1px solid var(--border)', borderRadius: 12, overflow: 'hidden' }}>
|
||||
<MobileEdgeBoard rows={edgeRows} breadth={breadth} />
|
||||
</div>
|
||||
)}
|
||||
{/* Cards: on mobile they hide ONLY when the flat board is present
|
||||
(an ungraded slate still shows the game cards on phones). */}
|
||||
<div className={hasBoard ? 'edge-board-desktop' : ''} style={{ display: 'grid', gap: 16 }}>
|
||||
{cards.map((card, i) => (
|
||||
<VyndrGameCard
|
||||
key={`${card.sport}-${card.away.abbr}-${card.home.abbr}-${i}`}
|
||||
game={card}
|
||||
preferredBooks={preferredBooks}
|
||||
onOpen={() => router.push('/scan')}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
})()}
|
||||
|
||||
{/* Session 23 — intelligence layer. These coexist WITH the odds
|
||||
above; they never replace games. Both self-hide when empty, so
|
||||
|
||||
Reference in New Issue
Block a user