@sensiblestats/widget-react 0.25.0 → 0.26.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 useEffect6, useMemo as useMemo2, useRef as useRef3 } from "react";
2
+ import { useEffect as useEffect7, useMemo as useMemo2, useRef as useRef4 } 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, useMemo, useReducer, useRef } from "react";
228
+ import { useCallback, useEffect as useEffect2, useMemo, useReducer, useRef as useRef2 } from "react";
229
229
  import { WidgetSdkError as WidgetSdkError2 } from "@sensiblestats/widget-sdk";
230
230
 
231
231
  // src/reducer.ts
@@ -567,20 +567,63 @@ function reducer(state, action) {
567
567
  return { ...state, error: action.error, isStreaming: false, status: null, actionsLoading: false, messages: mapActive(state, (t) => ({ ...t, done: true })) };
568
568
  case "STOP":
569
569
  return { ...state, isStreaming: false, status: null, actionsLoading: false, messages: mapActive(state, (t) => ({ ...t, done: true })) };
570
+ case "LIVE_REFRESH":
571
+ return {
572
+ ...state,
573
+ messages: state.messages.map(
574
+ (m) => m.role === "assistant" && m.id === action.messageId && m.clubFanDashboard ? { ...m, clubFanDashboard: { ...m.clubFanDashboard, cards: action.cards } } : m
575
+ )
576
+ };
570
577
  }
571
578
  }
572
579
 
580
+ // src/useLiveDashboardPoll.ts
581
+ import { useEffect, useRef } from "react";
582
+ var POLL_INTERVAL_MS = 6e4;
583
+ function findLiveMessageId(messages) {
584
+ for (const m of messages.slice().reverse()) {
585
+ if (m.role === "assistant" && m.clubFanDashboard?.phase === "live") return m.id;
586
+ }
587
+ return null;
588
+ }
589
+ function useLiveDashboardPoll({ messages, dispatch, getLiveDashboard }) {
590
+ const messageId = findLiveMessageId(messages);
591
+ const inFlightRef = useRef(false);
592
+ useEffect(() => {
593
+ if (!messageId) return;
594
+ const tick = async () => {
595
+ if (inFlightRef.current) return;
596
+ inFlightRef.current = true;
597
+ try {
598
+ const payload = await getLiveDashboard();
599
+ if (payload && payload.phase === "live") {
600
+ dispatch({ kind: "LIVE_REFRESH", messageId, cards: payload.cards });
601
+ } else {
602
+ window.clearInterval(id);
603
+ }
604
+ } catch {
605
+ } finally {
606
+ inFlightRef.current = false;
607
+ }
608
+ };
609
+ const id = window.setInterval(() => {
610
+ void tick();
611
+ }, POLL_INTERVAL_MS);
612
+ return () => window.clearInterval(id);
613
+ }, [messageId, dispatch, getLiveDashboard]);
614
+ }
615
+
573
616
  // src/useChatStream.ts
574
617
  var STREAM_FALLBACK_TIMEOUT_MS = 3e4;
