@sensiblestats/widget-react 0.28.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";
@@ -1718,139 +1782,352 @@ function RestingFormCard({ card }) {
1718
1782
  ] });
1719
1783
  }
1720
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
+
1721
1812
  // src/components/dashboard/LiveScoreCard.tsx
1722
1813
  import { jsx as jsx34, jsxs as jsxs30 } from "react/jsx-runtime";
1723
- 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
+ }) {
1724
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);
1725
1844
  return /* @__PURE__ */ jsxs30("div", { className: "ss-w-fandash-card ss-w-fandash-live-score", children: [
1726
1845
  card.title ? /* @__PURE__ */ jsx34("span", { className: "ss-w-fandash-eyebrow", children: card.title }) : null,
1727
- /* @__PURE__ */ jsxs30("div", { className: "ss-w-fandash-postmatch-scoreboard", children: [
1728
- /* @__PURE__ */ jsx34("span", { className: "ss-w-fandash-postmatch-team", children: data.homeName }),
1729
- /* @__PURE__ */ jsx34("span", { className: "ss-w-fandash-postmatch-score", children: data.homeScore }),
1730
- /* @__PURE__ */ jsx34("span", { className: "ss-w-fandash-postmatch-sep", children: "\u2013" }),
1731
- /* @__PURE__ */ jsx34("span", { className: "ss-w-fandash-postmatch-score", children: data.awayScore }),
1732
- /* @__PURE__ */ jsx34("span", { className: "ss-w-fandash-postmatch-team ss-w-fandash-postmatch-team--away", children: data.awayName })
1733
- ] }),
1734
- /* @__PURE__ */ jsxs30("div", { className: "ss-w-fandash-live-meta", children: [
1735
- /* @__PURE__ */ jsx34("span", { className: "ss-w-fandash-live-minute", children: data.minuteLabel }),
1736
- /* @__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}` }),
1737
1852
  data.competition ? /* @__PURE__ */ jsx34("span", { className: "ss-w-fandash-live-competition", children: data.competition }) : null
1738
- ] })
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
1739
1873
  ] });
1740
1874
  }
1741
1875
 
1742
1876
  // src/components/dashboard/LiveTimelineCard.tsx
1877
+ import { useState as useState9 } from "react";
1743
1878
  import { jsx as jsx35, jsxs as jsxs31 } from "react/jsx-runtime";
1744
- 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";
1745
1927
  return /* @__PURE__ */ jsxs31("div", { className: "ss-w-fandash-card ss-w-fandash-live-timeline", children: [
1746
1928
  card.title ? /* @__PURE__ */ jsx35("span", { className: "ss-w-fandash-eyebrow", children: card.title }) : null,
1747
- /* @__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: [
1748
- /* @__PURE__ */ jsx35("span", { className: "ss-w-fandash-live-event-minute", children: e.minuteLabel }),
1749
- /* @__PURE__ */ jsx35("span", { className: "ss-w-fandash-live-event-text", children: e.text })
1750
- ] }, `${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
1751
1940
  ] });
1752
1941
  }
1753
1942
 
1754
- // src/components/dashboard/LiveStatsCard.tsx
1943
+ // src/components/dashboard/MomentumWave.tsx
1944
+ import { useId } from "react";
1755
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
+ }
1756
2006
  function LiveStatsCard({ card }) {
1757
2007
  const { data } = card;
1758
- return /* @__PURE__ */ jsxs32("div", { className: "ss-w-fandash-card ss-w-fandash-live-stats", children: [
1759
- card.title ? /* @__PURE__ */ jsx36("span", { className: "ss-w-fandash-eyebrow", children: card.title }) : null,
1760
- /* @__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: [
1761
- /* @__PURE__ */ jsx36("span", { className: "ss-w-fandash-live-stat-value", children: r.clubValue }),
1762
- /* @__PURE__ */ jsx36("span", { className: "ss-w-fandash-live-stat-label", children: r.label }),
1763
- /* @__PURE__ */ jsx36("span", { className: "ss-w-fandash-live-stat-value", children: r.oppValue })
1764
- ] }, `${r.label}-${i}`)) }),
1765
- 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
1766
2039
  ] });
1767
2040
  }
1768
2041
 
1769
2042
  // src/components/dashboard/ClubFanDashboard.tsx
1770
- import { jsx as jsx37, jsxs as jsxs33 } from "react/jsx-runtime";
2043
+ import { jsx as jsx38, jsxs as jsxs34 } from "react/jsx-runtime";
1771
2044
  function ClubFanDashboard({ dashboard, onTap, ui }) {
1772
2045
  const orderedCards = [
1773
2046
  ...dashboard.cards.filter((c) => c.cardType === "countdown"),
1774
2047
  ...dashboard.cards.filter((c) => c.cardType !== "countdown")
1775
2048
  ];
1776
- return /* @__PURE__ */ jsxs33("div", { className: "ss-w-fandash", children: [
1777
- dashboard.greeting ? /* @__PURE__ */ jsx37("span", { className: "ss-w-fandash-greeting", children: dashboard.greeting }) : null,
1778
- /* @__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}`)) })
1779
2056
  ] });
