@sensiblestats/widget-react 0.27.0 → 0.29.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +56 -0
- package/dist/index.cjs +530 -174
- 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 +529 -173
- package/dist/index.js.map +1 -1
- package/dist/styles.css +98 -35
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// src/StatsWidget.tsx
|
|
2
|
-
import { useEffect as
|
|
2
|
+
import { useEffect as useEffect8, useMemo as useMemo2, useRef as useRef5 } from "react";
|
|
3
3
|
import { StatsWidgetClient } from "@sensiblestats/widget-sdk";
|
|
4
4
|
|
|
5
5
|
// src/kickoff.ts
|
|
@@ -225,7 +225,7 @@ function themeAttrs(ui) {
|
|
|
225
225
|
}
|
|
226
226
|
|
|
227
227
|
// src/useChatStream.ts
|
|
228
|
-
import { useCallback, useEffect as useEffect2, useMemo, useReducer, useRef as useRef2 } from "react";
|
|
228
|
+
import { useCallback, useEffect as useEffect2, useMemo, useReducer as useReducer2, useRef as useRef2 } from "react";
|
|
229
229
|
import { WidgetSdkError as WidgetSdkError2 } from "@sensiblestats/widget-sdk";
|
|
230
230
|
|
|
231
231
|
// src/reducer.ts
|
|
@@ -571,35 +571,90 @@ function reducer(state, action) {
|
|
|
571
571
|
return {
|
|
572
572
|
...state,
|
|
573
573
|
messages: state.messages.map(
|
|
574
|
-
(m) => m.role === "assistant" && m.id === action.messageId && m.clubFanDashboard ? { ...m, clubFanDashboard: { ...m.clubFanDashboard, cards: action.cards } } : m
|
|
574
|
+
(m) => m.role === "assistant" && m.id === action.messageId && m.clubFanDashboard ? { ...m, clubFanDashboard: { ...m.clubFanDashboard, cards: action.cards, greeting: action.greeting ?? m.clubFanDashboard.greeting } } : m
|
|
575
|
+
)
|
|
576
|
+
};
|
|
577
|
+
case "LIVE_TRANSITION":
|
|
578
|
+
return {
|
|
579
|
+
...state,
|
|
580
|
+
messages: state.messages.map(
|
|
581
|
+
(m) => m.role === "assistant" && m.id === action.messageId && m.clubFanDashboard ? { ...m, clubFanDashboard: action.dashboard } : m
|
|
575
582
|
)
|
|
576
583
|
};
|
|
577
584
|
}
|
|
578
585
|
}
|
|
579
586
|
|
|
580
587
|
// src/useLiveDashboardPoll.ts
|
|
581
|
-
import { useEffect, useRef } from "react";
|
|
588
|
+
import { useEffect, useReducer, useRef } from "react";
|
|
582
589
|
var POLL_INTERVAL_MS = 6e4;
|
|
583
|
-
|
|
590
|
+
var LEAD_MS = 12e4;
|
|
591
|
+
var GIVE_UP_MS = 30 * 6e4;
|
|
592
|
+
function extractKickoffMs(dashboard) {
|
|
593
|
+
const countdown = dashboard.cards.find((c) => c.cardType === "countdown");
|
|
594
|
+
if (countdown) return countdown.data.kickoffUnix * 1e3;
|
|
595
|
+
for (const c of dashboard.cards) {
|
|
596
|
+
const d = c.data;
|
|
597
|
+
if (typeof d?.kickoffUnix === "number") return d.kickoffUnix * 1e3;
|
|
598
|
+
}
|
|
599
|
+
return null;
|
|
600
|
+
}
|
|
601
|
+
function findAnchor(messages) {
|
|
602
|
+
const now = Date.now();
|
|
584
603
|
for (const m of messages.slice().reverse()) {
|
|
585
|
-
if (m.role
|
|
604
|
+
if (m.role !== "assistant" || !m.clubFanDashboard) continue;
|
|
605
|
+
const { phase } = m.clubFanDashboard;
|
|
606
|
+
if (phase === "live") return { messageId: m.id, kind: "live" };
|
|
607
|
+
if (phase === "preMatch") {
|
|
608
|
+
const kickoffMs = extractKickoffMs(m.clubFanDashboard);
|
|
609
|
+
if (kickoffMs === null) continue;
|
|
610
|
+
if (now >= kickoffMs - LEAD_MS && now <= kickoffMs + GIVE_UP_MS) {
|
|
611
|
+
return { messageId: m.id, kind: "preMatch", kickoffMs };
|
|
612
|
+
}
|
|
613
|
+
}
|
|
586
614
|
}
|
|
587
615
|
return null;
|
|
588
616
|
}
|
|
617
|
+
function findEarliestWakeupMs(messages) {
|
|
618
|
+
const now = Date.now();
|
|
619
|
+
let earliest = null;
|
|
620
|
+
for (const m of messages) {
|
|
621
|
+
if (m.role !== "assistant" || m.clubFanDashboard?.phase !== "preMatch") continue;
|
|
622
|
+
const kickoffMs = extractKickoffMs(m.clubFanDashboard);
|
|
623
|
+
if (kickoffMs === null) continue;
|
|
624
|
+
const wakeAt = kickoffMs - LEAD_MS;
|
|
625
|
+
if (wakeAt > now && (earliest === null || wakeAt < earliest)) earliest = wakeAt;
|
|
626
|
+
}
|
|
627
|
+
return earliest;
|
|
628
|
+
}
|
|
589
629
|
function useLiveDashboardPoll({ messages, dispatch, getLiveDashboard }) {
|
|
590
|
-
const
|
|
630
|
+
const [, forceUpdate] = useReducer((x) => x + 1, 0);
|
|
631
|
+
const anchor = findAnchor(messages);
|
|
632
|
+
const anchorRef = useRef(anchor);
|
|
633
|
+
anchorRef.current = anchor;
|
|
591
634
|
const inFlightRef = useRef(false);
|
|
635
|
+
const anchorKey = anchor ? `${anchor.messageId}:${anchor.kind}` : null;
|
|
592
636
|
useEffect(() => {
|
|
593
|
-
if (!
|
|
637
|
+
if (!anchorKey || !anchorRef.current) return;
|
|
594
638
|
const tick = async () => {
|
|
595
639
|
if (inFlightRef.current) return;
|
|
596
640
|
inFlightRef.current = true;
|
|
597
641
|
try {
|
|
598
642
|
const payload = await getLiveDashboard();
|
|
599
|
-
|
|
600
|
-
|
|
643
|
+
const current = anchorRef.current;
|
|
644
|
+
if (!current) return;
|
|
645
|
+
if (payload?.phase === "live") {
|
|
646
|
+
if (current.kind === "preMatch") {
|
|
647
|
+
dispatch({ kind: "LIVE_TRANSITION", messageId: current.messageId, dashboard: payload });
|
|
648
|
+
window.clearInterval(id);
|
|
649
|
+
} else {
|
|
650
|
+
dispatch({ kind: "LIVE_REFRESH", messageId: current.messageId, cards: payload.cards, greeting: payload.greeting });
|
|
651
|
+
}
|
|
601
652
|
} else {
|
|
602
|
-
|
|
653
|
+
if (current.kind === "live") {
|
|
654
|
+
window.clearInterval(id);
|
|
655
|
+
} else if (Date.now() > current.kickoffMs + GIVE_UP_MS) {
|
|
656
|
+
window.clearInterval(id);
|
|
657
|
+
}
|
|
603
658
|
}
|
|
604
659
|
} catch {
|
|
605
660
|
} finally {
|
|
@@ -610,14 +665,23 @@ function useLiveDashboardPoll({ messages, dispatch, getLiveDashboard }) {
|
|
|
610
665
|
void tick();
|
|
611
666
|
}, POLL_INTERVAL_MS);
|
|
612
667
|
return () => window.clearInterval(id);
|
|
613
|
-
}, [
|
|
668
|
+
}, [anchorKey, dispatch, getLiveDashboard]);
|
|
669
|
+
useEffect(() => {
|
|
670
|
+
if (anchorKey !== null) return;
|
|
671
|
+
const wakeAt = findEarliestWakeupMs(messages);
|
|
672
|
+
if (wakeAt === null) return;
|
|
673
|
+
const delay = wakeAt - Date.now();
|
|
674
|
+
if (delay <= 0) return;
|
|
675
|
+
const id = window.setTimeout(forceUpdate, delay);
|
|
676
|
+
return () => window.clearTimeout(id);
|
|
677
|
+
}, [anchorKey, messages, forceUpdate]);
|
|
614
678
|
}
|
|
615
679
|
|
|
616
680
|
// src/useChatStream.ts
|
|
617
681
|
var STREAM_FALLBACK_TIMEOUT_MS = 3e4;
|
|
618
682
|
function useChatStream(conversation, starterTexts, greetingMessage) {
|
|
619
683
|
const seed = useMemo(() => initialState(starterTexts.map(chipFromText)), []);
|
|
620
|
-
const [state, dispatch] =
|
|
684
|
+
const [state, dispatch] = useReducer2(reducer, seed);
|
|
621
685
|
const idRef = useRef2(0);
|
|
622
686
|
const handleRef = useRef2(null);
|
|
623
687
|
const fallbackTimerRef = useRef2(null);
|
|
@@ -775,7 +839,7 @@ function ChatFab({ label, greeting, popupText, onOpen, onDismissPopup, dismissLa
|
|
|
775
839
|
}
|
|
776
840
|
|
|
777
841
|
// src/components/ChatPanel.tsx
|
|
778
|
-
import { useEffect as
|
|
842
|
+
import { useEffect as useEffect7, useState as useState11 } from "react";
|
|
779
843
|
|
|
780
844
|
// src/components/ChatHeader.tsx
|
|
781
845
|
import { jsx as jsx4, jsxs as jsxs3 } from "react/jsx-runtime";
|
|
@@ -788,7 +852,7 @@ function ChatHeader({ onClose, closeLabel = "Close chat" }) {
|
|
|
788
852
|
}
|
|
789
853
|
|
|
790
854
|
// src/components/MessageList.tsx
|
|
791
|
-
import { useEffect as
|
|
855
|
+
import { useEffect as useEffect6, useRef as useRef4 } from "react";
|
|
792
856
|
|
|
793
857
|
// src/components/Markdown.tsx
|
|
794
858
|
import ReactMarkdown from "react-markdown";
|
|
@@ -1098,22 +1162,44 @@ function ReasonStrip({ it, ui }) {
|
|
|
1098
1162
|
) : null
|
|
1099
1163
|
] });
|
|
1100
1164
|
}
|
|
1165
|
+
function CardFixture({ it }) {
|
|
1166
|
+
if (it.homeTeamName && it.awayTeamName) {
|
|
1167
|
+
return /* @__PURE__ */ jsxs7(Fragment, { children: [
|
|
1168
|
+
/* @__PURE__ */ jsx11(Crest2, { kind: "team", url: it.homeCrest, name: it.homeTeamName }),
|
|
1169
|
+
/* @__PURE__ */ jsx11("span", { className: "ss-w-bi-team", children: it.homeTeamName }),
|
|
1170
|
+
/* @__PURE__ */ jsx11("span", { className: "ss-w-bi-vs", "aria-hidden": "true", children: "vs" }),
|
|
1171
|
+
/* @__PURE__ */ jsx11("span", { className: "ss-w-bi-team", children: it.awayTeamName }),
|
|
1172
|
+
/* @__PURE__ */ jsx11(Crest2, { kind: "team", url: it.awayCrest, name: it.awayTeamName })
|
|
1173
|
+
] });
|
|
1174
|
+
}
|
|
1175
|
+
if (it.fixture) return /* @__PURE__ */ jsx11("span", { className: "ss-w-bi-fixture-txt", children: it.fixture });
|
|
1176
|
+
if (it.competition) {
|
|
1177
|
+
return /* @__PURE__ */ jsxs7(Fragment, { children: [
|
|
1178
|
+
/* @__PURE__ */ jsx11(Crest2, { kind: "comp", url: it.competitionCrest, name: it.competition }),
|
|
1179
|
+
/* @__PURE__ */ jsx11("span", { className: "ss-w-bi-comp", children: it.competition })
|
|
1180
|
+
] });
|
|
1181
|
+
}
|
|
1182
|
+
return null;
|
|
1183
|
+
}
|
|
1101
1184
|
function InsightCard({ it, ui, slip }) {
|
|
1102
1185
|
const live = it.isLive || it.phase === "live";
|
|
1103
1186
|
const kickoff = it.kickoffUtc && ui?.bettingKickoff ? ui.bettingKickoff(it.kickoffUtc) : void 0;
|
|
1104
1187
|
const hasNamedTeams = !!it.homeTeamName && !!it.awayTeamName;
|
|
1105
|
-
const
|
|
1188
|
+
const leftIsFixture = hasNamedTeams || !!it.fixture;
|
|
1189
|
+
const compInMeta = leftIsFixture && !!it.competition;
|
|
1190
|
+
const showHeader = leftIsFixture || !!it.competition || live || !!kickoff;
|
|
1106
1191
|
return /* @__PURE__ */ jsxs7("div", { className: "ss-w-binsight", children: [
|
|
1107
1192
|
showHeader ? /* @__PURE__ */ jsxs7("div", { className: "ss-w-bi-header", children: [
|
|
1108
|
-
/* @__PURE__ */ jsx11("span", { className: "ss-w-bi-header-left", children:
|
|
1109
|
-
/* @__PURE__ */ jsx11(Crest2, { kind: "comp", url: it.competitionCrest, name: it.competition }),
|
|
1110
|
-
/* @__PURE__ */ jsx11("span", { className: "ss-w-bi-comp", children: it.competition })
|
|
1111
|
-
] }) : null }),
|
|
1193
|
+
/* @__PURE__ */ jsx11("span", { className: "ss-w-bi-header-left", children: /* @__PURE__ */ jsx11(CardFixture, { it }) }),
|
|
1112
1194
|
/* @__PURE__ */ jsxs7("span", { className: "ss-w-bi-header-right", children: [
|
|
1113
1195
|
live ? /* @__PURE__ */ jsxs7("span", { className: "ss-w-bi-live", children: [
|
|
1114
1196
|
/* @__PURE__ */ jsx11("span", { className: "ss-w-bi-live-dot", "aria-hidden": "true" }),
|
|
1115
1197
|
ui?.liveLabel ?? "Live"
|
|
1116
1198
|
] }) : null,
|
|
1199
|
+
compInMeta ? /* @__PURE__ */ jsxs7("span", { className: "ss-w-bi-comp-meta", children: [
|
|
1200
|
+
/* @__PURE__ */ jsx11(Crest2, { kind: "comp", url: it.competitionCrest, name: it.competition }),
|
|
1201
|
+
/* @__PURE__ */ jsx11("span", { className: "ss-w-bi-comp", children: it.competition })
|
|
1202
|
+
] }) : null,
|
|
1117
1203
|
kickoff ? /* @__PURE__ */ jsxs7("span", { className: "ss-w-bi-kick", children: [
|
|
1118
1204
|
kickoff.dayLabel,
|
|
1119
1205
|
" ",
|
|
@@ -1121,28 +1207,22 @@ function InsightCard({ it, ui, slip }) {
|
|
|
1121
1207
|
] }) : null
|
|
1122
1208
|
] })
|
|
1123
1209
|
] }) : null,
|
|
1124
|
-
/* @__PURE__ */ jsxs7("div", { className: "ss-w-bi-
|
|
1125
|
-
/* @__PURE__ */
|
|
1126
|
-
/* @__PURE__ */ jsx11(
|
|
1127
|
-
/* @__PURE__ */
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
] })
|
|
1132
|
-
/* @__PURE__ */ jsxs7("
|
|
1133
|
-
/* @__PURE__ */
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
it.odds ? /* @__PURE__ */ jsxs7("span", { className: "ss-w-bi-odds", children: [
|
|
1139
|
-
/* @__PURE__ */ jsx11("b", { children: it.odds }),
|
|
1140
|
-
/* @__PURE__ */ jsx11(OddsMovementBadge, { previous: it.previousOdds, current: it.odds, direction: it.movementDirection, ui }),
|
|
1141
|
-
it.bookmaker ? /* @__PURE__ */ jsx11("span", { className: "ss-w-bi-book", children: it.bookmaker }) : null
|
|
1142
|
-
] }) : null,
|
|
1143
|
-
/* @__PURE__ */ jsx11(AddToSlipButton, { it, slip, ui })
|
|
1144
|
-
] })
|
|
1210
|
+
/* @__PURE__ */ jsxs7("div", { className: "ss-w-bi-action-row", children: [
|
|
1211
|
+
/* @__PURE__ */ jsxs7("span", { className: "ss-w-bi-pick", children: [
|
|
1212
|
+
/* @__PURE__ */ jsx11("span", { className: "ss-w-bi-market", children: it.market }),
|
|
1213
|
+
it.selection ? /* @__PURE__ */ jsxs7(Fragment, { children: [
|
|
1214
|
+
/* @__PURE__ */ jsx11("span", { className: "ss-w-bi-pick-dot", "aria-hidden": "true", children: "\xB7" }),
|
|
1215
|
+
/* @__PURE__ */ jsx11("span", { className: "ss-w-bi-sel", children: it.selection })
|
|
1216
|
+
] }) : null
|
|
1217
|
+
] }),
|
|
1218
|
+
it.odds ? /* @__PURE__ */ jsxs7("span", { className: "ss-w-bi-odds", children: [
|
|
1219
|
+
/* @__PURE__ */ jsx11("b", { children: it.odds }),
|
|
1220
|
+
/* @__PURE__ */ jsx11(OddsMovementBadge, { previous: it.previousOdds, current: it.odds, direction: it.movementDirection, ui }),
|
|
1221
|
+
it.bookmaker ? /* @__PURE__ */ jsx11("span", { className: "ss-w-bi-book", children: it.bookmaker }) : null
|
|
1222
|
+
] }) : null,
|
|
1223
|
+
/* @__PURE__ */ jsx11(AddToSlipButton, { it, slip, ui })
|
|
1145
1224
|
] }),
|
|
1225
|
+
it.isMarketFloor ? /* @__PURE__ */ jsx11("span", { className: "ss-w-bi-floor", children: ui?.marketFloorLabel ?? "Market price \xB7 not a value pick" }) : null,
|
|
1146
1226
|
/* @__PURE__ */ jsx11(ReasonStrip, { it, ui }),
|
|
1147
1227
|
it.disclaimer ? /* @__PURE__ */ jsx11("p", { className: "ss-w-bi-disc", children: it.disclaimer }) : null
|
|
1148
1228
|
] });
|
|
@@ -1702,139 +1782,352 @@ function RestingFormCard({ card }) {
|
|
|
1702
1782
|
] });
|
|
1703
1783
|
}
|
|
1704
1784
|
|
|
1785
|
+
// src/components/dashboard/LiveScoreCard.tsx
|
|
1786
|
+
import { useRef as useRef3, useState as useState8 } from "react";
|
|
1787
|
+
|
|
1788
|
+
// src/useLiveClock.ts
|
|
1789
|
+
import { useEffect as useEffect5, useState as useState7 } from "react";
|
|
1790
|
+
var TICK_MS = 6e4;
|
|
1791
|
+
var MAX_LIVE_TICK_MINUTES = 15;
|
|
1792
|
+
function computeDisplayMinute(minute, clockRunning, receivedAtMs) {
|
|
1793
|
+
if (!clockRunning) return minute;
|
|
1794
|
+
const elapsedMinutes = Math.floor((Date.now() - receivedAtMs) / TICK_MS);
|
|
1795
|
+
return minute + Math.min(Math.max(0, elapsedMinutes), MAX_LIVE_TICK_MINUTES);
|
|
1796
|
+
}
|
|
1797
|
+
function useLiveClock(minute, clockRunning, receivedAtMs) {
|
|
1798
|
+
const [displayMinute, setDisplayMinute] = useState7(
|
|
1799
|
+
() => computeDisplayMinute(minute, clockRunning, receivedAtMs)
|
|
1800
|
+
);
|
|
1801
|
+
useEffect5(() => {
|
|
1802
|
+
setDisplayMinute(computeDisplayMinute(minute, clockRunning, receivedAtMs));
|
|
1803
|
+
if (!clockRunning) return;
|
|
1804
|
+
const id = window.setInterval(() => {
|
|
1805
|
+
setDisplayMinute(computeDisplayMinute(minute, clockRunning, receivedAtMs));
|
|
1806
|
+
}, TICK_MS);
|
|
1807
|
+
return () => window.clearInterval(id);
|
|
1808
|
+
}, [minute, clockRunning, receivedAtMs]);
|
|
1809
|
+
return displayMinute;
|
|
1810
|
+
}
|
|
1811
|
+
|
|
1705
1812
|
// src/components/dashboard/LiveScoreCard.tsx
|
|
1706
1813
|
import { jsx as jsx34, jsxs as jsxs30 } from "react/jsx-runtime";
|
|
1707
|
-
function
|
|
1814
|
+
function initials3(name) {
|
|
1815
|
+
return name.trim().slice(0, 3).toUpperCase();
|
|
1816
|
+
}
|
|
1817
|
+
function LiveCrest({ url, name, side }) {
|
|
1818
|
+
const [failed, setFailed] = useState8(false);
|
|
1819
|
+
const cls = `ss-w-fandash-live-crest ss-w-fandash-live-crest--${side}`;
|
|
1820
|
+
if (url && !failed) {
|
|
1821
|
+
return /* @__PURE__ */ jsx34("img", { className: cls, src: url, alt: "", "aria-hidden": "true", onError: () => setFailed(true) });
|
|
1822
|
+
}
|
|
1823
|
+
return /* @__PURE__ */ jsx34("span", { className: `${cls} ss-w-fandash-live-crest--fallback`, "aria-hidden": "true", children: initials3(name) });
|
|
1824
|
+
}
|
|
1825
|
+
function scorersLine(goalEvents) {
|
|
1826
|
+
if (!goalEvents || goalEvents.length === 0) return null;
|
|
1827
|
+
const named = goalEvents.filter((e) => e.type === "goal" && e.player);
|
|
1828
|
+
if (named.length === 0) return null;
|
|
1829
|
+
return named.slice().sort((a, b) => a.minute - b.minute).map((e) => `${e.player} ${e.minuteLabel}`).join(", ");
|
|
1830
|
+
}
|
|
1831
|
+
function LiveScoreCard({
|
|
1832
|
+
card,
|
|
1833
|
+
goalEvents
|
|
1834
|
+
}) {
|
|
1708
1835
|
const { data } = card;
|
|
1836
|
+
const seedKey = `${data.minute}:${data.homeScore}:${data.awayScore}`;
|
|
1837
|
+
const seedRef = useRef3(null);
|
|
1838
|
+
if (!seedRef.current || seedRef.current.key !== seedKey) {
|
|
1839
|
+
seedRef.current = { key: seedKey, receivedAtMs: Date.now() };
|
|
1840
|
+
}
|
|
1841
|
+
const displayMinute = useLiveClock(data.minute, data.clockRunning, seedRef.current.receivedAtMs);
|
|
1842
|
+
const minuteSuffix = data.minuteLabel.replace(/^\d+/, "");
|
|
1843
|
+
const scorers = scorersLine(goalEvents);
|
|
1709
1844
|
return /* @__PURE__ */ jsxs30("div", { className: "ss-w-fandash-card ss-w-fandash-live-score", children: [
|
|
1710
1845
|
card.title ? /* @__PURE__ */ jsx34("span", { className: "ss-w-fandash-eyebrow", children: card.title }) : null,
|
|
1711
|
-
/* @__PURE__ */ jsxs30("div", { className: "ss-w-fandash-
|
|
1712
|
-
/* @__PURE__ */
|
|
1713
|
-
|
|
1714
|
-
|
|
1715
|
-
|
|
1716
|
-
/* @__PURE__ */ jsx34("span", { className: "ss-w-fandash-
|
|
1717
|
-
] }),
|
|
1718
|
-
/* @__PURE__ */ jsxs30("div", { className: "ss-w-fandash-live-meta", children: [
|
|
1719
|
-
/* @__PURE__ */ jsx34("span", { className: "ss-w-fandash-live-minute", children: data.minuteLabel }),
|
|
1720
|
-
/* @__PURE__ */ jsx34("span", { className: "ss-w-fandash-live-status", children: data.statusLabel }),
|
|
1846
|
+
/* @__PURE__ */ jsxs30("div", { className: "ss-w-fandash-live-top", children: [
|
|
1847
|
+
/* @__PURE__ */ jsxs30("span", { className: "ss-w-fandash-live-pill", children: [
|
|
1848
|
+
/* @__PURE__ */ jsx34("span", { className: "ss-w-fandash-live-dot", "aria-hidden": "true" }),
|
|
1849
|
+
data.statusLabel
|
|
1850
|
+
] }),
|
|
1851
|
+
/* @__PURE__ */ jsx34("span", { className: "ss-w-fandash-live-minute", children: `${displayMinute}${minuteSuffix}` }),
|
|
1721
1852
|
data.competition ? /* @__PURE__ */ jsx34("span", { className: "ss-w-fandash-live-competition", children: data.competition }) : null
|
|
1722
|
-
] })
|
|
1853
|
+
] }),
|
|
1854
|
+
/* @__PURE__ */ jsxs30("div", { className: "ss-w-fandash-live-main", children: [
|
|
1855
|
+
/* @__PURE__ */ jsxs30("div", { className: "ss-w-fandash-live-side ss-w-fandash-live-side--home", children: [
|
|
1856
|
+
/* @__PURE__ */ jsx34(LiveCrest, { url: data.homeCrestUrl, name: data.homeName, side: "home" }),
|
|
1857
|
+
/* @__PURE__ */ jsx34("span", { className: "ss-w-fandash-live-team-name", children: data.homeName })
|
|
1858
|
+
] }),
|
|
1859
|
+
/* @__PURE__ */ jsxs30("div", { className: "ss-w-fandash-live-score-main", children: [
|
|
1860
|
+
/* @__PURE__ */ jsx34("span", { className: "ss-w-fandash-live-score-value", children: data.homeScore }),
|
|
1861
|
+
/* @__PURE__ */ jsx34("span", { className: "ss-w-fandash-live-score-sep", children: "\u2013" }),
|
|
1862
|
+
/* @__PURE__ */ jsx34("span", { className: "ss-w-fandash-live-score-value", children: data.awayScore })
|
|
1863
|
+
] }),
|
|
1864
|
+
/* @__PURE__ */ jsxs30("div", { className: "ss-w-fandash-live-side ss-w-fandash-live-side--away", children: [
|
|
1865
|
+
/* @__PURE__ */ jsx34(LiveCrest, { url: data.awayCrestUrl, name: data.awayName, side: "away" }),
|
|
1866
|
+
/* @__PURE__ */ jsx34("span", { className: "ss-w-fandash-live-team-name", children: data.awayName })
|
|
1867
|
+
] })
|
|
1868
|
+
] }),
|
|
1869
|
+
scorers ? /* @__PURE__ */ jsxs30("div", { className: "ss-w-fandash-live-scorers", children: [
|
|
1870
|
+
/* @__PURE__ */ jsx34("span", { className: "ss-w-fandash-live-scorers-icon", "aria-hidden": "true", children: "\u26BD" }),
|
|
1871
|
+
scorers
|
|
1872
|
+
] }) : null
|
|
1723
1873
|
] });
|
|
1724
1874
|
}
|
|
1725
1875
|
|
|
1726
1876
|
// src/components/dashboard/LiveTimelineCard.tsx
|
|
1877
|
+
import { useState as useState9 } from "react";
|
|
1727
1878
|
import { jsx as jsx35, jsxs as jsxs31 } from "react/jsx-runtime";
|
|
1728
|
-
|
|
1879
|
+
var SHOWN = 3;
|
|
1880
|
+
function EventIcon({ type }) {
|
|
1881
|
+
if (type === "goal") {
|
|
1882
|
+
return /* @__PURE__ */ jsx35("span", { className: "ss-w-fandash-live-tl-ico ss-w-fandash-live-tl-ico--goal", "aria-hidden": "true", children: /* @__PURE__ */ jsx35("span", { className: "ss-w-fandash-live-tl-ball" }) });
|
|
1883
|
+
}
|
|
1884
|
+
if (type === "yellow" || type === "red") {
|
|
1885
|
+
return /* @__PURE__ */ jsx35("span", { className: "ss-w-fandash-live-tl-ico", "aria-hidden": "true", children: /* @__PURE__ */ jsx35("span", { className: `ss-w-fandash-live-tl-card ss-w-fandash-live-tl-card--${type}` }) });
|
|
1886
|
+
}
|
|
1887
|
+
if (type === "sub") {
|
|
1888
|
+
return /* @__PURE__ */ jsx35("span", { className: "ss-w-fandash-live-tl-ico", "aria-hidden": "true", children: /* @__PURE__ */ jsx35("span", { className: "ss-w-fandash-live-tl-subarrows", children: "\u21C5" }) });
|
|
1889
|
+
}
|
|
1890
|
+
return /* @__PURE__ */ jsx35("span", { className: "ss-w-fandash-live-tl-ico", "aria-hidden": "true", children: /* @__PURE__ */ jsx35("span", { className: "ss-w-fandash-live-tl-dot" }) });
|
|
1891
|
+
}
|
|
1892
|
+
function EventLabel({ e }) {
|
|
1893
|
+
if (e.type === "sub") {
|
|
1894
|
+
return /* @__PURE__ */ jsxs31("span", { className: "ss-w-fandash-live-tl-who", children: [
|
|
1895
|
+
e.player ?? e.text,
|
|
1896
|
+
e.playerOff ? /* @__PURE__ */ jsxs31("span", { className: "ss-w-fandash-live-tl-off", children: [
|
|
1897
|
+
" for ",
|
|
1898
|
+
e.playerOff
|
|
1899
|
+
] }) : null
|
|
1900
|
+
] });
|
|
1901
|
+
}
|
|
1902
|
+
return /* @__PURE__ */ jsx35("span", { className: "ss-w-fandash-live-tl-who", children: e.player ?? e.text });
|
|
1903
|
+
}
|
|
1904
|
+
function EventRow({ e }) {
|
|
1905
|
+
const cls = [
|
|
1906
|
+
"ss-w-fandash-live-tl-ev",
|
|
1907
|
+
e.side === "home" ? "ss-w-fandash-live-tl-ev--home" : "ss-w-fandash-live-tl-ev--away",
|
|
1908
|
+
e.type === "goal" ? "ss-w-fandash-live-tl-ev--goal" : ""
|
|
1909
|
+
].filter(Boolean).join(" ");
|
|
1910
|
+
return /* @__PURE__ */ jsxs31("div", { className: cls, children: [
|
|
1911
|
+
/* @__PURE__ */ jsxs31("span", { className: "ss-w-fandash-live-tl-txt", children: [
|
|
1912
|
+
/* @__PURE__ */ jsx35(EventLabel, { e }),
|
|
1913
|
+
/* @__PURE__ */ jsx35(EventIcon, { type: e.type })
|
|
1914
|
+
] }),
|
|
1915
|
+
/* @__PURE__ */ jsx35("span", { className: "ss-w-fandash-live-tl-badge", children: e.minuteLabel })
|
|
1916
|
+
] });
|
|
1917
|
+
}
|
|
1918
|
+
function LiveTimelineCard({ card, ui }) {
|
|
1919
|
+
const [expanded, setExpanded] = useState9(false);
|
|
1920
|
+
const events = card.data.events;
|
|
1921
|
+
if (!events || events.length === 0) return null;
|
|
1922
|
+
const sorted = [...events].sort((a, b) => b.minute - a.minute);
|
|
1923
|
+
const rest = sorted.length - SHOWN;
|
|
1924
|
+
const shown = expanded ? sorted : sorted.slice(0, SHOWN);
|
|
1925
|
+
const showMore = ui?.showMore ?? ((n) => `Show ${n} more`);
|
|
1926
|
+
const showLess = ui?.showLess ?? "Show less";
|
|
1729
1927
|
return /* @__PURE__ */ jsxs31("div", { className: "ss-w-fandash-card ss-w-fandash-live-timeline", children: [
|
|
1730
1928
|
card.title ? /* @__PURE__ */ jsx35("span", { className: "ss-w-fandash-eyebrow", children: card.title }) : null,
|
|
1731
|
-
/* @__PURE__ */ jsx35("
|
|
1732
|
-
|
|
1733
|
-
|
|
1734
|
-
|
|
1929
|
+
/* @__PURE__ */ jsx35("div", { className: "ss-w-fandash-live-tl", children: shown.map((e, i) => /* @__PURE__ */ jsx35(EventRow, { e }, `${e.minute}-${e.side}-${e.type}-${i}`)) }),
|
|
1930
|
+
rest > 0 ? /* @__PURE__ */ jsx35(
|
|
1931
|
+
"button",
|
|
1932
|
+
{
|
|
1933
|
+
type: "button",
|
|
1934
|
+
className: "ss-w-fandash-live-tl-more",
|
|
1935
|
+
"aria-expanded": expanded,
|
|
1936
|
+
onClick: () => setExpanded((v) => !v),
|
|
1937
|
+
children: expanded ? showLess : showMore(rest)
|
|
1938
|
+
}
|
|
1939
|
+
) : null
|
|
1735
1940
|
] });
|
|
1736
1941
|
}
|
|
1737
1942
|
|
|
1738
|
-
// src/components/dashboard/
|
|
1943
|
+
// src/components/dashboard/MomentumWave.tsx
|
|
1944
|
+
import { useId } from "react";
|
|
1739
1945
|
import { jsx as jsx36, jsxs as jsxs32 } from "react/jsx-runtime";
|
|
1946
|
+
var WIDTH = 270;
|
|
1947
|
+
var HEIGHT = 56;
|
|
1948
|
+
var BASELINE = HEIGHT / 2;
|
|
1949
|
+
var SCALE = 0.26;
|
|
1950
|
+
function buildPaths(points) {
|
|
1951
|
+
const lastIndex = points.length - 1;
|
|
1952
|
+
const px = (i) => lastIndex === 0 ? 0 : i / lastIndex * WIDTH;
|
|
1953
|
+
const py = (v) => BASELINE - v * SCALE;
|
|
1954
|
+
const linePts = points.map((p, i) => `${px(i).toFixed(1)},${py(p.value).toFixed(1)}`);
|
|
1955
|
+
const area = `M0,${BASELINE} L${linePts.join(" L")} L${WIDTH},${BASELINE} Z`;
|
|
1956
|
+
const line = `M${linePts.join(" L")}`;
|
|
1957
|
+
const lastValue = points[lastIndex]?.value ?? 0;
|
|
1958
|
+
return { area, line, lastX: px(lastIndex).toFixed(1), lastY: py(lastValue).toFixed(1) };
|
|
1959
|
+
}
|
|
1960
|
+
function MomentumWave({
|
|
1961
|
+
points,
|
|
1962
|
+
caption,
|
|
1963
|
+
clubIsHome
|
|
1964
|
+
}) {
|
|
1965
|
+
if (points.length === 0) return null;
|
|
1966
|
+
const { area, line, lastX, lastY } = buildPaths(points);
|
|
1967
|
+
const clipId = useId();
|
|
1968
|
+
const topClipId = `${clipId}-top`;
|
|
1969
|
+
const bottomClipId = `${clipId}-bottom`;
|
|
1970
|
+
const topClass = clubIsHome ? "ss-w-fandash-live-mom-home" : "ss-w-fandash-live-mom-away";
|
|
1971
|
+
const bottomClass = clubIsHome ? "ss-w-fandash-live-mom-away" : "ss-w-fandash-live-mom-home";
|
|
1972
|
+
return /* @__PURE__ */ jsxs32("div", { className: "ss-w-fandash-live-mom", children: [
|
|
1973
|
+
caption ? /* @__PURE__ */ jsx36("span", { className: "ss-w-fandash-live-mom-caption", children: caption }) : null,
|
|
1974
|
+
/* @__PURE__ */ jsxs32(
|
|
1975
|
+
"svg",
|
|
1976
|
+
{
|
|
1977
|
+
className: "ss-w-fandash-live-mom-svg",
|
|
1978
|
+
viewBox: `0 0 ${WIDTH} ${HEIGHT}`,
|
|
1979
|
+
preserveAspectRatio: "none",
|
|
1980
|
+
role: "img",
|
|
1981
|
+
"aria-label": caption ?? "Momentum",
|
|
1982
|
+
children: [
|
|
1983
|
+
/* @__PURE__ */ jsxs32("defs", { children: [
|
|
1984
|
+
/* @__PURE__ */ jsx36("clipPath", { id: topClipId, children: /* @__PURE__ */ jsx36("rect", { x: 0, y: 0, width: WIDTH, height: BASELINE }) }),
|
|
1985
|
+
/* @__PURE__ */ jsx36("clipPath", { id: bottomClipId, children: /* @__PURE__ */ jsx36("rect", { x: 0, y: BASELINE, width: WIDTH, height: HEIGHT - BASELINE }) })
|
|
1986
|
+
] }),
|
|
1987
|
+
/* @__PURE__ */ jsx36("path", { d: area, className: topClass, clipPath: `url(#${topClipId})` }),
|
|
1988
|
+
/* @__PURE__ */ jsx36("path", { d: area, className: bottomClass, clipPath: `url(#${bottomClipId})` }),
|
|
1989
|
+
/* @__PURE__ */ jsx36("line", { x1: 0, y1: BASELINE, x2: WIDTH, y2: BASELINE, className: "ss-w-fandash-live-mom-base" }),
|
|
1990
|
+
/* @__PURE__ */ jsx36("path", { d: line, className: "ss-w-fandash-live-mom-line", vectorEffect: "non-scaling-stroke" }),
|
|
1991
|
+
/* @__PURE__ */ jsx36("circle", { cx: lastX, cy: lastY, r: 3.2, className: "ss-w-fandash-live-mom-now", vectorEffect: "non-scaling-stroke" })
|
|
1992
|
+
]
|
|
1993
|
+
}
|
|
1994
|
+
)
|
|
1995
|
+
] });
|
|
1996
|
+
}
|
|
1997
|
+
|
|
1998
|
+
// src/components/dashboard/LiveStatsCard.tsx
|
|
1999
|
+
import { jsx as jsx37, jsxs as jsxs33 } from "react/jsx-runtime";
|
|
2000
|
+
function splitWidths(homeNum, awayNum) {
|
|
2001
|
+
const total = homeNum + awayNum;
|
|
2002
|
+
if (total <= 0) return { home: "50%", away: "50%" };
|
|
2003
|
+
const homePct = Math.round(homeNum / total * 1e3) / 10;
|
|
2004
|
+
return { home: `${homePct}%`, away: `${Math.round((100 - homePct) * 10) / 10}%` };
|
|
2005
|
+
}
|
|
1740
2006
|
function LiveStatsCard({ card }) {
|
|
1741
2007
|
const { data } = card;
|
|
1742
|
-
return /* @__PURE__ */
|
|
1743
|
-
card.title ? /* @__PURE__ */
|
|
1744
|
-
/* @__PURE__ */
|
|
1745
|
-
|
|
1746
|
-
|
|
1747
|
-
|
|
1748
|
-
|
|
1749
|
-
|
|
2008
|
+
return /* @__PURE__ */ jsxs33("div", { className: "ss-w-fandash-card ss-w-fandash-live-stats", children: [
|
|
2009
|
+
card.title ? /* @__PURE__ */ jsx37("span", { className: "ss-w-fandash-eyebrow", children: card.title }) : null,
|
|
2010
|
+
/* @__PURE__ */ jsx37("div", { className: "ss-w-fandash-live-stat-rows", children: data.rows.map((r, i) => {
|
|
2011
|
+
const homeValue = data.clubIsHome ? r.clubValue : r.oppValue;
|
|
2012
|
+
const awayValue = data.clubIsHome ? r.oppValue : r.clubValue;
|
|
2013
|
+
const homeNum = data.clubIsHome ? r.clubNum : r.oppNum;
|
|
2014
|
+
const awayNum = data.clubIsHome ? r.oppNum : r.clubNum;
|
|
2015
|
+
const { home, away } = splitWidths(homeNum, awayNum);
|
|
2016
|
+
return /* @__PURE__ */ jsxs33("div", { className: "ss-w-fandash-live-stat", children: [
|
|
2017
|
+
/* @__PURE__ */ jsxs33("div", { className: "ss-w-fandash-live-stat-row", children: [
|
|
2018
|
+
/* @__PURE__ */ jsx37("span", { className: "ss-w-fandash-live-stat-value ss-w-fandash-live-stat-value--home", children: homeValue }),
|
|
2019
|
+
/* @__PURE__ */ jsx37("span", { className: "ss-w-fandash-live-stat-label", children: r.label }),
|
|
2020
|
+
/* @__PURE__ */ jsx37("span", { className: "ss-w-fandash-live-stat-value ss-w-fandash-live-stat-value--away", children: awayValue })
|
|
2021
|
+
] }),
|
|
2022
|
+
/* @__PURE__ */ jsxs33("div", { className: "ss-w-fandash-live-bar", children: [
|
|
2023
|
+
/* @__PURE__ */ jsx37("span", { className: "ss-w-fandash-live-bar-home", style: { width: home } }),
|
|
2024
|
+
/* @__PURE__ */ jsx37("span", { className: "ss-w-fandash-live-bar-away", style: { width: away } })
|
|
2025
|
+
] })
|
|
2026
|
+
] }, `${r.label}-${i}`);
|
|
2027
|
+
}) }),
|
|
2028
|
+
data.momentum.length > 0 ? /* @__PURE__ */ jsx37(MomentumWave, { points: data.momentum, caption: data.momentumLabel, clubIsHome: data.clubIsHome }) : data.momentumLabel ? /* @__PURE__ */ jsx37("span", { className: "ss-w-fandash-live-momentum", children: data.momentumLabel }) : null,
|
|
2029
|
+
data.rows.length > 0 ? /* @__PURE__ */ jsxs33("div", { className: "ss-w-fandash-live-legend", children: [
|
|
2030
|
+
/* @__PURE__ */ jsxs33("span", { className: "ss-w-fandash-live-legend-item", children: [
|
|
2031
|
+
/* @__PURE__ */ jsx37("i", { className: "ss-w-fandash-live-legend-swatch ss-w-fandash-live-legend-swatch--home" }),
|
|
2032
|
+
data.homeName
|
|
2033
|
+
] }),
|
|
2034
|
+
/* @__PURE__ */ jsxs33("span", { className: "ss-w-fandash-live-legend-item", children: [
|
|
2035
|
+
/* @__PURE__ */ jsx37("i", { className: "ss-w-fandash-live-legend-swatch ss-w-fandash-live-legend-swatch--away" }),
|
|
2036
|
+
data.awayName
|
|
2037
|
+
] })
|
|
2038
|
+
] }) : null
|
|
1750
2039
|
] });
|
|
1751
2040
|
}
|
|
1752
2041
|
|
|
1753
2042
|
// src/components/dashboard/ClubFanDashboard.tsx
|
|
1754
|
-
import { jsx as
|
|
2043
|
+
import { jsx as jsx38, jsxs as jsxs34 } from "react/jsx-runtime";
|
|
1755
2044
|
function ClubFanDashboard({ dashboard, onTap, ui }) {
|
|
1756
2045
|
const orderedCards = [
|
|
1757
2046
|
...dashboard.cards.filter((c) => c.cardType === "countdown"),
|
|
1758
2047
|
...dashboard.cards.filter((c) => c.cardType !== "countdown")
|
|
1759
2048
|
];
|
|
1760
|
-
|
|
1761
|
-
|
|
1762
|
-
|
|
2049
|
+
const timelineCard = dashboard.cards.find(
|
|
2050
|
+
(c) => c.cardType === "liveTimeline"
|
|
2051
|
+
);
|
|
2052
|
+
const goalEvents = timelineCard?.data.events.filter((e) => e.type === "goal");
|
|
2053
|
+
return /* @__PURE__ */ jsxs34("div", { className: "ss-w-fandash", children: [
|
|
2054
|
+
dashboard.greeting ? /* @__PURE__ */ jsx38("span", { className: "ss-w-fandash-greeting", children: dashboard.greeting }) : null,
|
|
2055
|
+
/* @__PURE__ */ jsx38("div", { className: "ss-w-fandash-cards", children: orderedCards.map((card, i) => /* @__PURE__ */ jsx38(FanDashboardCard, { card, onTap, ui, goalEvents }, `${card.cardType}-${i}`)) })
|
|
1763
2056
|
] });
|
|
1764
2057
|
}
|
|
1765
|
-
function FanDashboardCard({ card, onTap, ui }) {
|
|
2058
|
+
function FanDashboardCard({ card, onTap, ui, goalEvents }) {
|
|
1766
2059
|
switch (card.cardType) {
|
|
1767
2060
|
case "fixture":
|
|
1768
|
-
return /* @__PURE__ */
|
|
2061
|
+
return /* @__PURE__ */ jsx38(FixtureCard, { card, onTap });
|
|
1769
2062
|
case "countdown":
|
|
1770
|
-
return /* @__PURE__ */
|
|
2063
|
+
return /* @__PURE__ */ jsx38(CountdownCard, { card });
|
|
1771
2064
|
case "injuries":
|
|
1772
|
-
return /* @__PURE__ */
|
|
2065
|
+
return /* @__PURE__ */ jsx38(InjuriesCard, { card });
|
|
1773
2066
|
case "reasonToBet":
|
|
1774
|
-
return /* @__PURE__ */
|
|
2067
|
+
return /* @__PURE__ */ jsx38(ReasonToBetCard, { card });
|
|
1775
2068
|
case "whereToWatch":
|
|
1776
|
-
return /* @__PURE__ */
|
|
2069
|
+
return /* @__PURE__ */ jsx38(WhereToWatchCard, { card });
|
|
1777
2070
|
case "congestedWeek":
|
|
1778
|
-
return /* @__PURE__ */
|
|
2071
|
+
return /* @__PURE__ */ jsx38(CongestedWeekCard, { card, onTap });
|
|
1779
2072
|
case "transfers":
|
|
1780
|
-
return /* @__PURE__ */
|
|
2073
|
+
return /* @__PURE__ */ jsx38(TransfersCard, { card });
|
|
1781
2074
|
case "links":
|
|
1782
|
-
return /* @__PURE__ */
|
|
2075
|
+
return /* @__PURE__ */ jsx38(LinksCard, { card });
|
|
1783
2076
|
case "postMatchResult":
|
|
1784
|
-
return /* @__PURE__ */
|
|
2077
|
+
return /* @__PURE__ */ jsx38(PostMatchResultCard, { card });
|
|
1785
2078
|
case "postMatchGoals":
|
|
1786
|
-
return /* @__PURE__ */
|
|
2079
|
+
return /* @__PURE__ */ jsx38(PostMatchGoalsCard, { card, onTap });
|
|
1787
2080
|
case "postMatchStats":
|
|
1788
|
-
return /* @__PURE__ */
|
|
2081
|
+
return /* @__PURE__ */ jsx38(PostMatchStatsCard, { card, onTap });
|
|
1789
2082
|
case "postMatchTopPerformer":
|
|
1790
|
-
return /* @__PURE__ */
|
|
2083
|
+
return /* @__PURE__ */ jsx38(PostMatchTopPerformerCard, { card, onTap });
|
|
1791
2084
|
case "postMatchNext":
|
|
1792
|
-
return /* @__PURE__ */
|
|
2085
|
+
return /* @__PURE__ */ jsx38(PostMatchNextCard, { card });
|
|
1793
2086
|
case "restingNext":
|
|
1794
|
-
return /* @__PURE__ */
|
|
2087
|
+
return /* @__PURE__ */ jsx38(RestingNextCard, { card, onTap });
|
|
1795
2088
|
case "restingStandings":
|
|
1796
|
-
return /* @__PURE__ */
|
|
2089
|
+
return /* @__PURE__ */ jsx38(RestingStandingsCard, { card, onTap });
|
|
1797
2090
|
case "restingSeason":
|
|
1798
|
-
return /* @__PURE__ */
|
|
2091
|
+
return /* @__PURE__ */ jsx38(RestingSeasonCard, { card, onTap });
|
|
1799
2092
|
case "restingForm":
|
|
1800
|
-
return /* @__PURE__ */
|
|
2093
|
+
return /* @__PURE__ */ jsx38(RestingFormCard, { card });
|
|
1801
2094
|
case "liveScore":
|
|
1802
|
-
return /* @__PURE__ */
|
|
2095
|
+
return /* @__PURE__ */ jsx38(LiveScoreCard, { card, goalEvents });
|
|
1803
2096
|
case "liveTimeline":
|
|
1804
|
-
return /* @__PURE__ */
|
|
2097
|
+
return /* @__PURE__ */ jsx38(LiveTimelineCard, { card, ui });
|
|
1805
2098
|
case "liveStats":
|
|
1806
|
-
return /* @__PURE__ */
|
|
2099
|
+
return /* @__PURE__ */ jsx38(LiveStatsCard, { card });
|
|
1807
2100
|
default:
|
|
1808
2101
|
return null;
|
|
1809
2102
|
}
|
|
1810
2103
|
}
|
|
1811
2104
|
|
|
1812
2105
|
// src/components/ChatMessage.tsx
|
|
1813
|
-
import { jsx as
|
|
2106
|
+
import { jsx as jsx39, jsxs as jsxs35 } from "react/jsx-runtime";
|
|
1814
2107
|
function ChatMessage({ message, disabled, onAct, onTap, ui, slip }) {
|
|
1815
2108
|
if (message.role === "user") {
|
|
1816
|
-
return /* @__PURE__ */
|
|
2109
|
+
return /* @__PURE__ */ jsx39("div", { className: "ss-w-msg ss-w-user", "data-msg-id": message.id, children: /* @__PURE__ */ jsx39("div", { className: "ss-w-bubble", children: message.text }) });
|
|
1817
2110
|
}
|
|
1818
2111
|
const onFanDashboardTap = (card) => {
|
|
1819
2112
|
onAct({ message: card.actionLabel, actionRef: card.actionRef, label: card.actionLabel || card.title || "" });
|
|
1820
2113
|
};
|
|
1821
|
-
return /* @__PURE__ */
|
|
1822
|
-
message.text && !message.greetingDashboard && !message.clubFanDashboard ? /* @__PURE__ */
|
|
1823
|
-
message.greetingDashboard && !message.clubFanDashboard ? /* @__PURE__ */
|
|
1824
|
-
message.clubFanDashboard ? /* @__PURE__ */
|
|
1825
|
-
/* @__PURE__ */
|
|
1826
|
-
/* @__PURE__ */
|
|
1827
|
-
/* @__PURE__ */
|
|
1828
|
-
/* @__PURE__ */
|
|
1829
|
-
/* @__PURE__ */
|
|
2114
|
+
return /* @__PURE__ */ jsxs35("div", { className: "ss-w-msg ss-w-bot", "data-msg-id": message.id, children: [
|
|
2115
|
+
message.text && !message.greetingDashboard && !message.clubFanDashboard ? /* @__PURE__ */ jsx39(Markdown, { text: message.text }) : null,
|
|
2116
|
+
message.greetingDashboard && !message.clubFanDashboard ? /* @__PURE__ */ jsx39(GreetingDashboard, { dashboard: message.greetingDashboard, onTap }) : null,
|
|
2117
|
+
message.clubFanDashboard ? /* @__PURE__ */ jsx39(ClubFanDashboard, { dashboard: message.clubFanDashboard, onTap: onFanDashboardTap, ui }) : null,
|
|
2118
|
+
/* @__PURE__ */ jsx39(EntityCardList, { cards: message.cards, disabled, showLessLabel: ui?.showLess, showMoreLabel: ui?.showMore }),
|
|
2119
|
+
/* @__PURE__ */ jsx39(BettingInsightList, { insights: message.insights, ui, slip }),
|
|
2120
|
+
/* @__PURE__ */ jsx39(MarketOddsCardList, { cards: message.marketOdds, ui, slip }),
|
|
2121
|
+
/* @__PURE__ */ jsx39(OddsMovementCardList, { cards: message.oddsMovement, ui }),
|
|
2122
|
+
/* @__PURE__ */ jsx39(ActionButtons, { actions: message.actions, loading: false, disabled, onAct })
|
|
1830
2123
|
] });
|
|
1831
2124
|
}
|
|
1832
2125
|
|
|
1833
2126
|
// src/components/IntentChips.tsx
|
|
1834
|
-
import { jsx as
|
|
2127
|
+
import { jsx as jsx40 } from "react/jsx-runtime";
|
|
1835
2128
|
function IntentChips({ chips, onPick }) {
|
|
1836
2129
|
if (chips.length === 0) return null;
|
|
1837
|
-
return /* @__PURE__ */
|
|
2130
|
+
return /* @__PURE__ */ jsx40("div", { className: "ss-w-chips", children: chips.map((c, i) => /* @__PURE__ */ jsx40("button", { type: "button", className: "ss-w-chip", onClick: () => onPick(c.text), children: c.text }, `${c.text}-${i}`)) });
|
|
1838
2131
|
}
|
|
1839
2132
|
|
|
1840
2133
|
// src/scroll.ts
|
|
@@ -1844,7 +2137,7 @@ function isPinnedToBottom(el, threshold = PIN_THRESHOLD) {
|
|
|
1844
2137
|
}
|
|
1845
2138
|
|
|
1846
2139
|
// src/components/MessageList.tsx
|
|
1847
|
-
import { jsx as
|
|
2140
|
+
import { jsx as jsx41, jsxs as jsxs36 } from "react/jsx-runtime";
|
|
1848
2141
|
function turnAnchor(messages) {
|
|
1849
2142
|
const last = messages[messages.length - 1];
|
|
1850
2143
|
if (!last || last.role !== "assistant") return null;
|
|
@@ -1853,11 +2146,11 @@ function turnAnchor(messages) {
|
|
|
1853
2146
|
}
|
|
1854
2147
|
function MessageList({ controller, ui, slip, onDashboardTap }) {
|
|
1855
2148
|
const { state, send } = controller;
|
|
1856
|
-
const listRef =
|
|
1857
|
-
const endRef =
|
|
1858
|
-
const pinnedRef =
|
|
1859
|
-
const anchoredTurnRef =
|
|
1860
|
-
|
|
2149
|
+
const listRef = useRef4(null);
|
|
2150
|
+
const endRef = useRef4(null);
|
|
2151
|
+
const pinnedRef = useRef4(true);
|
|
2152
|
+
const anchoredTurnRef = useRef4(null);
|
|
2153
|
+
useEffect6(() => {
|
|
1861
2154
|
const anchor = turnAnchor(state.messages);
|
|
1862
2155
|
const list = listRef.current;
|
|
1863
2156
|
if (anchor && anchor.turnId !== anchoredTurnRef.current) {
|
|
@@ -1872,7 +2165,7 @@ function MessageList({ controller, ui, slip, onDashboardTap }) {
|
|
|
1872
2165
|
}, [state.messages, state.status, state.isStreaming]);
|
|
1873
2166
|
const hasUserMessage = state.messages.some((m) => m.role === "user");
|
|
1874
2167
|
const hasDashboard = state.messages.some((m) => m.role === "assistant" && (m.greetingDashboard || m.clubFanDashboard));
|
|
1875
|
-
return /* @__PURE__ */
|
|
2168
|
+
return /* @__PURE__ */ jsxs36(
|
|
1876
2169
|
"div",
|
|
1877
2170
|
{
|
|
1878
2171
|
className: "ss-w-list",
|
|
@@ -1882,7 +2175,7 @@ function MessageList({ controller, ui, slip, onDashboardTap }) {
|
|
|
1882
2175
|
if (listRef.current) pinnedRef.current = isPinnedToBottom(listRef.current);
|
|
1883
2176
|
},
|
|
1884
2177
|
children: [
|
|
1885
|
-
state.messages.map((m) => /* @__PURE__ */
|
|
2178
|
+
state.messages.map((m) => /* @__PURE__ */ jsx41(
|
|
1886
2179
|
ChatMessage,
|
|
1887
2180
|
{
|
|
1888
2181
|
message: m,
|
|
@@ -1897,25 +2190,25 @@ function MessageList({ controller, ui, slip, onDashboardTap }) {
|
|
|
1897
2190
|
},
|
|
1898
2191
|
m.id
|
|
1899
2192
|
)),
|
|
1900
|
-
state.isStreaming && state.actionsLoading ? /* @__PURE__ */
|
|
2193
|
+
state.isStreaming && state.actionsLoading ? /* @__PURE__ */ jsx41(ActionButtons, { actions: [], loading: true, onAct: () => {
|
|
1901
2194
|
} }) : null,
|
|
1902
|
-
state.status ? /* @__PURE__ */
|
|
1903
|
-
state.error ? /* @__PURE__ */
|
|
1904
|
-
/* @__PURE__ */
|
|
1905
|
-
/* @__PURE__ */
|
|
2195
|
+
state.status ? /* @__PURE__ */ jsx41("div", { className: "ss-w-status", children: state.status }) : null,
|
|
2196
|
+
state.error ? /* @__PURE__ */ jsxs36("div", { className: "ss-w-error", role: "alert", children: [
|
|
2197
|
+
/* @__PURE__ */ jsx41("span", { children: "message" in state.error ? state.error.message : ui.somethingWentWrong }),
|
|
2198
|
+
/* @__PURE__ */ jsx41("button", { type: "button", className: "ss-w-retry", onClick: () => controller.retry(), children: ui.retry })
|
|
1906
2199
|
] }) : null,
|
|
1907
|
-
!hasUserMessage && !hasDashboard ? /* @__PURE__ */
|
|
1908
|
-
/* @__PURE__ */
|
|
2200
|
+
!hasUserMessage && !hasDashboard ? /* @__PURE__ */ jsx41(IntentChips, { chips: state.starters, onPick: (t) => send(t) }) : null,
|
|
2201
|
+
/* @__PURE__ */ jsx41("div", { ref: endRef, className: "ss-w-turn-spacer", "aria-hidden": "true" })
|
|
1909
2202
|
]
|
|
1910
2203
|
}
|
|
1911
2204
|
);
|
|
1912
2205
|
}
|
|
1913
2206
|
|
|
1914
2207
|
// src/components/ChatInput.tsx
|
|
1915
|
-
import { useState as
|
|
1916
|
-
import { jsx as
|
|
2208
|
+
import { useState as useState10 } from "react";
|
|
2209
|
+
import { jsx as jsx42, jsxs as jsxs37 } from "react/jsx-runtime";
|
|
1917
2210
|
function ChatInput({ placeholder, streaming, onSend, onStop, stopLabel = "Stop response", sendLabel = "Send message" }) {
|
|
1918
|
-
const [value, setValue] =
|
|
2211
|
+
const [value, setValue] = useState10("");
|
|
1919
2212
|
const submit = () => {
|
|
1920
2213
|
const t = value.trim();
|
|
1921
2214
|
if (!t) return;
|
|
@@ -1928,18 +2221,18 @@ function ChatInput({ placeholder, streaming, onSend, onStop, stopLabel = "Stop r
|
|
|
1928
2221
|
submit();
|
|
1929
2222
|
}
|
|
1930
2223
|
};
|
|
1931
|
-
return /* @__PURE__ */
|
|
1932
|
-
/* @__PURE__ */
|
|
1933
|
-
streaming ? /* @__PURE__ */
|
|
2224
|
+
return /* @__PURE__ */ jsxs37("div", { className: "ss-w-input", children: [
|
|
2225
|
+
/* @__PURE__ */ jsx42("textarea", { className: "ss-w-textarea", rows: 1, placeholder, value, disabled: streaming, onChange: (e) => setValue(e.target.value), onKeyDown }),
|
|
2226
|
+
streaming ? /* @__PURE__ */ jsx42("button", { type: "button", className: "ss-w-send ss-w-stop", "aria-label": stopLabel, onClick: onStop, children: /* @__PURE__ */ jsx42("span", { className: "ss-w-stop-glyph" }) }) : /* @__PURE__ */ jsx42("button", { type: "button", className: "ss-w-send", "aria-label": sendLabel, onClick: submit, children: /* @__PURE__ */ jsx42(SendIcon, {}) })
|
|
1934
2227
|
] });
|
|
1935
2228
|
}
|
|
1936
2229
|
|
|
1937
2230
|
// src/components/ChatPanel.tsx
|
|
1938
|
-
import { jsx as
|
|
2231
|
+
import { jsx as jsx43, jsxs as jsxs38 } from "react/jsx-runtime";
|
|
1939
2232
|
var FULLSCREEN_QUERY = "(max-width: 639px)";
|
|
1940
2233
|
function useIsModal() {
|
|
1941
|
-
const [modal, setModal] =
|
|
1942
|
-
|
|
2234
|
+
const [modal, setModal] = useState11(false);
|
|
2235
|
+
useEffect7(() => {
|
|
1943
2236
|
if (typeof window === "undefined" || typeof window.matchMedia !== "function") return;
|
|
1944
2237
|
const mql = window.matchMedia(FULLSCREEN_QUERY);
|
|
1945
2238
|
const sync = () => setModal(mql.matches);
|
|
@@ -1952,17 +2245,17 @@ function useIsModal() {
|
|
|
1952
2245
|
function ChatPanel({ controller, ui, slip, onDashboardTap }) {
|
|
1953
2246
|
const { state, close, stop, send } = controller;
|
|
1954
2247
|
const isModal = useIsModal();
|
|
1955
|
-
|
|
2248
|
+
useEffect7(() => {
|
|
1956
2249
|
const onKey = (e) => {
|
|
1957
2250
|
if (e.key === "Escape") close();
|
|
1958
2251
|
};
|
|
1959
2252
|
window.addEventListener("keydown", onKey);
|
|
1960
2253
|
return () => window.removeEventListener("keydown", onKey);
|
|
1961
2254
|
}, [close]);
|
|
1962
|
-
return /* @__PURE__ */
|
|
1963
|
-
/* @__PURE__ */
|
|
1964
|
-
/* @__PURE__ */
|
|
1965
|
-
/* @__PURE__ */
|
|
2255
|
+
return /* @__PURE__ */ jsxs38("div", { className: "ss-w-panel", role: "dialog", "aria-modal": isModal, "aria-label": ui.chatDialogLabel, children: [
|
|
2256
|
+
/* @__PURE__ */ jsx43(ChatHeader, { onClose: close, closeLabel: ui.closeChat }),
|
|
2257
|
+
/* @__PURE__ */ jsx43(MessageList, { controller, ui, slip, onDashboardTap }),
|
|
2258
|
+
/* @__PURE__ */ jsx43(ChatInput, { placeholder: ui.placeholder, streaming: state.isStreaming, onSend: (t) => send(t), onStop: stop, stopLabel: ui.stopResponse, sendLabel: ui.sendMessage })
|
|
1966
2259
|
] });
|
|
1967
2260
|
}
|
|
1968
2261
|
|
|
@@ -1975,6 +2268,7 @@ var WIDGET_CSS = `.ss-w-root { all: revert; }
|
|
|
1975
2268
|
--ss-w-ink: #14181d; --ss-w-faint: #6b7580; --ss-w-line: #e4e8ec;
|
|
1976
2269
|
--ss-w-panel: #ffffff; --ss-w-bubble: #f3f5f7; --ss-w-nav: #0e1622;
|
|
1977
2270
|
--ss-w-pos: #1aa563; --ss-w-neg: #e0524b;
|
|
2271
|
+
--ss-w-home: #3a63a8; --ss-w-away: #c8102e; --ss-w-track: #e9edf1;
|
|
1978
2272
|
--ss-w-radius: 16px; --ss-w-offset-x: 20px; --ss-w-offset-y: 20px;
|
|
1979
2273
|
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
|
|
1980
2274
|
font-size: 13px; line-height: 1.5; color: var(--ss-w-ink);
|
|
@@ -1985,6 +2279,7 @@ var WIDGET_CSS = `.ss-w-root { all: revert; }
|
|
|
1985
2279
|
--ss-w-ink: #e8edf2; --ss-w-faint: #8b95a1;
|
|
1986
2280
|
--ss-w-line: #2a3441; --ss-w-panel: #141b24; --ss-w-bubble: #1e2732; --ss-w-nav: #0a0f16;
|
|
1987
2281
|
--ss-w-pos: #37c891; --ss-w-neg: #f0655d;
|
|
2282
|
+
--ss-w-home: #5b83c8; --ss-w-away: #e8384f; --ss-w-track: #232d3a;
|
|
1988
2283
|
}
|
|
1989
2284
|
}
|
|
1990
2285
|
.ss-w-root[data-ss-w-theme="dark"] {
|
|
@@ -1992,11 +2287,13 @@ var WIDGET_CSS = `.ss-w-root { all: revert; }
|
|
|
1992
2287
|
--ss-w-ink: #e8edf2; --ss-w-faint: #8b95a1;
|
|
1993
2288
|
--ss-w-line: #2a3441; --ss-w-panel: #141b24; --ss-w-bubble: #1e2732; --ss-w-nav: #0a0f16;
|
|
1994
2289
|
--ss-w-pos: #37c891; --ss-w-neg: #f0655d;
|
|
2290
|
+
--ss-w-home: #5b83c8; --ss-w-away: #e8384f; --ss-w-track: #232d3a;
|
|
1995
2291
|
}
|
|
1996
2292
|
.ss-w-root[data-ss-w-theme="light"] {
|
|
1997
2293
|
--ss-w-accent: #17936a; --ss-w-cta: #f5a623; --ss-w-cta-ink: #1b1300;
|
|
1998
2294
|
--ss-w-ink: #14181d; --ss-w-faint: #6b7580;
|
|
1999
2295
|
--ss-w-line: #e4e8ec; --ss-w-panel: #ffffff; --ss-w-bubble: #f3f5f7; --ss-w-nav: #0e1622;
|
|
2296
|
+
--ss-w-home: #3a63a8; --ss-w-away: #c8102e; --ss-w-track: #e9edf1;
|
|
2000
2297
|
}
|
|
2001
2298
|
|
|
2002
2299
|
/* launcher */
|
|
@@ -2111,7 +2408,7 @@ var WIDGET_CSS = `.ss-w-root { all: revert; }
|
|
|
2111
2408
|
|
|
2112
2409
|
/* row 1: competition logo + name on the left, live badge + kickoff on the right */
|
|
2113
2410
|
.ss-w-bi-header { display: flex; align-items: center; justify-content: space-between; gap: 8px; min-width: 0; padding-bottom: 7px; border-bottom: 1px solid var(--ss-w-line); }
|
|
2114
|
-
.ss-w-bi-header-left { display: inline-flex; align-items: center; gap: 6px; min-width: 0; }
|
|
2411
|
+
.ss-w-bi-header-left { display: inline-flex; align-items: center; gap: 6px; min-width: 0; flex: 1; font-size: 12.5px; font-weight: 650; color: var(--ss-w-ink); }
|
|
2115
2412
|
.ss-w-bi-header-right { display: inline-flex; align-items: center; gap: 8px; flex: none; }
|
|
2116
2413
|
.ss-w-bi-comp { font-size: 10.5px; color: var(--ss-w-faint); font-weight: 600; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
|
|
2117
2414
|
.ss-w-bi-kick { font-size: 10.5px; font-weight: 600; color: var(--ss-w-faint); white-space: nowrap; flex: none; }
|
|
@@ -2124,23 +2421,26 @@ var WIDGET_CSS = `.ss-w-root { all: revert; }
|
|
|
2124
2421
|
.ss-w-bi-crest { width: 24px; height: 24px; flex: none; display: block; border-radius: 50%; object-fit: contain; }
|
|
2125
2422
|
.ss-w-bi-crest-mono { display: grid; place-items: center; font-size: 10px; font-weight: 700; color: var(--ss-w-accent); background: color-mix(in srgb, var(--ss-w-accent) 16%, var(--ss-w-panel)); }
|
|
2126
2423
|
|
|
2127
|
-
/*
|
|
2128
|
-
.ss-w-bi-body { display: flex; align-items: center; gap: 10px; }
|
|
2129
|
-
.ss-w-bi-fixture { display: flex; align-items: center; gap: 6px; min-width: 0; flex: 1; font-size: 12px; font-weight: 650; color: var(--ss-w-ink); }
|
|
2424
|
+
/* header fixture atoms (team hero lives in .ss-w-bi-header-left; comp demotes to the meta) */
|
|
2130
2425
|
.ss-w-bi-team { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
2131
2426
|
.ss-w-bi-vs { font-size: 10px; font-weight: 600; color: var(--ss-w-faint); flex: none; }
|
|
2132
2427
|
.ss-w-bi-fixture-txt { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
2133
|
-
.ss-w-bi-
|
|
2134
|
-
|
|
2135
|
-
.ss-w-bi-
|
|
2136
|
-
|
|
2137
|
-
|
|
2138
|
-
.ss-w-bi-
|
|
2139
|
-
.ss-w-bi-
|
|
2140
|
-
.ss-w-bi-
|
|
2428
|
+
.ss-w-bi-comp-meta { display: inline-flex; align-items: center; gap: 5px; flex: none; min-width: 0; }
|
|
2429
|
+
/* shared crest-flanked team row, still consumed by MarketOddsCardList */
|
|
2430
|
+
.ss-w-bi-fixture { display: flex; align-items: center; gap: 6px; min-width: 0; flex: 1; font-size: 12px; font-weight: 650; color: var(--ss-w-ink); }
|
|
2431
|
+
|
|
2432
|
+
/* row 2: one action line -- market \xB7 selection (left, market ellipsises first), odds, slip button */
|
|
2433
|
+
.ss-w-bi-action-row { display: flex; align-items: center; gap: 9px; }
|
|
2434
|
+
.ss-w-bi-pick { flex: 1; min-width: 0; display: flex; align-items: baseline; gap: 6px; }
|
|
2435
|
+
.ss-w-bi-market { font-size: 12px; font-weight: 600; line-height: 1.2; color: var(--ss-w-faint); min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
2436
|
+
.ss-w-bi-pick-dot { flex: none; color: var(--ss-w-faint); font-weight: 700; }
|
|
2437
|
+
.ss-w-bi-sel { flex: none; font-size: 12.5px; font-weight: 700; line-height: 1.2; color: var(--ss-w-ink); white-space: nowrap; }
|
|
2141
2438
|
.ss-w-bi-odds { flex: none; display: flex; align-items: baseline; justify-content: flex-end; gap: 5px; }
|
|
2142
|
-
.ss-w-bi-odds b { font-size:
|
|
2439
|
+
.ss-w-bi-odds b { font-size: 20px; font-family: ui-monospace, monospace; font-weight: 800; font-variant-numeric: tabular-nums; color: var(--ss-w-accent); line-height: 1.05; }
|
|
2143
2440
|
.ss-w-bi-book { font-size: 9.5px; color: var(--ss-w-faint); }
|
|
2441
|
+
.ss-w-bi-action-row .ss-w-slip { flex: none; }
|
|
2442
|
+
/* let the slip toast size to its own text and anchor to the card's right edge, not the button width */
|
|
2443
|
+
.ss-w-bi-action-row .ss-w-toast { left: auto; right: 0; width: max-content; max-width: 210px; white-space: normal; }
|
|
2144
2444
|
|
|
2145
2445
|
/* row 3: bar-chart glyph + reasoning prose + stats-expander chevron, in a subtle strip */
|
|
2146
2446
|
.ss-w-bi-reason { display: flex; align-items: flex-start; gap: 8px; background: var(--ss-w-bubble); border-radius: 10px; padding: 8px 10px; }
|
|
@@ -2482,34 +2782,90 @@ var WIDGET_CSS = `.ss-w-root { all: revert; }
|
|
|
2482
2782
|
/* resting form: reuses the fixture last-five pill classes (ss-w-fandash-form / -form-pill) */
|
|
2483
2783
|
.ss-w-fandash-resting-form .ss-w-fandash-form { margin-top: 2px; }
|
|
2484
2784
|
|
|
2485
|
-
/* live score:
|
|
2486
|
-
|
|
2487
|
-
|
|
2488
|
-
|
|
2489
|
-
.ss-w-fandash-live-
|
|
2490
|
-
.ss-w-fandash-live-
|
|
2491
|
-
|
|
2492
|
-
|
|
2493
|
-
|
|
2494
|
-
.ss-w-fandash-live-
|
|
2495
|
-
|
|
2496
|
-
.ss-w-fandash-live-
|
|
2497
|
-
.ss-w-fandash-live-
|
|
2498
|
-
.ss-w-fandash-live-
|
|
2499
|
-
.ss-w-fandash-live-
|
|
2500
|
-
|
|
2501
|
-
|
|
2502
|
-
|
|
2503
|
-
.ss-w-fandash-live-
|
|
2504
|
-
.ss-w-fandash-live-
|
|
2785
|
+
/* live score: pulsing Live pill, crests (real image or a coloured initials fallback), a big
|
|
2786
|
+
tabular score, and an optional scorers line. Also where the shared home/away venue-colour
|
|
2787
|
+
tokens (--ss-w-home / --ss-w-away / --ss-w-track) are defined above -- the timeline and stats
|
|
2788
|
+
cards reuse them. */
|
|
2789
|
+
.ss-w-fandash-live-top { display: flex; align-items: center; gap: 8px; }
|
|
2790
|
+
.ss-w-fandash-live-pill { display: inline-flex; flex: none; align-items: center; gap: 5px; font-size: 10px; font-weight: 800; letter-spacing: .04em; text-transform: uppercase; color: #fff; background: var(--ss-w-neg); border-radius: 6px; padding: 3px 7px; }
|
|
2791
|
+
.ss-w-fandash-live-dot { width: 5px; height: 5px; border-radius: 50%; background: #fff; animation: ss-w-fandash-live-pulse 1.3s ease-in-out infinite; }
|
|
2792
|
+
@keyframes ss-w-fandash-live-pulse { 0%, 100% { opacity: 1; } 50% { opacity: .3; } }
|
|
2793
|
+
@media (prefers-reduced-motion: reduce) {
|
|
2794
|
+
.ss-w-fandash-live-dot { animation: none; }
|
|
2795
|
+
}
|
|
2796
|
+
.ss-w-fandash-live-minute { flex: none; font-size: 12px; font-weight: 800; font-variant-numeric: tabular-nums; color: var(--ss-w-ink); }
|
|
2797
|
+
.ss-w-fandash-live-competition { margin-left: auto; font-size: 11px; font-weight: 650; color: var(--ss-w-faint); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
2798
|
+
.ss-w-fandash-live-main { display: grid; grid-template-columns: 1fr auto 1fr; align-items: center; gap: 8px; }
|
|
2799
|
+
.ss-w-fandash-live-side { display: flex; align-items: center; gap: 8px; min-width: 0; }
|
|
2800
|
+
.ss-w-fandash-live-side--away { flex-direction: row-reverse; text-align: right; }
|
|
2801
|
+
.ss-w-fandash-live-crest { width: 30px; height: 30px; border-radius: 8px; flex: none; display: grid; place-items: center; color: #fff; font-weight: 800; font-size: 10.5px; object-fit: cover; }
|
|
2802
|
+
.ss-w-fandash-live-crest--fallback.ss-w-fandash-live-crest--home { background: var(--ss-w-home); }
|
|
2803
|
+
.ss-w-fandash-live-crest--fallback.ss-w-fandash-live-crest--away { background: var(--ss-w-away); }
|
|
2804
|
+
.ss-w-fandash-live-team-name { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-size: 13px; font-weight: 700; color: var(--ss-w-ink); }
|
|
2805
|
+
.ss-w-fandash-live-score-main { display: flex; align-items: baseline; justify-content: center; font-size: 26px; font-weight: 800; font-variant-numeric: tabular-nums; line-height: 1; white-space: nowrap; color: var(--ss-w-ink); }
|
|
2806
|
+
.ss-w-fandash-live-score-sep { color: var(--ss-w-faint); font-weight: 500; margin: 0 4px; }
|
|
2807
|
+
.ss-w-fandash-live-scorers { margin-top: 2px; display: flex; justify-content: flex-end; gap: 6px; align-items: center; font-size: 11.5px; color: var(--ss-w-faint); overflow-wrap: anywhere; }
|
|
2808
|
+
.ss-w-fandash-live-scorers-icon { flex: none; color: var(--ss-w-away); }
|
|
2809
|
+
|
|
2810
|
+
/* live timeline: a compact two-sided event list down a centre spine -- each club's events sit on
|
|
2811
|
+
its own side (home left, away right), most-recent 3 shown with a "See more" toggle for the
|
|
2812
|
+
rest. A 3-column grid (txt | badge | txt) per row; the spine itself is a ::before on the list. */
|
|
2813
|
+
.ss-w-fandash-live-tl { position: relative; display: flex; flex-direction: column; gap: 6px; }
|
|
2814
|
+
.ss-w-fandash-live-tl::before { content: ""; position: absolute; left: 50%; top: 1px; bottom: 1px; width: 2px; background: var(--ss-w-line); transform: translateX(-1px); }
|
|
2815
|
+
.ss-w-fandash-live-tl-ev { display: grid; grid-template-columns: 1fr 32px 1fr; align-items: center; gap: 6px; }
|
|
2816
|
+
.ss-w-fandash-live-tl-txt { display: flex; align-items: center; gap: 6px; font-size: 12.5px; min-width: 0; }
|
|
2817
|
+
.ss-w-fandash-live-tl-ev--home .ss-w-fandash-live-tl-txt { grid-column: 1; justify-content: flex-end; }
|
|
2818
|
+
.ss-w-fandash-live-tl-ev--away .ss-w-fandash-live-tl-txt { grid-column: 3; justify-content: flex-start; flex-direction: row-reverse; }
|
|
2819
|
+
.ss-w-fandash-live-tl-who { font-weight: 650; color: var(--ss-w-ink); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
|
|
2820
|
+
.ss-w-fandash-live-tl-off { font-weight: 500; color: var(--ss-w-faint); }
|
|
2821
|
+
.ss-w-fandash-live-tl-ev--goal .ss-w-fandash-live-tl-who { font-weight: 800; }
|
|
2822
|
+
.ss-w-fandash-live-tl-badge { grid-column: 2; justify-self: center; min-width: 27px; height: 18px; padding: 0 4px; border-radius: 6px; display: grid; place-items: center; font-size: 10.5px; font-weight: 800; color: var(--ss-w-ink); background: var(--ss-w-bubble); border: 1px solid var(--ss-w-line); font-variant-numeric: tabular-nums; z-index: 1; }
|
|
2823
|
+
.ss-w-fandash-live-tl-ev--goal .ss-w-fandash-live-tl-badge { background: var(--ss-w-accent); color: #fff; border-color: transparent; }
|
|
2824
|
+
.ss-w-fandash-live-tl-ico { width: 16px; height: 16px; flex: none; display: grid; place-items: center; }
|
|
2825
|
+
.ss-w-fandash-live-tl-ico--goal { border-radius: 50%; background: color-mix(in srgb, var(--ss-w-away) 16%, transparent); }
|
|
2826
|
+
.ss-w-fandash-live-tl-ball { width: 11px; height: 11px; border-radius: 50%; background: var(--ss-w-ink); border: 1px solid var(--ss-w-ink); }
|
|
2827
|
+
.ss-w-fandash-live-tl-card { width: 8px; height: 11px; border-radius: 2px; display: block; }
|
|
2828
|
+
.ss-w-fandash-live-tl-card--yellow { background: var(--ss-w-cta); }
|
|
2829
|
+
.ss-w-fandash-live-tl-card--red { background: var(--ss-w-away); }
|
|
2830
|
+
.ss-w-fandash-live-tl-subarrows { font-size: 11px; font-weight: 900; line-height: 1; letter-spacing: -1px; color: var(--ss-w-accent); }
|
|
2831
|
+
.ss-w-fandash-live-tl-dot { width: 5px; height: 5px; border-radius: 50%; background: var(--ss-w-faint); }
|
|
2832
|
+
.ss-w-fandash-live-tl-more { margin-top: 4px; width: 100%; background: var(--ss-w-bubble); border: 1px solid var(--ss-w-line); border-radius: 9px; padding: 7px; font: inherit; font-size: 11.5px; font-weight: 700; color: var(--ss-w-accent); cursor: pointer; }
|
|
2833
|
+
.ss-w-fandash-live-tl-more:focus-visible { outline: 2px solid var(--ss-w-accent); outline-offset: 2px; }
|
|
2834
|
+
|
|
2835
|
+
/* live stats: club value / stat label / opponent value, each row backed by a home/away split
|
|
2836
|
+
bar, plus an optional momentum wave (or its text fallback) and a club/opponent colour legend.
|
|
2837
|
+
No side signal reaches this card, so the club is always the home token and the opponent is
|
|
2838
|
+
always the away token -- see LiveStatsCard.tsx. */
|
|
2839
|
+
.ss-w-fandash-live-stat-rows { display: flex; flex-direction: column; gap: 14px; }
|
|
2840
|
+
.ss-w-fandash-live-stat { display: flex; flex-direction: column; gap: 6px; }
|
|
2841
|
+
.ss-w-fandash-live-stat-row { display: grid; grid-template-columns: 1fr auto 1fr; align-items: baseline; gap: 8px; }
|
|
2505
2842
|
.ss-w-fandash-live-stat-value { font-size: 13px; font-weight: 700; font-variant-numeric: tabular-nums; color: var(--ss-w-ink); }
|
|
2506
|
-
.ss-w-fandash-live-stat-
|
|
2507
|
-
.ss-w-fandash-live-stat-
|
|
2843
|
+
.ss-w-fandash-live-stat-value--home { color: var(--ss-w-home); }
|
|
2844
|
+
.ss-w-fandash-live-stat-value--away { color: var(--ss-w-away); text-align: right; }
|
|
2845
|
+
.ss-w-fandash-live-stat-label { font-size: 10px; font-weight: 700; text-align: center; color: var(--ss-w-faint); text-transform: uppercase; letter-spacing: .04em; }
|
|
2846
|
+
.ss-w-fandash-live-bar { position: relative; height: 7px; border-radius: 999px; background: var(--ss-w-track); overflow: hidden; display: flex; }
|
|
2847
|
+
.ss-w-fandash-live-bar-home { height: 100%; background: var(--ss-w-home); }
|
|
2848
|
+
.ss-w-fandash-live-bar-away { height: 100%; background: var(--ss-w-away); margin-left: auto; }
|
|
2849
|
+
|
|
2508
2850
|
.ss-w-fandash-live-momentum { font-size: 11.5px; font-weight: 650; color: var(--ss-w-accent); }
|
|
2851
|
+
.ss-w-fandash-live-mom { margin-top: 15px; padding-top: 13px; border-top: 1px solid var(--ss-w-line); }
|
|
2852
|
+
.ss-w-fandash-live-mom-caption { display: block; margin-bottom: 5px; font-size: 10.5px; font-weight: 600; color: var(--ss-w-faint); }
|
|
2853
|
+
.ss-w-fandash-live-mom-svg { width: 100%; height: 50px; display: block; overflow: visible; }
|
|
2854
|
+
.ss-w-fandash-live-mom-home { fill: var(--ss-w-home); fill-opacity: .85; }
|
|
2855
|
+
.ss-w-fandash-live-mom-away { fill: var(--ss-w-away); fill-opacity: .85; }
|
|
2856
|
+
.ss-w-fandash-live-mom-line { fill: none; stroke: color-mix(in srgb, var(--ss-w-ink) 55%, transparent); stroke-width: 1.5; }
|
|
2857
|
+
.ss-w-fandash-live-mom-base { stroke: var(--ss-w-line); stroke-width: 1; stroke-dasharray: 3 3; }
|
|
2858
|
+
.ss-w-fandash-live-mom-now { fill: var(--ss-w-panel); stroke: var(--ss-w-ink); stroke-width: 1.5; }
|
|
2859
|
+
|
|
2860
|
+
.ss-w-fandash-live-legend { display: flex; gap: 14px; margin-top: 13px; padding-top: 13px; border-top: 1px solid var(--ss-w-line); font-size: 11px; color: var(--ss-w-faint); }
|
|
2861
|
+
.ss-w-fandash-live-legend-item { display: inline-flex; align-items: center; gap: 6px; }
|
|
2862
|
+
.ss-w-fandash-live-legend-swatch { width: 10px; height: 10px; border-radius: 3px; flex: none; }
|
|
2863
|
+
.ss-w-fandash-live-legend-swatch--home { background: var(--ss-w-home); }
|
|
2864
|
+
.ss-w-fandash-live-legend-swatch--away { background: var(--ss-w-away); }
|
|
2509
2865
|
`;
|
|
2510
2866
|
|
|
2511
2867
|
// src/StatsWidget.tsx
|
|
2512
|
-
import { jsx as
|
|
2868
|
+
import { jsx as jsx44 } from "react/jsx-runtime";
|
|
2513
2869
|
function buildClient(props) {
|
|
2514
2870
|
try {
|
|
2515
2871
|
if ("client" in props && props.client) {
|
|
@@ -2536,7 +2892,7 @@ function StatsWidget(props) {
|
|
|
2536
2892
|
const cfg = props.config;
|
|
2537
2893
|
const built = useMemo2(() => buildClient(props), [injectedClient, cfg?.operatorId, cfg?.publicKey, cfg?.baseUrl]);
|
|
2538
2894
|
const ui = useMemo2(() => normalizeUi(props.config ?? { operatorId: "", publicKey: "" }), [props.config]);
|
|
2539
|
-
const rootRef =
|
|
2895
|
+
const rootRef = useRef5(null);
|
|
2540
2896
|
const controller = useChatStream(built?.conversation ?? emptyConversation(), ui.starters, built ? ui.greetingMessage : void 0);
|
|
2541
2897
|
const slip = useMemo2(
|
|
2542
2898
|
() => ({
|
|
@@ -2555,14 +2911,14 @@ function StatsWidget(props) {
|
|
|
2555
2911
|
() => built ? createDashboardTapSignal((input) => built.conversation.sendSignal(input)) : void 0,
|
|
2556
2912
|
[built]
|
|
2557
2913
|
);
|
|
2558
|
-
|
|
2914
|
+
useEffect8(() => {
|
|
2559
2915
|
if (!built || !ui.injectStyles) return;
|
|
2560
2916
|
const node = rootRef.current?.getRootNode();
|
|
2561
2917
|
if (node) injectStyles(node);
|
|
2562
2918
|
}, [built, ui.injectStyles]);
|
|
2563
2919
|
if (!built) return null;
|
|
2564
2920
|
const attrs = themeAttrs(ui);
|
|
2565
|
-
return /* @__PURE__ */
|
|
2921
|
+
return /* @__PURE__ */ jsx44("div", { ref: rootRef, className: "ss-w-root", "data-ss-w-theme": attrs["data-ss-w-theme"], "data-ss-w-pos": attrs["data-ss-w-pos"], style: attrs.style, children: controller.state.isOpen ? /* @__PURE__ */ jsx44(ChatPanel, { controller, ui, slip, onDashboardTap }) : /* @__PURE__ */ jsx44(
|
|
2566
2922
|
ChatFab,
|
|
2567
2923
|
{
|
|
2568
2924
|
label: ui.launcherLabel,
|