@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 +16 -0
- package/dist/index.cjs +197 -76
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +178 -57
- package/dist/index.js.map +1 -1
- package/dist/styles.css +66 -25
- package/package.json +2 -2
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
|
|
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
|
|
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
|
|
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
|
-
|
|
1699
|
-
|
|
1700
|
-
/* @__PURE__ */ (0, import_jsx_runtime25.jsx)("span", { className:
|
|
1701
|
-
|
|
1702
|
-
|
|
1703
|
-
/* @__PURE__ */ (0, import_jsx_runtime25.
|
|
1704
|
-
|
|
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.
|
|
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:
|
|
1731
|
-
|
|
1732
|
-
|
|
1733
|
-
|
|
1734
|
-
|
|
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-
|
|
1746
|
-
/* @__PURE__ */ (0, import_jsx_runtime28.jsx)("span", { className: "ss-w-fandash-postmatch-
|
|
1747
|
-
/* @__PURE__ */ (0, import_jsx_runtime28.jsx)("span", { className: "ss-w-fandash-postmatch-
|
|
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
|
|
1903
|
+
var import_react12 = require("react");
|
|
1824
1904
|
|
|
1825
1905
|
// src/useLiveClock.ts
|
|
1826
|
-
var
|
|
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,
|
|
1915
|
+
const [displayMinute, setDisplayMinute] = (0, import_react11.useState)(
|
|
1836
1916
|
() => computeDisplayMinute(minute, clockRunning, receivedAtMs)
|
|
1837
1917
|
);
|
|
1838
|
-
(0,
|
|
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
|
|
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,
|
|
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:
|
|
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,
|
|
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
|
|
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,
|
|
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
|
|
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,
|
|
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
|
|
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 } =
|
|
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,
|
|
2187
|
-
const endRef = (0,
|
|
2188
|
-
const pinnedRef = (0,
|
|
2189
|
-
const anchoredTurnRef = (0,
|
|
2190
|
-
(0,
|
|
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
|
|
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,
|
|
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,
|
|
2272
|
-
(0,
|
|
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,
|
|
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
|
|
2776
|
-
|
|
2777
|
-
|
|
2778
|
-
|
|
2779
|
-
.ss-w-fandash-postmatch-
|
|
2780
|
-
.ss-w-fandash-postmatch-
|
|
2781
|
-
.ss-w-fandash-postmatch-
|
|
2782
|
-
|
|
2783
|
-
|
|
2784
|
-
.ss-w-fandash-postmatch-
|
|
2785
|
-
.ss-w-fandash-postmatch-
|
|
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:
|
|
2789
|
-
|
|
2790
|
-
|
|
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-
|
|
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
|
-
|
|
2796
|
-
.ss-w-fandash-postmatch-
|
|
2797
|
-
.ss-w-fandash-postmatch-
|
|
2798
|
-
.ss-w-fandash-postmatch-
|
|
2799
|
-
|
|
2800
|
-
|
|
2801
|
-
.ss-w-fandash-postmatch-
|
|
2802
|
-
|
|
2803
|
-
|
|
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,
|
|
2931
|
-
const ui = (0,
|
|
2932
|
-
const rootRef = (0,
|
|
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,
|
|
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,
|
|
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,
|
|
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);
|