1780
2057
  }
1781
- function FanDashboardCard({ card, onTap, ui }) {
2058
+ function FanDashboardCard({ card, onTap, ui, goalEvents }) {
1782
2059
  switch (card.cardType) {
1783
2060
  case "fixture":
1784
- return /* @__PURE__ */ jsx37(FixtureCard, { card, onTap });
2061
+ return /* @__PURE__ */ jsx38(FixtureCard, { card, onTap });
1785
2062
  case "countdown":
1786
- return /* @__PURE__ */ jsx37(CountdownCard, { card });
2063
+ return /* @__PURE__ */ jsx38(CountdownCard, { card });
1787
2064
  case "injuries":
1788
- return /* @__PURE__ */ jsx37(InjuriesCard, { card });
2065
+ return /* @__PURE__ */ jsx38(InjuriesCard, { card });
1789
2066
  case "reasonToBet":
1790
- return /* @__PURE__ */ jsx37(ReasonToBetCard, { card });
2067
+ return /* @__PURE__ */ jsx38(ReasonToBetCard, { card });
1791
2068
  case "whereToWatch":
1792
- return /* @__PURE__ */ jsx37(WhereToWatchCard, { card });
2069
+ return /* @__PURE__ */ jsx38(WhereToWatchCard, { card });
1793
2070
  case "congestedWeek":
1794
- return /* @__PURE__ */ jsx37(CongestedWeekCard, { card, onTap });
2071
+ return /* @__PURE__ */ jsx38(CongestedWeekCard, { card, onTap });
1795
2072
  case "transfers":
1796
- return /* @__PURE__ */ jsx37(TransfersCard, { card });
2073
+ return /* @__PURE__ */ jsx38(TransfersCard, { card });
1797
2074
  case "links":
1798
- return /* @__PURE__ */ jsx37(LinksCard, { card });
2075
+ return /* @__PURE__ */ jsx38(LinksCard, { card });
1799
2076
  case "postMatchResult":
1800
- return /* @__PURE__ */ jsx37(PostMatchResultCard, { card });
2077
+ return /* @__PURE__ */ jsx38(PostMatchResultCard, { card });
1801
2078
  case "postMatchGoals":
1802
- return /* @__PURE__ */ jsx37(PostMatchGoalsCard, { card, onTap });
2079
+ return /* @__PURE__ */ jsx38(PostMatchGoalsCard, { card, onTap });
1803
2080
  case "postMatchStats":
1804
- return /* @__PURE__ */ jsx37(PostMatchStatsCard, { card, onTap });
2081
+ return /* @__PURE__ */ jsx38(PostMatchStatsCard, { card, onTap });
1805
2082
  case "postMatchTopPerformer":
1806
- return /* @__PURE__ */ jsx37(PostMatchTopPerformerCard, { card, onTap });
2083
+ return /* @__PURE__ */ jsx38(PostMatchTopPerformerCard, { card, onTap });
1807
2084
  case "postMatchNext":
1808
- return /* @__PURE__ */ jsx37(PostMatchNextCard, { card });
2085
+ return /* @__PURE__ */ jsx38(PostMatchNextCard, { card });
1809
2086
  case "restingNext":
1810
- return /* @__PURE__ */ jsx37(RestingNextCard, { card, onTap });
2087
+ return /* @__PURE__ */ jsx38(RestingNextCard, { card, onTap });
1811
2088
  case "restingStandings":
1812
- return /* @__PURE__ */ jsx37(RestingStandingsCard, { card, onTap });
2089
+ return /* @__PURE__ */ jsx38(RestingStandingsCard, { card, onTap });
1813
2090
  case "restingSeason":
1814
- return /* @__PURE__ */ jsx37(RestingSeasonCard, { card, onTap });
2091
+ return /* @__PURE__ */ jsx38(RestingSeasonCard, { card, onTap });
1815
2092
  case "restingForm":
1816
- return /* @__PURE__ */ jsx37(RestingFormCard, { card });
2093
+ return /* @__PURE__ */ jsx38(RestingFormCard, { card });
1817
2094
  case "liveScore":
1818
- return /* @__PURE__ */ jsx37(LiveScoreCard, { card });
2095
+ return /* @__PURE__ */ jsx38(LiveScoreCard, { card, goalEvents });
1819
2096
  case "liveTimeline":
1820
- return /* @__PURE__ */ jsx37(LiveTimelineCard, { card });
2097
+ return /* @__PURE__ */ jsx38(LiveTimelineCard, { card, ui });
1821
2098
  case "liveStats":
1822
- return /* @__PURE__ */ jsx37(LiveStatsCard, { card });
2099
+ return /* @__PURE__ */ jsx38(LiveStatsCard, { card });
1823
2100
  default:
1824
2101
  return null;
1825
2102
  }
1826
2103
  }
1827
2104
 
1828
2105
  // src/components/ChatMessage.tsx
1829
- import { jsx as jsx38, jsxs as jsxs34 } from "react/jsx-runtime";
2106
+ import { jsx as jsx39, jsxs as jsxs35 } from "react/jsx-runtime";
1830
2107
  function ChatMessage({ message, disabled, onAct, onTap, ui, slip }) {
1831
2108
  if (message.role === "user") {
1832
- 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 }) });
1833
2110
  }
