AIBRUH Claude Sonnet 5 commited on
Commit
7344b62
·
1 Parent(s): 66b039d

PARLAY: left mascot rail with cigar smoke, Crazy Horse moved to the top, ticket confidence score removed

Browse files
docs/HANDOFF.md CHANGED
@@ -550,8 +550,7 @@ haircut) is returned as `crazyHorse` and shown as the hero at the bottom of the
550
  `GET /api/jimmy/scan` is the deep-scan funnel (sides scored, legs per tier, tickets built).
551
  NFL game legs trust only half of the model's gap from 50% (Vegas is sharper than a 2-game model).
552
  The CHARTS and PLAYER tabs are hidden from the nav (the player page still opens from a leaders click); the
553
- default view is PARLAY and the node canvas is hidden behind a NODES tab. Every ticket carries a `confidence`
554
- (average leg probability), a photo strip when it pays +800 or more, and a FAKE IT (default) / TAKE IT control
555
  (`TicketCard.tsx`, `TakeItFakeIt.tsx`). SPY BOY is grouped into tickets A and B (up to C), five legs max, best
556
  payouts first; CRAZY HORSE is 10 high-probability legs; `jimmy_featured.py` builds the featured single-player RB
557
  parlay (anytime TD + 70+ rush yds + 3+ rec) shown in the white panel at the top of PARLAY. The lake has no
 
550
  `GET /api/jimmy/scan` is the deep-scan funnel (sides scored, legs per tier, tickets built).
551
  NFL game legs trust only half of the model's gap from 50% (Vegas is sharper than a 2-game model).
552
  The CHARTS and PLAYER tabs are hidden from the nav (the player page still opens from a leaders click); the
553
+ default view is PARLAY and the node canvas is hidden behind a NODES tab. Every ticket carries a photo strip when it pays +800 or more, and a FAKE IT (default) / TAKE IT control
 
554
  (`TicketCard.tsx`, `TakeItFakeIt.tsx`). SPY BOY is grouped into tickets A and B (up to C), five legs max, best
555
  payouts first; CRAZY HORSE is 10 high-probability legs; `jimmy_featured.py` builds the featured single-player RB
556
  parlay (anytime TD + 70+ rush yds + 3+ rec) shown in the white panel at the top of PARLAY. The lake has no
frontend/src/components/EngineTab.tsx CHANGED
@@ -2,7 +2,7 @@ import { useEffect, useState } from "react";
2
  import { api } from "../api";
3
  import type { EngineSlip, PowSummary } from "../types";
4
  import TakeItFakeIt from "./TakeItFakeIt";
5
- import TicketCard, { ConfidenceRing, LegLine, TYPE_META, american } from "./TicketCard";
6
  import { useEngine } from "./useEngine";
7
 
8
  const TYPE_ORDER = ["BPL_EDGE", "SPY_BOY"];
@@ -32,7 +32,6 @@ export function CrazyHorseHero({ slip }: { slip: EngineSlip }) {
32
  {american(slip.boostedAmericanOdds)} · {slip.hitProbability !== undefined ? `${(slip.hitProbability * 100).toFixed(1)}%` : "—"} to hit all {slip.legs.length}
33
  </span>
34
  </div>
35
- <ConfidenceRing value={slip.confidence ?? 0} size={78} />
36
  </div>
37
  </div>
38
 
@@ -63,7 +62,7 @@ function PowLine({ pow }: { pow: PowSummary | null }) {
63
  );
64
  }
65
 
