@sensiblestats/widget-react 0.27.0 → 0.29.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -1,5 +1,5 @@
1
1
  // src/StatsWidget.tsx
2
- import { useEffect as useEffect7, useMemo as useMemo2, useRef as useRef4 } from "react";
2
+ import { useEffect as useEffect8, useMemo as useMemo2, useRef as useRef5 } from "react";
3
3
  import { StatsWidgetClient } from "@sensiblestats/widget-sdk";
4
4
 
5
5
  // src/kickoff.ts
@@ -225,7 +225,7 @@ function themeAttrs(ui) {
225
225
  }
226
226
 
227
227
  // src/useChatStream.ts
228
- import { useCallback, useEffect as useEffect2, useMemo, useReducer, useRef as useRef2 } from "react";
228
+ import { useCallback, useEffect as useEffect2, useMemo, useReducer as useReducer2, useRef as useRef2 } from "react";
229
229
  import { WidgetSdkError as WidgetSdkError2 } from "@sensiblestats/widget-sdk";
230
230
 
231
231
  // src/reducer.ts
@@ -571,35 +571,90 @@ function reducer(state, action) {
571
571
  return {
572
572
  ...state,
573
573
  messages: state.messages.map(
574
- (m) => m.role === "assistant" && m.id === action.messageId && m.clubFanDashboard ? { ...m, clubFanDashboard: { ...m.clubFanDashboard, cards: action.cards } } : m
574
+ (m) => m.role === "assistant" && m.id === action.messageId && m.clubFanDashboard ? { ...m, clubFanDashboard: { ...m.clubFanDashboard, cards: action.cards, greeting: action.greeting ?? m.clubFanDashboard.greeting } } : m
575
+ )
576
+ };
577
+ case "LIVE_TRANSITION":
578
+ return {
579
+ ...state,
580
+ messages: state.messages.map(
581
+ (m) => m.role === "assistant" && m.id === action.messageId && m.clubFanDashboard ? { ...m, clubFanDashboard: action.dashboard } : m
575
582
  )
576
583
  };
577
584
  }
578
585
  }
579
586
 
580
587
  // src/useLiveDashboardPoll.ts
581
- import { useEffect, useRef } from "react";
588
+ import { useEffect, useReducer, useRef } from "react";
582
589
  var POLL_INTERVAL_MS = 6e4;
583
- function findLiveMessageId(messages) {
590
+ var LEAD_MS = 12e4;
591
+ var GIVE_UP_MS = 30 * 6e4;
592
+ function extractKickoffMs(dashboard) {
593
+ const countdown = dashboard.cards.find((c) => c.cardType === "countdown");
594
+ if (countdown) return countdown.data.kickoffUnix * 1e3;
595
+ for (const c of dashboard.cards) {
596
+ const d = c.data;
597
+ if (typeof d?.kickoffUnix === "number") return d.kickoffUnix * 1e3;
598
+ }
599
+ return null;
600
+ }
601
+ function findAnchor(messages) {
602
+ const now = Date.now();
584
603
  for (const m of messages.slice().reverse()) {
585
- if (m.role === "assistant" && m.clubFanDashboard?.phase === "live") return m.id;
604
+ if (m.role !== "assistant" || !m.clubFanDashboard) continue;
605
+ const { phase } = m.clubFanDashboard;
606
+ if (phase === "live") return { messageId: m.id, kind: "live" };
607
+ if (phase === "preMatch") {
608
+ const kickoffMs = extractKickoffMs(m.clubFanDashboard);
609
+ if (kickoffMs === null) continue;
610
+ if (now >= kickoffMs - LEAD_MS && now <= kickoffMs + GIVE_UP_MS) {
611
+ return { messageId: m.id, kind: "preMatch", kickoffMs };
612
+ }
613
+ }
586
614
  }
587
615
  return null;
588
616
  }
617
+ function findEarliestWakeupMs(messages) {
618
+ const now = Date.now();
619
+ let earliest = null;
620
+ for (const m of messages) {
621
+ if (m.role !== "assistant" || m.clubFanDashboard?.phase !== "preMatch") continue;
622
+ const kickoffMs = extractKickoffMs(m.clubFanDashboard);
623
+ if (kickoffMs === null) continue;
624
+ const wakeAt = kickoffMs - LEAD_MS;
625
+ if (wakeAt > now && (earliest === null || wakeAt < earliest)) earliest = wakeAt;
626
+ }
627
+ return earliest;
628
+ }
589
629
  function useLiveDashboardPoll({ messages, dispatch, getLiveDashboard }) {
590
- const messageId = findLiveMessageId(messages);
630
+ const [, forceUpdate] = useReducer((x) => x + 1, 0);
631
+ const anchor = findAnchor(messages);
632
+ const anchorRef = useRef(anchor);
633
+ anchorRef.current = anchor;
591
634
  const inFlightRef = useRef(false);
635
+ const anchorKey = anchor ? `${anchor.messageId}:${anchor.kind}` : null;
592
636
  useEffect(() => {
593
- if (!messageId) return;
637
+ if (!anchorKey || !anchorRef.current) return;
594
638
  const tick = async () => {
595
639
  if (inFlightRef.current) return;
596
640
  inFlightRef.current = true;
597
641
  try {
598
642
  const payload = await getLiveDashboard();
599
- if (payload && payload.phase === "live") {
600
- dispatch({ kind: "LIVE_REFRESH", messageId, cards: payload.cards });
643
+ const current = anchorRef.current;
644
+ if (!current) return;
645
+ if (payload?.phase === "live") {
646
+ if (current.kind === "preMatch") {
647
+ dispatch({ kind: "LIVE_TRANSITION", messageId: current.messageId, dashboard: payload });
648
+ window.clearInterval(id);
649
+ } else {
650
+ dispatch({ kind: "LIVE_REFRESH", messageId: current.messageId, cards: payload.cards, greeting: payload.greeting });
651
+ }
601
652
  } else {
602
- window.clearInterval(id);
653
+ if (current.kind === "live") {
654
+ window.clearInterval(id);
655
+ } else if (Date.now() > current.kickoffMs + GIVE_UP_MS) {
656
+ window.clearInterval(id);
657
+ }
603
658
  }
604
659
  } catch {
605
660
  } finally {
@@ -610,14 +665,23 @@ function useLiveDashboardPoll({ messages, dispatch, getLiveDashboard }) {
610
665
  void tick();
611
666
  }, POLL_INTERVAL_MS);
612
667
  return () => window.clearInterval(id);
613
- }, [messageId, dispatch, getLiveDashboard]);
668
+ }, [anchorKey, dispatch, getLiveDashboard]);
669
+ useEffect(() => {
670
+ if (anchorKey !== null) return;
671
+ const wakeAt = findEarliestWakeupMs(messages);
672
+ if (wakeAt === null) return;
673
+ const delay = wakeAt - Date.now();
674
+ if (delay <= 0) return;
675
+ const id = window.setTimeout(forceUpdate, delay);
676
+ return () => window.clearTimeout(id);
677
+ }, [anchorKey, messages, forceUpdate]);
614
678
  }
615
679
 
616
680
  // src/useChatStream.ts
617
681
  var STREAM_FALLBACK_TIMEOUT_MS = 3e4;
618
682
  function useChatStream(conversation, starterTexts, greetingMessage) {
619
683
  const seed = useMemo(() => initialState(starterTexts.map(chipFromText)), []);
620
- const [state, dispatch] = useReducer(reducer, seed);
684
+ const [state, dispatch] = useReducer2(reducer, seed);
621
685
  const idRef = useRef2(0);
622
686
  const handleRef = useRef2(null);
623
687
  const fallbackTimerRef = useRef2(null);
@@ -775,7 +839,7 @@ function ChatFab({ label, greeting, popupText, onOpen, onDismissPopup, dismissLa
775
839
  }
776
840
 
777
841
  // src/components/ChatPanel.tsx
778
- import { useEffect as useEffect6, useState as useState8 } from "react";
842
+ import { useEffect as useEffect7, useState as useState11 } from "react";
779
843
 
780
844
  // src/components/ChatHeader.tsx
781
845
  import { jsx as jsx4, jsxs as jsxs3 } from "react/jsx-runtime";
@@ -788,7 +852,7 @@ function ChatHeader({ onClose, closeLabel = "Close chat" }) {
788
852
  }
789
853
 
790
854
  // src/components/MessageList.tsx
791
- import { useEffect as useEffect5, useRef as useRef3 } from "react";
855
+ import { useEffect as useEffect6, useRef as useRef4 } from "react";
792
856
 
793
857
  // src/components/Markdown.tsx
794
858
  import ReactMarkdown from "react-markdown";
@@ -1098,22 +1162,44 @@ function ReasonStrip({ it, ui }) {
1098
1162
  ) : null
1099
1163
  ] });
1100
1164
  }
