@sensiblestats/widget-react 0.29.1 → 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,21 @@
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
+
11
+ ## 0.29.2
12
+
13
+ ### Patch Changes
14
+
15
+ - 4eed68b: Fix the post-match "Match stats" card rendering each team's stats under the opposite team. The row values are club-first, but the card was always drawing the club value on the left while the result card above renders home-left/away-right — so when the club played away (e.g. Liverpool at Ipswich) the club's stats appeared under the home team. The card now orients by venue via a new `clubIsHome` field, matching the result card.
16
+ - Updated dependencies [4eed68b]
17
+ - @sensiblestats/widget-sdk@0.25.1
18
+
3
19
  ## 0.29.1
4
20
 
5
21
  ### 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,34 +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) => /* @__PURE__ */ (0, import_jsx_runtime27.jsxs)("div", { className: "ss-w-fandash-postmatch-stat-row", children: [
1731
- /* @__PURE__ */ (0, import_jsx_runtime27.jsx)("span", { className: "ss-w-fandash-postmatch-stat-value", children: r.clubValue }),
1732
- /* @__PURE__ */ (0, import_jsx_runtime27.jsx)("span", { className: "ss-w-fandash-postmatch-stat-label", children: r.label }),
1733
- /* @__PURE__ */ (0, import_jsx_runtime27.jsx)("span", { className: "ss-w-fandash-postmatch-stat-value", children: r.oppValue })
1734
- ] }, `${r.label}-${i}`)) }),
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
+ ] })
1795
+ ] }, `${r.label}-${i}`);
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,
1735
1807
  /* @__PURE__ */ (0, import_jsx_runtime27.jsx)("span", { className: "ss-w-fandash-card-cta", children: card.actionLabel })
1736
1808
  ] });
1737
1809
  }
1738
1810
 
1739
1811
  // src/components/dashboard/PostMatchTopPerformerCard.tsx