66
- export default function EngineTab() {
67
  const { data, error } = useEngine();
68
  const [filter, setFilter] = useState<string>("ALL");
69
  const [pow, setPow] = useState<PowSummary | null>(null);
@@ -75,10 +74,11 @@ export default function EngineTab() {
75
 
76
  return (
77
  <div style={{ display: "flex", flexDirection: "column", gap: 14, maxWidth: 1100, width: "100%", margin: "0 auto" }}>
 
78
  <div style={{ display: "flex", flexDirection: "column", gap: 4 }}>
79
- <span style={{ fontSize: 12, fontWeight: 800, letterSpacing: "0.14em", color: "var(--bp-muted)" }}>PROPPA ENGINE · EVERY TICKET SCORED</span>
80
  <span style={{ fontSize: 11, color: "var(--bp-muted)" }}>
81
- Big Proppa Line vs FanDuel · player props, NFL games and college · one leg per game · confidence = average leg probability · every ticket defaults to FAKE IT in MY BOO
82
  </span>
83
  <PowLine pow={pow} />
84
  </div>
@@ -108,7 +108,6 @@ export default function EngineTab() {
108
 
109
  {visible.map((slip) => <TicketCard key={slip.id} slip={slip} />)}
110
 
111
- {slips && slips.length > 0 && data?.crazyHorse && <CrazyHorseHero slip={data.crazyHorse} />}
112
  </div>
113
  );
114
  }
 
2
  import { api } from "../api";
3
  import type { EngineSlip, PowSummary } from "../types";
4
  import TakeItFakeIt from "./TakeItFakeIt";
5
+ import TicketCard, { LegLine, TYPE_META, american } from "./TicketCard";
6
  import { useEngine } from "./useEngine";
7
 
8
  const TYPE_ORDER = ["BPL_EDGE", "SPY_BOY"];
 
32
  {american(slip.boostedAmericanOdds)} · {slip.hitProbability !== undefined ? `${(slip.hitProbability * 100).toFixed(1)}%` : "—"} to hit all {slip.legs.length}
33
  </span>
34
  </div>
 
35
  </div>
36
  </div>
37
 
 
62
  );
63
  }
64
 
65
+ export default function EngineTab({ showHorse = true }: { showHorse?: boolean }) {
66
  const { data, error } = useEngine();
67
  const [filter, setFilter] = useState<string>("ALL");
68
  const [pow, setPow] = useState<PowSummary | null>(null);
 
74
 
75
  return (
76
  <div style={{ display: "flex", flexDirection: "column", gap: 14, maxWidth: 1100, width: "100%", margin: "0 auto" }}>
77
+ {showHorse && data?.crazyHorse && <CrazyHorseHero slip={data.crazyHorse} />}
78
  <div style={{ display: "flex", flexDirection: "column", gap: 4 }}>
79
+ <span style={{ fontSize: 12, fontWeight: 800, letterSpacing: "0.14em", color: "var(--bp-muted)" }}>PROPPA ENGINE · EVERY TICKET PRICED</span>
80
  <span style={{ fontSize: 11, color: "var(--bp-muted)" }}>
81
+ Big Proppa Line vs FanDuel · player props, NFL games and college · one leg per game · every ticket defaults to FAKE IT in MY BOO
82
  </span>
83
  <PowLine pow={pow} />
84
  </div>
 
108
 
109
  {visible.map((slip) => <TicketCard key={slip.id} slip={slip} />)}
110
 
 
111
  </div>
112
  );
113
  }
frontend/src/components/FeaturedPlayer.tsx CHANGED
@@ -1,7 +1,7 @@
1
  import { useState } from "react";
2
  import type { EngineSlip } from "../types";
3
  import TakeItFakeIt from "./TakeItFakeIt";
4
- import { ConfidenceRing, american } from "./TicketCard";
5
 
6
  const MONO = "var(--font-mono, monospace)";
7
 