1834
2111
  const onFanDashboardTap = (card) => {
1835
2112
  onAct({ message: card.actionLabel, actionRef: card.actionRef, label: card.actionLabel || card.title || "" });
1836
2113
  };
1837
- return /* @__PURE__ */ jsxs34("div", { className: "ss-w-msg ss-w-bot", "data-msg-id": message.id, children: [
1838
- message.text && !message.greetingDashboard && !message.clubFanDashboard ? /* @__PURE__ */ jsx38(Markdown, { text: message.text }) : null,
1839
- message.greetingDashboard && !message.clubFanDashboard ? /* @__PURE__ */ jsx38(GreetingDashboard, { dashboard: message.greetingDashboard, onTap }) : null,
1840
- message.clubFanDashboard ? /* @__PURE__ */ jsx38(ClubFanDashboard, { dashboard: message.clubFanDashboard, onTap: onFanDashboardTap, ui }) : null,
1841
- /* @__PURE__ */ jsx38(EntityCardList, { cards: message.cards, disabled, showLessLabel: ui?.showLess, showMoreLabel: ui?.showMore }),
1842
- /* @__PURE__ */ jsx38(BettingInsightList, { insights: message.insights, ui, slip }),
1843
- /* @__PURE__ */ jsx38(MarketOddsCardList, { cards: message.marketOdds, ui, slip }),
1844
- /* @__PURE__ */ jsx38(OddsMovementCardList, { cards: message.oddsMovement, ui }),
1845
- /* @__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 })
1846
2123
  ] });
1847
2124
  }
1848
2125
 
1849
2126
  // src/components/IntentChips.tsx
1850
- import { jsx as jsx39 } from "react/jsx-runtime";
2127
+ import { jsx as jsx40 } from "react/jsx-runtime";
1851
2128
  function IntentChips({ chips, onPick }) {
1852
2129
  if (chips.length === 0) return null;
1853
- 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}`)) });
1854
2131
  }
1855
2132
 
1856
2133
  // src/scroll.ts
@@ -1860,7 +2137,7 @@ function isPinnedToBottom(el, threshold = PIN_THRESHOLD) {
1860
2137
  }
1861
2138
 
1862
2139
  // src/components/MessageList.tsx
1863
- import { jsx as jsx40, jsxs as jsxs35 } from "react/jsx-runtime";
2140
+ import { jsx as jsx41, jsxs as jsxs36 } from "react/jsx-runtime";
1864
2141
  function turnAnchor(messages) {
1865
2142
  const last = messages[messages.length - 1];
1866
2143
  if (!last || last.role !== "assistant") return null;
@@ -1869,11 +2146,11 @@ function turnAnchor(messages) {
1869
2146
  }
1870
2147
  function MessageList({ controller, ui, slip, onDashboardTap }) {
1871
2148
  const { state, send } = controller;
1872
- const listRef = useRef3(null);
1873
- const endRef = useRef3(null);
1874
- const pinnedRef = useRef3(true);
1875
- const anchoredTurnRef = useRef3(null);
1876
- useEffect5(() => {
2149
+ const listRef = useRef4(null);
2150
+ const endRef = useRef4(null);
2151
+ const pinnedRef = useRef4(true);
2152
+ const anchoredTurnRef = useRef4(null);
2153
+ useEffect6(() => {
1877
2154
  const anchor = turnAnchor(state.messages);
1878
2155
  const list = listRef.current;
1879
2156
  if (anchor && anchor.turnId !== anchoredTurnRef.current) {
@@ -1888,7 +2165,7 @@ function MessageList({ controller, ui, slip, onDashboardTap }) {
1888
2165
  }, [state.messages, state.status, state.isStreaming]);
1889
2166
  const hasUserMessage = state.messages.some((m) => m.role === "user");
1890
2167
  const hasDashboard = state.messages.some((m) => m.role === "assistant" && (m.greetingDashboard || m.clubFanDashboard));
1891
- return /* @__PURE__ */ jsxs35(
2168
+ return /* @__PURE__ */ jsxs36(
1892
2169
  "div",
1893
2170
  {
1894
2171
  className: "ss-w-list",
@@ -1898,7 +2175,7 @@ function MessageList({ controller, ui, slip, onDashboardTap }) {
1898
2175
  if (listRef.current) pinnedRef.current = isPinnedToBottom(listRef.current);
1899
2176
  },
1900
2177
  children: [
1901
- state.messages.map((m) => /* @__PURE__ */ jsx40(
2178
+ state.messages.map((m) => /* @__PURE__ */ jsx41(
1902
2179
  ChatMessage,
1903
2180
  {
1904
2181
  message: m,
@@ -1913,25 +2190,25 @@ function MessageList({ controller, ui, slip, onDashboardTap }) {
1913
2190
  },
1914
2191
  m.id
1915
2192
  )),
1916
- state.isStreaming && state.actionsLoading ? /* @__PURE__ */ jsx40(ActionButtons, { actions: [], loading: true, onAct: () => {
2193
+ state.isStreaming && state.actionsLoading ? /* @__PURE__ */ jsx41(ActionButtons, { actions: [], loading: true, onAct: () => {
1917
2194
  } }) : null,
1918
- state.status ? /* @__PURE__ */ jsx40("div", { className: "ss-w-status", children: state.status }) : null,
1919
- state.error ? /* @__PURE__ */ jsxs35("div", { className: "ss-w-error", role: "alert", children: [
1920
- /* @__PURE__ */ jsx40("span", { children: "message" in state.error ? state.error.message : ui.somethingWentWrong }),
1921
- /* @__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 })
1922
2199
  ] }) : null,
1923
- !hasUserMessage && !hasDashboard ? /* @__PURE__ */ jsx40(IntentChips, { chips: state.starters, onPick: (t) => send(t) }) : null,
1924
- /* @__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" })
1925
2202
  ]
1926
2203
  }
1927
2204
  );
1928
2205
  }
1929
2206
 
1930
2207
  // src/components/ChatInput.tsx
1931
- import { useState as useState7 } from "react";
1932
- 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";
1933
2210
  function ChatInput({ placeholder, streaming, onSend, onStop, stopLabel = "Stop response", sendLabel = "Send message" }) {
1934
- const [value, setValue] = useState7("");
2211
+ const [value, setValue] = useState10("");
1935
2212
  const submit = () => {
1936
2213
  const t = value.trim();
1937
2214
  if (!t) return;
@@ -1944,18 +2221,18 @@ function ChatInput({ placeholder, streaming, onSend, onStop, stopLabel = "Stop r
1944
2221
  submit();
1945
2222
  }
1946
2223
  };
1947
- return /* @__PURE__ */ jsxs36("div", { className: "ss-w-input", children: [
1948
- /* @__PURE__ */ jsx41("textarea", { className: "ss-w-textarea", rows: 1, placeholder, value, disabled: streaming, onChange: (e) => setValue(e.target.value), onKeyDown }),
1949
- 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, {}) })
1950
2227
  ] });
1951
2228
  }
1952
2229
 
1953
2230
  // src/components/ChatPanel.tsx
1954
- import { jsx as jsx42, jsxs as jsxs37 } from "react/jsx-runtime";
2231
+ import { jsx as jsx43, jsxs as jsxs38 } from "react/jsx-runtime";
1955
2232
  var FULLSCREEN_QUERY = "(max-width: 639px)";
1956
2233
  function useIsModal() {
1957
- const [modal, setModal] = useState8(false);
1958
- useEffect6(() => {
2234
+ const [modal, setModal] = useState11(false);
2235
+ useEffect7(() => {
1959
2236
  if (typeof window === "undefined" || typeof window.matchMedia !== "function") return;
1960
2237
  const mql = window.matchMedia(FULLSCREEN_QUERY);
1961
2238
  const sync = () => setModal(mql.matches);
@@ -1968,17 +2245,17 @@ function useIsModal() {
1968
2245
  function ChatPanel({ controller, ui, slip, onDashboardTap }) {
1969
2246
  const { state, close, stop, send } = controller;
1970
2247
  const isModal = useIsModal();
1971
- useEffect6(() => {
2248
+ useEffect7(() => {
1972
2249
  const onKey = (e) => {
1973
2250
  if (e.key === "Escape") close();
1974
2251
  };
1975
2252
  window.addEventListener("keydown", onKey);
1976
2253
  return () => window.removeEventListener("keydown", onKey);
1977
2254
  }, [close]);
1978
- return /* @__PURE__ */ jsxs37("div", { className: "ss-w-panel", role: "dialog", "aria-modal": isModal, "aria-label": ui.chatDialogLabel, children: [
1979
- /* @__PURE__ */ jsx42(ChatHeader, { onClose: close, closeLabel: ui.closeChat }),
1980
- /* @__PURE__ */ jsx42(MessageList, { controller, ui, slip, onDashboardTap }),
1981
- /* @__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 })
1982
2259
  ] });
1983
2260
  }
1984
2261
 
@@ -1991,6 +2268,7 @@ var WIDGET_CSS = `.ss-w-root { all: revert; }
1991
2268
  --ss-w-ink: #14181d; --ss-w-faint: #6b7580; --ss-w-line: #e4e8ec;
1992
2269
  --ss-w-panel: #ffffff; --ss-w-bubble: #f3f5f7; --ss-w-nav: #0e1622;
1993
2270
  --ss-w-pos: #1aa563; --ss-w-neg: #e0524b;
2271
+ --ss-w-home: #3a63a8; --ss-w-away: #c8102e; --ss-w-track: #e9edf1;
1994
2272
  --ss-w-radius: 16px; --ss-w-offset-x: 20px; --ss-w-offset-y: 20px;
1995
2273
  font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
1996
2274
  font-size: 13px; line-height: 1.5; color: var(--ss-w-ink);
@@ -2001,6 +2279,7 @@ var WIDGET_CSS = `.ss-w-root { all: revert; }
2001
2279
  --ss-w-ink: #e8edf2; --ss-w-faint: #8b95a1;
2002
2280
  --ss-w-line: #2a3441; --ss-w-panel: #141b24; --ss-w-bubble: #1e2732; --ss-w-nav: #0a0f16;
2003
2281
  --ss-w-pos: #37c891; --ss-w-neg: #f0655d;
2282
+ --ss-w-home: #5b83c8; --ss-w-away: #e8384f; --ss-w-track: #232d3a;
2004
2283
  }
2005
2284
  }
2006
2285
  .ss-w-root[data-ss-w-theme="dark"] {
@@ -2008,11 +2287,13 @@ var WIDGET_CSS = `.ss-w-root { all: revert; }
2008
2287
  --ss-w-ink: #e8edf2; --ss-w-faint: #8b95a1;
2009
2288
  --ss-w-line: #2a3441; --ss-w-panel: #141b24; --ss-w-bubble: #1e2732; --ss-w-nav: #0a0f16;
2010
2289
  --ss-w-pos: #37c891; --ss-w-neg: #f0655d;
2290
+ --ss-w-home: #5b83c8; --ss-w-away: #e8384f; --ss-w-track: #232d3a;
2011
2291
  }
2012
2292
  .ss-w-root[data-ss-w-theme="light"] {
2013
2293
  --ss-w-accent: #17936a; --ss-w-cta: #f5a623; --ss-w-cta-ink: #1b1300;
2014
2294
  --ss-w-ink: #14181d; --ss-w-faint: #6b7580;
2015
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;
2016
2297
  }
2017
2298
 
2018
2299
  /* launcher */
@@ -2501,34 +2782,90 @@ var WIDGET_CSS = `.ss-w-root { all: revert; }
2501
2782
  /* resting form: reuses the fixture last-five pill classes (ss-w-fandash-form / -form-pill) */
2502
2783
  .ss-w-fandash-resting-form .ss-w-fandash-form { margin-top: 2px; }
2503
2784
 
2504
- /* live score: reuses the post-match scoreboard classes for the team/score row, with its own
2505
- meta row -- minute called out in the accent color, status and competition beside it */
2506
- .ss-w-fandash-live-meta { display: flex; align-items: center; gap: 6px; flex-wrap: wrap; font-size: 11.5px; color: var(--ss-w-faint); }
2507
- .ss-w-fandash-live-minute { flex: none; font-weight: 700; font-variant-numeric: tabular-nums; color: var(--ss-w-accent); }
2508
- .ss-w-fandash-live-status { font-weight: 650; color: var(--ss-w-ink); }
2509
- .ss-w-fandash-live-competition { color: var(--ss-w-faint); }
2510
-
2511
- /* live timeline: a plain event list, the club's own events picked out in the accent color --
2512
- same mechanism as the post-match goal list, plus a minute column */
2513
- .ss-w-fandash-live-event-list { list-style: none; display: flex; flex-direction: column; gap: 4px; }
2514
- .ss-w-fandash-live-event { display: flex; align-items: baseline; gap: 8px; font-size: 12.5px; color: var(--ss-w-faint); }
2515
- .ss-w-fandash-live-event--club { color: var(--ss-w-ink); font-weight: 650; }
2516
- .ss-w-fandash-live-event-minute { flex: none; font-variant-numeric: tabular-nums; color: var(--ss-w-faint); }
2517
- .ss-w-fandash-live-event--club .ss-w-fandash-live-event-minute { color: var(--ss-w-accent); }
2518
- .ss-w-fandash-live-event-text { min-width: 0; }
2519
-
2520
- /* live stats: club value / stat label / opponent value, same 3-column layout as post-match
2521
- stats, plus an optional momentum line underneath */
2522
- .ss-w-fandash-live-stat-rows { display: flex; flex-direction: column; gap: 5px; }
2523
- .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; }
2524
2842
  .ss-w-fandash-live-stat-value { font-size: 13px; font-weight: 700; font-variant-numeric: tabular-nums; color: var(--ss-w-ink); }
2525
- .ss-w-fandash-live-stat-row .ss-w-fandash-live-stat-value:last-child { text-align: right; }
2526
- .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
+
2527
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); }
2528
2865
  `;
2529
2866
 
2530
2867
  // src/StatsWidget.tsx
2531
- import { jsx as jsx43 } from "react/jsx-runtime";
2868
+ import { jsx as jsx44 } from "react/jsx-runtime";
2532
2869
  function buildClient(props) {
2533
2870
  try {
2534
2871
  if ("client" in props && props.client) {
@@ -2555,7 +2892,7 @@ function StatsWidget(props) {
2555
2892
  const cfg = props.config;
2556
2893
  const built = useMemo2(() => buildClient(props), [injectedClient, cfg?.operatorId, cfg?.publicKey, cfg?.baseUrl]);
2557
2894
  const ui = useMemo2(() => normalizeUi(props.config ?? { operatorId: "", publicKey: "" }), [props.config]);
2558
- const rootRef = useRef4(null);
2895
+ const rootRef = useRef5(null);
2559
2896
  const controller = useChatStream(built?.conversation ?? emptyConversation(), ui.starters, built ? ui.greetingMessage : void 0);
2560
2897
  const slip = useMemo2(
2561
2898
  () => ({
@@ -2574,14 +2911,14 @@ function StatsWidget(props) {
2574
2911
  () => built ? createDashboardTapSignal((input) => built.conversation.sendSignal(input)) : void 0,
2575
2912
  [built]
2576
2913
  );
2577
- useEffect7(() => {
2914
+ useEffect8(() => {
2578
2915
  if (!built || !ui.injectStyles) return;
2579
2916
  const node = rootRef.current?.getRootNode();
2580
2917
  if (node) injectStyles(node);
2581
2918
  }, [built, ui.injectStyles]);
2582
2919
  if (!built) return null;
2583
2920
  const attrs = themeAttrs(ui);
2584
- 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(
2585
2922
  ChatFab,
2586
2923
  {
2587
2924
  label: ui.launcherLabel,