@sensiblestats/widget-react 0.24.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);
@@ -1591,77 +1636,198 @@ function PostMatchNextCard({ card }) {
1591
1636
  ] });
1592
1637
  }
1593
1638
 
1594
- // src/components/dashboard/ClubFanDashboard.tsx
1639
+ // src/components/dashboard/RestingNextCard.tsx
1595
1640
  import { jsx as jsx30, jsxs as jsxs26 } from "react/jsx-runtime";
1641
+ function RestingNextCard({ card, onTap }) {
1642
+ const { data } = card;
1643
+ return /* @__PURE__ */ jsxs26("button", { type: "button", className: "ss-w-fandash-card ss-w-fandash-card--tap ss-w-fandash-resting-next", onClick: () => onTap(card), children: [
1644
+ card.title ? /* @__PURE__ */ jsx30("span", { className: "ss-w-fandash-eyebrow", children: card.title }) : null,
1645
+ /* @__PURE__ */ jsxs26("div", { className: "ss-w-fandash-resting-next-row", children: [
1646
+ /* @__PURE__ */ jsx30("span", { className: "ss-w-fandash-resting-next-when", children: data.whenLabel }),
1647
+ data.competition ? /* @__PURE__ */ jsx30("span", { className: "ss-w-fandash-resting-next-competition", children: data.competition }) : null
1648
+ ] }),
1649
+ /* @__PURE__ */ jsx30("span", { className: "ss-w-fandash-card-cta", children: card.actionLabel })
1650
+ ] });
1651
+ }
1652
+
1653
+ // src/components/dashboard/RestingStandingsCard.tsx
1654
+ import { jsx as jsx31, jsxs as jsxs27 } from "react/jsx-runtime";
1655
+ function RestingStandingsCard({ card, onTap }) {
1656
+ const { data } = card;
1657
+ return /* @__PURE__ */ jsxs27("button", { type: "button", className: "ss-w-fandash-card ss-w-fandash-card--tap ss-w-fandash-resting-standings", onClick: () => onTap(card), children: [
1658
+ card.title ? /* @__PURE__ */ jsx31("span", { className: "ss-w-fandash-eyebrow", children: card.title }) : null,
1659
+ /* @__PURE__ */ jsxs27("div", { className: "ss-w-fandash-resting-standings-row", children: [
1660
+ /* @__PURE__ */ jsx31("span", { className: "ss-w-fandash-resting-standings-position", children: data.positionLabel }),
1661
+ /* @__PURE__ */ jsx31("span", { className: "ss-w-fandash-resting-standings-record", children: data.recordLabel })
1662
+ ] }),
1663
+ /* @__PURE__ */ jsx31("span", { className: "ss-w-fandash-card-cta", children: card.actionLabel })
1664
+ ] });
1665
+ }
1666
+
1667
+ // src/components/dashboard/RestingSeasonCard.tsx
1668
+ import { jsx as jsx32, jsxs as jsxs28 } from "react/jsx-runtime";
1669
+ function RestingSeasonCard({ card, onTap }) {
1670
+ const { data } = card;
1671
+ return /* @__PURE__ */ jsxs28("button", { type: "button", className: "ss-w-fandash-card ss-w-fandash-card--tap ss-w-fandash-resting-season", onClick: () => onTap(card), children: [
1672
+ card.title ? /* @__PURE__ */ jsx32("span", { className: "ss-w-fandash-eyebrow", children: card.title }) : null,
1673
+ /* @__PURE__ */ jsxs28("div", { className: "ss-w-fandash-resting-season-rows", children: [
1674
+ data.scorerLabel ? /* @__PURE__ */ jsx32("span", { className: "ss-w-fandash-resting-season-row", children: data.scorerLabel }) : null,
1675
+ data.playerLabel ? /* @__PURE__ */ jsx32("span", { className: "ss-w-fandash-resting-season-row", children: data.playerLabel }) : null,
1676
+ /* @__PURE__ */ jsx32("span", { className: "ss-w-fandash-resting-season-row", children: data.tallyLabel })
1677
+ ] }),
1678
+ /* @__PURE__ */ jsx32("span", { className: "ss-w-fandash-card-cta", children: card.actionLabel })
1679
+ ] });
1680
+ }
1681
+
1682
+ // src/components/dashboard/RestingFormCard.tsx
1683
+ import { jsx as jsx33, jsxs as jsxs29 } from "react/jsx-runtime";
1684
+ function resultClass2(result) {
1685
+ const upper = result.toUpperCase();
1686
+ if (upper === "W") return "ss-w-fandash-form-pill--win";
1687
+ if (upper === "L") return "ss-w-fandash-form-pill--loss";
1688
+ return "ss-w-fandash-form-pill--draw";
1689
+ }
1690
+ function RestingFormCard({ card }) {
1691
+ const { data } = card;
1692
+ return /* @__PURE__ */ jsxs29("div", { className: "ss-w-fandash-card ss-w-fandash-resting-form", children: [
1693
+ card.title ? /* @__PURE__ */ jsx33("span", { className: "ss-w-fandash-eyebrow", children: card.title }) : null,
1694
+ /* @__PURE__ */ jsx33("span", { className: "ss-w-fandash-form", children: data.letters.map((r, i) => /* @__PURE__ */ jsx33("span", { className: `ss-w-fandash-form-pill ${resultClass2(r)}`, children: r }, `${r}-${i}`)) })
1695
+ ] });
1696
+ }
1697
+
1698
+ // src/components/dashboard/LiveScoreCard.tsx
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";
1596
1748
  function ClubFanDashboard({ dashboard, onTap, ui }) {
1597
1749
  const orderedCards = [
1598
1750
  ...dashboard.cards.filter((c) => c.cardType === "countdown"),
1599
1751
  ...dashboard.cards.filter((c) => c.cardType !== "countdown")
1600
1752
  ];
1601
- return /* @__PURE__ */ jsxs26("div", { className: "ss-w-fandash", children: [
1602
- dashboard.greeting ? /* @__PURE__ */ jsx30("span", { className: "ss-w-fandash-greeting", children: dashboard.greeting }) : null,
1603
- /* @__PURE__ */ jsx30("div", { className: "ss-w-fandash-cards", children: orderedCards.map((card, i) => /* @__PURE__ */ jsx30(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}`)) })
1604
1756
  ] });
1605
1757
  }
1606
1758
  function FanDashboardCard({ card, onTap, ui }) {
1607
1759
  switch (card.cardType) {
1608
1760
  case "fixture":
1609
- return /* @__PURE__ */ jsx30(FixtureCard, { card, onTap });
1761
+ return /* @__PURE__ */ jsx37(FixtureCard, { card, onTap });
1610
1762
  case "countdown":
1611
- return /* @__PURE__ */ jsx30(CountdownCard, { card });
1763
+ return /* @__PURE__ */ jsx37(CountdownCard, { card });
1612
1764
  case "injuries":
1613
- return /* @__PURE__ */ jsx30(InjuriesCard, { card });
1765
+ return /* @__PURE__ */ jsx37(InjuriesCard, { card });
1614
1766
  case "reasonToBet":
1615
- return /* @__PURE__ */ jsx30(ReasonToBetCard, { card });
1767
+ return /* @__PURE__ */ jsx37(ReasonToBetCard, { card });
1616
1768
  case "whereToWatch":
1617
- return /* @__PURE__ */ jsx30(WhereToWatchCard, { card });
1769
+ return /* @__PURE__ */ jsx37(WhereToWatchCard, { card });
1618
1770
  case "congestedWeek":
1619
- return /* @__PURE__ */ jsx30(CongestedWeekCard, { card, onTap });
1771
+ return /* @__PURE__ */ jsx37(CongestedWeekCard, { card, onTap });
1620
1772
  case "transfers":
1621
- return /* @__PURE__ */ jsx30(TransfersCard, { card });
1773
+ return /* @__PURE__ */ jsx37(TransfersCard, { card });
1622
1774
  case "links":
1623
- return /* @__PURE__ */ jsx30(LinksCard, { card });
1775
+ return /* @__PURE__ */ jsx37(LinksCard, { card });
1624
1776
  case "postMatchResult":
1625
- return /* @__PURE__ */ jsx30(PostMatchResultCard, { card });
1777
+ return /* @__PURE__ */ jsx37(PostMatchResultCard, { card });
1626
1778
  case "postMatchGoals":
1627
- return /* @__PURE__ */ jsx30(PostMatchGoalsCard, { card, onTap });
1779
+ return /* @__PURE__ */ jsx37(PostMatchGoalsCard, { card, onTap });
1628
1780
  case "postMatchStats":
1629
- return /* @__PURE__ */ jsx30(PostMatchStatsCard, { card, onTap });
1781
+ return /* @__PURE__ */ jsx37(PostMatchStatsCard, { card, onTap });
1630
1782
  case "postMatchTopPerformer":
1631
- return /* @__PURE__ */ jsx30(PostMatchTopPerformerCard, { card, onTap });
1783
+ return /* @__PURE__ */ jsx37(PostMatchTopPerformerCard, { card, onTap });
1632
1784
  case "postMatchNext":
1633
- return /* @__PURE__ */ jsx30(PostMatchNextCard, { card });
1785
+ return /* @__PURE__ */ jsx37(PostMatchNextCard, { card });
1786
+ case "restingNext":
1787
+ return /* @__PURE__ */ jsx37(RestingNextCard, { card, onTap });
1788
+ case "restingStandings":
1789
+ return /* @__PURE__ */ jsx37(RestingStandingsCard, { card, onTap });
1790
+ case "restingSeason":
1791
+ return /* @__PURE__ */ jsx37(RestingSeasonCard, { card, onTap });
1792
+ case "restingForm":
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 });
1634
1800
  default:
1635
1801
  return null;
1636
1802
  }
1637
1803
  }
1638
1804
 
1639
1805
  // src/components/ChatMessage.tsx
1640
- import { jsx as jsx31, jsxs as jsxs27 } from "react/jsx-runtime";
1806
+ import { jsx as jsx38, jsxs as jsxs34 } from "react/jsx-runtime";
1641
1807
  function ChatMessage({ message, disabled, onAct, onTap, ui, slip }) {
1642
1808
  if (message.role === "user") {
1643
- return /* @__PURE__ */ jsx31("div", { className: "ss-w-msg ss-w-user", children: /* @__PURE__ */ jsx31("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 }) });
1644
1810
  }
1645
1811
  const onFanDashboardTap = (card) => {
1646
1812
  onAct({ message: card.actionLabel, actionRef: card.actionRef, label: card.actionLabel || card.title || "" });
1647
1813
  };
1648
- return /* @__PURE__ */ jsxs27("div", { className: "ss-w-msg ss-w-bot", children: [
1649
- message.text && !message.greetingDashboard && !message.clubFanDashboard ? /* @__PURE__ */ jsx31(Markdown, { text: message.text }) : null,
1650
- message.greetingDashboard && !message.clubFanDashboard ? /* @__PURE__ */ jsx31(GreetingDashboard, { dashboard: message.greetingDashboard, onTap }) : null,
1651
- message.clubFanDashboard ? /* @__PURE__ */ jsx31(ClubFanDashboard, { dashboard: message.clubFanDashboard, onTap: onFanDashboardTap, ui }) : null,
1652
- /* @__PURE__ */ jsx31(EntityCardList, { cards: message.cards, disabled, showLessLabel: ui?.showLess, showMoreLabel: ui?.showMore }),
1653
- /* @__PURE__ */ jsx31(BettingInsightList, { insights: message.insights, ui, slip }),
1654
- /* @__PURE__ */ jsx31(MarketOddsCardList, { cards: message.marketOdds, ui, slip }),
1655
- /* @__PURE__ */ jsx31(OddsMovementCardList, { cards: message.oddsMovement, ui }),
1656
- /* @__PURE__ */ jsx31(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 })
1657
1823
  ] });
1658
1824
  }
1659
1825
 
1660
1826
  // src/components/IntentChips.tsx
1661
- import { jsx as jsx32 } from "react/jsx-runtime";
1827
+ import { jsx as jsx39 } from "react/jsx-runtime";
1662
1828
  function IntentChips({ chips, onPick }) {
1663
1829
  if (chips.length === 0) return null;
1664
- return /* @__PURE__ */ jsx32("div", { className: "ss-w-chips", children: chips.map((c, i) => /* @__PURE__ */ jsx32("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}`)) });
1665
1831
  }
1666
1832
 
1667
1833
  // src/scroll.ts
@@ -1671,18 +1837,18 @@ function isPinnedToBottom(el, threshold = PIN_THRESHOLD) {
1671
1837
  }
1672
1838
 
1673
1839
  // src/components/MessageList.tsx
1674
- import { jsx as jsx33, jsxs as jsxs28 } from "react/jsx-runtime";
1840
+ import { jsx as jsx40, jsxs as jsxs35 } from "react/jsx-runtime";
1675
1841
  function MessageList({ controller, ui, slip, onDashboardTap }) {
1676
1842
  const { state, send } = controller;
1677
- const listRef = useRef2(null);
1678
- const endRef = useRef2(null);
1679
- const pinnedRef = useRef2(true);
1680
- useEffect4(() => {
1843
+ const listRef = useRef3(null);
1844
+ const endRef = useRef3(null);
1845
+ const pinnedRef = useRef3(true);
1846
+ useEffect5(() => {
1681
1847
  if (pinnedRef.current) endRef.current?.scrollIntoView({ block: "end" });
1682
1848
  }, [state.messages, state.status]);
1683
1849
  const hasUserMessage = state.messages.some((m) => m.role === "user");
1684
1850
  const hasDashboard = state.messages.some((m) => m.role === "assistant" && (m.greetingDashboard || m.clubFanDashboard));
1685
- return /* @__PURE__ */ jsxs28(
1851
+ return /* @__PURE__ */ jsxs35(
1686
1852
  "div",
1687
1853
  {
1688
1854
  className: "ss-w-list",
@@ -1692,7 +1858,7 @@ function MessageList({ controller, ui, slip, onDashboardTap }) {
1692
1858
  if (listRef.current) pinnedRef.current = isPinnedToBottom(listRef.current);
1693
1859
  },
1694
1860
  children: [
1695
- state.messages.map((m) => /* @__PURE__ */ jsx33(
1861
+ state.messages.map((m) => /* @__PURE__ */ jsx40(
1696
1862
  ChatMessage,
1697
1863
  {
1698
1864
  message: m,
@@ -1707,15 +1873,15 @@ function MessageList({ controller, ui, slip, onDashboardTap }) {
1707
1873
  },
1708
1874
  m.id
1709
1875
  )),
1710
- state.isStreaming && state.actionsLoading ? /* @__PURE__ */ jsx33(ActionButtons, { actions: [], loading: true, onAct: () => {
1876
+ state.isStreaming && state.actionsLoading ? /* @__PURE__ */ jsx40(ActionButtons, { actions: [], loading: true, onAct: () => {
1711
1877
  } }) : null,
1712
- state.status ? /* @__PURE__ */ jsx33("div", { className: "ss-w-status", children: state.status }) : null,
1713
- state.error ? /* @__PURE__ */ jsxs28("div", { className: "ss-w-error", role: "alert", children: [
1714
- /* @__PURE__ */ jsx33("span", { children: "message" in state.error ? state.error.message : ui.somethingWentWrong }),
1715
- /* @__PURE__ */ jsx33("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 })
1716
1882
  ] }) : null,
1717
- !hasUserMessage && !hasDashboard ? /* @__PURE__ */ jsx33(IntentChips, { chips: state.starters, onPick: (t) => send(t) }) : null,
1718
- /* @__PURE__ */ jsx33("div", { ref: endRef })
1883
+ !hasUserMessage && !hasDashboard ? /* @__PURE__ */ jsx40(IntentChips, { chips: state.starters, onPick: (t) => send(t) }) : null,
1884
+ /* @__PURE__ */ jsx40("div", { ref: endRef })
1719
1885
  ]
1720
1886
  }
1721
1887
  );
@@ -1723,7 +1889,7 @@ function MessageList({ controller, ui, slip, onDashboardTap }) {
1723
1889
 
1724
1890
  // src/components/ChatInput.tsx
1725
1891
  import { useState as useState7 } from "react";
1726
- import { jsx as jsx34, jsxs as jsxs29 } from "react/jsx-runtime";
1892
+ import { jsx as jsx41, jsxs as jsxs36 } from "react/jsx-runtime";
1727
1893
  function ChatInput({ placeholder, streaming, onSend, onStop, stopLabel = "Stop response", sendLabel = "Send message" }) {
1728
1894
  const [value, setValue] = useState7("");
1729
1895
  const submit = () => {
@@ -1738,18 +1904,18 @@ function ChatInput({ placeholder, streaming, onSend, onStop, stopLabel = "Stop r
1738
1904
  submit();
1739
1905
  }
1740
1906
  };
1741
- return /* @__PURE__ */ jsxs29("div", { className: "ss-w-input", children: [
1742
- /* @__PURE__ */ jsx34("textarea", { className: "ss-w-textarea", rows: 1, placeholder, value, disabled: streaming, onChange: (e) => setValue(e.target.value), onKeyDown }),
1743
- streaming ? /* @__PURE__ */ jsx34("button", { type: "button", className: "ss-w-send ss-w-stop", "aria-label": stopLabel, onClick: onStop, children: /* @__PURE__ */ jsx34("span", { className: "ss-w-stop-glyph" }) }) : /* @__PURE__ */ jsx34("button", { type: "button", className: "ss-w-send", "aria-label": sendLabel, onClick: submit, children: /* @__PURE__ */ jsx34(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, {}) })
1744
1910
  ] });
1745
1911
  }
1746
1912
 
1747
1913
  // src/components/ChatPanel.tsx
1748
- import { jsx as jsx35, jsxs as jsxs30 } from "react/jsx-runtime";
1914
+ import { jsx as jsx42, jsxs as jsxs37 } from "react/jsx-runtime";
1749
1915
  var FULLSCREEN_QUERY = "(max-width: 639px)";
1750
1916
  function useIsModal() {
1751
1917
  const [modal, setModal] = useState8(false);
1752
- useEffect5(() => {
1918
+ useEffect6(() => {
1753
1919
  if (typeof window === "undefined" || typeof window.matchMedia !== "function") return;
1754
1920
  const mql = window.matchMedia(FULLSCREEN_QUERY);
1755
1921
  const sync = () => setModal(mql.matches);
@@ -1762,17 +1928,17 @@ function useIsModal() {
1762
1928
  function ChatPanel({ controller, ui, slip, onDashboardTap }) {
1763
1929
  const { state, close, stop, send } = controller;
1764
1930
  const isModal = useIsModal();
1765
- useEffect5(() => {
1931
+ useEffect6(() => {
1766
1932
  const onKey = (e) => {
1767
1933
  if (e.key === "Escape") close();
1768
1934
  };
1769
1935
  window.addEventListener("keydown", onKey);
1770
1936
  return () => window.removeEventListener("keydown", onKey);
1771
1937
  }, [close]);
1772
- return /* @__PURE__ */ jsxs30("div", { className: "ss-w-panel", role: "dialog", "aria-modal": isModal, "aria-label": ui.chatDialogLabel, children: [
1773
- /* @__PURE__ */ jsx35(ChatHeader, { onClose: close, closeLabel: ui.closeChat }),
1774
- /* @__PURE__ */ jsx35(MessageList, { controller, ui, slip, onDashboardTap }),
1775
- /* @__PURE__ */ jsx35(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 })
1776
1942
  ] });
1777
1943
  }
1778
1944
 
@@ -2267,10 +2433,52 @@ var WIDGET_CSS = `.ss-w-root { all: revert; }
2267
2433
  .ss-w-fandash-postmatch-next-row { display: flex; align-items: center; justify-content: space-between; gap: 10px; }
2268
2434
  .ss-w-fandash-postmatch-next-opponent { font-size: 13px; font-weight: 650; overflow-wrap: anywhere; }
2269
2435
  .ss-w-fandash-postmatch-next-when { flex: none; font-size: 11.5px; color: var(--ss-w-faint); }
2436
+
2437
+ /* resting next: the between-matches fixture teaser -- when-label left, competition badge right */
2438
+ .ss-w-fandash-resting-next-row { display: flex; align-items: center; justify-content: space-between; gap: 10px; }
2439
+ .ss-w-fandash-resting-next-when { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-size: 13px; font-weight: 650; }
2440
+ .ss-w-fandash-resting-next-competition { flex: none; font-size: 10.5px; font-weight: 700; text-transform: uppercase; letter-spacing: .03em; color: var(--ss-w-faint); }
2441
+
2442
+ /* resting standings: position figure left, W-D-L/points record right */
2443
+ .ss-w-fandash-resting-standings-row { display: flex; align-items: baseline; justify-content: space-between; gap: 10px; }
2444
+ .ss-w-fandash-resting-standings-position { font-size: 15px; font-weight: 750; color: var(--ss-w-ink); }
2445
+ .ss-w-fandash-resting-standings-record { font-size: 11.5px; color: var(--ss-w-faint); text-align: right; }
2446
+
2447
+ /* resting season: top scorer / key player / season tally as stacked rows */
2448
+ .ss-w-fandash-resting-season-rows { display: flex; flex-direction: column; gap: 4px; }
2449
+ .ss-w-fandash-resting-season-row { font-size: 12.5px; color: var(--ss-w-ink); }
2450
+
2451
+ /* resting form: reuses the fixture last-five pill classes (ss-w-fandash-form / -form-pill) */
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); }
2270
2478
  `;
2271
2479
 
2272
2480
  // src/StatsWidget.tsx
2273
- import { jsx as jsx36 } from "react/jsx-runtime";
2481
+ import { jsx as jsx43 } from "react/jsx-runtime";
2274
2482
  function buildClient(props) {
2275
2483
  try {
2276
2484
  if ("client" in props && props.client) {
@@ -2297,7 +2505,7 @@ function StatsWidget(props) {
2297
2505
  const cfg = props.config;
2298
2506
  const built = useMemo2(() => buildClient(props), [injectedClient, cfg?.operatorId, cfg?.publicKey, cfg?.baseUrl]);
2299
2507
  const ui = useMemo2(() => normalizeUi(props.config ?? { operatorId: "", publicKey: "" }), [props.config]);
2300
- const rootRef = useRef3(null);
2508
+ const rootRef = useRef4(null);
2301
2509
  const controller = useChatStream(built?.conversation ?? emptyConversation(), ui.starters, built ? ui.greetingMessage : void 0);
2302
2510
  const slip = useMemo2(
2303
2511
  () => ({
@@ -2316,14 +2524,14 @@ function StatsWidget(props) {
2316
2524
  () => built ? createDashboardTapSignal((input) => built.conversation.sendSignal(input)) : void 0,
2317
2525
  [built]
2318
2526
  );
2319
- useEffect6(() => {
2527
+ useEffect7(() => {
2320
2528
  if (!built || !ui.injectStyles) return;
2321
2529
  const node = rootRef.current?.getRootNode();
2322
2530
  if (node) injectStyles(node);
2323
2531
  }, [built, ui.injectStyles]);
2324
2532
  if (!built) return null;
2325
2533
  const attrs = themeAttrs(ui);
2326
- return /* @__PURE__ */ jsx36("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__ */ jsx36(ChatPanel, { controller, ui, slip, onDashboardTap }) : /* @__PURE__ */ jsx36(
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(
2327
2535
  ChatFab,
2328
2536
  {
2329
2537
  label: ui.launcherLabel,