@sensiblestats/widget-react 0.29.2 → 0.29.3

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/CHANGELOG.md CHANGED
@@ -1,5 +1,13 @@
1
1
  # @sensiblestats/widget-react
2
2
 
3
+ ## 0.29.3
4
+
5
+ ### Patch Changes
6
+
7
+ - aed14de: Redesign the club post-match dashboard cards: result card now shows team crests and a big score, goals card shows per-goal icons, stats card renders split comparison bars oriented by venue, and top performer / next fixture cards get a player avatar and next-opponent teaser. Adds crest URLs and numeric stat fields to the widget-sdk post-match types backing these cards.
8
+ - Updated dependencies [aed14de]
9
+ - @sensiblestats/widget-sdk@0.25.2
10
+
3
11
  ## 0.29.2
4
12
 
5
13
  ### Patch Changes
package/dist/index.cjs CHANGED
@@ -36,7 +36,7 @@ __export(index_exports, {
36
36
  module.exports = __toCommonJS(index_exports);
37
37
 
38
38
  // src/StatsWidget.tsx
39
- var import_react17 = require("react");
39
+ var import_react18 = require("react");
40
40
  var import_widget_sdk5 = require("@sensiblestats/widget-sdk");
41
41
 
42
42
  // src/kickoff.ts
@@ -876,7 +876,7 @@ function ChatFab({ label, greeting, popupText, onOpen, onDismissPopup, dismissLa
876
876
  }
877
877
 
878
878
  // src/components/ChatPanel.tsx
879
- var import_react16 = require("react");
879
+ var import_react17 = require("react");
880
880
 
881
881
  // src/components/ChatHeader.tsx
882
882
  var import_jsx_runtime4 = require("react/jsx-runtime");
@@ -889,7 +889,7 @@ function ChatHeader({ onClose, closeLabel = "Close chat" }) {
889
889
  }
890
890
 
891
891
  // src/components/MessageList.tsx
892
- var import_react14 = require("react");
892
+ var import_react15 = require("react");
893
893
 
894
894
  // src/components/Markdown.tsx
895
895
  var import_react_markdown = __toESM(require("react-markdown"), 1);
@@ -1691,21 +1691,60 @@ function LinksCard({ card }) {
1691
1691
  }
1692
1692
 
1693
1693
  // src/components/dashboard/PostMatchResultCard.tsx
1694
+ var import_react10 = require("react");
1694
1695
  var import_jsx_runtime25 = require("react/jsx-runtime");
1696
+ function initials3(name) {
1697
+ return name.trim().slice(0, 3).toUpperCase();
1698
+ }
1699
+ function PostMatchCrest({
1700
+ url,
1701
+ name,
1702
+ side
1703
+ }) {
1704
+ const [failed, setFailed] = (0, import_react10.useState)(false);
1705
+ const cls = `ss-w-fandash-postmatch-crest ss-w-fandash-postmatch-crest--${side}`;
1706
+ if (url && !failed) {
1707
+ return /* @__PURE__ */ (0, import_jsx_runtime25.jsx)("img", { className: cls, src: url, alt: "", "aria-hidden": "true", onError: () => setFailed(true) });
1708
+ }
1709
+ return /* @__PURE__ */ (0, import_jsx_runtime25.jsx)("span", { className: `${cls} ss-w-fandash-postmatch-crest--fallback`, "aria-hidden": "true", children: initials3(name) });
1710
+ }
1695
1711
  function PostMatchResultCard({ card }) {
1696
1712
  const { data } = card;
1713
+ const homeWon = data.homeScore >= data.awayScore;
1714
+ const awayWon = data.awayScore >= data.homeScore;
1697
1715
  return /* @__PURE__ */ (0, import_jsx_runtime25.jsxs)("div", { className: "ss-w-fandash-card ss-w-fandash-postmatch-result", children: [
1698
- card.title ? /* @__PURE__ */ (0, import_jsx_runtime25.jsx)("span", { className: "ss-w-fandash-eyebrow", children: card.title }) : null,
1699
- /* @__PURE__ */ (0, import_jsx_runtime25.jsxs)("div", { className: "ss-w-fandash-postmatch-scoreboard", children: [
1700
- /* @__PURE__ */ (0, import_jsx_runtime25.jsx)("span", { className: "ss-w-fandash-postmatch-team", children: data.homeName }),
1701
- /* @__PURE__ */ (0, import_jsx_runtime25.jsx)("span", { className: "ss-w-fandash-postmatch-score", children: data.homeScore }),
1702
- /* @__PURE__ */ (0, import_jsx_runtime25.jsx)("span", { className: "ss-w-fandash-postmatch-sep", children: "\u2013" }),
1703
- /* @__PURE__ */ (0, import_jsx_runtime25.jsx)("span", { className: "ss-w-fandash-postmatch-score", children: data.awayScore }),
1704
- /* @__PURE__ */ (0, import_jsx_runtime25.jsx)("span", { className: "ss-w-fandash-postmatch-team ss-w-fandash-postmatch-team--away", children: data.awayName })
1716
+ /* @__PURE__ */ (0, import_jsx_runtime25.jsxs)("div", { className: "ss-w-fandash-postmatch-top", children: [
1717
+ card.title ? /* @__PURE__ */ (0, import_jsx_runtime25.jsx)("span", { className: "ss-w-fandash-postmatch-pill", children: card.title }) : null,
1718
+ /* @__PURE__ */ (0, import_jsx_runtime25.jsx)("span", { className: `ss-w-fandash-postmatch-tag ss-w-fandash-postmatch-tag--${data.outcome}`, children: data.resultLabel })
1719
+ ] }),
1720
+ /* @__PURE__ */ (0, import_jsx_runtime25.jsxs)("div", { className: "ss-w-fandash-postmatch-main", children: [
1721
+ /* @__PURE__ */ (0, import_jsx_runtime25.jsxs)("div", { className: "ss-w-fandash-postmatch-side ss-w-fandash-postmatch-side--home", children: [
1722
+ /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(PostMatchCrest, { url: data.homeCrestUrl, name: data.homeName, side: "home" }),
1723
+ /* @__PURE__ */ (0, import_jsx_runtime25.jsx)("span", { className: "ss-w-fandash-postmatch-team-name", children: data.homeName })
1724
+ ] }),
1725
+ /* @__PURE__ */ (0, import_jsx_runtime25.jsxs)("div", { className: "ss-w-fandash-postmatch-score-main", children: [
1726
+ /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(
1727
+ "span",
1728
+ {
1729
+ className: `ss-w-fandash-postmatch-score-value ss-w-fandash-postmatch-score-value--${homeWon ? "win" : "lose"}`,
1730
+ children: data.homeScore
1731
+ }
1732
+ ),
1733
+ /* @__PURE__ */ (0, import_jsx_runtime25.jsx)("span", { className: "ss-w-fandash-postmatch-score-sep", children: "\u2013" }),
1734
+ /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(
1735
+ "span",
1736
+ {
1737
+ className: `ss-w-fandash-postmatch-score-value ss-w-fandash-postmatch-score-value--${awayWon ? "win" : "lose"}`,
1738
+ children: data.awayScore
1739
+ }
1740
+ )
1741
+ ] }),
1742
+ /* @__PURE__ */ (0, import_jsx_runtime25.jsxs)("div", { className: "ss-w-fandash-postmatch-side ss-w-fandash-postmatch-side--away", children: [
1743
+ /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(PostMatchCrest, { url: data.awayCrestUrl, name: data.awayName, side: "away" }),
1744
+ /* @__PURE__ */ (0, import_jsx_runtime25.jsx)("span", { className: "ss-w-fandash-postmatch-team-name", children: data.awayName })
1745
+ ] })
1705
1746
  ] }),
1706
1747
  /* @__PURE__ */ (0, import_jsx_runtime25.jsxs)("div", { className: "ss-w-fandash-postmatch-meta", children: [
1707
- data.resultLabel,
1708
- " \xB7 ",
1709
1748
  data.kickoffLabel,
1710
1749
  data.competition ? ` \xB7 ${data.competition}` : ""
1711
1750
  ] })
@@ -1717,38 +1756,75 @@ var import_jsx_runtime26 = require("react/jsx-runtime");
1717
1756
  function PostMatchGoalsCard({ card, onTap }) {
1718
1757
  return /* @__PURE__ */ (0, import_jsx_runtime26.jsxs)("button", { type: "button", className: "ss-w-fandash-card ss-w-fandash-card--tap ss-w-fandash-postmatch-goals", onClick: () => onTap(card), children: [
1719
1758
  card.title ? /* @__PURE__ */ (0, import_jsx_runtime26.jsx)("span", { className: "ss-w-fandash-eyebrow", children: card.title }) : null,
1720
- /* @__PURE__ */ (0, import_jsx_runtime26.jsx)("ul", { className: "ss-w-fandash-postmatch-goal-list", children: card.data.goals.map((g, i) => /* @__PURE__ */ (0, import_jsx_runtime26.jsx)("li", { className: g.scoredByClub ? "ss-w-fandash-postmatch-goal ss-w-fandash-postmatch-goal--club" : "ss-w-fandash-postmatch-goal", children: g.text }, `${g.text}-${i}`)) }),
1759
+ /* @__PURE__ */ (0, import_jsx_runtime26.jsx)("ul", { className: "ss-w-fandash-postmatch-goal-list", children: card.data.goals.map((g, i) => /* @__PURE__ */ (0, import_jsx_runtime26.jsxs)("li", { className: g.scoredByClub ? "ss-w-fandash-postmatch-goal ss-w-fandash-postmatch-goal--club" : "ss-w-fandash-postmatch-goal", children: [
1760
+ /* @__PURE__ */ (0, import_jsx_runtime26.jsx)("span", { className: "ss-w-fandash-postmatch-goal-ball" }),
1761
+ /* @__PURE__ */ (0, import_jsx_runtime26.jsx)("span", { children: g.text })
1762
+ ] }, `${g.text}-${i}`)) }),
1721
1763
  /* @__PURE__ */ (0, import_jsx_runtime26.jsx)("span", { className: "ss-w-fandash-card-cta", children: card.actionLabel })
1722
1764
  ] });
1723
1765
  }
1724
1766
 
1725
1767
  // src/components/dashboard/PostMatchStatsCard.tsx
1726
1768
  var import_jsx_runtime27 = require("react/jsx-runtime");
1769
+ function splitWidths(homeNum, awayNum) {
1770
+ const total = homeNum + awayNum;
1771
+ if (total <= 0) return { home: "50%", away: "50%" };
1772
+ const homePct = Math.round(homeNum / total * 1e3) / 10;
1773
+ return { home: `${homePct}%`, away: `${Math.round((100 - homePct) * 10) / 10}%` };
1774
+ }
1727
1775
  function PostMatchStatsCard({ card, onTap }) {
1776
+ const { data } = card;
1728
1777
  return /* @__PURE__ */ (0, import_jsx_runtime27.jsxs)("button", { type: "button", className: "ss-w-fandash-card ss-w-fandash-card--tap ss-w-fandash-postmatch-stats", onClick: () => onTap(card), children: [
1729
1778
  card.title ? /* @__PURE__ */ (0, import_jsx_runtime27.jsx)("span", { className: "ss-w-fandash-eyebrow", children: card.title }) : null,
1730
- /* @__PURE__ */ (0, import_jsx_runtime27.jsx)("div", { className: "ss-w-fandash-postmatch-stat-rows", children: card.data.rows.map((r, i) => {
1731
- const leftValue = card.data.clubIsHome ? r.clubValue : r.oppValue;
1732
- const rightValue = card.data.clubIsHome ? r.oppValue : r.clubValue;
1733
- return /* @__PURE__ */ (0, import_jsx_runtime27.jsxs)("div", { className: "ss-w-fandash-postmatch-stat-row", children: [
1734
- /* @__PURE__ */ (0, import_jsx_runtime27.jsx)("span", { className: "ss-w-fandash-postmatch-stat-value", children: leftValue }),
1735
- /* @__PURE__ */ (0, import_jsx_runtime27.jsx)("span", { className: "ss-w-fandash-postmatch-stat-label", children: r.label }),
1736
- /* @__PURE__ */ (0, import_jsx_runtime27.jsx)("span", { className: "ss-w-fandash-postmatch-stat-value", children: rightValue })
1779
+ /* @__PURE__ */ (0, import_jsx_runtime27.jsx)("div", { className: "ss-w-fandash-postmatch-stat-rows", children: data.rows.map((r, i) => {
1780
+ const leftValue = data.clubIsHome ? r.clubValue : r.oppValue;
1781
+ const rightValue = data.clubIsHome ? r.oppValue : r.clubValue;
1782
+ const homeNum = data.clubIsHome ? r.clubNum : r.oppNum;
1783
+ const awayNum = data.clubIsHome ? r.oppNum : r.clubNum;
1784
+ const { home, away } = splitWidths(homeNum, awayNum);
1785
+ return /* @__PURE__ */ (0, import_jsx_runtime27.jsxs)("div", { className: "ss-w-fandash-postmatch-stat", children: [
1786
+ /* @__PURE__ */ (0, import_jsx_runtime27.jsxs)("div", { className: "ss-w-fandash-postmatch-stat-row", children: [
1787
+ /* @__PURE__ */ (0, import_jsx_runtime27.jsx)("span", { className: "ss-w-fandash-postmatch-stat-value ss-w-fandash-postmatch-stat-value--home", children: leftValue }),
1788
+ /* @__PURE__ */ (0, import_jsx_runtime27.jsx)("span", { className: "ss-w-fandash-postmatch-stat-label", children: r.label }),
1789
+ /* @__PURE__ */ (0, import_jsx_runtime27.jsx)("span", { className: "ss-w-fandash-postmatch-stat-value ss-w-fandash-postmatch-stat-value--away", children: rightValue })
1790
+ ] }),
1791
+ /* @__PURE__ */ (0, import_jsx_runtime27.jsxs)("div", { className: "ss-w-fandash-postmatch-stat-bar", children: [
1792
+ /* @__PURE__ */ (0, import_jsx_runtime27.jsx)("span", { className: "ss-w-fandash-postmatch-stat-bar-home", style: { width: home } }),
1793
+ /* @__PURE__ */ (0, import_jsx_runtime27.jsx)("span", { className: "ss-w-fandash-postmatch-stat-bar-away", style: { width: away } })
1794
+ ] })
1737
1795
  ] }, `${r.label}-${i}`);
1738
1796
  }) }),
1797
+ data.rows.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime27.jsxs)("div", { className: "ss-w-fandash-postmatch-legend", children: [
1798
+ /* @__PURE__ */ (0, import_jsx_runtime27.jsxs)("span", { className: "ss-w-fandash-postmatch-legend-item", children: [
1799
+ /* @__PURE__ */ (0, import_jsx_runtime27.jsx)("i", { className: "ss-w-fandash-postmatch-legend-swatch ss-w-fandash-postmatch-legend-swatch--home" }),
1800
+ data.homeName
1801
+ ] }),
1802
+ /* @__PURE__ */ (0, import_jsx_runtime27.jsxs)("span", { className: "ss-w-fandash-postmatch-legend-item", children: [
1803
+ /* @__PURE__ */ (0, import_jsx_runtime27.jsx)("i", { className: "ss-w-fandash-postmatch-legend-swatch ss-w-fandash-postmatch-legend-swatch--away" }),
1804
+ data.awayName
1805
+ ] })
1806
+ ] }) : null,
1739
1807
  /* @__PURE__ */ (0, import_jsx_runtime27.jsx)("span", { className: "ss-w-fandash-card-cta", children: card.actionLabel })
1740
1808
  ] });
1741
1809
  }
1742
1810
 
1743
1811
  // src/components/dashboard/PostMatchTopPerformerCard.tsx
1744
1812
  var import_jsx_runtime28 = require("react/jsx-runtime");
1813
+ function initials4(playerName) {
1814
+ const words = playerName.trim().split(/\s+/).filter(Boolean);
1815
+ if (words.length === 0) return "?";
1816
+ const first = words[0].charAt(0);
1817
+ const last = words[words.length - 1].charAt(0);
1818
+ return (words.length > 1 ? `${first}${last}` : first).toUpperCase();
1819
+ }
1745
1820
  function PostMatchTopPerformerCard({ card, onTap }) {
1746
1821
  const { data } = card;
1747
1822
  return /* @__PURE__ */ (0, import_jsx_runtime28.jsxs)("button", { type: "button", className: "ss-w-fandash-card ss-w-fandash-card--tap ss-w-fandash-postmatch-performer", onClick: () => onTap(card), children: [
1748
1823
  card.title ? /* @__PURE__ */ (0, import_jsx_runtime28.jsx)("span", { className: "ss-w-fandash-eyebrow", children: card.title }) : null,
1749
- /* @__PURE__ */ (0, import_jsx_runtime28.jsxs)("div", { className: "ss-w-fandash-postmatch-performer-row", children: [
1750
- /* @__PURE__ */ (0, import_jsx_runtime28.jsx)("span", { className: "ss-w-fandash-postmatch-performer-name", children: data.playerName }),
1751
- /* @__PURE__ */ (0, import_jsx_runtime28.jsx)("span", { className: "ss-w-fandash-postmatch-performer-rating", children: data.ratingLabel })
1824
+ /* @__PURE__ */ (0, import_jsx_runtime28.jsxs)("div", { className: "ss-w-fandash-postmatch-perf-row", children: [
1825
+ /* @__PURE__ */ (0, import_jsx_runtime28.jsx)("span", { className: "ss-w-fandash-postmatch-perf-avatar", "aria-hidden": "true", children: initials4(data.playerName) }),
1826
+ /* @__PURE__ */ (0, import_jsx_runtime28.jsx)("span", { className: "ss-w-fandash-postmatch-perf-name", children: data.playerName }),
1827
+ /* @__PURE__ */ (0, import_jsx_runtime28.jsx)("span", { className: "ss-w-fandash-postmatch-perf-rating", children: data.ratingLabel })
1752
1828
  ] }),
1753
1829
  /* @__PURE__ */ (0, import_jsx_runtime28.jsx)("span", { className: "ss-w-fandash-card-cta", children: card.actionLabel })
1754
1830
  ] });
@@ -1824,10 +1900,10 @@ function RestingFormCard({ card }) {
1824
1900
  }
1825
1901
 
1826
1902
  // src/components/dashboard/LiveScoreCard.tsx
1827
- var import_react11 = require("react");
1903
+ var import_react12 = require("react");
1828
1904
 
1829
1905
  // src/useLiveClock.ts
1830
- var import_react10 = require("react");
1906
+ var import_react11 = require("react");
1831
1907
  var TICK_MS = 6e4;
1832
1908
  var MAX_LIVE_TICK_MINUTES = 15;
1833
1909
  function computeDisplayMinute(minute, clockRunning, receivedAtMs) {
@@ -1836,10 +1912,10 @@ function computeDisplayMinute(minute, clockRunning, receivedAtMs) {
1836
1912
  return minute + Math.min(Math.max(0, elapsedMinutes), MAX_LIVE_TICK_MINUTES);
1837
1913
  }
1838
1914
  function useLiveClock(minute, clockRunning, receivedAtMs) {
1839
- const [displayMinute, setDisplayMinute] = (0, import_react10.useState)(
1915
+ const [displayMinute, setDisplayMinute] = (0, import_react11.useState)(
1840
1916
  () => computeDisplayMinute(minute, clockRunning, receivedAtMs)
1841
1917
  );
1842
- (0, import_react10.useEffect)(() => {
1918
+ (0, import_react11.useEffect)(() => {
1843
1919
  setDisplayMinute(computeDisplayMinute(minute, clockRunning, receivedAtMs));
1844
1920
  if (!clockRunning) return;
1845
1921
  const id = window.setInterval(() => {
@@ -1852,16 +1928,16 @@ function useLiveClock(minute, clockRunning, receivedAtMs) {
1852
1928
 
1853
1929
  // src/components/dashboard/LiveScoreCard.tsx
1854
1930
  var import_jsx_runtime34 = require("react/jsx-runtime");
1855
- function initials3(name) {
1931
+ function initials5(name) {
1856
1932
  return name.trim().slice(0, 3).toUpperCase();
1857
1933
  }
1858
1934
  function LiveCrest({ url, name, side }) {
1859
- const [failed, setFailed] = (0, import_react11.useState)(false);
1935
+ const [failed, setFailed] = (0, import_react12.useState)(false);
1860
1936
  const cls = `ss-w-fandash-live-crest ss-w-fandash-live-crest--${side}`;
1861
1937
  if (url && !failed) {
1862
1938
  return /* @__PURE__ */ (0, import_jsx_runtime34.jsx)("img", { className: cls, src: url, alt: "", "aria-hidden": "true", onError: () => setFailed(true) });
1863
1939
  }
1864
- return /* @__PURE__ */ (0, import_jsx_runtime34.jsx)("span", { className: `${cls} ss-w-fandash-live-crest--fallback`, "aria-hidden": "true", children: initials3(name) });
1940
+ return /* @__PURE__ */ (0, import_jsx_runtime34.jsx)("span", { className: `${cls} ss-w-fandash-live-crest--fallback`, "aria-hidden": "true", children: initials5(name) });
1865
1941
  }
1866
1942
  function scorersLine(goalEvents) {
1867
1943
  if (!goalEvents || goalEvents.length === 0) return null;
@@ -1875,7 +1951,7 @@ function LiveScoreCard({
1875
1951
  }) {
1876
1952
  const { data } = card;
1877
1953
  const seedKey = `${data.minute}:${data.homeScore}:${data.awayScore}`;
1878
- const seedRef = (0, import_react11.useRef)(null);
1954
+ const seedRef = (0, import_react12.useRef)(null);
1879
1955
  if (!seedRef.current || seedRef.current.key !== seedKey) {
1880
1956
  seedRef.current = { key: seedKey, receivedAtMs: Date.now() };
1881
1957
  }
@@ -1915,7 +1991,7 @@ function LiveScoreCard({
1915
1991
  }
1916
1992
 
1917
1993
  // src/components/dashboard/LiveTimelineCard.tsx
1918
- var import_react12 = require("react");
1994
+ var import_react13 = require("react");
1919
1995
  var import_jsx_runtime35 = require("react/jsx-runtime");
1920
1996
  var SHOWN = 3;
1921
1997
  function EventIcon({ type }) {
@@ -1957,7 +2033,7 @@ function EventRow({ e }) {
1957
2033
  ] });
1958
2034
  }
1959
2035
  function LiveTimelineCard({ card, ui }) {
1960
- const [expanded, setExpanded] = (0, import_react12.useState)(false);
2036
+ const [expanded, setExpanded] = (0, import_react13.useState)(false);
1961
2037
  const events = card.data.events;
1962
2038
  if (!events || events.length === 0) return null;
1963
2039
  const sorted = [...events].sort((a, b) => b.minute - a.minute);
@@ -1982,7 +2058,7 @@ function LiveTimelineCard({ card, ui }) {
1982
2058
  }
1983
2059
 
1984
2060
  // src/components/dashboard/MomentumWave.tsx
1985
- var import_react13 = require("react");
2061
+ var import_react14 = require("react");
1986
2062
  var import_jsx_runtime36 = require("react/jsx-runtime");
1987
2063
  var WIDTH = 270;
1988
2064
  var HEIGHT = 56;
@@ -2005,7 +2081,7 @@ function MomentumWave({
2005
2081
  }) {
2006
2082
  if (points.length === 0) return null;
2007
2083
  const { area, line, lastX, lastY } = buildPaths(points);
2008
- const clipId = (0, import_react13.useId)();
2084
+ const clipId = (0, import_react14.useId)();
2009
2085
  const topClipId = `${clipId}-top`;
2010
2086
  const bottomClipId = `${clipId}-bottom`;
2011
2087
  const topClass = clubIsHome ? "ss-w-fandash-live-mom-home" : "ss-w-fandash-live-mom-away";
@@ -2038,7 +2114,7 @@ function MomentumWave({
2038
2114
 
2039
2115
  // src/components/dashboard/LiveStatsCard.tsx
2040
2116
  var import_jsx_runtime37 = require("react/jsx-runtime");
2041
- function splitWidths(homeNum, awayNum) {
2117
+ function splitWidths2(homeNum, awayNum) {
2042
2118
  const total = homeNum + awayNum;
2043
2119
  if (total <= 0) return { home: "50%", away: "50%" };
2044
2120
  const homePct = Math.round(homeNum / total * 1e3) / 10;
@@ -2053,7 +2129,7 @@ function LiveStatsCard({ card }) {
2053
2129
  const awayValue = data.clubIsHome ? r.oppValue : r.clubValue;
2054
2130
  const homeNum = data.clubIsHome ? r.clubNum : r.oppNum;
2055
2131
  const awayNum = data.clubIsHome ? r.oppNum : r.clubNum;
2056
- const { home, away } = splitWidths(homeNum, awayNum);
2132
+ const { home, away } = splitWidths2(homeNum, awayNum);
2057
2133
  return /* @__PURE__ */ (0, import_jsx_runtime37.jsxs)("div", { className: "ss-w-fandash-live-stat", children: [
2058
2134
  /* @__PURE__ */ (0, import_jsx_runtime37.jsxs)("div", { className: "ss-w-fandash-live-stat-row", children: [
2059
2135
  /* @__PURE__ */ (0, import_jsx_runtime37.jsx)("span", { className: "ss-w-fandash-live-stat-value ss-w-fandash-live-stat-value--home", children: homeValue }),
@@ -2187,11 +2263,11 @@ function turnAnchor(messages) {
2187
2263
  }
2188
2264
  function MessageList({ controller, ui, slip, onDashboardTap }) {
2189
2265
  const { state, send } = controller;
2190
- const listRef = (0, import_react14.useRef)(null);
2191
- const endRef = (0, import_react14.useRef)(null);
2192
- const pinnedRef = (0, import_react14.useRef)(true);
2193
- const anchoredTurnRef = (0, import_react14.useRef)(null);
2194
- (0, import_react14.useEffect)(() => {
2266
+ const listRef = (0, import_react15.useRef)(null);
2267
+ const endRef = (0, import_react15.useRef)(null);
2268
+ const pinnedRef = (0, import_react15.useRef)(true);
2269
+ const anchoredTurnRef = (0, import_react15.useRef)(null);
2270
+ (0, import_react15.useEffect)(() => {
2195
2271
  const anchor = turnAnchor(state.messages);
2196
2272
  const list = listRef.current;
2197
2273
  if (anchor && anchor.turnId !== anchoredTurnRef.current) {
@@ -2246,10 +2322,10 @@ function MessageList({ controller, ui, slip, onDashboardTap }) {
2246
2322
  }
2247
2323
 
2248
2324
  // src/components/ChatInput.tsx
2249
- var import_react15 = require("react");
2325
+ var import_react16 = require("react");
2250
2326
  var import_jsx_runtime42 = require("react/jsx-runtime");
2251
2327
  function ChatInput({ placeholder, streaming, onSend, onStop, stopLabel = "Stop response", sendLabel = "Send message" }) {
2252
- const [value, setValue] = (0, import_react15.useState)("");
2328
+ const [value, setValue] = (0, import_react16.useState)("");
2253
2329
  const submit = () => {
2254
2330
  const t = value.trim();
2255
2331
  if (!t) return;
@@ -2272,8 +2348,8 @@ function ChatInput({ placeholder, streaming, onSend, onStop, stopLabel = "Stop r
2272
2348
  var import_jsx_runtime43 = require("react/jsx-runtime");
2273
2349
  var FULLSCREEN_QUERY = "(max-width: 639px)";
2274
2350
  function useIsModal() {
2275
- const [modal, setModal] = (0, import_react16.useState)(false);
2276
- (0, import_react16.useEffect)(() => {
2351
+ const [modal, setModal] = (0, import_react17.useState)(false);
2352
+ (0, import_react17.useEffect)(() => {
2277
2353
  if (typeof window === "undefined" || typeof window.matchMedia !== "function") return;
2278
2354
  const mql = window.matchMedia(FULLSCREEN_QUERY);
2279
2355
  const sync = () => setModal(mql.matches);
@@ -2286,7 +2362,7 @@ function useIsModal() {
2286
2362
  function ChatPanel({ controller, ui, slip, onDashboardTap }) {
2287
2363
  const { state, close, stop, send } = controller;
2288
2364
  const isModal = useIsModal();
2289
- (0, import_react16.useEffect)(() => {
2365
+ (0, import_react17.useEffect)(() => {
2290
2366
  const onKey = (e) => {
2291
2367
  if (e.key === "Escape") close();
2292
2368
  };
@@ -2776,35 +2852,76 @@ var WIDGET_CSS = `.ss-w-root { all: revert; }
2776
2852
  .ss-w-fandash-link-label { font-size: 12.5px; font-weight: 650; overflow-wrap: anywhere; }
2777
2853
  .ss-w-fandash-link-chevron { flex: none; width: 12px; height: 12px; color: var(--ss-w-accent); }
2778
2854
 
2779
- /* post-match result: full-time scoreboard -- team names flank the score, meta line underneath */
2780
- .ss-w-fandash-postmatch-scoreboard { display: flex; align-items: center; gap: 8px; }
2781
- .ss-w-fandash-postmatch-team { min-width: 0; flex: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-size: 13px; font-weight: 650; }
2782
- .ss-w-fandash-postmatch-team--away { text-align: right; }
2783
- .ss-w-fandash-postmatch-score { flex: none; font-size: 20px; font-weight: 750; font-variant-numeric: tabular-nums; color: var(--ss-w-ink); }
2784
- .ss-w-fandash-postmatch-sep { flex: none; font-size: 14px; font-weight: 700; color: var(--ss-w-faint); }
2785
- .ss-w-fandash-postmatch-meta { font-size: 11.5px; color: var(--ss-w-faint); }
2786
-
2787
- /* post-match goals: a plain list, the club's own goals picked out in the accent color */
2788
- .ss-w-fandash-postmatch-goal-list { list-style: none; display: flex; flex-direction: column; gap: 4px; }
2789
- .ss-w-fandash-postmatch-goal { font-size: 12.5px; color: var(--ss-w-faint); }
2855
+ /* post-match result: full-time scoreboard, mirroring the live card's crest + big-score treatment.
2856
+ A neutral "Full time" pill sits left of a coloured Win/Draw/Loss tag; the 3-column main row
2857
+ (home crest+name | score | away crest+name, away reversed) puts the winner's score in full ink
2858
+ and the loser's dimmed; a centered meta line sits under a divider. */
2859
+ .ss-w-fandash-postmatch-top { display: flex; align-items: center; gap: 8px; margin-bottom: 15px; }
2860
+ .ss-w-fandash-postmatch-pill { display: inline-flex; align-items: center; gap: 5px; font-size: 10.5px; font-weight: 800; letter-spacing: .05em; text-transform: uppercase; color: var(--ss-w-faint); background: var(--ss-w-bubble); border: 1px solid var(--ss-w-line); border-radius: 6px; padding: 3px 8px; }
2861
+ .ss-w-fandash-postmatch-tag { margin-left: auto; flex: none; font-size: 11.5px; font-weight: 800; text-transform: uppercase; letter-spacing: .04em; }
2862
+ .ss-w-fandash-postmatch-tag--win { color: var(--ss-w-pos); }
2863
+ .ss-w-fandash-postmatch-tag--loss { color: var(--ss-w-neg); }
2864
+ .ss-w-fandash-postmatch-tag--draw { color: var(--ss-w-faint); }
2865
+ .ss-w-fandash-postmatch-main { display: grid; grid-template-columns: 1fr auto 1fr; align-items: center; gap: 10px; }
2866
+ .ss-w-fandash-postmatch-side { display: flex; align-items: center; gap: 10px; min-width: 0; }
2867
+ .ss-w-fandash-postmatch-side--away { flex-direction: row-reverse; text-align: right; }
2868
+ .ss-w-fandash-postmatch-crest { width: 38px; height: 38px; border-radius: 10px; flex: none; display: grid; place-items: center; color: #fff; font-weight: 800; font-size: 12.5px; object-fit: cover; box-shadow: 0 2px 6px rgba(0, 0, 0, .18); }
2869
+ .ss-w-fandash-postmatch-crest--fallback.ss-w-fandash-postmatch-crest--home { background: var(--ss-w-home); }
2870
+ .ss-w-fandash-postmatch-crest--fallback.ss-w-fandash-postmatch-crest--away { background: var(--ss-w-away); }
2871
+ .ss-w-fandash-postmatch-team-name { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-size: 15px; font-weight: 700; line-height: 1.15; color: var(--ss-w-ink); }
2872
+ .ss-w-fandash-postmatch-score-main { display: flex; align-items: baseline; justify-content: center; font-size: 36px; font-weight: 850; font-variant-numeric: tabular-nums; line-height: 1; white-space: nowrap; }
2873
+ .ss-w-fandash-postmatch-score-value--win { color: var(--ss-w-ink); }
2874
+ .ss-w-fandash-postmatch-score-value--lose { color: var(--ss-w-faint); }
2875
+ .ss-w-fandash-postmatch-score-sep { color: var(--ss-w-faint); font-weight: 500; margin: 0 5px; }
2876
+ .ss-w-fandash-postmatch-meta { margin-top: 13px; padding-top: 12px; border-top: 1px solid var(--ss-w-line); font-size: 12px; color: var(--ss-w-faint); text-align: center; }
2877
+
2878
+ /* post-match goals: a football-ball icon per goal (mirrors the live timeline ball, duplicated here
2879
+ under the postmatch prefix), text dimmed for opponent goals and picked out in full ink + bold
2880
+ for the club's own goals */
2881
+ .ss-w-fandash-postmatch-goal-list { list-style: none; display: flex; flex-direction: column; gap: 6px; }
2882
+ .ss-w-fandash-postmatch-goal { display: flex; align-items: center; gap: 8px; font-size: 12.5px; color: var(--ss-w-faint); }
2883
+ .ss-w-fandash-postmatch-goal-ball { width: 11px; height: 11px; flex: none; border-radius: 50%; background: radial-gradient(circle at 50% 42%, #fff 0 34%, transparent 36%), var(--ss-w-ink); border: 1px solid var(--ss-w-ink); }
2790
2884
  .ss-w-fandash-postmatch-goal--club { color: var(--ss-w-ink); font-weight: 650; }
2791
2885
 
2792
- /* post-match stats: club value / stat label / opponent value, three columns per row */
2793
- .ss-w-fandash-postmatch-stat-rows { display: flex; flex-direction: column; gap: 5px; }
2794
- .ss-w-fandash-postmatch-stat-row { display: grid; grid-template-columns: 1fr auto 1fr; align-items: center; gap: 8px; }
2886
+ /* post-match stats: home value / stat label / away value, oriented by venue (u316), each row now
2887
+ backed by a home/away split bar -- the big upgrade from plain text -- plus a legend. Mirrors
2888
+ LiveStatsCard's split-bar treatment under the postmatch-prefixed classes; the shared
2889
+ --ss-w-home/--ss-w-away/--ss-w-track tokens are defined above. */
2890
+ .ss-w-fandash-postmatch-stat-rows { display: flex; flex-direction: column; gap: 15px; }
2891
+ .ss-w-fandash-postmatch-stat { display: flex; flex-direction: column; gap: 7px; }
2892
+ .ss-w-fandash-postmatch-stat-row { display: grid; grid-template-columns: 1fr auto 1fr; align-items: baseline; gap: 8px; }
2795
2893
  .ss-w-fandash-postmatch-stat-value { font-size: 13px; font-weight: 700; font-variant-numeric: tabular-nums; color: var(--ss-w-ink); }
2796
- .ss-w-fandash-postmatch-stat-row .ss-w-fandash-postmatch-stat-value:last-child { text-align: right; }
2894
+ .ss-w-fandash-postmatch-stat-value--home { color: var(--ss-w-home); }
2895
+ .ss-w-fandash-postmatch-stat-value--away { color: var(--ss-w-away); text-align: right; }
2797
2896
  .ss-w-fandash-postmatch-stat-label { font-size: 10.5px; text-align: center; color: var(--ss-w-faint); text-transform: uppercase; letter-spacing: .03em; }
2798
-
2799
- /* post-match top performer: player name left, rating pill right */
2800
- .ss-w-fandash-postmatch-performer-row { display: flex; align-items: center; justify-content: space-between; gap: 10px; }
2801
- .ss-w-fandash-postmatch-performer-name { font-size: 13px; font-weight: 650; overflow-wrap: anywhere; }
2802
- .ss-w-fandash-postmatch-performer-rating { flex: none; font-size: 12px; font-weight: 700; color: var(--ss-w-accent); background: color-mix(in srgb, var(--ss-w-accent) 13%, var(--ss-w-panel)); border-radius: 999px; padding: 2px 9px; }
2803
-
2804
- /* post-match next: the upcoming fixture teaser -- opponent left, when-label right */
2805
- .ss-w-fandash-postmatch-next-row { display: flex; align-items: center; justify-content: space-between; gap: 10px; }
2806
- .ss-w-fandash-postmatch-next-opponent { font-size: 13px; font-weight: 650; overflow-wrap: anywhere; }
2807
- .ss-w-fandash-postmatch-next-when { flex: none; font-size: 11.5px; color: var(--ss-w-faint); }
2897
+ .ss-w-fandash-postmatch-stat-bar { position: relative; height: 8px; border-radius: 999px; background: var(--ss-w-track); overflow: hidden; display: flex; }
2898
+ .ss-w-fandash-postmatch-stat-bar-home { height: 100%; background: var(--ss-w-home); }
2899
+ .ss-w-fandash-postmatch-stat-bar-away { height: 100%; background: var(--ss-w-away); margin-left: auto; }
2900
+ .ss-w-fandash-postmatch-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); }
2901
+ .ss-w-fandash-postmatch-legend-item { display: inline-flex; align-items: center; gap: 6px; }
2902
+ .ss-w-fandash-postmatch-legend-swatch { width: 10px; height: 10px; border-radius: 3px; flex: none; }
2903
+ .ss-w-fandash-postmatch-legend-swatch--home { background: var(--ss-w-home); }
2904
+ .ss-w-fandash-postmatch-legend-swatch--away { background: var(--ss-w-away); }
2905
+
2906
+ /* post-match top performer (full mockup treatment): gradient initials avatar + name + a
2907
+ prominent rating chip. Avatar gradient reuses the --ss-w-away token, same pairing as the live
2908
+ crest fallback; "?" renders through the same avatar span when the name is blank. */
2909
+ .ss-w-fandash-postmatch-perf-row { display: flex; align-items: center; gap: 12px; }
2910
+ .ss-w-fandash-postmatch-perf-avatar {
2911
+ flex: none; width: 42px; height: 42px; border-radius: 50%;
2912
+ display: grid; place-items: center;
2913
+ font-size: 14px; font-weight: 800; color: #fff;
2914
+ background: linear-gradient(135deg, var(--ss-w-away), color-mix(in srgb, var(--ss-w-away) 55%, #000));
2915
+ }
2916
+ .ss-w-fandash-postmatch-perf-name { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-size: 15px; font-weight: 700; }
2917
+ .ss-w-fandash-postmatch-perf-rating { margin-left: auto; flex: none; font-size: 13px; font-weight: 800; font-variant-numeric: tabular-nums; color: var(--ss-w-accent); background: color-mix(in srgb, var(--ss-w-accent) 14%, var(--ss-w-panel)); border-radius: 999px; padding: 3px 11px; }
2918
+
2919
+ /* post-match next (v1, modest): whenLabel is already a complete localized phrase (opponent +
2920
+ venue + kickoff) composed server-side, so this card renders just that one line -- dressed up
2921
+ with an accent rule rather than a duplicated opponent line. The richer opponent-headline/venue-
2922
+ chip/crest layout from the mockup needs a server field split and is deferred. */
2923
+ .ss-w-fandash-postmatch-next-row { display: flex; align-items: center; gap: 10px; border-left: 3px solid var(--ss-w-accent); padding-left: 10px; }
2924
+ .ss-w-fandash-postmatch-next-when { font-size: 13.5px; font-weight: 650; color: var(--ss-w-ink); line-height: 1.3; }
2808
2925
 
2809
2926
  /* resting next: the between-matches fixture teaser -- when-label left, competition badge right */
2810
2927
  .ss-w-fandash-resting-next-row { display: flex; align-items: center; justify-content: space-between; gap: 10px; }
@@ -2931,11 +3048,11 @@ function injectStyles(root) {
2931
3048
  function StatsWidget(props) {
2932
3049
  const injectedClient = "client" in props ? props.client : void 0;
2933
3050
  const cfg = props.config;
2934
- const built = (0, import_react17.useMemo)(() => buildClient(props), [injectedClient, cfg?.operatorId, cfg?.publicKey, cfg?.baseUrl]);
2935
- const ui = (0, import_react17.useMemo)(() => normalizeUi(props.config ?? { operatorId: "", publicKey: "" }), [props.config]);
2936
- const rootRef = (0, import_react17.useRef)(null);
3051
+ const built = (0, import_react18.useMemo)(() => buildClient(props), [injectedClient, cfg?.operatorId, cfg?.publicKey, cfg?.baseUrl]);
3052
+ const ui = (0, import_react18.useMemo)(() => normalizeUi(props.config ?? { operatorId: "", publicKey: "" }), [props.config]);
3053
+ const rootRef = (0, import_react18.useRef)(null);
2937
3054
  const controller = useChatStream(built?.conversation ?? emptyConversation(), ui.starters, built ? ui.greetingMessage : void 0);
2938
- const slip = (0, import_react17.useMemo)(
3055
+ const slip = (0, import_react18.useMemo)(
2939
3056
  () => ({
2940
3057
  onAddToSlip: props.config?.onAddToSlip,
2941
3058
  conversationId: controller.state.conversationId ?? void 0,
@@ -2948,11 +3065,11 @@ function StatsWidget(props) {
2948
3065
  }),
2949
3066
  [props.config?.onAddToSlip, controller.state.conversationId, built]
2950
3067
  );
2951
- const onDashboardTap = (0, import_react17.useMemo)(
3068
+ const onDashboardTap = (0, import_react18.useMemo)(
2952
3069
  () => built ? createDashboardTapSignal((input) => built.conversation.sendSignal(input)) : void 0,
2953
3070
  [built]
2954
3071
  );
2955
- (0, import_react17.useEffect)(() => {
3072
+ (0, import_react18.useEffect)(() => {
2956
3073
  if (!built || !ui.injectStyles) return;
2957
3074
  const node = rootRef.current?.getRootNode();
2958
3075
  if (node) injectStyles(node);