1165
+ function CardFixture({ it }) {
1166
+ if (it.homeTeamName && it.awayTeamName) {
1167
+ return /* @__PURE__ */ jsxs7(Fragment, { children: [
1168
+ /* @__PURE__ */ jsx11(Crest2, { kind: "team", url: it.homeCrest, name: it.homeTeamName }),
1169
+ /* @__PURE__ */ jsx11("span", { className: "ss-w-bi-team", children: it.homeTeamName }),
1170
+ /* @__PURE__ */ jsx11("span", { className: "ss-w-bi-vs", "aria-hidden": "true", children: "vs" }),
1171
+ /* @__PURE__ */ jsx11("span", { className: "ss-w-bi-team", children: it.awayTeamName }),
1172
+ /* @__PURE__ */ jsx11(Crest2, { kind: "team", url: it.awayCrest, name: it.awayTeamName })
1173
+ ] });
1174
+ }
1175
+ if (it.fixture) return /* @__PURE__ */ jsx11("span", { className: "ss-w-bi-fixture-txt", children: it.fixture });
1176
+ if (it.competition) {
1177
+ return /* @__PURE__ */ jsxs7(Fragment, { children: [
1178
+ /* @__PURE__ */ jsx11(Crest2, { kind: "comp", url: it.competitionCrest, name: it.competition }),
1179
+ /* @__PURE__ */ jsx11("span", { className: "ss-w-bi-comp", children: it.competition })
1180
+ ] });
1181
+ }
1182
+ return null;
1183
+ }
1101
1184
  function InsightCard({ it, ui, slip }) {
1102
1185
  const live = it.isLive || it.phase === "live";
1103
1186
  const kickoff = it.kickoffUtc && ui?.bettingKickoff ? ui.bettingKickoff(it.kickoffUtc) : void 0;
1104
1187
  const hasNamedTeams = !!it.homeTeamName && !!it.awayTeamName;
1105
- const showHeader = !!it.competition || live || !!kickoff;
1188
+ const leftIsFixture = hasNamedTeams || !!it.fixture;
1189
+ const compInMeta = leftIsFixture && !!it.competition;
1190
+ const showHeader = leftIsFixture || !!it.competition || live || !!kickoff;
1106
1191
  return /* @__PURE__ */ jsxs7("div", { className: "ss-w-binsight", children: [
1107
1192
  showHeader ? /* @__PURE__ */ jsxs7("div", { className: "ss-w-bi-header", children: [
1108
- /* @__PURE__ */ jsx11("span", { className: "ss-w-bi-header-left", children: it.competition ? /* @__PURE__ */ jsxs7(Fragment, { children: [
1109
- /* @__PURE__ */ jsx11(Crest2, { kind: "comp", url: it.competitionCrest, name: it.competition }),
1110
- /* @__PURE__ */ jsx11("span", { className: "ss-w-bi-comp", children: it.competition })
1111
- ] }) : null }),
1193
+ /* @__PURE__ */ jsx11("span", { className: "ss-w-bi-header-left", children: /* @__PURE__ */ jsx11(CardFixture, { it }) }),
1112
1194
  /* @__PURE__ */ jsxs7("span", { className: "ss-w-bi-header-right", children: [
1113
1195
  live ? /* @__PURE__ */ jsxs7("span", { className: "ss-w-bi-live", children: [
1114
1196
  /* @__PURE__ */ jsx11("span", { className: "ss-w-bi-live-dot", "aria-hidden": "true" }),
1115
1197
  ui?.liveLabel ?? "Live"
1116
1198
  ] }) : null,
1199
+ compInMeta ? /* @__PURE__ */ jsxs7("span", { className: "ss-w-bi-comp-meta", children: [
1200
+ /* @__PURE__ */ jsx11(Crest2, { kind: "comp", url: it.competitionCrest, name: it.competition }),
1201
+ /* @__PURE__ */ jsx11("span", { className: "ss-w-bi-comp", children: it.competition })
1202
+ ] }) : null,
1117
1203
  kickoff ? /* @__PURE__ */ jsxs7("span", { className: "ss-w-bi-kick", children: [
1118
1204
  kickoff.dayLabel,
1119
1205
  " ",
@@ -1121,28 +1207,22 @@ function InsightCard({ it, ui, slip }) {
1121
1207
  ] }) : null
1122
1208
  ] })
1123
1209
  ] }) : null,
1124
- /* @__PURE__ */ jsxs7("div", { className: "ss-w-bi-body", children: [
1125
- /* @__PURE__ */ jsx11("div", { className: "ss-w-bi-fixture", children: hasNamedTeams ? /* @__PURE__ */ jsxs7(Fragment, { children: [
1126
- /* @__PURE__ */ jsx11(Crest2, { kind: "team", url: it.homeCrest, name: it.homeTeamName }),
1127
- /* @__PURE__ */ jsx11("span", { className: "ss-w-bi-team", children: it.homeTeamName }),
1128
- /* @__PURE__ */ jsx11("span", { className: "ss-w-bi-vs", "aria-hidden": "true", children: "vs" }),
1129
- /* @__PURE__ */ jsx11("span", { className: "ss-w-bi-team", children: it.awayTeamName }),
1130
- /* @__PURE__ */ jsx11(Crest2, { kind: "team", url: it.awayCrest, name: it.awayTeamName })
1131
- ] }) : it.fixture ? /* @__PURE__ */ jsx11("span", { className: "ss-w-bi-fixture-txt", children: it.fixture }) : null }),
1132
- /* @__PURE__ */ jsxs7("div", { className: "ss-w-bi-action", children: [
1133
- /* @__PURE__ */ jsxs7("span", { className: "ss-w-bi-mkt", children: [
1134
- /* @__PURE__ */ jsx11("span", { className: "ss-w-bi-market", children: it.market }),
1135
- it.selection ? /* @__PURE__ */ jsx11("span", { className: "ss-w-bi-sel", children: it.selection }) : null
1136
- ] }),
1137
- it.isMarketFloor ? /* @__PURE__ */ jsx11("span", { className: "ss-w-bi-floor", children: ui?.marketFloorLabel ?? "Market price \xB7 not a value pick" }) : null,
1138
- it.odds ? /* @__PURE__ */ jsxs7("span", { className: "ss-w-bi-odds", children: [
1139
- /* @__PURE__ */ jsx11("b", { children: it.odds }),
1140
- /* @__PURE__ */ jsx11(OddsMovementBadge, { previous: it.previousOdds, current: it.odds, direction: it.movementDirection, ui }),
1141
- it.bookmaker ? /* @__PURE__ */ jsx11("span", { className: "ss-w-bi-book", children: it.bookmaker }) : null
1142
- ] }) : null,
1143
- /* @__PURE__ */ jsx11(AddToSlipButton, { it, slip, ui })
1144
- ] })
1210
+ /* @__PURE__ */ jsxs7("div", { className: "ss-w-bi-action-row", children: [
1211
+ /* @__PURE__ */ jsxs7("span", { className: "ss-w-bi-pick", children: [
1212
+ /* @__PURE__ */ jsx11("span", { className: "ss-w-bi-market", children: it.market }),
1213
+ it.selection ? /* @__PURE__ */ jsxs7(Fragment, { children: [
1214
+ /* @__PURE__ */ jsx11("span", { className: "ss-w-bi-pick-dot", "aria-hidden": "true", children: "\xB7" }),
1215
+ /* @__PURE__ */ jsx11("span", { className: "ss-w-bi-sel", children: it.selection })
1216
+ ] }) : null
1217
+ ] }),
1218
+ it.odds ? /* @__PURE__ */ jsxs7("span", { className: "ss-w-bi-odds", children: [
1219
+ /* @__PURE__ */ jsx11("b", { children: it.odds }),
1220
+ /* @__PURE__ */ jsx11(OddsMovementBadge, { previous: it.previousOdds, current: it.odds, direction: it.movementDirection, ui }),
1221
+ it.bookmaker ? /* @__PURE__ */ jsx11("span", { className: "ss-w-bi-book", children: it.bookmaker }) : null
1222
+ ] }) : null,
1223
+ /* @__PURE__ */ jsx11(AddToSlipButton, { it, slip, ui })
1145
1224
  ] }),
1225
+ it.isMarketFloor ? /* @__PURE__ */ jsx11("span", { className: "ss-w-bi-floor", children: ui?.marketFloorLabel ?? "Market price \xB7 not a value pick" }) : null,
1146
1226
  /* @__PURE__ */ jsx11(ReasonStrip, { it, ui }),
1147
1227
  it.disclaimer ? /* @__PURE__ */ jsx11("p", { className: "ss-w-bi-disc", children: it.disclaimer }) : null
1148
1228
  ] });
@@ -1702,139 +1782,352 @@ function RestingFormCard({ card }) {
1702
1782
  ] });
1703
1783
  }
1704
1784
 
1785
+ // src/components/dashboard/LiveScoreCard.tsx
1786
+ import { useRef as useRef3, useState as useState8 } from "react";
1787
+
1788
+ // src/useLiveClock.ts
1789
+ import { useEffect as useEffect5, useState as useState7 } from "react";
1790
+ var TICK_MS = 6e4;
1791
+ var MAX_LIVE_TICK_MINUTES = 15;
1792
+ function computeDisplayMinute(minute, clockRunning, receivedAtMs) {
1793
+ if (!clockRunning) return minute;
1794
+ const elapsedMinutes = Math.floor((Date.now() - receivedAtMs) / TICK_MS);
1795
+ return minute + Math.min(Math.max(0, elapsedMinutes), MAX_LIVE_TICK_MINUTES);
1796
+ }
1797
+ function useLiveClock(minute, clockRunning, receivedAtMs) {
1798
+ const [displayMinute, setDisplayMinute] = useState7(
1799
+ () => computeDisplayMinute(minute, clockRunning, receivedAtMs)
1800
+ );
1801
+ useEffect5(() => {
1802
+ setDisplayMinute(computeDisplayMinute(minute, clockRunning, receivedAtMs));
1803
+ if (!clockRunning) return;
1804
+ const id = window.setInterval(() => {
1805
+ setDisplayMinute(computeDisplayMinute(minute, clockRunning, receivedAtMs));
1806
+ }, TICK_MS);
1807
+ return () => window.clearInterval(id);
1808
+ }, [minute, clockRunning, receivedAtMs]);
1809
+ return displayMinute;
1810
+ }
1811
+
1705
1812
  // src/components/dashboard/LiveScoreCard.tsx
1706
1813
  import { jsx as jsx34, jsxs as jsxs30 } from "react/jsx-runtime";
1707
- function LiveScoreCard({ card }) {
1814
+ function initials3(name) {
1815
+ return name.trim().slice(0, 3).toUpperCase();
1816
+ }
1817
+ function LiveCrest({ url, name, side }) {
1818
+ const [failed, setFailed] = useState8(false);
1819
+ const cls = `ss-w-fandash-live-crest ss-w-fandash-live-crest--${side}`;
1820
+ if (url && !failed) {
1821
+ return /* @__PURE__ */ jsx34("img", { className: cls, src: url, alt: "", "aria-hidden": "true", onError: () => setFailed(true) });
1822
+ }
1823
+ return /* @__PURE__ */ jsx34("span", { className: `${cls} ss-w-fandash-live-crest--fallback`, "aria-hidden": "true", children: initials3(name) });
1824
+ }
1825
+ function scorersLine(goalEvents) {
1826
+ if (!goalEvents || goalEvents.length === 0) return null;
1827
+ const named = goalEvents.filter((e) => e.type === "goal" && e.player);
1828
+ if (named.length === 0) return null;
1829
+ return named.slice().sort((a, b) => a.minute - b.minute).map((e) => `${e.player} ${e.minuteLabel}`).join(", ");
1830
+ }
1831
+ function LiveScoreCard({
1832
+ card,
1833
+ goalEvents
1834
+ }) {
1708
1835
  const { data } = card;
1836
+ const seedKey = `${data.minute}:${data.homeScore}:${data.awayScore}`;
1837
+ const seedRef = useRef3(null);
1838
+ if (!seedRef.current || seedRef.current.key !== seedKey) {
1839
+ seedRef.current = { key: seedKey, receivedAtMs: Date.now() };
1840
+ }
1841
+ const displayMinute = useLiveClock(data.minute, data.clockRunning, seedRef.current.receivedAtMs);
1842
+ const minuteSuffix = data.minuteLabel.replace(/^\d+/, "");
1843
+ const scorers = scorersLine(goalEvents);
1709
1844
  return /* @__PURE__ */ jsxs30("div", { className: "ss-w-fandash-card ss-w-fandash-live-score", children: [
1710
1845
  card.title ? /* @__PURE__ */ jsx34("span", { className: "ss-w-fandash-eyebrow", children: card.title }) : null,
1711
- /* @__PURE__ */ jsxs30("div", { className: "ss-w-fandash-postmatch-scoreboard", children: [
1712
- /* @__PURE__ */ jsx34("span", { className: "ss-w-fandash-postmatch-team", children: data.homeName }),
1713
- /* @__PURE__ */ jsx34("span", { className: "ss-w-fandash-postmatch-score", children: data.homeScore }),
1714
- /* @__PURE__ */ jsx34("span", { className: "ss-w-fandash-postmatch-sep", children: "\u2013" }),
1715
- /* @__PURE__ */ jsx34("span", { className: "ss-w-fandash-postmatch-score", children: data.awayScore }),
1716
- /* @__PURE__ */ jsx34("span", { className: "ss-w-fandash-postmatch-team ss-w-fandash-postmatch-team--away", children: data.awayName })
1717
- ] }),
1718
- /* @__PURE__ */ jsxs30("div", { className: "ss-w-fandash-live-meta", children: [
1719
- /* @__PURE__ */ jsx34("span", { className: "ss-w-fandash-live-minute", children: data.minuteLabel }),
1720
- /* @__PURE__ */ jsx34("span", { className: "ss-w-fandash-live-status", children: data.statusLabel }),
1846
+ /* @__PURE__ */ jsxs30("div", { className: "ss-w-fandash-live-top", children: [
1847
+ /* @__PURE__ */ jsxs30("span", { className: "ss-w-fandash-live-pill", children: [
1848
+ /* @__PURE__ */ jsx34("span", { className: "ss-w-fandash-live-dot", "aria-hidden": "true" }),
1849
+ data.statusLabel
1850
+ ] }),
1851
+ /* @__PURE__ */ jsx34("span", { className: "ss-w-fandash-live-minute", children: `${displayMinute}${minuteSuffix}` }),
1721
1852
  data.competition ? /* @__PURE__ */ jsx34("span", { className: "ss-w-fandash-live-competition", children: data.competition }) : null
1722
- ] })
1853
+ ] }),
1854
+ /* @__PURE__ */ jsxs30("div", { className: "ss-w-fandash-live-main", children: [
1855
+ /* @__PURE__ */ jsxs30("div", { className: "ss-w-fandash-live-side ss-w-fandash-live-side--home", children: [
1856
+ /* @__PURE__ */ jsx34(LiveCrest, { url: data.homeCrestUrl, name: data.homeName, side: "home" }),
1857
+ /* @__PURE__ */ jsx34("span", { className: "ss-w-fandash-live-team-name", children: data.homeName })
1858
+ ] }),
1859
+ /* @__PURE__ */ jsxs30("div", { className: "ss-w-fandash-live-score-main", children: [
1860
+ /* @__PURE__ */ jsx34("span", { className: "ss-w-fandash-live-score-value", children: data.homeScore }),
1861
+ /* @__PURE__ */ jsx34("span", { className: "ss-w-fandash-live-score-sep", children: "\u2013" }),
1862
+ /* @__PURE__ */ jsx34("span", { className: "ss-w-fandash-live-score-value", children: data.awayScore })
1863
+ ] }),
1864
+ /* @__PURE__ */ jsxs30("div", { className: "ss-w-fandash-live-side ss-w-fandash-live-side--away", children: [
1865
+ /* @__PURE__ */ jsx34(LiveCrest, { url: data.awayCrestUrl, name: data.awayName, side: "away" }),
1866
+ /* @__PURE__ */ jsx34("span", { className: "ss-w-fandash-live-team-name", children: data.awayName })
1867
+ ] })
1868
+ ] }),
1869
+ scorers ? /* @__PURE__ */ jsxs30("div", { className: "ss-w-fandash-live-scorers", children: [
1870
+ /* @__PURE__ */ jsx34("span", { className: "ss-w-fandash-live-scorers-icon", "aria-hidden": "true", children: "\u26BD" }),
1871
+ scorers
1872
+ ] }) : null
1723
1873
  ] });
1724
1874
  }
1725
1875
 
1726
1876
  // src/components/dashboard/LiveTimelineCard.tsx
1877
+ import { useState as useState9 } from "react";
1727
1878
  import { jsx as jsx35, jsxs as jsxs31 } from "react/jsx-runtime";
1728
- function LiveTimelineCard({ card }) {
1879
+ var SHOWN = 3;
1880
+ function EventIcon({ type }) {
1881
+ if (type === "goal") {
1882
+ return /* @__PURE__ */ jsx35("span", { className: "ss-w-fandash-live-tl-ico ss-w-fandash-live-tl-ico--goal", "aria-hidden": "true", children: /* @__PURE__ */ jsx35("span", { className: "ss-w-fandash-live-tl-ball" }) });
1883
+ }
1884
+ if (type === "yellow" || type === "red") {
1885
+ return /* @__PURE__ */ jsx35("span", { className: "ss-w-fandash-live-tl-ico", "aria-hidden": "true", children: /* @__PURE__ */ jsx35("span", { className: `ss-w-fandash-live-tl-card ss-w-fandash-live-tl-card--${type}` }) });
1886
+ }
1887
+ if (type === "sub") {
1888
+ return /* @__PURE__ */ jsx35("span", { className: "ss-w-fandash-live-tl-ico", "aria-hidden": "true", children: /* @__PURE__ */ jsx35("span", { className: "ss-w-fandash-live-tl-subarrows", children: "\u21C5" }) });
1889
+ }
1890
+ return /* @__PURE__ */ jsx35("span", { className: "ss-w-fandash-live-tl-ico", "aria-hidden": "true", children: /* @__PURE__ */ jsx35("span", { className: "ss-w-fandash-live-tl-dot" }) });
1891
+ }
1892
+ function EventLabel({ e }) {
1893
+ if (e.type === "sub") {
1894
+ return /* @__PURE__ */ jsxs31("span", { className: "ss-w-fandash-live-tl-who", children: [
1895
+ e.player ?? e.text,
1896
+ e.playerOff ? /* @__PURE__ */ jsxs31("span", { className: "ss-w-fandash-live-tl-off", children: [
1897
+ " for ",
1898
+ e.playerOff
1899
+ ] }) : null
1900
+ ] });
1901
+ }
1902
+ return /* @__PURE__ */ jsx35("span", { className: "ss-w-fandash-live-tl-who", children: e.player ?? e.text });
1903
+ }
1904
+ function EventRow({ e }) {
1905
+ const cls = [
1906
+ "ss-w-fandash-live-tl-ev",
1907
+ e.side === "home" ? "ss-w-fandash-live-tl-ev--home" : "ss-w-fandash-live-tl-ev--away",
1908
+ e.type === "goal" ? "ss-w-fandash-live-tl-ev--goal" : ""
1909
+ ].filter(Boolean).join(" ");
1910
+ return /* @__PURE__ */ jsxs31("div", { className: cls, children: [
1911
+ /* @__PURE__ */ jsxs31("span", { className: "ss-w-fandash-live-tl-txt", children: [
1912
+ /* @__PURE__ */ jsx35(EventLabel, { e }),
1913
+ /* @__PURE__ */ jsx35(EventIcon, { type: e.type })
1914
+ ] }),
1915
+ /* @__PURE__ */ jsx35("span", { className: "ss-w-fandash-live-tl-badge", children: e.minuteLabel })
1916
+ ] });
1917
+ }
1918
+ function LiveTimelineCard({ card, ui }) {
1919
+ const [expanded, setExpanded] = useState9(false);
1920
+ const events = card.data.events;
1921
+ if (!events || events.length === 0) return null;
1922
+ const sorted = [...events].sort((a, b) => b.minute - a.minute);
1923
+ const rest = sorted.length - SHOWN;
1924
+ const shown = expanded ? sorted : sorted.slice(0, SHOWN);
1925
+ const showMore = ui?.showMore ?? ((n) => `Show ${n} more`);
1926
+ const showLess = ui?.showLess ?? "Show less";
1729
1927
  return /* @__PURE__ */ jsxs31("div", { className: "ss-w-fandash-card ss-w-fandash-live-timeline", children: [
1730
1928
  card.title ? /* @__PURE__ */ jsx35("span", { className: "ss-w-fandash-eyebrow", children: card.title }) : null,
1731
- /* @__PURE__ */ jsx35("ul", { className: "ss-w-fandash-live-event-list", children: card.data.events.map((e, i) => /* @__PURE__ */ jsxs31("li", { className: e.scoredByClub ? "ss-w-fandash-live-event ss-w-fandash-live-event--club" : "ss-w-fandash-live-event", children: [
1732
- /* @__PURE__ */ jsx35("span", { className: "ss-w-fandash-live-event-minute", children: e.minuteLabel }),
1733
- /* @__PURE__ */ jsx35("span", { className: "ss-w-fandash-live-event-text", children: e.text })
1734
- ] }, `${e.minuteLabel}-${i}`)) })
1929
+ /* @__PURE__ */ jsx35("div", { className: "ss-w-fandash-live-tl", children: shown.map((e, i) => /* @__PURE__ */ jsx35(EventRow, { e }, `${e.minute}-${e.side}-${e.type}-${i}`)) }),
1930
+ rest > 0 ? /* @__PURE__ */ jsx35(
1931
+ "button",
1932
+ {
1933
+ type: "button",
1934
+ className: "ss-w-fandash-live-tl-more",
1935
+ "aria-expanded": expanded,
1936
+ onClick: () => setExpanded((v) => !v),
1937
+ children: expanded ? showLess : showMore(rest)
1938
+ }
1939
+ ) : null
1735
1940
  ] });
1736
1941
  }
1737
1942
 
1738
- // src/components/dashboard/LiveStatsCard.tsx
1943
+ // src/components/dashboard/MomentumWave.tsx
1944
+ import { useId } from "react";
1739
1945
  import { jsx as jsx36, jsxs as jsxs32 } from "react/jsx-runtime";
1946
+ var WIDTH = 270;
1947
+ var HEIGHT = 56;
1948
+ var BASELINE = HEIGHT / 2;
1949
+ var SCALE = 0.26;
1950
+ function buildPaths(points) {
1951
+ const lastIndex = points.length - 1;
1952
+ const px = (i) => lastIndex === 0 ? 0 : i / lastIndex * WIDTH;
1953
+ const py = (v) => BASELINE - v * SCALE;
1954
+ const linePts = points.map((p, i) => `${px(i).toFixed(1)},${py(p.value).toFixed(1)}`);
1955
+ const area = `M0,${BASELINE} L${linePts.join(" L")} L${WIDTH},${BASELINE} Z`;
1956
+ const line = `M${linePts.join(" L")}`;
1957
+ const lastValue = points[lastIndex]?.value ?? 0;
1958
+ return { area, line, lastX: px(lastIndex).toFixed(1), lastY: py(lastValue).toFixed(1) };
1959
+ }
1960
+ function MomentumWave({
1961
+ points,
1962
+ caption,
1963
+ clubIsHome
1964
+ }) {
1965
+ if (points.length === 0) return null;
1966
+ const { area, line, lastX, lastY } = buildPaths(points);
1967
+ const clipId = useId();
1968
+ const topClipId = `${clipId}-top`;
1969
+ const bottomClipId = `${clipId}-bottom`;
1970
+ const topClass = clubIsHome ? "ss-w-fandash-live-mom-home" : "ss-w-fandash-live-mom-away";
1971
+ const bottomClass = clubIsHome ? "ss-w-fandash-live-mom-away" : "ss-w-fandash-live-mom-home";
1972
+ return /* @__PURE__ */ jsxs32("div", { className: "ss-w-fandash-live-mom", children: [
1973
+ caption ? /* @__PURE__ */ jsx36("span", { className: "ss-w-fandash-live-mom-caption", children: caption }) : null,
1974
+ /* @__PURE__ */ jsxs32(
1975
+ "svg",
1976
+ {
1977
+ className: "ss-w-fandash-live-mom-svg",
1978
+ viewBox: `0 0 ${WIDTH} ${HEIGHT}`,
1979
+ preserveAspectRatio: "none",
1980
+ role: "img",
1981
+ "aria-label": caption ?? "Momentum",
1982
+ children: [
1983
+ /* @__PURE__ */ jsxs32("defs", { children: [
1984
+ /* @__PURE__ */ jsx36("clipPath", { id: topClipId, children: /* @__PURE__ */ jsx36("rect", { x: 0, y: 0, width: WIDTH, height: BASELINE }) }),
1985
+ /* @__PURE__ */ jsx36("clipPath", { id: bottomClipId, children: /* @__PURE__ */ jsx36("rect", { x: 0, y: BASELINE, width: WIDTH, height: HEIGHT - BASELINE }) })
1986
+ ] }),
1987
+ /* @__PURE__ */ jsx36("path", { d: area, className: topClass, clipPath: `url(#${topClipId})` }),
1988
+ /* @__PURE__ */ jsx36("path", { d: area, className: bottomClass, clipPath: `url(#${bottomClipId})` }),
1989
+ /* @__PURE__ */ jsx36("line", { x1: 0, y1: BASELINE, x2: WIDTH, y2: BASELINE, className: "ss-w-fandash-live-mom-base" }),
1990
+ /* @__PURE__ */ jsx36("path", { d: line, className: "ss-w-fandash-live-mom-line", vectorEffect: "non-scaling-stroke" }),
1991
+ /* @__PURE__ */ jsx36("circle", { cx: lastX, cy: lastY, r: 3.2, className: "ss-w-fandash-live-mom-now", vectorEffect: "non-scaling-stroke" })
1992
+ ]
1993
+ }
1994
+ )
1995
+ ] });
1996
+ }
1997
+
1998
+ // src/components/dashboard/LiveStatsCard.tsx
1999
+ import { jsx as jsx37, jsxs as jsxs33 } from "react/jsx-runtime";
2000
+ function splitWidths(homeNum, awayNum) {
2001
+ const total = homeNum + awayNum;
2002
+ if (total <= 0) return { home: "50%", away: "50%" };
2003
+ const homePct = Math.round(homeNum / total * 1e3) / 10;
2004
+ return { home: `${homePct}%`, away: `${Math.round((100 - homePct) * 10) / 10}%` };
2005
+ }
1740
2006
  function LiveStatsCard({ card }) {
1741
2007
  const { data } = card;
1742
- return /* @__PURE__ */ jsxs32("div", { className: "ss-w-fandash-card ss-w-fandash-live-stats", children: [
1743
- card.title ? /* @__PURE__ */ jsx36("span", { className: "ss-w-fandash-eyebrow", children: card.title }) : null,
1744
- /* @__PURE__ */ jsx36("div", { className: "ss-w-fandash-live-stat-rows", children: data.rows.map((r, i) => /* @__PURE__ */ jsxs32("div", { className: "ss-w-fandash-live-stat-row", children: [
1745
- /* @__PURE__ */ jsx36("span", { className: "ss-w-fandash-live-stat-value", children: r.clubValue }),
1746
- /* @__PURE__ */ jsx36("span", { className: "ss-w-fandash-live-stat-label", children: r.label }),
1747
- /* @__PURE__ */ jsx36("span", { className: "ss-w-fandash-live-stat-value", children: r.oppValue })
1748
- ] }, `${r.label}-${i}`)) }),
1749
- data.momentumLabel ? /* @__PURE__ */ jsx36("span", { className: "ss-w-fandash-live-momentum", children: data.momentumLabel }) : null
2008
+ return /* @__PURE__ */ jsxs33("div", { className: "ss-w-fandash-card ss-w-fandash-live-stats", children: [
2009
+ card.title ? /* @__PURE__ */ jsx37("span", { className: "ss-w-fandash-eyebrow", children: card.title }) : null,
2010
+ /* @__PURE__ */ jsx37("div", { className: "ss-w-fandash-live-stat-rows", children: data.rows.map((r, i) => {
2011
+ const homeValue = data.clubIsHome ? r.clubValue : r.oppValue;
2012
+ const awayValue = data.clubIsHome ? r.oppValue : r.clubValue;
2013
+ const homeNum = data.clubIsHome ? r.clubNum : r.oppNum;
2014
+ const awayNum = data.clubIsHome ? r.oppNum : r.clubNum;
2015
+ const { home, away } = splitWidths(homeNum, awayNum);
2016
+ return /* @__PURE__ */ jsxs33("div", { className: "ss-w-fandash-live-stat", children: [
2017
+ /* @__PURE__ */ jsxs33("div", { className: "ss-w-fandash-live-stat-row", children: [
2018
+ /* @__PURE__ */ jsx37("span", { className: "ss-w-fandash-live-stat-value ss-w-fandash-live-stat-value--home", children: homeValue }),
2019
+ /* @__PURE__ */ jsx37("span", { className: "ss-w-fandash-live-stat-label", children: r.label }),
2020
+ /* @__PURE__ */ jsx37("span", { className: "ss-w-fandash-live-stat-value ss-w-fandash-live-stat-value--away", children: awayValue })
2021
+ ] }),
2022
+ /* @__PURE__ */ jsxs33("div", { className: "ss-w-fandash-live-bar", children: [
2023
+ /* @__PURE__ */ jsx37("span", { className: "ss-w-fandash-live-bar-home", style: { width: home } }),
2024
+ /* @__PURE__ */ jsx37("span", { className: "ss-w-fandash-live-bar-away", style: { width: away } })
2025
+ ] })
2026
+ ] }, `${r.label}-${i}`);
2027
+ }) }),
2028
+ data.momentum.length > 0 ? /* @__PURE__ */ jsx37(MomentumWave, { points: data.momentum, caption: data.momentumLabel, clubIsHome: data.clubIsHome }) : data.momentumLabel ? /* @__PURE__ */ jsx37("span", { className: "ss-w-fandash-live-momentum", children: data.momentumLabel }) : null,
2029
+ data.rows.length > 0 ? /* @__PURE__ */ jsxs33("div", { className: "ss-w-fandash-live-legend", children: [
2030
+ /* @__PURE__ */ jsxs33("span", { className: "ss-w-fandash-live-legend-item", children: [
2031
+ /* @__PURE__ */ jsx37("i", { className: "ss-w-fandash-live-legend-swatch ss-w-fandash-live-legend-swatch--home" }),
2032
+ data.homeName
2033
+ ] }),
2034
+ /* @__PURE__ */ jsxs33("span", { className: "ss-w-fandash-live-legend-item", children: [
2035
+ /* @__PURE__ */ jsx37("i", { className: "ss-w-fandash-live-legend-swatch ss-w-fandash-live-legend-swatch--away" }),
2036
+ data.awayName
2037
+ ] })
2038
+ ] }) : null
1750
2039
  ] });
1751
2040
  }
1752
2041
 
1753
2042
  // src/components/dashboard/ClubFanDashboard.tsx
1754
- import { jsx as jsx37, jsxs as jsxs33 } from "react/jsx-runtime";
2043
+ import { jsx as jsx38, jsxs as jsxs34 } from "react/jsx-runtime";
1755
2044
  function ClubFanDashboard({ dashboard, onTap, ui }) {
1756
2045
  const orderedCards = [
1757
2046
  ...dashboard.cards.filter((c) => c.cardType === "countdown"),
1758
2047
  ...dashboard.cards.filter((c) => c.cardType !== "countdown")
1759
2048
  ];
1760
- return /* @__PURE__ */ jsxs33("div", { className: "ss-w-fandash", children: [
1761
- dashboard.greeting ? /* @__PURE__ */ jsx37("span", { className: "ss-w-fandash-greeting", children: dashboard.greeting }) : null,
1762
- /* @__PURE__ */ jsx37("div", { className: "ss-w-fandash-cards", children: orderedCards.map((card, i) => /* @__PURE__ */ jsx37(FanDashboardCard, { card, onTap, ui }, `${card.cardType}-${i}`)) })
2049
+ const timelineCard = dashboard.cards.find(
2050
+ (c) => c.cardType === "liveTimeline"
2051
+ );
2052
+ const goalEvents = timelineCard?.data.events.filter((e) => e.type === "goal");
2053
+ return /* @__PURE__ */ jsxs34("div", { className: "ss-w-fandash", children: [
2054
+ dashboard.greeting ? /* @__PURE__ */ jsx38("span", { className: "ss-w-fandash-greeting", children: dashboard.greeting }) : null,
2055
+ /* @__PURE__ */ jsx38("div", { className: "ss-w-fandash-cards", children: orderedCards.map((card, i) => /* @__PURE__ */ jsx38(FanDashboardCard, { card, onTap, ui, goalEvents }, `${card.cardType}-${i}`)) })
1763
2056
  ] });
1764
2057
  }
1765
- function FanDashboardCard({ card, onTap, ui }) {
2058
+ function FanDashboardCard({ card, onTap, ui, goalEvents }) {
1766
2059
  switch (card.cardType) {
1767
2060
  case "fixture":
1768
- return /* @__PURE__ */ jsx37(FixtureCard, { card, onTap });
2061
+ return /* @__PURE__ */ jsx38(FixtureCard, { card, onTap });
1769
2062
  case "countdown":
1770
- return /* @__PURE__ */ jsx37(CountdownCard, { card });
2063
+ return /* @__PURE__ */ jsx38(CountdownCard, { card });
1771
2064
  case "injuries":
1772
- return /* @__PURE__ */ jsx37(InjuriesCard, { card });
2065
+ return /* @__PURE__ */ jsx38(InjuriesCard, { card });
1773
2066
  case "reasonToBet":
1774
- return /* @__PURE__ */ jsx37(ReasonToBetCard, { card });
2067
+ return /* @__PURE__ */ jsx38(ReasonToBetCard, { card });
1775
2068
  case "whereToWatch":
1776
- return /* @__PURE__ */ jsx37(WhereToWatchCard, { card });
2069
+ return /* @__PURE__ */ jsx38(WhereToWatchCard, { card });
1777
2070
  case "congestedWeek":
1778
- return /* @__PURE__ */ jsx37(CongestedWeekCard, { card, onTap });
2071
+ return /* @__PURE__ */ jsx38(CongestedWeekCard, { card, onTap });
1779
2072
  case "transfers":
1780
- return /* @__PURE__ */ jsx37(TransfersCard, { card });
2073
+ return /* @__PURE__ */ jsx38(TransfersCard, { card });
1781
2074
  case "links":
1782
- return /* @__PURE__ */ jsx37(LinksCard, { card });
2075
+ return /* @__PURE__ */ jsx38(LinksCard, { card });
1783
2076
  case "postMatchResult":
1784
- return /* @__PURE__ */ jsx37(PostMatchResultCard, { card });
2077
+ return /* @__PURE__ */ jsx38(PostMatchResultCard, { card });
1785
2078
  case "postMatchGoals":
1786
- return /* @__PURE__ */ jsx37(PostMatchGoalsCard, { card, onTap });
2079
+ return /* @__PURE__ */ jsx38(PostMatchGoalsCard, { card, onTap });
1787
2080
  case "postMatchStats":
1788
- return /* @__PURE__ */ jsx37(PostMatchStatsCard, { card, onTap });
2081
+ return /* @__PURE__ */ jsx38(PostMatchStatsCard, { card, onTap });
1789
2082
  case "postMatchTopPerformer":
1790
- return /* @__PURE__ */ jsx37(PostMatchTopPerformerCard, { card, onTap });
2083
+ return /* @__PURE__ */ jsx38(PostMatchTopPerformerCard, { card, onTap });
1791
2084
  case "postMatchNext":
1792
- return /* @__PURE__ */ jsx37(PostMatchNextCard, { card });
2085
+ return /* @__PURE__ */ jsx38(PostMatchNextCard, { card });
1793
2086
  case "restingNext":
1794
- return /* @__PURE__ */ jsx37(RestingNextCard, { card, onTap });
2087
+ return /* @__PURE__ */ jsx38(RestingNextCard, { card, onTap });
1795
2088
  case "restingStandings":
1796
- return /* @__PURE__ */ jsx37(RestingStandingsCard, { card, onTap });
2089
+ return /* @__PURE__ */ jsx38(RestingStandingsCard, { card, onTap });
1797
2090
  case "restingSeason":
1798
- return /* @__PURE__ */ jsx37(RestingSeasonCard, { card, onTap });
2091
+ return /* @__PURE__ */ jsx38(RestingSeasonCard, { card, onTap });
1799
2092
  case "restingForm":
1800
- return /* @__PURE__ */ jsx37(RestingFormCard, { card });
2093
+ return /* @__PURE__ */ jsx38(RestingFormCard, { card });
1801
2094
  case "liveScore":
1802
- return /* @__PURE__ */ jsx37(LiveScoreCard, { card });
2095
+ return /* @__PURE__ */ jsx38(LiveScoreCard, { card, goalEvents });
1803
2096
  case "liveTimeline":
1804
- return /* @__PURE__ */ jsx37(LiveTimelineCard, { card });
2097
+ return /* @__PURE__ */ jsx38(LiveTimelineCard, { card, ui });
1805
2098
  case "liveStats":
1806
- return /* @__PURE__ */ jsx37(LiveStatsCard, { card });
2099
+ return /* @__PURE__ */ jsx38(LiveStatsCard, { card });
1807
2100
  default:
1808
2101
  return null;
1809
2102
  }
1810
2103
  }
1811
2104
 
1812
2105
  // src/components/ChatMessage.tsx
1813
- import { jsx as jsx38, jsxs as jsxs34 } from "react/jsx-runtime";
2106
+ import { jsx as jsx39, jsxs as jsxs35 } from "react/jsx-runtime";
1814
2107
  function ChatMessage({ message, disabled, onAct, onTap, ui, slip }) {
1815
2108
  if (message.role === "user") {
1816
- return /* @__PURE__ */ jsx38("div", { className: "ss-w-msg ss-w-user", "data-msg-id": message.id, children: /* @__PURE__ */ jsx38("div", { className: "ss-w-bubble", children: message.text }) });
2109
+ return /* @__PURE__ */ jsx39("div", { className: "ss-w-msg ss-w-user", "data-msg-id": message.id, children: /* @__PURE__ */ jsx39("div", { className: "ss-w-bubble", children: message.text }) });
1817
2110
  }
1818
2111
  const onFanDashboardTap = (card) => {
1819
2112
  onAct({ message: card.actionLabel, actionRef: card.actionRef, label: card.actionLabel || card.title || "" });
1820
2113
  };
1821
- return /* @__PURE__ */ jsxs34("div", { className: "ss-w-msg ss-w-bot", "data-msg-id": message.id, children: [
1822
- message.text && !message.greetingDashboard && !message.clubFanDashboard ? /* @__PURE__ */ jsx38(Markdown, { text: message.text }) : null,
1823
- message.greetingDashboard && !message.clubFanDashboard ? /* @__PURE__ */ jsx38(GreetingDashboard, { dashboard: message.greetingDashboard, onTap }) : null,
1824
- message.clubFanDashboard ? /* @__PURE__ */ jsx38(ClubFanDashboard, { dashboard: message.clubFanDashboard, onTap: onFanDashboardTap, ui }) : null,
1825
- /* @__PURE__ */ jsx38(EntityCardList, { cards: message.cards, disabled, showLessLabel: ui?.showLess, showMoreLabel: ui?.showMore }),
1826
- /* @__PURE__ */ jsx38(BettingInsightList, { insights: message.insights, ui, slip }),
1827
- /* @__PURE__ */ jsx38(MarketOddsCardList, { cards: message.marketOdds, ui, slip }),
1828
- /* @__PURE__ */ jsx38(OddsMovementCardList, { cards: message.oddsMovement, ui }),
1829
- /* @__PURE__ */ jsx38(ActionButtons, { actions: message.actions, loading: false, disabled, onAct })
2114
+ return /* @__PURE__ */ jsxs35("div", { className: "ss-w-msg ss-w-bot", "data-msg-id": message.id, children: [
2115
+ message.text && !message.greetingDashboard && !message.clubFanDashboard ? /* @__PURE__ */ jsx39(Markdown, { text: message.text }) : null,
2116
+ message.greetingDashboard && !message.clubFanDashboard ? /* @__PURE__ */ jsx39(GreetingDashboard, { dashboard: message.greetingDashboard, onTap }) : null,
2117
+ message.clubFanDashboard ? /* @__PURE__ */ jsx39(ClubFanDashboard, { dashboard: message.clubFanDashboard, onTap: onFanDashboardTap, ui }) : null,
2118
+ /* @__PURE__ */ jsx39(EntityCardList, { cards: message.cards, disabled, showLessLabel: ui?.showLess, showMoreLabel: ui?.showMore }),
2119
+ /* @__PURE__ */ jsx39(BettingInsightList, { insights: message.insights, ui, slip }),
2120
+ /* @__PURE__ */ jsx39(MarketOddsCardList, { cards: message.marketOdds, ui, slip }),
2121
+ /* @__PURE__ */ jsx39(OddsMovementCardList, { cards: message.oddsMovement, ui }),
2122
+ /* @__PURE__ */ jsx39(ActionButtons, { actions: message.actions, loading: false, disabled, onAct })
1830
2123
  ] });
1831
2124
  }
1832
2125
 
1833
2126
  // src/components/IntentChips.tsx
1834
- import { jsx as jsx39 } from "react/jsx-runtime";
2127
+ import { jsx as jsx40 } from "react/jsx-runtime";
1835
2128
  function IntentChips({ chips, onPick }) {
1836
2129
  if (chips.length === 0) return null;
1837
- return /* @__PURE__ */ jsx39("div", { className: "ss-w-chips", children: chips.map((c, i) => /* @__PURE__ */ jsx39("button", { type: "button", className: "ss-w-chip", onClick: () => onPick(c.text), children: c.text }, `${c.text}-${i}`)) });
2130
+ return /* @__PURE__ */ jsx40("div", { className: "ss-w-chips", children: chips.map((c, i) => /* @__PURE__ */ jsx40("button", { type: "button", className: "ss-w-chip", onClick: () => onPick(c.text), children: c.text }, `${c.text}-${i}`)) });
1838
2131
  }
1839
2132
 
1840
2133
  // src/scroll.ts
@@ -1844,7 +2137,7 @@ function isPinnedToBottom(el, threshold = PIN_THRESHOLD) {
1844
2137
  }
1845
2138
 
1846
2139
  // src/components/MessageList.tsx
1847
- import { jsx as jsx40, jsxs as jsxs35 } from "react/jsx-runtime";
2140
+ import { jsx as jsx41, jsxs as jsxs36 } from "react/jsx-runtime";
1848
2141
  function turnAnchor(messages) {
1849
2142
  const last = messages[messages.length - 1];
1850
2143
  if (!last || last.role !== "assistant") return null;
@@ -1853,11 +2146,11 @@ function turnAnchor(messages) {
1853
2146
  }
1854
2147
  function MessageList({ controller, ui, slip, onDashboardTap }) {
1855
2148
  const { state, send } = controller;
1856
- const listRef = useRef3(null);
1857
- const endRef = useRef3(null);
1858
- const pinnedRef = useRef3(true);
1859
- const anchoredTurnRef = useRef3(null);
1860
- useEffect5(() => {
2149
+ const listRef = useRef4(null);
2150
+ const endRef = useRef4(null);
2151
+ const pinnedRef = useRef4(true);
2152
+ const anchoredTurnRef = useRef4(null);
2153
+ useEffect6(() => {
1861
2154
  const anchor = turnAnchor(state.messages);
1862
2155
  const list = listRef.current;
1863
2156
  if (anchor && anchor.turnId !== anchoredTurnRef.current) {
@@ -1872,7 +2165,7 @@ function MessageList({ controller, ui, slip, onDashboardTap }) {
1872
2165
  }, [state.messages, state.status, state.isStreaming]);
1873
2166
  const hasUserMessage = state.messages.some((m) => m.role === "user");
1874
2167
  const hasDashboard = state.messages.some((m) => m.role === "assistant" && (m.greetingDashboard || m.clubFanDashboard));
1875
- return /* @__PURE__ */ jsxs35(
2168
+ return /* @__PURE__ */ jsxs36(
1876
2169
  "div",
1877
2170
  {
1878
2171
  className: "ss-w-list",
@@ -1882,7 +2175,7 @@ function MessageList({ controller, ui, slip, onDashboardTap }) {
1882
2175
  if (listRef.current) pinnedRef.current = isPinnedToBottom(listRef.current);
1883
2176
  },
1884
2177
  children: [
1885
- state.messages.map((m) => /* @__PURE__ */ jsx40(
2178
+ state.messages.map((m) => /* @__PURE__ */ jsx41(
1886
2179
  ChatMessage,
1887
2180
  {
1888
2181
  message: m,
@@ -1897,25 +2190,25 @@ function MessageList({ controller, ui, slip, onDashboardTap }) {
1897
2190
  },
1898
2191
  m.id
1899
2192
  )),
1900
- state.isStreaming && state.actionsLoading ? /* @__PURE__ */ jsx40(ActionButtons, { actions: [], loading: true, onAct: () => {
2193
+ state.isStreaming && state.actionsLoading ? /* @__PURE__ */ jsx41(ActionButtons, { actions: [], loading: true, onAct: () => {
1901
2194
  } }) : null,
1902
- state.status ? /* @__PURE__ */ jsx40("div", { className: "ss-w-status", children: state.status }) : null,
1903
- state.error ? /* @__PURE__ */ jsxs35("div", { className: "ss-w-error", role: "alert", children: [
1904
- /* @__PURE__ */ jsx40("span", { children: "message" in state.error ? state.error.message : ui.somethingWentWrong }),
1905
- /* @__PURE__ */ jsx40("button", { type: "button", className: "ss-w-retry", onClick: () => controller.retry(), children: ui.retry })
2195
+ state.status ? /* @__PURE__ */ jsx41("div", { className: "ss-w-status", children: state.status }) : null,
2196
+ state.error ? /* @__PURE__ */ jsxs36("div", { className: "ss-w-error", role: "alert", children: [
2197
+ /* @__PURE__ */ jsx41("span", { children: "message" in state.error ? state.error.message : ui.somethingWentWrong }),
2198
+ /* @__PURE__ */ jsx41("button", { type: "button", className: "ss-w-retry", onClick: () => controller.retry(), children: ui.retry })
1906
2199
  ] }) : null,
1907
- !hasUserMessage && !hasDashboard ? /* @__PURE__ */ jsx40(IntentChips, { chips: state.starters, onPick: (t) => send(t) }) : null,
1908
- /* @__PURE__ */ jsx40("div", { ref: endRef, className: "ss-w-turn-spacer", "aria-hidden": "true" })
2200
+ !hasUserMessage && !hasDashboard ? /* @__PURE__ */ jsx41(IntentChips, { chips: state.starters, onPick: (t) => send(t) }) : null,
2201
+ /* @__PURE__ */ jsx41("div", { ref: endRef, className: "ss-w-turn-spacer", "aria-hidden": "true" })
1909
2202
  ]
1910
2203
  }
1911
2204
  );
1912
2205
  }
1913
2206
 
1914
2207
  // src/components/ChatInput.tsx
1915
- import { useState as useState7 } from "react";
1916
- import { jsx as jsx41, jsxs as jsxs36 } from "react/jsx-runtime";
2208
+ import { useState as useState10 } from "react";
2209
+ import { jsx as jsx42, jsxs as jsxs37 } from "react/jsx-runtime";
1917
2210
  function ChatInput({ placeholder, streaming, onSend, onStop, stopLabel = "Stop response", sendLabel = "Send message" }) {
1918
- const [value, setValue] = useState7("");
2211
+ const [value, setValue] = useState10("");
1919
2212
  const submit = () => {
1920
2213
  const t = value.trim();
1921
2214
  if (!t) return;
@@ -1928,18 +2221,18 @@ function ChatInput({ placeholder, streaming, onSend, onStop, stopLabel = "Stop r
1928
2221
  submit();
1929
2222
  }
1930
2223
  };
1931
- return /* @__PURE__ */ jsxs36("div", { className: "ss-w-input", children: [
1932
- /* @__PURE__ */ jsx41("textarea", { className: "ss-w-textarea", rows: 1, placeholder, value, disabled: streaming, onChange: (e) => setValue(e.target.value), onKeyDown }),
1933
- streaming ? /* @__PURE__ */ jsx41("button", { type: "button", className: "ss-w-send ss-w-stop", "aria-label": stopLabel, onClick: onStop, children: /* @__PURE__ */ jsx41("span", { className: "ss-w-stop-glyph" }) }) : /* @__PURE__ */ jsx41("button", { type: "button", className: "ss-w-send", "aria-label": sendLabel, onClick: submit, children: /* @__PURE__ */ jsx41(SendIcon, {}) })
2224
+ return /* @__PURE__ */ jsxs37("div", { className: "ss-w-input", children: [
2225
+ /* @__PURE__ */ jsx42("textarea", { className: "ss-w-textarea", rows: 1, placeholder, value, disabled: streaming, onChange: (e) => setValue(e.target.value), onKeyDown }),
2226
+ streaming ? /* @__PURE__ */ jsx42("button", { type: "button", className: "ss-w-send ss-w-stop", "aria-label": stopLabel, onClick: onStop, children: /* @__PURE__ */ jsx42("span", { className: "ss-w-stop-glyph" }) }) : /* @__PURE__ */ jsx42("button", { type: "button", className: "ss-w-send", "aria-label": sendLabel, onClick: submit, children: /* @__PURE__ */ jsx42(SendIcon, {}) })
1934
2227
  ] });
1935
2228
  }
1936
2229
 
1937
2230
  // src/components/ChatPanel.tsx
1938
- import { jsx as jsx42, jsxs as jsxs37 } from "react/jsx-runtime";
2231
+ import { jsx as jsx43, jsxs as jsxs38 } from "react/jsx-runtime";
1939
2232
  var FULLSCREEN_QUERY = "(max-width: 639px)";
1940
2233
  function useIsModal() {
1941
- const [modal, setModal] = useState8(false);
1942
- useEffect6(() => {
2234
+ const [modal, setModal] = useState11(false);
2235
+ useEffect7(() => {
1943
2236
  if (typeof window === "undefined" || typeof window.matchMedia !== "function") return;
1944
2237
  const mql = window.matchMedia(FULLSCREEN_QUERY);
1945
2238
  const sync = () => setModal(mql.matches);
@@ -1952,17 +2245,17 @@ function useIsModal() {
1952
2245
  function ChatPanel({ controller, ui, slip, onDashboardTap }) {
1953
2246
  const { state, close, stop, send } = controller;
1954
2247
  const isModal = useIsModal();
1955
- useEffect6(() => {
2248
+ useEffect7(() => {
1956
2249
  const onKey = (e) => {
1957
2250
  if (e.key === "Escape") close();
1958
2251
  };
1959
2252
  window.addEventListener("keydown", onKey);
1960
2253
  return () => window.removeEventListener("keydown", onKey);
1961
2254
  }, [close]);
1962
- return /* @__PURE__ */ jsxs37("div", { className: "ss-w-panel", role: "dialog", "aria-modal": isModal, "aria-label": ui.chatDialogLabel, children: [
1963
- /* @__PURE__ */ jsx42(ChatHeader, { onClose: close, closeLabel: ui.closeChat }),
1964
- /* @__PURE__ */ jsx42(MessageList, { controller, ui, slip, onDashboardTap }),
1965
- /* @__PURE__ */ jsx42(ChatInput, { placeholder: ui.placeholder, streaming: state.isStreaming, onSend: (t) => send(t), onStop: stop, stopLabel: ui.stopResponse, sendLabel: ui.sendMessage })
2255
+ return /* @__PURE__ */ jsxs38("div", { className: "ss-w-panel", role: "dialog", "aria-modal": isModal, "aria-label": ui.chatDialogLabel, children: [
2256
+ /* @__PURE__ */ jsx43(ChatHeader, { onClose: close, closeLabel: ui.closeChat }),
2257
+ /* @__PURE__ */ jsx43(MessageList, { controller, ui, slip, onDashboardTap }),
2258
+ /* @__PURE__ */ jsx43(ChatInput, { placeholder: ui.placeholder, streaming: state.isStreaming, onSend: (t) => send(t), onStop: stop, stopLabel: ui.stopResponse, sendLabel: ui.sendMessage })
1966
2259
  ] });
1967
2260
  }
1968
2261
 
@@ -1975,6 +2268,7 @@ var WIDGET_CSS = `.ss-w-root { all: revert; }
1975
2268
  --ss-w-ink: #14181d; --ss-w-faint: #6b7580; --ss-w-line: #e4e8ec;
1976
2269
  --ss-w-panel: #ffffff; --ss-w-bubble: #f3f5f7; --ss-w-nav: #0e1622;
1977
2270
  --ss-w-pos: #1aa563; --ss-w-neg: #e0524b;
2271
+ --ss-w-home: #3a63a8; --ss-w-away: #c8102e; --ss-w-track: #e9edf1;
1978
2272
  --ss-w-radius: 16px; --ss-w-offset-x: 20px; --ss-w-offset-y: 20px;
1979
2273
  font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
1980
2274
  font-size: 13px; line-height: 1.5; color: var(--ss-w-ink);
@@ -1985,6 +2279,7 @@ var WIDGET_CSS = `.ss-w-root { all: revert; }
1985
2279
  --ss-w-ink: #e8edf2; --ss-w-faint: #8b95a1;
1986
2280
  --ss-w-line: #2a3441; --ss-w-panel: #141b24; --ss-w-bubble: #1e2732; --ss-w-nav: #0a0f16;
1987
2281
  --ss-w-pos: #37c891; --ss-w-neg: #f0655d;
2282
+ --ss-w-home: #5b83c8; --ss-w-away: #e8384f; --ss-w-track: #232d3a;
1988
2283
  }
1989
2284
  }
1990
2285
  .ss-w-root[data-ss-w-theme="dark"] {
@@ -1992,11 +2287,13 @@ var WIDGET_CSS = `.ss-w-root { all: revert; }
1992
2287
  --ss-w-ink: #e8edf2; --ss-w-faint: #8b95a1;
1993
2288
  --ss-w-line: #2a3441; --ss-w-panel: #141b24; --ss-w-bubble: #1e2732; --ss-w-nav: #0a0f16;
1994
2289
  --ss-w-pos: #37c891; --ss-w-neg: #f0655d;
2290
+ --ss-w-home: #5b83c8; --ss-w-away: #e8384f; --ss-w-track: #232d3a;
1995
2291
  }
1996
2292
  .ss-w-root[data-ss-w-theme="light"] {
1997
2293
  --ss-w-accent: #17936a; --ss-w-cta: #f5a623; --ss-w-cta-ink: #1b1300;
1998
2294
  --ss-w-ink: #14181d; --ss-w-faint: #6b7580;
1999
2295
  --ss-w-line: #e4e8ec; --ss-w-panel: #ffffff; --ss-w-bubble: #f3f5f7; --ss-w-nav: #0e1622;
2296
+ --ss-w-home: #3a63a8; --ss-w-away: #c8102e; --ss-w-track: #e9edf1;
2000
2297
  }
2001
2298
 
2002
2299
  /* launcher */
@@ -2111,7 +2408,7 @@ var WIDGET_CSS = `.ss-w-root { all: revert; }
2111
2408
 
2112
2409
  /* row 1: competition logo + name on the left, live badge + kickoff on the right */
2113
2410
  .ss-w-bi-header { display: flex; align-items: center; justify-content: space-between; gap: 8px; min-width: 0; padding-bottom: 7px; border-bottom: 1px solid var(--ss-w-line); }
2114
- .ss-w-bi-header-left { display: inline-flex; align-items: center; gap: 6px; min-width: 0; }
2411
+ .ss-w-bi-header-left { display: inline-flex; align-items: center; gap: 6px; min-width: 0; flex: 1; font-size: 12.5px; font-weight: 650; color: var(--ss-w-ink); }
2115
2412
  .ss-w-bi-header-right { display: inline-flex; align-items: center; gap: 8px; flex: none; }
2116
2413
  .ss-w-bi-comp { font-size: 10.5px; color: var(--ss-w-faint); font-weight: 600; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
2117
2414
  .ss-w-bi-kick { font-size: 10.5px; font-weight: 600; color: var(--ss-w-faint); white-space: nowrap; flex: none; }
@@ -2124,23 +2421,26 @@ var WIDGET_CSS = `.ss-w-root { all: revert; }
2124
2421
  .ss-w-bi-crest { width: 24px; height: 24px; flex: none; display: block; border-radius: 50%; object-fit: contain; }
2125
2422
  .ss-w-bi-crest-mono { display: grid; place-items: center; font-size: 10px; font-weight: 700; color: var(--ss-w-accent); background: color-mix(in srgb, var(--ss-w-accent) 16%, var(--ss-w-panel)); }
2126
2423
 
2127
- /* row 2: crest-flanked fixture on the left, boxed action panel on the right */
2128
- .ss-w-bi-body { display: flex; align-items: center; gap: 10px; }
2129
- .ss-w-bi-fixture { display: flex; align-items: center; gap: 6px; min-width: 0; flex: 1; font-size: 12px; font-weight: 650; color: var(--ss-w-ink); }
2424
+ /* header fixture atoms (team hero lives in .ss-w-bi-header-left; comp demotes to the meta) */
2130
2425
  .ss-w-bi-team { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
2131
2426
  .ss-w-bi-vs { font-size: 10px; font-weight: 600; color: var(--ss-w-faint); flex: none; }
2132
2427
  .ss-w-bi-fixture-txt { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
2133
- .ss-w-bi-action { margin-left: auto; flex: none; display: flex; flex-direction: column; align-items: flex-end; gap: 4px; min-width: 0; }
2134
- .ss-w-bi-action .ss-w-slip { width: 100%; }
2135
- .ss-w-bi-action .ss-w-slip > button { flex: 1; width: 100%; text-align: center; border-radius: 8px; }
2136
- /* The action column is narrow; let its toast size to its own text and anchor to the card's right edge instead of being clipped to the button width. */
2137
- .ss-w-bi-action .ss-w-toast { left: auto; right: 0; width: max-content; max-width: 210px; white-space: normal; }
2138
- .ss-w-bi-mkt { display: flex; flex-direction: column; gap: 1px; min-width: 0; align-items: flex-end; text-align: right; }
2139
- .ss-w-bi-market { font-size: 12px; font-weight: 600; line-height: 1.2; color: var(--ss-w-faint); white-space: nowrap; }
2140
- .ss-w-bi-sel { font-size: 11px; line-height: 1.25; color: var(--ss-w-faint); }
2428
+ .ss-w-bi-comp-meta { display: inline-flex; align-items: center; gap: 5px; flex: none; min-width: 0; }
2429
+ /* shared crest-flanked team row, still consumed by MarketOddsCardList */
2430
+ .ss-w-bi-fixture { display: flex; align-items: center; gap: 6px; min-width: 0; flex: 1; font-size: 12px; font-weight: 650; color: var(--ss-w-ink); }
2431
+
2432
+ /* row 2: one action line -- market \xB7 selection (left, market ellipsises first), odds, slip button */
2433
+ .ss-w-bi-action-row { display: flex; align-items: center; gap: 9px; }
2434
+ .ss-w-bi-pick { flex: 1; min-width: 0; display: flex; align-items: baseline; gap: 6px; }
2435
+ .ss-w-bi-market { font-size: 12px; font-weight: 600; line-height: 1.2; color: var(--ss-w-faint); min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
2436
+ .ss-w-bi-pick-dot { flex: none; color: var(--ss-w-faint); font-weight: 700; }
2437
+ .ss-w-bi-sel { flex: none; font-size: 12.5px; font-weight: 700; line-height: 1.2; color: var(--ss-w-ink); white-space: nowrap; }
2141
2438
  .ss-w-bi-odds { flex: none; display: flex; align-items: baseline; justify-content: flex-end; gap: 5px; }
2142
- .ss-w-bi-odds b { font-size: 23px; font-family: ui-monospace, monospace; font-weight: 800; font-variant-numeric: tabular-nums; color: var(--ss-w-accent); line-height: 1.05; }
2439
+ .ss-w-bi-odds b { font-size: 20px; font-family: ui-monospace, monospace; font-weight: 800; font-variant-numeric: tabular-nums; color: var(--ss-w-accent); line-height: 1.05; }
2143
2440
  .ss-w-bi-book { font-size: 9.5px; color: var(--ss-w-faint); }
2441
+ .ss-w-bi-action-row .ss-w-slip { flex: none; }
2442
+ /* let the slip toast size to its own text and anchor to the card's right edge, not the button width */
2443
+ .ss-w-bi-action-row .ss-w-toast { left: auto; right: 0; width: max-content; max-width: 210px; white-space: normal; }
2144
2444
 
2145
2445
  /* row 3: bar-chart glyph + reasoning prose + stats-expander chevron, in a subtle strip */
2146
2446
  .ss-w-bi-reason { display: flex; align-items: flex-start; gap: 8px; background: var(--ss-w-bubble); border-radius: 10px; padding: 8px 10px; }
@@ -2482,34 +2782,90 @@ var WIDGET_CSS = `.ss-w-root { all: revert; }
2482
2782
  /* resting form: reuses the fixture last-five pill classes (ss-w-fandash-form / -form-pill) */
2483
2783
  .ss-w-fandash-resting-form .ss-w-fandash-form { margin-top: 2px; }
2484
2784
 
2485
- /* live score: reuses the post-match scoreboard classes for the team/score row, with its own
2486
- meta row -- minute called out in the accent color, status and competition beside it */
2487
- .ss-w-fandash-live-meta { display: flex; align-items: center; gap: 6px; flex-wrap: wrap; font-size: 11.5px; color: var(--ss-w-faint); }
2488
- .ss-w-fandash-live-minute { flex: none; font-weight: 700; font-variant-numeric: tabular-nums; color: var(--ss-w-accent); }
2489
- .ss-w-fandash-live-status { font-weight: 650; color: var(--ss-w-ink); }
2490
- .ss-w-fandash-live-competition { color: var(--ss-w-faint); }
2491
-
2492
- /* live timeline: a plain event list, the club's own events picked out in the accent color --
2493
- same mechanism as the post-match goal list, plus a minute column */
2494
- .ss-w-fandash-live-event-list { list-style: none; display: flex; flex-direction: column; gap: 4px; }
2495
- .ss-w-fandash-live-event { display: flex; align-items: baseline; gap: 8px; font-size: 12.5px; color: var(--ss-w-faint); }
2496
- .ss-w-fandash-live-event--club { color: var(--ss-w-ink); font-weight: 650; }
2497
- .ss-w-fandash-live-event-minute { flex: none; font-variant-numeric: tabular-nums; color: var(--ss-w-faint); }
2498
- .ss-w-fandash-live-event--club .ss-w-fandash-live-event-minute { color: var(--ss-w-accent); }
2499
- .ss-w-fandash-live-event-text { min-width: 0; }
2500
-
2501
- /* live stats: club value / stat label / opponent value, same 3-column layout as post-match
2502
- stats, plus an optional momentum line underneath */
2503
- .ss-w-fandash-live-stat-rows { display: flex; flex-direction: column; gap: 5px; }
2504
- .ss-w-fandash-live-stat-row { display: grid; grid-template-columns: 1fr auto 1fr; align-items: center; gap: 8px; }
2785
+ /* live score: pulsing Live pill, crests (real image or a coloured initials fallback), a big
2786
+ tabular score, and an optional scorers line. Also where the shared home/away venue-colour
2787
+ tokens (--ss-w-home / --ss-w-away / --ss-w-track) are defined above -- the timeline and stats
2788
+ cards reuse them. */
2789
+ .ss-w-fandash-live-top { display: flex; align-items: center; gap: 8px; }
2790
+ .ss-w-fandash-live-pill { display: inline-flex; flex: none; align-items: center; gap: 5px; font-size: 10px; font-weight: 800; letter-spacing: .04em; text-transform: uppercase; color: #fff; background: var(--ss-w-neg); border-radius: 6px; padding: 3px 7px; }
2791
+ .ss-w-fandash-live-dot { width: 5px; height: 5px; border-radius: 50%; background: #fff; animation: ss-w-fandash-live-pulse 1.3s ease-in-out infinite; }
2792
+ @keyframes ss-w-fandash-live-pulse { 0%, 100% { opacity: 1; } 50% { opacity: .3; } }
2793
+ @media (prefers-reduced-motion: reduce) {
2794
+ .ss-w-fandash-live-dot { animation: none; }
2795
+ }
2796
+ .ss-w-fandash-live-minute { flex: none; font-size: 12px; font-weight: 800; font-variant-numeric: tabular-nums; color: var(--ss-w-ink); }
2797
+ .ss-w-fandash-live-competition { margin-left: auto; font-size: 11px; font-weight: 650; color: var(--ss-w-faint); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
2798
+ .ss-w-fandash-live-main { display: grid; grid-template-columns: 1fr auto 1fr; align-items: center; gap: 8px; }
2799
+ .ss-w-fandash-live-side { display: flex; align-items: center; gap: 8px; min-width: 0; }
2800
+ .ss-w-fandash-live-side--away { flex-direction: row-reverse; text-align: right; }
2801
+ .ss-w-fandash-live-crest { width: 30px; height: 30px; border-radius: 8px; flex: none; display: grid; place-items: center; color: #fff; font-weight: 800; font-size: 10.5px; object-fit: cover; }
2802
+ .ss-w-fandash-live-crest--fallback.ss-w-fandash-live-crest--home { background: var(--ss-w-home); }
2803
+ .ss-w-fandash-live-crest--fallback.ss-w-fandash-live-crest--away { background: var(--ss-w-away); }
2804
+ .ss-w-fandash-live-team-name { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-size: 13px; font-weight: 700; color: var(--ss-w-ink); }
2805
+ .ss-w-fandash-live-score-main { display: flex; align-items: baseline; justify-content: center; font-size: 26px; font-weight: 800; font-variant-numeric: tabular-nums; line-height: 1; white-space: nowrap; color: var(--ss-w-ink); }
2806
+ .ss-w-fandash-live-score-sep { color: var(--ss-w-faint); font-weight: 500; margin: 0 4px; }
2807
+ .ss-w-fandash-live-scorers { margin-top: 2px; display: flex; justify-content: flex-end; gap: 6px; align-items: center; font-size: 11.5px; color: var(--ss-w-faint); overflow-wrap: anywhere; }
2808
+ .ss-w-fandash-live-scorers-icon { flex: none; color: var(--ss-w-away); }
2809
+
2810
+ /* live timeline: a compact two-sided event list down a centre spine -- each club's events sit on
2811
+ its own side (home left, away right), most-recent 3 shown with a "See more" toggle for the
2812
+ rest. A 3-column grid (txt | badge | txt) per row; the spine itself is a ::before on the list. */
2813
+ .ss-w-fandash-live-tl { position: relative; display: flex; flex-direction: column; gap: 6px; }
2814
+ .ss-w-fandash-live-tl::before { content: ""; position: absolute; left: 50%; top: 1px; bottom: 1px; width: 2px; background: var(--ss-w-line); transform: translateX(-1px); }
2815
+ .ss-w-fandash-live-tl-ev { display: grid; grid-template-columns: 1fr 32px 1fr; align-items: center; gap: 6px; }
2816
+ .ss-w-fandash-live-tl-txt { display: flex; align-items: center; gap: 6px; font-size: 12.5px; min-width: 0; }
2817
+ .ss-w-fandash-live-tl-ev--home .ss-w-fandash-live-tl-txt { grid-column: 1; justify-content: flex-end; }
2818
+ .ss-w-fandash-live-tl-ev--away .ss-w-fandash-live-tl-txt { grid-column: 3; justify-content: flex-start; flex-direction: row-reverse; }
2819
+ .ss-w-fandash-live-tl-who { font-weight: 650; color: var(--ss-w-ink); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
2820
+ .ss-w-fandash-live-tl-off { font-weight: 500; color: var(--ss-w-faint); }
2821
+ .ss-w-fandash-live-tl-ev--goal .ss-w-fandash-live-tl-who { font-weight: 800; }
2822
+ .ss-w-fandash-live-tl-badge { grid-column: 2; justify-self: center; min-width: 27px; height: 18px; padding: 0 4px; border-radius: 6px; display: grid; place-items: center; font-size: 10.5px; font-weight: 800; color: var(--ss-w-ink); background: var(--ss-w-bubble); border: 1px solid var(--ss-w-line); font-variant-numeric: tabular-nums; z-index: 1; }
2823
+ .ss-w-fandash-live-tl-ev--goal .ss-w-fandash-live-tl-badge { background: var(--ss-w-accent); color: #fff; border-color: transparent; }
2824
+ .ss-w-fandash-live-tl-ico { width: 16px; height: 16px; flex: none; display: grid; place-items: center; }
2825
+ .ss-w-fandash-live-tl-ico--goal { border-radius: 50%; background: color-mix(in srgb, var(--ss-w-away) 16%, transparent); }
2826
+ .ss-w-fandash-live-tl-ball { width: 11px; height: 11px; border-radius: 50%; background: var(--ss-w-ink); border: 1px solid var(--ss-w-ink); }
2827
+ .ss-w-fandash-live-tl-card { width: 8px; height: 11px; border-radius: 2px; display: block; }
2828
+ .ss-w-fandash-live-tl-card--yellow { background: var(--ss-w-cta); }
2829
+ .ss-w-fandash-live-tl-card--red { background: var(--ss-w-away); }
2830
+ .ss-w-fandash-live-tl-subarrows { font-size: 11px; font-weight: 900; line-height: 1; letter-spacing: -1px; color: var(--ss-w-accent); }
2831
+ .ss-w-fandash-live-tl-dot { width: 5px; height: 5px; border-radius: 50%; background: var(--ss-w-faint); }
2832
+ .ss-w-fandash-live-tl-more { margin-top: 4px; width: 100%; background: var(--ss-w-bubble); border: 1px solid var(--ss-w-line); border-radius: 9px; padding: 7px; font: inherit; font-size: 11.5px; font-weight: 700; color: var(--ss-w-accent); cursor: pointer; }
2833
+ .ss-w-fandash-live-tl-more:focus-visible { outline: 2px solid var(--ss-w-accent); outline-offset: 2px; }
2834
+
2835
+ /* live stats: club value / stat label / opponent value, each row backed by a home/away split
2836
+ bar, plus an optional momentum wave (or its text fallback) and a club/opponent colour legend.
2837
+ No side signal reaches this card, so the club is always the home token and the opponent is
2838
+ always the away token -- see LiveStatsCard.tsx. */
2839
+ .ss-w-fandash-live-stat-rows { display: flex; flex-direction: column; gap: 14px; }
2840
+ .ss-w-fandash-live-stat { display: flex; flex-direction: column; gap: 6px; }
2841
+ .ss-w-fandash-live-stat-row { display: grid; grid-template-columns: 1fr auto 1fr; align-items: baseline; gap: 8px; }
2505
2842
  .ss-w-fandash-live-stat-value { font-size: 13px; font-weight: 700; font-variant-numeric: tabular-nums; color: var(--ss-w-ink); }
2506
- .ss-w-fandash-live-stat-row .ss-w-fandash-live-stat-value:last-child { text-align: right; }
2507
- .ss-w-fandash-live-stat-label { font-size: 10.5px; text-align: center; color: var(--ss-w-faint); text-transform: uppercase; letter-spacing: .03em; }
2843
+ .ss-w-fandash-live-stat-value--home { color: var(--ss-w-home); }
2844
+ .ss-w-fandash-live-stat-value--away { color: var(--ss-w-away); text-align: right; }
2845
+ .ss-w-fandash-live-stat-label { font-size: 10px; font-weight: 700; text-align: center; color: var(--ss-w-faint); text-transform: uppercase; letter-spacing: .04em; }
2846
+ .ss-w-fandash-live-bar { position: relative; height: 7px; border-radius: 999px; background: var(--ss-w-track); overflow: hidden; display: flex; }
2847
+ .ss-w-fandash-live-bar-home { height: 100%; background: var(--ss-w-home); }
2848
+ .ss-w-fandash-live-bar-away { height: 100%; background: var(--ss-w-away); margin-left: auto; }
2849
+
2508
2850
  .ss-w-fandash-live-momentum { font-size: 11.5px; font-weight: 650; color: var(--ss-w-accent); }
2851
+ .ss-w-fandash-live-mom { margin-top: 15px; padding-top: 13px; border-top: 1px solid var(--ss-w-line); }
2852
+ .ss-w-fandash-live-mom-caption { display: block; margin-bottom: 5px; font-size: 10.5px; font-weight: 600; color: var(--ss-w-faint); }
2853
+ .ss-w-fandash-live-mom-svg { width: 100%; height: 50px; display: block; overflow: visible; }
2854
+ .ss-w-fandash-live-mom-home { fill: var(--ss-w-home); fill-opacity: .85; }
2855
+ .ss-w-fandash-live-mom-away { fill: var(--ss-w-away); fill-opacity: .85; }
2856
+ .ss-w-fandash-live-mom-line { fill: none; stroke: color-mix(in srgb, var(--ss-w-ink) 55%, transparent); stroke-width: 1.5; }
2857
+ .ss-w-fandash-live-mom-base { stroke: var(--ss-w-line); stroke-width: 1; stroke-dasharray: 3 3; }
2858
+ .ss-w-fandash-live-mom-now { fill: var(--ss-w-panel); stroke: var(--ss-w-ink); stroke-width: 1.5; }
2859
+
2860
+ .ss-w-fandash-live-legend { display: flex; gap: 14px; margin-top: 13px; padding-top: 13px; border-top: 1px solid var(--ss-w-line); font-size: 11px; color: var(--ss-w-faint); }
2861
+ .ss-w-fandash-live-legend-item { display: inline-flex; align-items: center; gap: 6px; }
2862
+ .ss-w-fandash-live-legend-swatch { width: 10px; height: 10px; border-radius: 3px; flex: none; }
2863
+ .ss-w-fandash-live-legend-swatch--home { background: var(--ss-w-home); }
2864
+ .ss-w-fandash-live-legend-swatch--away { background: var(--ss-w-away); }
2509
2865
  `;
2510
2866
 
2511
2867
  // src/StatsWidget.tsx
2512
- import { jsx as jsx43 } from "react/jsx-runtime";
2868
+ import { jsx as jsx44 } from "react/jsx-runtime";
2513
2869
  function buildClient(props) {
2514
2870
  try {
2515
2871
  if ("client" in props && props.client) {
@@ -2536,7 +2892,7 @@ function StatsWidget(props) {
2536
2892
  const cfg = props.config;
2537
2893
  const built = useMemo2(() => buildClient(props), [injectedClient, cfg?.operatorId, cfg?.publicKey, cfg?.baseUrl]);
2538
2894
  const ui = useMemo2(() => normalizeUi(props.config ?? { operatorId: "", publicKey: "" }), [props.config]);
2539
- const rootRef = useRef4(null);
2895
+ const rootRef = useRef5(null);
2540
2896
  const controller = useChatStream(built?.conversation ?? emptyConversation(), ui.starters, built ? ui.greetingMessage : void 0);
2541
2897
  const slip = useMemo2(
2542
2898
  () => ({
@@ -2555,14 +2911,14 @@ function StatsWidget(props) {
2555
2911
  () => built ? createDashboardTapSignal((input) => built.conversation.sendSignal(input)) : void 0,
2556
2912
  [built]
2557
2913
  );
2558
- useEffect7(() => {
2914
+ useEffect8(() => {
2559
2915
  if (!built || !ui.injectStyles) return;
2560
2916
  const node = rootRef.current?.getRootNode();
2561
2917
  if (node) injectStyles(node);
2562
2918
  }, [built, ui.injectStyles]);
2563
2919
  if (!built) return null;
2564
2920
  const attrs = themeAttrs(ui);
2565
- return /* @__PURE__ */ jsx43("div", { ref: rootRef, className: "ss-w-root", "data-ss-w-theme": attrs["data-ss-w-theme"], "data-ss-w-pos": attrs["data-ss-w-pos"], style: attrs.style, children: controller.state.isOpen ? /* @__PURE__ */ jsx43(ChatPanel, { controller, ui, slip, onDashboardTap }) : /* @__PURE__ */ jsx43(
2921
+ return /* @__PURE__ */ jsx44("div", { ref: rootRef, className: "ss-w-root", "data-ss-w-theme": attrs["data-ss-w-theme"], "data-ss-w-pos": attrs["data-ss-w-pos"], style: attrs.style, children: controller.state.isOpen ? /* @__PURE__ */ jsx44(ChatPanel, { controller, ui, slip, onDashboardTap }) : /* @__PURE__ */ jsx44(
2566
2922
  ChatFab,
2567
2923
  {
2568
2924
  label: ui.launcherLabel,