@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 +8 -0
- package/dist/index.cjs +195 -78
- 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 +176 -59
- 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,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
|
|
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,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.
|
|
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
|
-
const leftValue =
|
|
1732
|
-
const rightValue =
|
|
1733
|
-
|
|
1734
|
-
|
|
1735
|
-
|
|
1736
|
-
|
|
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-
|
|
1750
|
-
/* @__PURE__ */ (0, import_jsx_runtime28.jsx)("span", { className: "ss-w-fandash-postmatch-
|
|
1751
|
-
/* @__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 })
|
|
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
|
|
1903
|
+
var import_react12 = require("react");
|
|
1828
1904
|
|
|
1829
1905
|
// src/useLiveClock.ts
|
|
1830
|
-
var
|
|
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,
|
|
1915
|
+
const [displayMinute, setDisplayMinute] = (0, import_react11.useState)(
|
|
1840
1916
|
() => computeDisplayMinute(minute, clockRunning, receivedAtMs)
|
|
1841
1917
|
);
|
|
1842
|
-
(0,
|
|
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
|
|
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,
|
|
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:
|
|
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,
|
|
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
|
|
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,
|
|
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
|
|
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,
|
|
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
|
|
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 } =
|
|
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,
|
|
2191
|
-
const endRef = (0,
|
|
2192
|
-
const pinnedRef = (0,
|
|
2193
|
-
const anchoredTurnRef = (0,
|
|
2194
|
-
(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)(() => {
|
|
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
|
|
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,
|
|
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,
|
|
2276
|
-
(0,
|
|
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,
|
|
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
|
|
2780
|
-
|
|
2781
|
-
|
|
2782
|
-
|
|
2783
|
-
.ss-w-fandash-postmatch-
|
|
2784
|
-
.ss-w-fandash-postmatch-
|
|
2785
|
-
.ss-w-fandash-postmatch-
|
|
2786
|
-
|
|
2787
|
-
|
|
2788
|
-
.ss-w-fandash-postmatch-
|
|
2789
|
-
.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); }
|
|
2790
2884
|
.ss-w-fandash-postmatch-goal--club { color: var(--ss-w-ink); font-weight: 650; }
|
|
2791
2885
|
|
|
2792
|
-
/* post-match stats:
|
|
2793
|
-
|
|
2794
|
-
|
|
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-
|
|
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
|
-
|
|
2800
|
-
.ss-w-fandash-postmatch-
|
|
2801
|
-
.ss-w-fandash-postmatch-
|
|
2802
|
-
.ss-w-fandash-postmatch-
|
|
2803
|
-
|
|
2804
|
-
|
|
2805
|
-
.ss-w-fandash-postmatch-
|
|
2806
|
-
|
|
2807
|
-
|
|
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,
|
|
2935
|
-
const ui = (0,
|
|
2936
|
-
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);
|
|
2937
3054
|
const controller = useChatStream(built?.conversation ?? emptyConversation(), ui.starters, built ? ui.greetingMessage : void 0);
|
|
2938
|
-
const slip = (0,
|
|
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,
|
|
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,
|
|
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);
|