575
618
  function useChatStream(conversation, starterTexts, greetingMessage) {
576
619
  const seed = useMemo(() => initialState(starterTexts.map(chipFromText)), []);
577
620
  const [state, dispatch] = useReducer(reducer, seed);
578
- const idRef = useRef(0);
579
- const handleRef = useRef(null);
580
- const fallbackTimerRef = useRef(null);
581
- const stateRef = useRef(state);
621
+ const idRef = useRef2(0);
622
+ const handleRef = useRef2(null);
623
+ const fallbackTimerRef = useRef2(null);
624
+ const stateRef = useRef2(state);
582
625
  stateRef.current = state;
583
- const greetedRef = useRef(false);
626
+ const greetedRef = useRef2(false);
584
627
  const nextId = () => `m${idRef.current++}`;
585
628
  const clearFallbackTimer = useCallback(() => {
586
629
  if (fallbackTimerRef.current != null) {
@@ -636,7 +679,7 @@ function useChatStream(conversation, starterTexts, greetingMessage) {
636
679
  const last = stateRef.current.lastUserInput;
637
680
  if (last) run(last, stateRef.current.lastDisplayText ?? void 0);
638
681
  }, [run]);
639
- useEffect(() => {
682
+ useEffect2(() => {
640
683
  const message = greetingMessage?.trim();
641
684
  if (!message || greetedRef.current) return;
642
685
  greetedRef.current = true;
@@ -670,6 +713,8 @@ function useChatStream(conversation, starterTexts, greetingMessage) {
670
713
  else open();
671
714
  }, [close, open]);
672
715
  const dismissPopup = useCallback(() => dispatch({ kind: "DISMISS_POPUP" }), []);
716
+ const getLiveDashboard = useCallback(() => conversation.getLiveDashboard(), [conversation]);
717
+ useLiveDashboardPoll({ messages: state.messages, dispatch, getLiveDashboard });
673
718
  return { state, open, close, stop, toggle, send, retry, dismissPopup };
674
719
  }
675
720
 
@@ -730,7 +775,7 @@ function ChatFab({ label, greeting, popupText, onOpen, onDismissPopup, dismissLa
730
775
  }
731
776
 
732
777
  // src/components/ChatPanel.tsx
733
- import { useEffect as useEffect5, useState as useState8 } from "react";
778
+ import { useEffect as useEffect6, useState as useState8 } from "react";
734
779
 
735
780
  // src/components/ChatHeader.tsx
736
781
  import { jsx as jsx4, jsxs as jsxs3 } from "react/jsx-runtime";
@@ -743,7 +788,7 @@ function ChatHeader({ onClose, closeLabel = "Close chat" }) {
743
788
  }
744
789
 
745
790
  // src/components/MessageList.tsx
746
- import { useEffect as useEffect4, useRef as useRef2 } from "react";
791
+ import { useEffect as useEffect5, useRef as useRef3 } from "react";
747
792
 
748
793
  // src/components/Markdown.tsx
749
794
  import ReactMarkdown from "react-markdown";
@@ -898,11 +943,11 @@ function resultToToast(result, shownOdds, ui) {
898
943
  }
899
944
 
900
945
  // src/components/Toast.tsx
901
- import { useEffect as useEffect2 } from "react";
946
+ import { useEffect as useEffect3 } from "react";
902
947
  import { jsx as jsx7 } from "react/jsx-runtime";
903
948
  var DISMISS_MS = 4e3;
904
949
  function Toast({ toast, onDismiss }) {
905
- useEffect2(() => {
950
+ useEffect3(() => {
906
951
  if (!toast) return;
907
952
  const timer = setTimeout(onDismiss, DISMISS_MS);
908
953
  return () => clearTimeout(timer);
@@ -1370,7 +1415,7 @@ function FixtureCard({ card, onTap }) {
1370
1415
  }
1371
1416
 
1372
1417
  // src/components/dashboard/CountdownCard.tsx
1373
- import { Fragment as Fragment6, useEffect as useEffect3, useState as useState6 } from "react";
1418
+ import { Fragment as Fragment6, useEffect as useEffect4, useState as useState6 } from "react";
1374
1419
  import { jsx as jsx18, jsxs as jsxs14 } from "react/jsx-runtime";
1375
1420
  var DAY_MS = 864e5;
1376
1421
  function remainingSegments(kickoffUnix, nowMs, withSeconds) {
@@ -1394,7 +1439,7 @@ function CountdownCard({ card }) {
1394
1439
  () => typeof window !== "undefined" && window.matchMedia("(prefers-reduced-motion: reduce)").matches
1395
1440
  );
1396
1441
  const withinDay = data.kickoffUnix * 1e3 - nowMs < DAY_MS;
1397
- useEffect3(() => {
1442
+ useEffect4(() => {
1398
1443
  if (reduced) return;
1399
1444
  const id = window.setInterval(() => setNowMs(Date.now()), withinDay ? 1e3 : 6e4);
1400
1445
  return () => window.clearInterval(id);
@@ -1650,85 +1695,139 @@ function RestingFormCard({ card }) {
1650
1695
  ] });
1651
1696
  }
1652
1697
 
1653
- // src/components/dashboard/ClubFanDashboard.tsx
1698
+ // src/components/dashboard/LiveScoreCard.tsx
1654
1699
  import { jsx as jsx34, jsxs as jsxs30 } from "react/jsx-runtime";
1700
+ function LiveScoreCard({ card }) {
1701
+ const { data } = card;
1702
+ return /* @__PURE__ */ jsxs30("div", { className: "ss-w-fandash-card ss-w-fandash-live-score", children: [
1703
+ card.title ? /* @__PURE__ */ jsx34("span", { className: "ss-w-fandash-eyebrow", children: card.title }) : null,
1704
+ /* @__PURE__ */ jsxs30("div", { className: "ss-w-fandash-postmatch-scoreboard", children: [
1705
+ /* @__PURE__ */ jsx34("span", { className: "ss-w-fandash-postmatch-team", children: data.homeName }),
1706
+ /* @__PURE__ */ jsx34("span", { className: "ss-w-fandash-postmatch-score", children: data.homeScore }),
1707
+ /* @__PURE__ */ jsx34("span", { className: "ss-w-fandash-postmatch-sep", children: "\u2013" }),
1708
+ /* @__PURE__ */ jsx34("span", { className: "ss-w-fandash-postmatch-score", children: data.awayScore }),
1709
+ /* @__PURE__ */ jsx34("span", { className: "ss-w-fandash-postmatch-team ss-w-fandash-postmatch-team--away", children: data.awayName })
1710
+ ] }),
1711
+ /* @__PURE__ */ jsxs30("div", { className: "ss-w-fandash-live-meta", children: [
1712
+ /* @__PURE__ */ jsx34("span", { className: "ss-w-fandash-live-minute", children: data.minuteLabel }),
1713
+ /* @__PURE__ */ jsx34("span", { className: "ss-w-fandash-live-status", children: data.statusLabel }),
1714
+ data.competition ? /* @__PURE__ */ jsx34("span", { className: "ss-w-fandash-live-competition", children: data.competition }) : null
1715
+ ] })
1716
+ ] });
1717
+ }
1718
+
1719
+ // src/components/dashboard/LiveTimelineCard.tsx
1720
+ import { jsx as jsx35, jsxs as jsxs31 } from "react/jsx-runtime";
1721
+ function LiveTimelineCard({ card }) {
1722
+ return /* @__PURE__ */ jsxs31("div", { className: "ss-w-fandash-card ss-w-fandash-live-timeline", children: [
1723
+ card.title ? /* @__PURE__ */ jsx35("span", { className: "ss-w-fandash-eyebrow", children: card.title }) : null,
1724
+ /* @__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: [
1725
+ /* @__PURE__ */ jsx35("span", { className: "ss-w-fandash-live-event-minute", children: e.minuteLabel }),
1726
+ /* @__PURE__ */ jsx35("span", { className: "ss-w-fandash-live-event-text", children: e.text })
1727
+ ] }, `${e.minuteLabel}-${i}`)) })
1728
+ ] });
1729
+ }
1730
+
1731
+ // src/components/dashboard/LiveStatsCard.tsx
1732
+ import { jsx as jsx36, jsxs as jsxs32 } from "react/jsx-runtime";
1733
+ function LiveStatsCard({ card }) {
1734
+ const { data } = card;
1735
+ return /* @__PURE__ */ jsxs32("div", { className: "ss-w-fandash-card ss-w-fandash-live-stats", children: [
1736
+ card.title ? /* @__PURE__ */ jsx36("span", { className: "ss-w-fandash-eyebrow", children: card.title }) : null,
1737
+ /* @__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: [
1738
+ /* @__PURE__ */ jsx36("span", { className: "ss-w-fandash-live-stat-value", children: r.clubValue }),
1739
+ /* @__PURE__ */ jsx36("span", { className: "ss-w-fandash-live-stat-label", children: r.label }),
1740
+ /* @__PURE__ */ jsx36("span", { className: "ss-w-fandash-live-stat-value", children: r.oppValue })
1741
+ ] }, `${r.label}-${i}`)) }),
1742
+ data.momentumLabel ? /* @__PURE__ */ jsx36("span", { className: "ss-w-fandash-live-momentum", children: data.momentumLabel }) : null
1743
+ ] });
1744
+ }
1745
+
1746
+ // src/components/dashboard/ClubFanDashboard.tsx
1747
+ import { jsx as jsx37, jsxs as jsxs33 } from "react/jsx-runtime";
1655
1748
  function ClubFanDashboard({ dashboard, onTap, ui }) {
1656
1749
  const orderedCards = [
1657
1750
  ...dashboard.cards.filter((c) => c.cardType === "countdown"),
1658
1751
  ...dashboard.cards.filter((c) => c.cardType !== "countdown")
1659
1752
  ];
1660
- return /* @__PURE__ */ jsxs30("div", { className: "ss-w-fandash", children: [
1661
- dashboard.greeting ? /* @__PURE__ */ jsx34("span", { className: "ss-w-fandash-greeting", children: dashboard.greeting }) : null,
1662
- /* @__PURE__ */ jsx34("div", { className: "ss-w-fandash-cards", children: orderedCards.map((card, i) => /* @__PURE__ */ jsx34(FanDashboardCard, { card, onTap, ui }, `${card.cardType}-${i}`)) })
1753
+ return /* @__PURE__ */ jsxs33("div", { className: "ss-w-fandash", children: [
1754
+ dashboard.greeting ? /* @__PURE__ */ jsx37("span", { className: "ss-w-fandash-greeting", children: dashboard.greeting }) : null,
1755
+ /* @__PURE__ */ jsx37("div", { className: "ss-w-fandash-cards", children: orderedCards.map((card, i) => /* @__PURE__ */ jsx37(FanDashboardCard, { card, onTap, ui }, `${card.cardType}-${i}`)) })
1663
1756
  ] });
1664
1757
  }
1665
1758
  function FanDashboardCard({ card, onTap, ui }) {
1666
1759
  switch (card.cardType) {
1667
1760
  case "fixture":
1668
- return /* @__PURE__ */ jsx34(FixtureCard, { card, onTap });
1761
+ return /* @__PURE__ */ jsx37(FixtureCard, { card, onTap });
1669
1762
  case "countdown":
1670
- return /* @__PURE__ */ jsx34(CountdownCard, { card });
1763
+ return /* @__PURE__ */ jsx37(CountdownCard, { card });
1671
1764
  case "injuries":
1672
- return /* @__PURE__ */ jsx34(InjuriesCard, { card });
1765
+ return /* @__PURE__ */ jsx37(InjuriesCard, { card });
1673
1766
  case "reasonToBet":
1674
- return /* @__PURE__ */ jsx34(ReasonToBetCard, { card });
1767
+ return /* @__PURE__ */ jsx37(ReasonToBetCard, { card });
1675
1768
  case "whereToWatch":
1676
- return /* @__PURE__ */ jsx34(WhereToWatchCard, { card });
1769
+ return /* @__PURE__ */ jsx37(WhereToWatchCard, { card });
1677
1770
  case "congestedWeek":
1678
- return /* @__PURE__ */ jsx34(CongestedWeekCard, { card, onTap });
1771
+ return /* @__PURE__ */ jsx37(CongestedWeekCard, { card, onTap });
1679
1772
  case "transfers":
1680
- return /* @__PURE__ */ jsx34(TransfersCard, { card });
1773
+ return /* @__PURE__ */ jsx37(TransfersCard, { card });
1681
1774
  case "links":
1682
- return /* @__PURE__ */ jsx34(LinksCard, { card });
1775
+ return /* @__PURE__ */ jsx37(LinksCard, { card });
1683
1776
  case "postMatchResult":
1684
- return /* @__PURE__ */ jsx34(PostMatchResultCard, { card });
1777
+ return /* @__PURE__ */ jsx37(PostMatchResultCard, { card });
1685
1778
  case "postMatchGoals":
1686
- return /* @__PURE__ */ jsx34(PostMatchGoalsCard, { card, onTap });
1779
+ return /* @__PURE__ */ jsx37(PostMatchGoalsCard, { card, onTap });
1687
1780
  case "postMatchStats":
1688
- return /* @__PURE__ */ jsx34(PostMatchStatsCard, { card, onTap });
1781
+ return /* @__PURE__ */ jsx37(PostMatchStatsCard, { card, onTap });
1689
1782
  case "postMatchTopPerformer":
1690
- return /* @__PURE__ */ jsx34(PostMatchTopPerformerCard, { card, onTap });
1783
+ return /* @__PURE__ */ jsx37(PostMatchTopPerformerCard, { card, onTap });
1691
1784
  case "postMatchNext":
1692
- return /* @__PURE__ */ jsx34(PostMatchNextCard, { card });
1785
+ return /* @__PURE__ */ jsx37(PostMatchNextCard, { card });
1693
1786
  case "restingNext":
1694
- return /* @__PURE__ */ jsx34(RestingNextCard, { card, onTap });
1787
+ return /* @__PURE__ */ jsx37(RestingNextCard, { card, onTap });
1695
1788
  case "restingStandings":
1696
- return /* @__PURE__ */ jsx34(RestingStandingsCard, { card, onTap });
1789
+ return /* @__PURE__ */ jsx37(RestingStandingsCard, { card, onTap });
1697
1790
  case "restingSeason":
1698
- return /* @__PURE__ */ jsx34(RestingSeasonCard, { card, onTap });
1791
+ return /* @__PURE__ */ jsx37(RestingSeasonCard, { card, onTap });
1699
1792
  case "restingForm":
1700
- return /* @__PURE__ */ jsx34(RestingFormCard, { card });
1793
+ return /* @__PURE__ */ jsx37(RestingFormCard, { card });
1794
+ case "liveScore":
1795
+ return /* @__PURE__ */ jsx37(LiveScoreCard, { card });
1796
+ case "liveTimeline":
1797
+ return /* @__PURE__ */ jsx37(LiveTimelineCard, { card });
1798
+ case "liveStats":
1799
+ return /* @__PURE__ */ jsx37(LiveStatsCard, { card });
1701
1800
  default:
1702
1801
  return null;
1703
1802
  }
1704
1803
  }
1705
1804
 
1706
1805
  // src/components/ChatMessage.tsx
1707
- import { jsx as jsx35, jsxs as jsxs31 } from "react/jsx-runtime";
1806
+ import { jsx as jsx38, jsxs as jsxs34 } from "react/jsx-runtime";
1708
1807
  function ChatMessage({ message, disabled, onAct, onTap, ui, slip }) {
1709
1808
  if (message.role === "user") {
1710
- return /* @__PURE__ */ jsx35("div", { className: "ss-w-msg ss-w-user", children: /* @__PURE__ */ jsx35("div", { className: "ss-w-bubble", children: message.text }) });
1809
+ return /* @__PURE__ */ jsx38("div", { className: "ss-w-msg ss-w-user", children: /* @__PURE__ */ jsx38("div", { className: "ss-w-bubble", children: message.text }) });
1711
1810
  }
1712
1811
  const onFanDashboardTap = (card) => {
1713
1812
  onAct({ message: card.actionLabel, actionRef: card.actionRef, label: card.actionLabel || card.title || "" });
1714
1813
  };
1715
- return /* @__PURE__ */ jsxs31("div", { className: "ss-w-msg ss-w-bot", children: [
1716
- message.text && !message.greetingDashboard && !message.clubFanDashboard ? /* @__PURE__ */ jsx35(Markdown, { text: message.text }) : null,
1717
- message.greetingDashboard && !message.clubFanDashboard ? /* @__PURE__ */ jsx35(GreetingDashboard, { dashboard: message.greetingDashboard, onTap }) : null,
1718
- message.clubFanDashboard ? /* @__PURE__ */ jsx35(ClubFanDashboard, { dashboard: message.clubFanDashboard, onTap: onFanDashboardTap, ui }) : null,
1719
- /* @__PURE__ */ jsx35(EntityCardList, { cards: message.cards, disabled, showLessLabel: ui?.showLess, showMoreLabel: ui?.showMore }),
1720
- /* @__PURE__ */ jsx35(BettingInsightList, { insights: message.insights, ui, slip }),
1721
- /* @__PURE__ */ jsx35(MarketOddsCardList, { cards: message.marketOdds, ui, slip }),
1722
- /* @__PURE__ */ jsx35(OddsMovementCardList, { cards: message.oddsMovement, ui }),
1723
- /* @__PURE__ */ jsx35(ActionButtons, { actions: message.actions, loading: false, disabled, onAct })
1814
+ return /* @__PURE__ */ jsxs34("div", { className: "ss-w-msg ss-w-bot", children: [
1815
+ message.text && !message.greetingDashboard && !message.clubFanDashboard ? /* @__PURE__ */ jsx38(Markdown, { text: message.text }) : null,
1816
+ message.greetingDashboard && !message.clubFanDashboard ? /* @__PURE__ */ jsx38(GreetingDashboard, { dashboard: message.greetingDashboard, onTap }) : null,
1817
+ message.clubFanDashboard ? /* @__PURE__ */ jsx38(ClubFanDashboard, { dashboard: message.clubFanDashboard, onTap: onFanDashboardTap, ui }) : null,
1818
+ /* @__PURE__ */ jsx38(EntityCardList, { cards: message.cards, disabled, showLessLabel: ui?.showLess, showMoreLabel: ui?.showMore }),
1819
+ /* @__PURE__ */ jsx38(BettingInsightList, { insights: message.insights, ui, slip }),
1820
+ /* @__PURE__ */ jsx38(MarketOddsCardList, { cards: message.marketOdds, ui, slip }),
1821
+ /* @__PURE__ */ jsx38(OddsMovementCardList, { cards: message.oddsMovement, ui }),
1822
+ /* @__PURE__ */ jsx38(ActionButtons, { actions: message.actions, loading: false, disabled, onAct })
1724
1823
  ] });
1725
1824
  }
1726
1825
 
1727
1826
  // src/components/IntentChips.tsx
1728
- import { jsx as jsx36 } from "react/jsx-runtime";
1827
+ import { jsx as jsx39 } from "react/jsx-runtime";
1729
1828
  function IntentChips({ chips, onPick }) {
1730
1829
  if (chips.length === 0) return null;
1731
- return /* @__PURE__ */ jsx36("div", { className: "ss-w-chips", children: chips.map((c, i) => /* @__PURE__ */ jsx36("button", { type: "button", className: "ss-w-chip", onClick: () => onPick(c.text), children: c.text }, `${c.text}-${i}`)) });
1830
+ 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}`)) });
1732
1831
  }
1733
1832
 
1734
1833
  // src/scroll.ts
@@ -1738,18 +1837,18 @@ function isPinnedToBottom(el, threshold = PIN_THRESHOLD) {
1738
1837
  }
1739
1838
 
1740
1839
  // src/components/MessageList.tsx
1741
- import { jsx as jsx37, jsxs as jsxs32 } from "react/jsx-runtime";
1840
+ import { jsx as jsx40, jsxs as jsxs35 } from "react/jsx-runtime";
1742
1841
  function MessageList({ controller, ui, slip, onDashboardTap }) {
1743
1842
  const { state, send } = controller;
1744
- const listRef = useRef2(null);
1745
- const endRef = useRef2(null);
1746
- const pinnedRef = useRef2(true);
1747
- useEffect4(() => {
1843
+ const listRef = useRef3(null);
1844
+ const endRef = useRef3(null);
1845
+ const pinnedRef = useRef3(true);
1846
+ useEffect5(() => {
1748
1847
  if (pinnedRef.current) endRef.current?.scrollIntoView({ block: "end" });
1749
1848
  }, [state.messages, state.status]);
1750
1849
  const hasUserMessage = state.messages.some((m) => m.role === "user");
1751
1850
  const hasDashboard = state.messages.some((m) => m.role === "assistant" && (m.greetingDashboard || m.clubFanDashboard));
1752
- return /* @__PURE__ */ jsxs32(
1851
+ return /* @__PURE__ */ jsxs35(
1753
1852
  "div",
1754
1853
  {
1755
1854
  className: "ss-w-list",
@@ -1759,7 +1858,7 @@ function MessageList({ controller, ui, slip, onDashboardTap }) {
1759
1858
  if (listRef.current) pinnedRef.current = isPinnedToBottom(listRef.current);
1760
1859
  },
1761
1860
  children: [
1762
- state.messages.map((m) => /* @__PURE__ */ jsx37(
1861
+ state.messages.map((m) => /* @__PURE__ */ jsx40(
1763
1862
  ChatMessage,
1764
1863
  {
1765
1864
  message: m,
@@ -1774,15 +1873,15 @@ function MessageList({ controller, ui, slip, onDashboardTap }) {
1774
1873
  },
1775
1874
  m.id
1776
1875
  )),
1777
- state.isStreaming && state.actionsLoading ? /* @__PURE__ */ jsx37(ActionButtons, { actions: [], loading: true, onAct: () => {
1876
+ state.isStreaming && state.actionsLoading ? /* @__PURE__ */ jsx40(ActionButtons, { actions: [], loading: true, onAct: () => {
1778
1877
  } }) : null,
1779
- state.status ? /* @__PURE__ */ jsx37("div", { className: "ss-w-status", children: state.status }) : null,
1780
- state.error ? /* @__PURE__ */ jsxs32("div", { className: "ss-w-error", role: "alert", children: [
1781
- /* @__PURE__ */ jsx37("span", { children: "message" in state.error ? state.error.message : ui.somethingWentWrong }),
1782
- /* @__PURE__ */ jsx37("button", { type: "button", className: "ss-w-retry", onClick: () => controller.retry(), children: ui.retry })
1878
+ state.status ? /* @__PURE__ */ jsx40("div", { className: "ss-w-status", children: state.status }) : null,
1879
+ state.error ? /* @__PURE__ */ jsxs35("div", { className: "ss-w-error", role: "alert", children: [
1880
+ /* @__PURE__ */ jsx40("span", { children: "message" in state.error ? state.error.message : ui.somethingWentWrong }),
1881
+ /* @__PURE__ */ jsx40("button", { type: "button", className: "ss-w-retry", onClick: () => controller.retry(), children: ui.retry })
1783
1882
  ] }) : null,
1784
- !hasUserMessage && !hasDashboard ? /* @__PURE__ */ jsx37(IntentChips, { chips: state.starters, onPick: (t) => send(t) }) : null,
1785
- /* @__PURE__ */ jsx37("div", { ref: endRef })
1883
+ !hasUserMessage && !hasDashboard ? /* @__PURE__ */ jsx40(IntentChips, { chips: state.starters, onPick: (t) => send(t) }) : null,
1884
+ /* @__PURE__ */ jsx40("div", { ref: endRef })
1786
1885
  ]
1787
1886
  }
1788
1887
  );
@@ -1790,7 +1889,7 @@ function MessageList({ controller, ui, slip, onDashboardTap }) {
1790
1889
 
1791
1890
  // src/components/ChatInput.tsx
1792
1891
  import { useState as useState7 } from "react";
1793
- import { jsx as jsx38, jsxs as jsxs33 } from "react/jsx-runtime";
1892
+ import { jsx as jsx41, jsxs as jsxs36 } from "react/jsx-runtime";
1794
1893
  function ChatInput({ placeholder, streaming, onSend, onStop, stopLabel = "Stop response", sendLabel = "Send message" }) {
1795
1894
  const [value, setValue] = useState7("");
1796
1895
  const submit = () => {
@@ -1805,18 +1904,18 @@ function ChatInput({ placeholder, streaming, onSend, onStop, stopLabel = "Stop r
1805
1904
  submit();
1806
1905
  }
1807
1906
  };
1808
- return /* @__PURE__ */ jsxs33("div", { className: "ss-w-input", children: [
1809
- /* @__PURE__ */ jsx38("textarea", { className: "ss-w-textarea", rows: 1, placeholder, value, disabled: streaming, onChange: (e) => setValue(e.target.value), onKeyDown }),
1810
- streaming ? /* @__PURE__ */ jsx38("button", { type: "button", className: "ss-w-send ss-w-stop", "aria-label": stopLabel, onClick: onStop, children: /* @__PURE__ */ jsx38("span", { className: "ss-w-stop-glyph" }) }) : /* @__PURE__ */ jsx38("button", { type: "button", className: "ss-w-send", "aria-label": sendLabel, onClick: submit, children: /* @__PURE__ */ jsx38(SendIcon, {}) })
1907
+ return /* @__PURE__ */ jsxs36("div", { className: "ss-w-input", children: [
1908
+ /* @__PURE__ */ jsx41("textarea", { className: "ss-w-textarea", rows: 1, placeholder, value, disabled: streaming, onChange: (e) => setValue(e.target.value), onKeyDown }),
1909
+ 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, {}) })
1811
1910
  ] });
1812
1911
  }
1813
1912
 
1814
1913
  // src/components/ChatPanel.tsx
1815
- import { jsx as jsx39, jsxs as jsxs34 } from "react/jsx-runtime";
1914
+ import { jsx as jsx42, jsxs as jsxs37 } from "react/jsx-runtime";
1816
1915
  var FULLSCREEN_QUERY = "(max-width: 639px)";
1817
1916
  function useIsModal() {
1818
1917
  const [modal, setModal] = useState8(false);
1819
- useEffect5(() => {
1918
+ useEffect6(() => {
1820
1919
  if (typeof window === "undefined" || typeof window.matchMedia !== "function") return;
1821
1920
  const mql = window.matchMedia(FULLSCREEN_QUERY);
1822
1921
  const sync = () => setModal(mql.matches);
@@ -1829,17 +1928,17 @@ function useIsModal() {
1829
1928
  function ChatPanel({ controller, ui, slip, onDashboardTap }) {
1830
1929
  const { state, close, stop, send } = controller;
1831
1930
  const isModal = useIsModal();
1832
- useEffect5(() => {
1931
+ useEffect6(() => {
1833
1932
  const onKey = (e) => {
1834
1933
  if (e.key === "Escape") close();
1835
1934
  };
1836
1935
  window.addEventListener("keydown", onKey);
1837
1936
  return () => window.removeEventListener("keydown", onKey);
1838
1937
  }, [close]);
1839
- return /* @__PURE__ */ jsxs34("div", { className: "ss-w-panel", role: "dialog", "aria-modal": isModal, "aria-label": ui.chatDialogLabel, children: [
1840
- /* @__PURE__ */ jsx39(ChatHeader, { onClose: close, closeLabel: ui.closeChat }),
1841
- /* @__PURE__ */ jsx39(MessageList, { controller, ui, slip, onDashboardTap }),
1842
- /* @__PURE__ */ jsx39(ChatInput, { placeholder: ui.placeholder, streaming: state.isStreaming, onSend: (t) => send(t), onStop: stop, stopLabel: ui.stopResponse, sendLabel: ui.sendMessage })
1938
+ return /* @__PURE__ */ jsxs37("div", { className: "ss-w-panel", role: "dialog", "aria-modal": isModal, "aria-label": ui.chatDialogLabel, children: [
1939
+ /* @__PURE__ */ jsx42(ChatHeader, { onClose: close, closeLabel: ui.closeChat }),
1940
+ /* @__PURE__ */ jsx42(MessageList, { controller, ui, slip, onDashboardTap }),
1941
+ /* @__PURE__ */ jsx42(ChatInput, { placeholder: ui.placeholder, streaming: state.isStreaming, onSend: (t) => send(t), onStop: stop, stopLabel: ui.stopResponse, sendLabel: ui.sendMessage })
1843
1942
  ] });
1844
1943
  }
1845
1944
 
@@ -2351,10 +2450,35 @@ var WIDGET_CSS = `.ss-w-root { all: revert; }
2351
2450
 
2352
2451
  /* resting form: reuses the fixture last-five pill classes (ss-w-fandash-form / -form-pill) */
2353
2452
  .ss-w-fandash-resting-form .ss-w-fandash-form { margin-top: 2px; }
2453
+
2454
+ /* live score: reuses the post-match scoreboard classes for the team/score row, with its own
2455
+ meta row -- minute called out in the accent color, status and competition beside it */
2456
+ .ss-w-fandash-live-meta { display: flex; align-items: center; gap: 6px; flex-wrap: wrap; font-size: 11.5px; color: var(--ss-w-faint); }
2457
+ .ss-w-fandash-live-minute { flex: none; font-weight: 700; font-variant-numeric: tabular-nums; color: var(--ss-w-accent); }
2458
+ .ss-w-fandash-live-status { font-weight: 650; color: var(--ss-w-ink); }
2459
+ .ss-w-fandash-live-competition { color: var(--ss-w-faint); }
2460
+
2461
+ /* live timeline: a plain event list, the club's own events picked out in the accent color --
2462
+ same mechanism as the post-match goal list, plus a minute column */
2463
+ .ss-w-fandash-live-event-list { list-style: none; display: flex; flex-direction: column; gap: 4px; }
2464
+ .ss-w-fandash-live-event { display: flex; align-items: baseline; gap: 8px; font-size: 12.5px; color: var(--ss-w-faint); }
2465
+ .ss-w-fandash-live-event--club { color: var(--ss-w-ink); font-weight: 650; }
2466
+ .ss-w-fandash-live-event-minute { flex: none; font-variant-numeric: tabular-nums; color: var(--ss-w-faint); }
2467
+ .ss-w-fandash-live-event--club .ss-w-fandash-live-event-minute { color: var(--ss-w-accent); }
2468
+ .ss-w-fandash-live-event-text { min-width: 0; }
2469
+
2470
+ /* live stats: club value / stat label / opponent value, same 3-column layout as post-match
2471
+ stats, plus an optional momentum line underneath */
2472
+ .ss-w-fandash-live-stat-rows { display: flex; flex-direction: column; gap: 5px; }
2473
+ .ss-w-fandash-live-stat-row { display: grid; grid-template-columns: 1fr auto 1fr; align-items: center; gap: 8px; }
2474
+ .ss-w-fandash-live-stat-value { font-size: 13px; font-weight: 700; font-variant-numeric: tabular-nums; color: var(--ss-w-ink); }
2475
+ .ss-w-fandash-live-stat-row .ss-w-fandash-live-stat-value:last-child { text-align: right; }
2476
+ .ss-w-fandash-live-stat-label { font-size: 10.5px; text-align: center; color: var(--ss-w-faint); text-transform: uppercase; letter-spacing: .03em; }
2477
+ .ss-w-fandash-live-momentum { font-size: 11.5px; font-weight: 650; color: var(--ss-w-accent); }
2354
2478
  `;
2355
2479
 
2356
2480
  // src/StatsWidget.tsx
2357
- import { jsx as jsx40 } from "react/jsx-runtime";
2481
+ import { jsx as jsx43 } from "react/jsx-runtime";
2358
2482
  function buildClient(props) {
2359
2483
  try {
2360
2484
  if ("client" in props && props.client) {
@@ -2381,7 +2505,7 @@ function StatsWidget(props) {
2381
2505
  const cfg = props.config;
2382
2506
  const built = useMemo2(() => buildClient(props), [injectedClient, cfg?.operatorId, cfg?.publicKey, cfg?.baseUrl]);
2383
2507
  const ui = useMemo2(() => normalizeUi(props.config ?? { operatorId: "", publicKey: "" }), [props.config]);
2384
- const rootRef = useRef3(null);
2508
+ const rootRef = useRef4(null);
2385
2509
  const controller = useChatStream(built?.conversation ?? emptyConversation(), ui.starters, built ? ui.greetingMessage : void 0);
2386
2510
  const slip = useMemo2(
2387
2511
  () => ({
@@ -2400,14 +2524,14 @@ function StatsWidget(props) {
2400
2524
  () => built ? createDashboardTapSignal((input) => built.conversation.sendSignal(input)) : void 0,
2401
2525
  [built]
2402
2526
  );
2403
- useEffect6(() => {
2527
+ useEffect7(() => {
2404
2528
  if (!built || !ui.injectStyles) return;
2405
2529
  const node = rootRef.current?.getRootNode();
2406
2530
  if (node) injectStyles(node);
2407
2531
  }, [built, ui.injectStyles]);
2408
2532
  if (!built) return null;
2409
2533
  const attrs = themeAttrs(ui);
2410
- return /* @__PURE__ */ jsx40("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__ */ jsx40(ChatPanel, { controller, ui, slip, onDashboardTap }) : /* @__PURE__ */ jsx40(
2534
+ 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(
2411
2535
  ChatFab,
2412
2536
  {
2413
2537
  label: ui.launcherLabel,