1740
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
+ }
1741
1820
  function PostMatchTopPerformerCard({ card, onTap }) {
1742
1821
  const { data } = card;
1743
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: [
1744
1823
  card.title ? /* @__PURE__ */ (0, import_jsx_runtime28.jsx)("span", { className: "ss-w-fandash-eyebrow", children: card.title }) : null,
1745
- /* @__PURE__ */ (0, import_jsx_runtime28.jsxs)("div", { className: "ss-w-fandash-postmatch-performer-row", children: [
1746
- /* @__PURE__ */ (0, import_jsx_runtime28.jsx)("span", { className: "ss-w-fandash-postmatch-performer-name", children: data.playerName }),
1747
- /* @__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 })
1748
1828
  ] }),
1749
1829
  /* @__PURE__ */ (0, import_jsx_runtime28.jsx)("span", { className: "ss-w-fandash-card-cta", children: card.actionLabel })
1750
1830
  ] });
@@ -1820,10 +1900,10 @@ function RestingFormCard({ card }) {
1820
1900
  }
1821
1901
 
1822
1902
  // src/components/dashboard/LiveScoreCard.tsx
1823
- var import_react11 = require("react");
1903
+ var import_react12 = require("react");
1824
1904
 
1825
1905
  // src/useLiveClock.ts
1826
- var import_react10 = require("react");
1906
+ var import_react11 = require("react");
1827
1907
  var TICK_MS = 6e4;
1828
1908
  var MAX_LIVE_TICK_MINUTES = 15;
1829
1909
  function computeDisplayMinute(minute, clockRunning, receivedAtMs) {
@@ -1832,10 +1912,10 @@ function computeDisplayMinute(minute, clockRunning, receivedAtMs) {
1832
1912
  return minute + Math.min(Math.max(0, elapsedMinutes), MAX_LIVE_TICK_MINUTES);
1833
1913
  }
1834
1914
  function useLiveClock(minute, clockRunning, receivedAtMs) {
1835
- const [displayMinute, setDisplayMinute] = (0, import_react10.useState)(
1915
+ const [displayMinute, setDisplayMinute] = (0, import_react11.useState)(
1836
1916
  () => computeDisplayMinute(minute, clockRunning, receivedAtMs)
1837
1917
  );
1838
- (0, import_react10.useEffect)(() => {
1918
+ (0, import_react11.useEffect)(() => {
1839
1919
  setDisplayMinute(computeDisplayMinute(minute, clockRunning, receivedAtMs));
1840
1920
  if (!clockRunning) return;
1841
1921
  const id = window.setInterval(() => {
@@ -1848,16 +1928,16 @@ function useLiveClock(minute, clockRunning, receivedAtMs) {
1848
1928
 
1849
1929
  // src/components/dashboard/LiveScoreCard.tsx
1850
1930
  var import_jsx_runtime34 = require("react/jsx-runtime");
1851
- function initials3(name) {
1931
+ function initials5(name) {
1852
1932
  return name.trim().slice(0, 3).toUpperCase();
1853
1933
  }
1854
1934
  function LiveCrest({ url, name, side }) {
1855
- const [failed, setFailed] = (0, import_react11.useState)(false);
1935
+ const [failed, setFailed] = (0, import_react12.useState)(false);
1856
1936
  const cls = `ss-w-fandash-live-crest ss-w-fandash-live-crest--${side}`;
1857
1937
  if (url && !failed) {
1858
1938
  return /* @__PURE__ */ (0, import_jsx_runtime34.jsx)("img", { className: cls, src: url, alt: "", "aria-hidden": "true", onError: () => setFailed(true) });
1859
1939
  }
1860
- 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) });
1861
1941
  }
1862
1942
  function scorersLine(goalEvents) {
1863
1943
  if (!goalEvents || goalEvents.length === 0) return null;
@@ -1871,7 +1951,7 @@ function LiveScoreCard({
1871
1951
  }) {
1872
1952
  const { data } = card;
1873
1953
  const seedKey = `${data.minute}:${data.homeScore}:${data.awayScore}`;
1874
- const seedRef = (0, import_react11.useRef)(null);
1954
+ const seedRef = (0, import_react12.useRef)(null);
1875
1955
  if (!seedRef.current || seedRef.current.key !== seedKey) {
1876
1956
  seedRef.current = { key: seedKey, receivedAtMs: Date.now() };
1877
1957
  }
@@ -1911,7 +1991,7 @@ function LiveScoreCard({
1911
1991
  }
1912
1992
 
1913
1993
  // src/components/dashboard/LiveTimelineCard.tsx
1914
- var import_react12 = require("react");
1994
+ var import_react13 = require("react");
1915
1995
  var import_jsx_runtime35 = require("react/jsx-runtime");
1916
1996
  var SHOWN = 3;
1917
1997
  function EventIcon({ type }) {
@@ -1953,7 +2033,7 @@ function EventRow({ e }) {
1953
2033
  ] });
1954
2034
  }
1955
2035
  function LiveTimelineCard({ card, ui }) {
1956
- const [expanded, setExpanded] = (0, import_react12.useState)(false);
2036
+ const [expanded, setExpanded] = (0, import_react13.useState)(false);
1957
2037
  const events = card.data.events;
1958
2038
  if (!events || events.length === 0) return null;
1959
2039
  const sorted = [...events].sort((a, b) => b.minute - a.minute);
@@ -1978,7 +2058,7 @@ function LiveTimelineCard({ card, ui }) {
1978
2058
  }
1979
2059
 
1980
2060
  // src/components/dashboard/MomentumWave.tsx
1981
- var import_react13 = require("react");
2061
+ var import_react14 = require("react");
1982
2062
  var import_jsx_runtime36 = require("react/jsx-runtime");
1983
2063
  var WIDTH = 270;
1984
2064
  var HEIGHT = 56;
@@ -2001,7 +2081,7 @@ function MomentumWave({
2001
2081
  }) {
2002
2082
  if (points.length === 0) return null;
2003
2083
  const { area, line, lastX, lastY } = buildPaths(points);
2004
- const clipId = (0, import_react13.useId)();
2084
+ const clipId = (0, import_react14.useId)();
2005
2085
  const topClipId = `${clipId}-top`;
2006
2086
  const bottomClipId = `${clipId}-bottom`;
2007
2087
  const topClass = clubIsHome ? "ss-w-fandash-live-mom-home" : "ss-w-fandash-live-mom-away";
@@ -2034,7 +2114,7 @@ function MomentumWave({
2034
2114
 
2035
2115
  // src/components/dashboard/LiveStatsCard.tsx
2036
2116
  var import_jsx_runtime37 = require("react/jsx-runtime");
2037
- function splitWidths(homeNum, awayNum) {
2117
+ function splitWidths2(homeNum, awayNum) {
2038
2118
  const total = homeNum + awayNum;
2039
2119
  if (total <= 0) return { home: "50%", away: "50%" };
2040
2120
  const homePct = Math.round(homeNum / total * 1e3) / 10;
@@ -2049,7 +2129,7 @@ function LiveStatsCard({ card }) {
2049
2129
  const awayValue = data.clubIsHome ? r.oppValue : r.clubValue;
2050
2130
  const homeNum = data.clubIsHome ? r.clubNum : r.oppNum;
2051
2131
  const awayNum = data.clubIsHome ? r.oppNum : r.clubNum;
2052
- const { home, away } = splitWidths(homeNum, awayNum);
2132
+ const { home, away } = splitWidths2(homeNum, awayNum);
2053
2133
  return /* @__PURE__ */ (0, import_jsx_runtime37.jsxs)("div", { className: "ss-w-fandash-live-stat", children: [
2054
2134
  /* @__PURE__ */ (0, import_jsx_runtime37.jsxs)("div", { className: "ss-w-fandash-live-stat-row", children: [
2055
2135
  /* @__PURE__ */ (0, import_jsx_runtime37.jsx)("span", { className: "ss-w-fandash-live-stat-value ss-w-fandash-live-stat-value--home", children: homeValue }),
@@ -2183,11 +2263,11 @@ function turnAnchor(messages) {
2183
2263
  }
2184
2264
  function MessageList({ controller, ui, slip, onDashboardTap }) {
2185
2265
  const { state, send } = controller;
2186
- const listRef = (0, import_react14.useRef)(null);
2187
- const endRef = (0, import_react14.useRef)(null);
2188
- const pinnedRef = (0, import_react14.useRef)(true);
2189
- const anchoredTurnRef = (0, import_react14.useRef)(null);
2190
- (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)(() => {
2191
2271
  const anchor = turnAnchor(state.messages);
2192
2272
  const list = listRef.current;
2193
2273
  if (anchor && anchor.turnId !== anchoredTurnRef.current) {
@@ -2242,10 +2322,10 @@ function MessageList({ controller, ui, slip, onDashboardTap }) {
2242
2322
  }
2243
2323
 
2244
2324
  // src/components/ChatInput.tsx
2245
- var import_react15 = require("react");
2325
+ var import_react16 = require("react");
2246
2326
  var import_jsx_runtime42 = require("react/jsx-runtime");
2247
2327
  function ChatInput({ placeholder, streaming, onSend, onStop, stopLabel = "Stop response", sendLabel = "Send message" }) {
2248
- const [value, setValue] = (0, import_react15.useState)("");
2328
+ const [value, setValue] = (0, import_react16.useState)("");
2249
2329
  const submit = () => {
2250
2330
  const t = value.trim();
2251
2331
  if (!t) return;
@@ -2268,8 +2348,8 @@ function ChatInput({ placeholder, streaming, onSend, onStop, stopLabel = "Stop r
2268
2348
  var import_jsx_runtime43 = require("react/jsx-runtime");
2269
2349
  var FULLSCREEN_QUERY = "(max-width: 639px)";
2270
2350
  function useIsModal() {
2271
- const [modal, setModal] = (0, import_react16.useState)(false);
2272
- (0, import_react16.useEffect)(() => {
2351
+ const [modal, setModal] = (0, import_react17.useState)(false);
2352
+ (0, import_react17.useEffect)(() => {
2273
2353
  if (typeof window === "undefined" || typeof window.matchMedia !== "function") return;
2274
2354
  const mql = window.matchMedia(FULLSCREEN_QUERY);
2275
2355
  const sync = () => setModal(mql.matches);
@@ -2282,7 +2362,7 @@ function useIsModal() {
2282
2362
  function ChatPanel({ controller, ui, slip, onDashboardTap }) {
2283
2363
  const { state, close, stop, send } = controller;
2284
2364
  const isModal = useIsModal();
2285
- (0, import_react16.useEffect)(() => {
2365
+ (0, import_react17.useEffect)(() => {
2286
2366
  const onKey = (e) => {
2287
2367
  if (e.key === "Escape") close();
2288
2368
  };
@@ -2772,35 +2852,76 @@ var WIDGET_CSS = `.ss-w-root { all: revert; }
2772
2852
  .ss-w-fandash-link-label { font-size: 12.5px; font-weight: 650; overflow-wrap: anywhere; }
2773
2853
  .ss-w-fandash-link-chevron { flex: none; width: 12px; height: 12px; color: var(--ss-w-accent); }
2774
2854
 
2775
- /* post-match result: full-time scoreboard -- team names flank the score, meta line underneath */
2776
- .ss-w-fandash-postmatch-scoreboard { display: flex; align-items: center; gap: 8px; }
2777
- .ss-w-fandash-postmatch-team { min-width: 0; flex: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-size: 13px; font-weight: 650; }
2778
- .ss-w-fandash-postmatch-team--away { text-align: right; }
2779
- .ss-w-fandash-postmatch-score { flex: none; font-size: 20px; font-weight: 750; font-variant-numeric: tabular-nums; color: var(--ss-w-ink); }
2780
- .ss-w-fandash-postmatch-sep { flex: none; font-size: 14px; font-weight: 700; color: var(--ss-w-faint); }
2781
- .ss-w-fandash-postmatch-meta { font-size: 11.5px; color: var(--ss-w-faint); }
2782
-
2783
- /* post-match goals: a plain list, the club's own goals picked out in the accent color */
2784
- .ss-w-fandash-postmatch-goal-list { list-style: none; display: flex; flex-direction: column; gap: 4px; }
2785
- .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); }
2786
2884
  .ss-w-fandash-postmatch-goal--club { color: var(--ss-w-ink); font-weight: 650; }
2787
2885
 
2788
- /* post-match stats: club value / stat label / opponent value, three columns per row */
2789
- .ss-w-fandash-postmatch-stat-rows { display: flex; flex-direction: column; gap: 5px; }
2790
- .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; }
2791
2893
  .ss-w-fandash-postmatch-stat-value { font-size: 13px; font-weight: 700; font-variant-numeric: tabular-nums; color: var(--ss-w-ink); }
2792
- .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; }
2793
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; }
2794
-
2795
- /* post-match top performer: player name left, rating pill right */
2796
- .ss-w-fandash-postmatch-performer-row { display: flex; align-items: center; justify-content: space-between; gap: 10px; }
2797
- .ss-w-fandash-postmatch-performer-name { font-size: 13px; font-weight: 650; overflow-wrap: anywhere; }
2798
- .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; }
2799
-
2800
- /* post-match next: the upcoming fixture teaser -- opponent left, when-label right */
2801
- .ss-w-fandash-postmatch-next-row { display: flex; align-items: center; justify-content: space-between; gap: 10px; }
2802
- .ss-w-fandash-postmatch-next-opponent { font-size: 13px; font-weight: 650; overflow-wrap: anywhere; }
2803
- .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; }
2804
2925
 
2805
2926
  /* resting next: the between-matches fixture teaser -- when-label left, competition badge right */
2806
2927
  .ss-w-fandash-resting-next-row { display: flex; align-items: center; justify-content: space-between; gap: 10px; }
@@ -2927,11 +3048,11 @@ function injectStyles(root) {
2927
3048
  function StatsWidget(props) {
2928
3049
  const injectedClient = "client" in props ? props.client : void 0;
2929
3050
  const cfg = props.config;
2930
- const built = (0, import_react17.useMemo)(() => buildClient(props), [injectedClient, cfg?.operatorId, cfg?.publicKey, cfg?.baseUrl]);
2931
- const ui = (0, import_react17.useMemo)(() => normalizeUi(props.config ?? { operatorId: "", publicKey: "" }), [props.config]);
2932
- 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);
2933
3054
  const controller = useChatStream(built?.conversation ?? emptyConversation(), ui.starters, built ? ui.greetingMessage : void 0);
2934
- const slip = (0, import_react17.useMemo)(
3055
+ const slip = (0, import_react18.useMemo)(
2935
3056
  () => ({
2936
3057
  onAddToSlip: props.config?.onAddToSlip,
2937
3058
  conversationId: controller.state.conversationId ?? void 0,
@@ -2944,11 +3065,11 @@ function StatsWidget(props) {
2944
3065
  }),
2945
3066
  [props.config?.onAddToSlip, controller.state.conversationId, built]
2946
3067
  );
2947
- const onDashboardTap = (0, import_react17.useMemo)(
3068
+ const onDashboardTap = (0, import_react18.useMemo)(
2948
3069
  () => built ? createDashboardTapSignal((input) => built.conversation.sendSignal(input)) : void 0,
2949
3070
  [built]
2950
3071
  );
2951
- (0, import_react17.useEffect)(() => {
3072
+ (0, import_react18.useEffect)(() => {
2952
3073
  if (!built || !ui.injectStyles) return;
2953
3074
  const node = rootRef.current?.getRootNode();
2954
3075
  if (node) injectStyles(node);