@@ -30,7 +30,6 @@ export default function FeaturedPlayer({ slip }: { slip: EngineSlip }) {
30
  <span style={{ fontSize: 40, fontWeight: 900, lineHeight: 1, letterSpacing: "-0.01em" }}>{p.name}</span>
31
  <span style={{ fontSize: 15, color: "#6b6553", fontWeight: 600 }}>{p.team} · {p.position}</span>
32
  </div>
33
- <ConfidenceRing value={slip.confidence ?? 0} size={72} light />
34
  </div>
35
 
36
  <div style={{ display: "grid", gridTemplateColumns: "repeat(auto-fit, minmax(150px, 1fr))", gap: 10 }}>
 
1
  import { useState } from "react";
2
  import type { EngineSlip } from "../types";
3
  import TakeItFakeIt from "./TakeItFakeIt";
4
+ import { american } from "./TicketCard";
5
 
6
  const MONO = "var(--font-mono, monospace)";
7
 
 
30
  <span style={{ fontSize: 40, fontWeight: 900, lineHeight: 1, letterSpacing: "-0.01em" }}>{p.name}</span>
31
  <span style={{ fontSize: 15, color: "#6b6553", fontWeight: 600 }}>{p.team} · {p.position}</span>
32
  </div>
 
33
  </div>
34
 
35
  <div style={{ display: "grid", gridTemplateColumns: "repeat(auto-fit, minmax(150px, 1fr))", gap: 10 }}>
frontend/src/components/MascotRail.tsx ADDED
@@ -0,0 +1,33 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /**
2
+ * Left-hand mascot column: the Big Proppa photo with the cigar ember and rising smoke, sized to the
3
+ * column. Uses the same smoke animation as SmokeBadge (theme.css) and its measured ember anchor
4
+ * (left 21.5%, top 61% of the photo). If a full-body image is supplied later, drop it in as
5
+ * /big-proppa-full.png, set FULL_BODY = true, and re-measure the ember anchor for the new cigar tip.
6
+ */
7
+ const FULL_BODY = false;
8
+ const SRC = FULL_BODY ? "/big-proppa-full.png" : "/big-proppa.png";
9
+ const ANCHOR = FULL_BODY ? { left: "21.5%", top: "61%" } : { left: "21.5%", top: "61%" };
10
+ const ASPECT = 520 / 512;
11
+
12
+ export default function MascotRail({ width = 300 }: { width?: number }) {
13
+ const puffs = ["bp-puff-a1", "bp-puff-b1", "bp-puff-c1", "bp-puff-a2", "bp-puff-b2", "bp-wisp-1", "bp-wisp-2", "bp-wisp-3"];
14
+ const opacity = [0.6, 0.55, 0.5, 0.45, 0.4, 0.4, 0.35, 0.35];
15
+ const em = width / 22;
16
+ return (
17
+ <aside className="mascot-rail" style={{ width, flex: `0 0 ${width}px`, position: "sticky", top: 8, alignSelf: "flex-start", display: "flex", flexDirection: "column", alignItems: "center", gap: 10 }}>
18
+ <style>{`@media (max-width: 1350px) { .mascot-rail { display: none !important; } }`}</style>
19
+ <div style={{ position: "relative", width, height: width / ASPECT, borderRadius: 18, overflow: "hidden", border: "1px solid #6b4a1c", boxShadow: "0 0 30px rgba(224,120,47,0.18)",
20
+ WebkitMaskImage: "linear-gradient(to bottom, #000 78%, transparent 100%)", maskImage: "linear-gradient(to bottom, #000 78%, transparent 100%)" }}>
21
+ <img src={SRC} alt="Big Proppa" style={{ width: "100%", height: "100%", objectFit: "cover", display: "block" }} />
22
+ <div style={{ position: "absolute", left: ANCHOR.left, top: ANCHOR.top, width: 0, height: 0, fontSize: `${em}px`, pointerEvents: "none" }}>
23
+ <div className="bp-ember" style={{ position: "absolute", left: "-2em", top: "-2em", width: "4em", height: "4em", borderRadius: "50%", background: "radial-gradient(circle, rgba(255,80,30,0.7), rgba(255,40,10,0) 70%)", mixBlendMode: "screen" }} />
24
+ <div className="bp-core" style={{ position: "absolute", left: "-.55em", top: "-.7em", width: "1.1em", height: "1.4em", borderRadius: "50%", background: "radial-gradient(circle, rgba(255,240,170,1), rgba(255,120,40,0.9) 45%, rgba(220,30,10,0) 80%)" }} />
25
+ {puffs.map((cls, i) => (
26
+ <div key={cls} className={cls} style={{ position: "absolute", left: "-1.6em", top: "-3.2em", width: "3.2em", height: "3.2em", borderRadius: "50%", background: `radial-gradient(circle, rgba(228,225,235,${opacity[i]}), rgba(228,225,235,0) 70%)`, filter: "blur(0.45em)", opacity: 0 }} />
27
+ ))}
28
+ </div>
29
+ </div>
30
+ <span style={{ fontSize: 13, fontWeight: 900, letterSpacing: "0.16em", background: "var(--bp-wordmark-gradient)", WebkitBackgroundClip: "text", backgroundClip: "text", color: "transparent" }}>HEY BIG PROPPA!</span>
31
+ </aside>
32
+ );
33
+ }
frontend/src/components/ParlaysTab.tsx CHANGED
@@ -1,7 +1,8 @@
1
  import { useEffect, useState } from "react";
2
  import { api } from "../api";
3
  import type { ParlaySlip, PlayerPropChart } from "../types";
4
- import EngineTab from "./EngineTab";
 
5
  import FeaturedPlayer from "./FeaturedPlayer";
6
  import TicketCard, { fromThemed } from "./TicketCard";
7
  import { useEngine } from "./useEngine";
@@ -42,7 +43,10 @@ export default function ParlaysTab({
42
  }) {
43
  const { data } = useEngine();
44
  return (
45
- <div style={{ display: "flex", flexDirection: "column", gap: 16, maxWidth: 1100, width: "100%", margin: "0 auto" }}>
 
 
 
46
  {data?.featured && <FeaturedPlayer slip={data.featured} />}
47
  <a
48
  href="/parlays"
@@ -86,7 +90,8 @@ export default function ParlaysTab({
86
  </div>
87
 
88
  <TonightsSlips />
89
- <EngineTab />
 
90
  </div>
91
  );
92
  }
 
1
  import { useEffect, useState } from "react";
2
  import { api } from "../api";
3
  import type { ParlaySlip, PlayerPropChart } from "../types";
4
+ import EngineTab, { CrazyHorseHero } from "./EngineTab";
5
+ import MascotRail from "./MascotRail";
6
  import FeaturedPlayer from "./FeaturedPlayer";
7
  import TicketCard, { fromThemed } from "./TicketCard";
8
  import { useEngine } from "./useEngine";
 
43
  }) {
44
  const { data } = useEngine();
45
  return (
46
+ <div style={{ display: "flex", gap: 22, alignItems: "flex-start", justifyContent: "center", width: "100%" }}>
47
+ <MascotRail />
48
+ <div style={{ display: "flex", flexDirection: "column", gap: 16, maxWidth: 1100, flex: "1 1 0", minWidth: 0 }}>
49
+ {data?.crazyHorse && <CrazyHorseHero slip={data.crazyHorse} />}
50
  {data?.featured && <FeaturedPlayer slip={data.featured} />}
51
  <a
52
  href="/parlays"
 
90
  </div>
91
 
92
  <TonightsSlips />
93
+ <EngineTab showHorse={false} />
94
+ </div>
95
  </div>
96
  );
97
  }
frontend/src/components/TicketCard.tsx CHANGED
@@ -26,7 +26,7 @@ export function toAmerican(decimal: number): string {
26
  export function fromThemed(s: ParlaySlip): EngineSlip {
27
  return {
28
  id: `SLIP-${s.id}`, title: s.title, correlationType: "THEMED", badge: s.title, insight: "",
29
- confidence: s.confidence, wager: s.wager, boost: s.boost,
30
  combinedDecimalOdds: s.combinedDecimalOdds, payout: s.payout, boostedPayout: s.boostedPayout, boostedAmericanOdds: s.boostedAmericanOdds,
31
  legs: s.legs.map((l) => ({
32
  playerId: l.playerId ?? l.teamId ?? l.name, name: l.name, team: l.team ?? "", market: l.market ?? "", direction: l.direction ?? "over",
@@ -54,24 +54,6 @@ export function Avatar({ leg, size, ring = "#c9a54e" }: { leg: Pick<EngineLeg, "
54
  );
55
  }
56
 
57
- export function ConfidenceRing({ value, size = 64, light = false }: { value: number; size?: number; light?: boolean }) {
58
- const color = value >= 75 ? "#16a34a" : value >= 62 ? "#c9a54e" : "#e0782f";
59
- const r = size / 2 - 5;
60
- const c = 2 * Math.PI * r;
61
- return (
62
- <span title="Confidence: the average hit probability of the legs on this ticket" style={{ display: "inline-flex", flexDirection: "column", alignItems: "center", gap: 2, flex: "0 0 auto" }}>
63
- <span style={{ position: "relative", width: size, height: size }}>
64
- <svg width={size} height={size} viewBox={`0 0 ${size} ${size}`} style={{ transform: "rotate(-90deg)" }}>
65
- <circle cx={size / 2} cy={size / 2} r={r} fill="none" stroke={light ? "#e8e2d0" : "rgba(255,255,255,0.12)"} strokeWidth="5" />
66
- <circle cx={size / 2} cy={size / 2} r={r} fill="none" stroke={color} strokeWidth="5" strokeLinecap="round" strokeDasharray={c} strokeDashoffset={c * (1 - Math.min(100, value) / 100)} />
67
- </svg>
68
- <span style={{ position: "absolute", inset: 0, display: "flex", alignItems: "center", justifyContent: "center", fontFamily: MONO, fontSize: size * 0.34, fontWeight: 800, color: light ? "#1a1a1a" : color }}>{value}</span>
69
- </span>
70
- <span style={{ fontSize: 8, fontWeight: 800, letterSpacing: "0.12em", color: light ? "#6b6553" : "var(--bp-muted)" }}>CONFIDENCE</span>
71
- </span>
72
- );
73
- }
74
-
75
  export function LegLine({ leg }: { leg: EngineLeg }) {
76
  const pct = Math.round(leg.probability * 100);
77
  const pc = pct >= 75 ? "#2ee6a6" : pct >= 60 ? "#d9b45a" : "#e0782f";
@@ -92,7 +74,6 @@ export default function TicketCard({ slip }: { slip: EngineSlip }) {
92
  const meta = TYPE_META[slip.correlationType] ?? { label: slip.correlationType, color: "#d9b45a" };
93
  const label = slip.badge ?? meta.label;
94
  const bigPayout = slip.combinedDecimalOdds >= BIG_PAYOUT_DECIMAL;
95
- const conf = slip.confidence ?? 0;
96
  return (
97
  <div style={{ background: "var(--bp-card-bg)", border: `1px solid ${meta.color}55`, borderLeft: `4px solid ${meta.color}`, borderRadius: 16, overflow: "hidden", display: "flex", flexDirection: "column" }}>
98
  <div style={{ padding: "14px 16px 12px", display: "flex", gap: 14, alignItems: "flex-start" }}>
@@ -114,7 +95,6 @@ export default function TicketCard({ slip }: { slip: EngineSlip }) {
114
  <span style={{ fontSize: 11, color: "var(--bp-muted)" }}>{(slip.hitProbability * 100).toFixed(1)}% to hit every leg</span>
115
  )}
116
  </div>
117
- <ConfidenceRing value={conf} />
118
  </div>
119
 
120
  {bigPayout && (
 
26
  export function fromThemed(s: ParlaySlip): EngineSlip {
27
  return {
28
  id: `SLIP-${s.id}`, title: s.title, correlationType: "THEMED", badge: s.title, insight: "",
29
+ wager: s.wager, boost: s.boost,
30
  combinedDecimalOdds: s.combinedDecimalOdds, payout: s.payout, boostedPayout: s.boostedPayout, boostedAmericanOdds: s.boostedAmericanOdds,
31
  legs: s.legs.map((l) => ({
32
  playerId: l.playerId ?? l.teamId ?? l.name, name: l.name, team: l.team ?? "", market: l.market ?? "", direction: l.direction ?? "over",
 
54
  );
55
  }
56
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
57
  export function LegLine({ leg }: { leg: EngineLeg }) {
58
  const pct = Math.round(leg.probability * 100);
59
  const pc = pct >= 75 ? "#2ee6a6" : pct >= 60 ? "#d9b45a" : "#e0782f";
 
74
  const meta = TYPE_META[slip.correlationType] ?? { label: slip.correlationType, color: "#d9b45a" };
75
  const label = slip.badge ?? meta.label;
76
  const bigPayout = slip.combinedDecimalOdds >= BIG_PAYOUT_DECIMAL;
 
77
  return (
78
  <div style={{ background: "var(--bp-card-bg)", border: `1px solid ${meta.color}55`, borderLeft: `4px solid ${meta.color}`, borderRadius: 16, overflow: "hidden", display: "flex", flexDirection: "column" }}>
79
  <div style={{ padding: "14px 16px 12px", display: "flex", gap: 14, alignItems: "flex-start" }}>
 
95
  <span style={{ fontSize: 11, color: "var(--bp-muted)" }}>{(slip.hitProbability * 100).toFixed(1)}% to hit every leg</span>
96
  )}
97
  </div>
 
98
  </div>
99
 
100
  {bigPayout && (