@sensiblestats/widget-react 0.18.0 → 0.20.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/dist/index.js CHANGED
@@ -435,7 +435,13 @@ function toMarketOddsVM(card) {
435
435
  id: `${typeof card.matchId === "number" ? card.matchId : 0}:${str(card.marketDeveloperName) ?? ""}`,
436
436
  market: str(card.marketLabel) ?? str(card.marketDeveloperName) ?? "",
437
437
  fixture: home && away ? `${home} vs ${away}` : void 0,
438
+ homeTeamName: home,
439
+ awayTeamName: away,
440
+ homeCrest: str(card.homeTeamImagePath),
441
+ awayCrest: str(card.awayTeamImagePath),
442
+ competitionCrest: str(card.competitionImagePath),
438
443
  competition: str(card.competitionName),
444
+ kickoffUtc: str(card.kickoffUtc),
439
445
  bookmaker: rows.map((r) => str(r.bookmakerName)).find(Boolean),
440
446
  updatedAt: updated.length > 0 ? updated[updated.length - 1] : void 0,
441
447
  isLive: rows.some((r) => r.isLive === true),
@@ -582,21 +588,42 @@ function reducer(state, action) {
582
588
  }
583
589
 
584
590
  // src/useChatStream.ts
591
+ var STREAM_FALLBACK_TIMEOUT_MS = 3e4;
585
592
  function useChatStream(conversation, starterTexts, greetingMessage) {
586
593
  const seed = useMemo(() => initialState(starterTexts.map(chipFromText)), []);
587
594
  const [state, dispatch] = useReducer(reducer, seed);
588
595
  const idRef = useRef(0);
589
596
  const handleRef = useRef(null);
597
+ const fallbackTimerRef = useRef(null);
590
598
  const stateRef = useRef(state);
591
599
  stateRef.current = state;
592
600
  const greetedRef = useRef(false);
593
601
  const nextId = () => `m${idRef.current++}`;
602
+ const clearFallbackTimer = useCallback(() => {
603
+ if (fallbackTimerRef.current != null) {
604
+ clearTimeout(fallbackTimerRef.current);
605
+ fallbackTimerRef.current = null;
606
+ }
607
+ }, []);
608
+ const armFallbackTimer = useCallback((onFire) => {
609
+ clearFallbackTimer();
610
+ fallbackTimerRef.current = setTimeout(() => {
611
+ fallbackTimerRef.current = null;
612
+ handleRef.current?.cancel();
613
+ handleRef.current = null;
614
+ onFire();
615
+ }, STREAM_FALLBACK_TIMEOUT_MS);
616
+ }, [clearFallbackTimer]);
594
617
  const consume = useCallback((handle, onError) => {
595
618
  ;
596
619
  (async () => {
597
620
  try {
598
- for await (const event of handle) dispatch({ kind: "EVENT", event });
621
+ for await (const event of handle) {
622
+ dispatch({ kind: "EVENT", event });
623
+ if (event.type === "turn_complete" || event.type === "error") clearFallbackTimer();
624
+ }
599
625
  } catch (err) {
626
+ if (handleRef.current === handle) clearFallbackTimer();
600
627
  if (err instanceof WidgetSdkError2) onError(err);
601
628
  else if (err?.name === "AbortError") {
602
629
  } else throw err;
@@ -604,18 +631,23 @@ function useChatStream(conversation, starterTexts, greetingMessage) {
604
631
  if (handleRef.current === handle) handleRef.current = null;
605
632
  }
606
633
  })();
607
- }, []);
634
+ }, [clearFallbackTimer]);
608
635
  const run = useCallback((input, displayText) => {
609
636
  handleRef.current?.cancel();
637
+ clearFallbackTimer();
610
638
  dispatch({ kind: "USER_SENT", input, userId: nextId(), assistantId: nextId(), displayText });
611
639
  const handle = conversation.send(input);
612
640
  handleRef.current = handle;
641
+ armFallbackTimer(() => {
642
+ dispatch({ kind: "STOP" });
643
+ dispatch({ kind: "STREAM_ERROR", error: new WidgetSdkError2("No response received in time", "timeout") });
644
+ });
613
645
  consume(handle, (err) => dispatch({ kind: "STREAM_ERROR", error: err }));
614
- }, [conversation, consume]);
646
+ }, [conversation, consume, clearFallbackTimer, armFallbackTimer]);
615
647
  const send = useCallback((text, opts) => {
616
648
  const trimmed = text.trim();
617
649
  if (!trimmed) return;
618
- run({ message: trimmed, actionId: opts?.actionId, grounding: opts?.grounding }, opts?.displayText);
650
+ run({ message: trimmed, actionId: opts?.actionId, actionRef: opts?.actionRef }, opts?.displayText);
619
651
  }, [run]);
620
652
  const retry = useCallback(() => {
621
653
  const last = stateRef.current.lastUserInput;
@@ -627,25 +659,29 @@ function useChatStream(conversation, starterTexts, greetingMessage) {
627
659
  greetedRef.current = true;
628
660
  try {
629
661
  handleRef.current?.cancel();
662
+ clearFallbackTimer();
630
663
  dispatch({ kind: "GREETING_SENT", assistantId: nextId() });
631
664
  const handle = conversation.send({ message });
632
665
  handleRef.current = handle;
666
+ armFallbackTimer(() => dispatch({ kind: "STOP" }));
633
667
  consume(handle, () => dispatch({ kind: "STOP" }));
634
668
  } catch {
635
669
  dispatch({ kind: "STOP" });
636
670
  }
637
- }, [greetingMessage, conversation, consume]);
671
+ }, [greetingMessage, conversation, consume, clearFallbackTimer, armFallbackTimer]);
638
672
  const open = useCallback(() => dispatch({ kind: "OPEN" }), []);
639
673
  const close = useCallback(() => {
640
674
  handleRef.current?.cancel();
641
675
  handleRef.current = null;
676
+ clearFallbackTimer();
642
677
  dispatch({ kind: "CLOSE" });
643
- }, []);
678
+ }, [clearFallbackTimer]);
644
679
  const stop = useCallback(() => {
645
680
  handleRef.current?.cancel();
646
681
  handleRef.current = null;
682
+ clearFallbackTimer();
647
683
  dispatch({ kind: "STOP" });
648
- }, []);
684
+ }, [clearFallbackTimer]);
649
685
  const toggle = useCallback(() => {
650
686
  if (stateRef.current.isOpen) close();
651
687
  else open();
@@ -711,7 +747,7 @@ function ChatFab({ label, greeting, popupText, onOpen, onDismissPopup, dismissLa
711
747
  }
712
748
 
713
749
  // src/components/ChatPanel.tsx
714
- import { useEffect as useEffect5, useState as useState8 } from "react";
750
+ import { useEffect as useEffect5, useState as useState9 } from "react";
715
751
 
716
752
  // src/components/ChatHeader.tsx
717
753
  import { jsx as jsx4, jsxs as jsxs3 } from "react/jsx-runtime";
@@ -844,7 +880,7 @@ function EntityCardList({ cards, disabled, showLessLabel = "Show less", showMore
844
880
  }
845
881
 
846
882
  // src/components/BettingInsightList.tsx
847
- import { useState as useState3 } from "react";
883
+ import { useState as useState4 } from "react";
848
884
 
849
885
  // src/components/AddToSlipButton.tsx
850
886
  import { useCallback as useCallback2, useState as useState2 } from "react";
@@ -967,53 +1003,64 @@ function OddsMovementBadge({
967
1003
  );
968
1004
  }
969
1005
 
1006
+ // src/components/Crest.tsx
1007
+ import { useState as useState3 } from "react";
1008
+ import { jsx as jsx10 } from "react/jsx-runtime";
1009
+ function initials2(name) {
1010
+ return (name ?? "").trim().slice(0, 1).toUpperCase() || "?";
1011
+ }
1012
+ function Crest2({ url, name, kind }) {
1013
+ const cls = kind === "comp" ? "ss-w-bi-comp-logo" : "ss-w-bi-crest";
1014
+ const [failed, setFailed] = useState3(false);
1015
+ if (url && !failed) {
1016
+ return /* @__PURE__ */ jsx10("img", { className: cls, src: url, alt: "", "aria-hidden": "true", onError: () => setFailed(true) });
1017
+ }
1018
+ return /* @__PURE__ */ jsx10("span", { className: `${cls} ss-w-bi-crest-mono`, "aria-hidden": "true", children: initials2(name) });
1019
+ }
1020
+
970
1021
  // src/components/BettingInsightList.tsx
971
- import { Fragment, jsx as jsx10, jsxs as jsxs7 } from "react/jsx-runtime";
1022
+ import { Fragment, jsx as jsx11, jsxs as jsxs7 } from "react/jsx-runtime";
972
1023
  function StatRows2({ stats }) {
973
- return /* @__PURE__ */ jsx10("div", { className: "ss-w-bi-rows", children: stats.map((s) => /* @__PURE__ */ jsxs7("div", { className: "ss-w-bi-row", children: [
974
- /* @__PURE__ */ jsx10("span", { className: "ss-w-bi-row-lbl", children: s.label }),
975
- /* @__PURE__ */ jsx10("span", { className: "ss-w-bi-row-num", children: s.value })
1024
+ return /* @__PURE__ */ jsx11("div", { className: "ss-w-bi-rows", children: stats.map((s) => /* @__PURE__ */ jsxs7("div", { className: "ss-w-bi-row", children: [
1025
+ /* @__PURE__ */ jsx11("span", { className: "ss-w-bi-row-lbl", children: s.label }),
1026
+ /* @__PURE__ */ jsx11("span", { className: "ss-w-bi-row-num", children: s.value })
976
1027
  ] }, `${s.label}:${s.value}`)) });
977
1028
  }
978
1029
  function ScopedStats({ scopes }) {
979
- const [active, setActive] = useState3(0);
1030
+ const [active, setActive] = useState4(0);
980
1031
  const current = scopes[active] ?? scopes[0] ?? { key: "", label: "", stats: [] };
981
1032
  return /* @__PURE__ */ jsxs7("div", { className: "ss-w-bi-scoped", children: [
982
- /* @__PURE__ */ jsx10("div", { className: "ss-w-bi-scopes", role: "group", children: scopes.map((s, i) => /* @__PURE__ */ jsx10("button", { type: "button", className: "ss-w-bi-chip", "aria-pressed": i === active, onClick: () => setActive(i), children: s.label }, s.key)) }),
983
- /* @__PURE__ */ jsx10(StatRows2, { stats: current.stats }, current.key)
1033
+ /* @__PURE__ */ jsx11("div", { className: "ss-w-bi-scopes", role: "group", children: scopes.map((s, i) => /* @__PURE__ */ jsx11("button", { type: "button", className: "ss-w-bi-chip", "aria-pressed": i === active, onClick: () => setActive(i), children: s.label }, s.key)) }),
1034
+ /* @__PURE__ */ jsx11(StatRows2, { stats: current.stats }, current.key)
984
1035
  ] });
985
1036
  }
986
1037
  function hasStatsContent(it) {
987
1038
  return !!it.scopes && it.scopes.length >= 2 || it.stats.length > 0;
988
1039
  }
989
- function initials2(name) {
990
- return (name ?? "").trim().slice(0, 1).toUpperCase() || "?";
991
- }
992
- function Crest2({ url, name, kind }) {
993
- const cls = kind === "comp" ? "ss-w-bi-comp-logo" : "ss-w-bi-crest";
994
- if (url) return /* @__PURE__ */ jsx10("span", { className: cls, style: { backgroundImage: `url(${url})` }, "aria-hidden": "true" });
995
- return /* @__PURE__ */ jsx10("span", { className: `${cls} ss-w-bi-crest-mono`, "aria-hidden": "true", children: initials2(name) });
996
- }
997
- function StatsDisclosure({ it, ui }) {
998
- const [open, setOpen] = useState3(false);
1040
+ function ReasonStrip({ it, ui }) {
1041
+ const [open, setOpen] = useState4(false);
999
1042
  const hasScopes = !!it.scopes && it.scopes.length >= 2;
1000
1043
  const hasStats = hasStatsContent(it);
1001
- if (!hasStats) return null;
1002
- return /* @__PURE__ */ jsxs7("div", { className: "ss-w-bi-stats", children: [
1003
- /* @__PURE__ */ jsxs7(
1044
+ const hasText = !!it.text;
1045
+ if (!hasText && !hasStats) return null;
1046
+ const expandable = hasStats || (it.text?.length ?? 0) > 90;
1047
+ return /* @__PURE__ */ jsxs7("div", { className: "ss-w-bi-reason", children: [
1048
+ /* @__PURE__ */ jsx11("span", { className: "ss-w-bi-chart", "aria-hidden": "true" }),
1049
+ /* @__PURE__ */ jsxs7("div", { className: "ss-w-bi-reason-body", children: [
1050
+ hasText ? /* @__PURE__ */ jsx11("p", { className: `ss-w-bi-text${open ? "" : " ss-w-bi-text-clamp"}`, children: it.text }) : null,
1051
+ open && hasStats ? hasScopes ? /* @__PURE__ */ jsx11(ScopedStats, { scopes: it.scopes }) : /* @__PURE__ */ jsx11(StatRows2, { stats: it.stats }) : null
1052
+ ] }),
1053
+ expandable ? /* @__PURE__ */ jsx11(
1004
1054
  "button",
1005
1055
  {
1006
1056
  type: "button",
1007
- className: "ss-w-bi-toggle",
1057
+ className: "ss-w-bi-expand",
1008
1058
  "aria-expanded": open,
1059
+ "aria-label": open ? ui?.hideStats ?? "Hide stats" : ui?.showStats ?? "Show stats",
1009
1060
  onClick: () => setOpen((v) => !v),
1010
- children: [
1011
- /* @__PURE__ */ jsx10("span", { children: open ? ui?.hideStats ?? "Hide stats" : ui?.showStats ?? "Show stats" }),
1012
- /* @__PURE__ */ jsx10("span", { className: `ss-w-bi-chev${open ? " ss-w-bi-chev-up" : ""}`, "aria-hidden": "true" })
1013
- ]
1061
+ children: /* @__PURE__ */ jsx11("span", { className: `ss-w-bi-arrow${open ? " ss-w-bi-arrow-open" : ""}`, "aria-hidden": "true" })
1014
1062
  }
1015
- ),
1016
- open ? hasScopes ? /* @__PURE__ */ jsx10(ScopedStats, { scopes: it.scopes }) : /* @__PURE__ */ jsx10(StatRows2, { stats: it.stats }) : null
1063
+ ) : null
1017
1064
  ] });
1018
1065
  }
1019
1066
  function InsightCard({ it, ui, slip }) {
@@ -1021,16 +1068,15 @@ function InsightCard({ it, ui, slip }) {
1021
1068
  const kickoff = it.kickoffUtc && ui?.bettingKickoff ? ui.bettingKickoff(it.kickoffUtc) : void 0;
1022
1069
  const hasNamedTeams = !!it.homeTeamName && !!it.awayTeamName;
1023
1070
  const showHeader = !!it.competition || live || !!kickoff;
1024
- const showReason = !!it.text || hasStatsContent(it);
1025
1071
  return /* @__PURE__ */ jsxs7("div", { className: "ss-w-binsight", children: [
1026
1072
  showHeader ? /* @__PURE__ */ jsxs7("div", { className: "ss-w-bi-header", children: [
1027
- /* @__PURE__ */ jsx10("span", { className: "ss-w-bi-header-left", children: it.competition ? /* @__PURE__ */ jsxs7(Fragment, { children: [
1028
- /* @__PURE__ */ jsx10(Crest2, { kind: "comp", url: it.competitionCrest, name: it.competition }),
1029
- /* @__PURE__ */ jsx10("span", { className: "ss-w-bi-comp", children: it.competition })
1073
+ /* @__PURE__ */ jsx11("span", { className: "ss-w-bi-header-left", children: it.competition ? /* @__PURE__ */ jsxs7(Fragment, { children: [
1074
+ /* @__PURE__ */ jsx11(Crest2, { kind: "comp", url: it.competitionCrest, name: it.competition }),
1075
+ /* @__PURE__ */ jsx11("span", { className: "ss-w-bi-comp", children: it.competition })
1030
1076
  ] }) : null }),
1031
1077
  /* @__PURE__ */ jsxs7("span", { className: "ss-w-bi-header-right", children: [
1032
1078
  live ? /* @__PURE__ */ jsxs7("span", { className: "ss-w-bi-live", children: [
1033
- /* @__PURE__ */ jsx10("span", { className: "ss-w-bi-live-dot", "aria-hidden": "true" }),
1079
+ /* @__PURE__ */ jsx11("span", { className: "ss-w-bi-live-dot", "aria-hidden": "true" }),
1034
1080
  ui?.liveLabel ?? "Live"
1035
1081
  ] }) : null,
1036
1082
  kickoff ? /* @__PURE__ */ jsxs7("span", { className: "ss-w-bi-kick", children: [
@@ -1041,85 +1087,100 @@ function InsightCard({ it, ui, slip }) {
1041
1087
  ] })
1042
1088
  ] }) : null,
1043
1089
  /* @__PURE__ */ jsxs7("div", { className: "ss-w-bi-body", children: [
1044
- /* @__PURE__ */ jsx10("div", { className: "ss-w-bi-fixture", children: hasNamedTeams ? /* @__PURE__ */ jsxs7(Fragment, { children: [
1045
- /* @__PURE__ */ jsx10(Crest2, { kind: "team", url: it.homeCrest, name: it.homeTeamName }),
1046
- /* @__PURE__ */ jsx10("span", { className: "ss-w-bi-team", children: it.homeTeamName }),
1047
- /* @__PURE__ */ jsx10("span", { className: "ss-w-bi-vs", "aria-hidden": "true", children: "vs" }),
1048
- /* @__PURE__ */ jsx10("span", { className: "ss-w-bi-team", children: it.awayTeamName }),
1049
- /* @__PURE__ */ jsx10(Crest2, { kind: "team", url: it.awayCrest, name: it.awayTeamName })
1050
- ] }) : it.fixture ? /* @__PURE__ */ jsx10("span", { className: "ss-w-bi-fixture-txt", children: it.fixture }) : null }),
1090
+ /* @__PURE__ */ jsx11("div", { className: "ss-w-bi-fixture", children: hasNamedTeams ? /* @__PURE__ */ jsxs7(Fragment, { children: [
1091
+ /* @__PURE__ */ jsx11(Crest2, { kind: "team", url: it.homeCrest, name: it.homeTeamName }),
1092
+ /* @__PURE__ */ jsx11("span", { className: "ss-w-bi-team", children: it.homeTeamName }),
1093
+ /* @__PURE__ */ jsx11("span", { className: "ss-w-bi-vs", "aria-hidden": "true", children: "vs" }),
1094
+ /* @__PURE__ */ jsx11("span", { className: "ss-w-bi-team", children: it.awayTeamName }),
1095
+ /* @__PURE__ */ jsx11(Crest2, { kind: "team", url: it.awayCrest, name: it.awayTeamName })
1096
+ ] }) : it.fixture ? /* @__PURE__ */ jsx11("span", { className: "ss-w-bi-fixture-txt", children: it.fixture }) : null }),
1051
1097
  /* @__PURE__ */ jsxs7("div", { className: "ss-w-bi-action", children: [
1052
1098
  /* @__PURE__ */ jsxs7("span", { className: "ss-w-bi-mkt", children: [
1053
- /* @__PURE__ */ jsx10("span", { className: "ss-w-bi-market", children: it.market }),
1054
- it.selection ? /* @__PURE__ */ jsx10("span", { className: "ss-w-bi-sel", children: it.selection }) : null
1099
+ /* @__PURE__ */ jsx11("span", { className: "ss-w-bi-market", children: it.market }),
1100
+ it.selection ? /* @__PURE__ */ jsx11("span", { className: "ss-w-bi-sel", children: it.selection }) : null
1055
1101
  ] }),
1056
- it.isMarketFloor ? /* @__PURE__ */ jsx10("span", { className: "ss-w-bi-floor", children: ui?.marketFloorLabel ?? "Market price \xB7 not a value pick" }) : null,
1102
+ it.isMarketFloor ? /* @__PURE__ */ jsx11("span", { className: "ss-w-bi-floor", children: ui?.marketFloorLabel ?? "Market price \xB7 not a value pick" }) : null,
1057
1103
  it.odds ? /* @__PURE__ */ jsxs7("span", { className: "ss-w-bi-odds", children: [
1058
- /* @__PURE__ */ jsx10("span", { className: "ss-w-bi-odds-cap", children: ui?.oddsLabel ?? "Odds" }),
1059
- /* @__PURE__ */ jsx10("b", { children: it.odds }),
1060
- /* @__PURE__ */ jsx10(OddsMovementBadge, { previous: it.previousOdds, current: it.odds, direction: it.movementDirection, ui }),
1061
- it.bookmaker ? /* @__PURE__ */ jsx10("span", { className: "ss-w-bi-book", children: it.bookmaker }) : null
1104
+ /* @__PURE__ */ jsx11("b", { children: it.odds }),
1105
+ /* @__PURE__ */ jsx11(OddsMovementBadge, { previous: it.previousOdds, current: it.odds, direction: it.movementDirection, ui }),
1106
+ it.bookmaker ? /* @__PURE__ */ jsx11("span", { className: "ss-w-bi-book", children: it.bookmaker }) : null
1062
1107
  ] }) : null,
1063
- /* @__PURE__ */ jsx10(AddToSlipButton, { it, slip, ui })
1108
+ /* @__PURE__ */ jsx11(AddToSlipButton, { it, slip, ui })
1064
1109
  ] })
1065
1110
  ] }),
1066
- showReason ? /* @__PURE__ */ jsxs7("div", { className: "ss-w-bi-reason", children: [
1067
- /* @__PURE__ */ jsx10("span", { className: "ss-w-bi-chart", "aria-hidden": "true" }),
1068
- /* @__PURE__ */ jsxs7("div", { className: "ss-w-bi-reason-body", children: [
1069
- it.text ? /* @__PURE__ */ jsx10("p", { className: "ss-w-bi-text", children: it.text }) : null,
1070
- /* @__PURE__ */ jsx10(StatsDisclosure, { it, ui })
1071
- ] })
1072
- ] }) : null,
1073
- it.disclaimer ? /* @__PURE__ */ jsx10("p", { className: "ss-w-bi-disc", children: it.disclaimer }) : null
1111
+ /* @__PURE__ */ jsx11(ReasonStrip, { it, ui }),
1112
+ it.disclaimer ? /* @__PURE__ */ jsx11("p", { className: "ss-w-bi-disc", children: it.disclaimer }) : null
1074
1113
  ] });
1075
1114
  }
1076
1115
  function BettingInsightList({ insights, ui, slip }) {
1077
1116
  if (!insights || insights.length === 0) return null;
1078
- return /* @__PURE__ */ jsx10("div", { className: "ss-w-binsights", children: insights.map((it) => /* @__PURE__ */ jsx10(InsightCard, { it, ui, slip }, it.id)) });
1117
+ return /* @__PURE__ */ jsx11("div", { className: "ss-w-binsights", children: insights.map((it) => /* @__PURE__ */ jsx11(InsightCard, { it, ui, slip }, it.id)) });
1079
1118
  }
1080
1119
 
1081
1120
  // src/components/MarketOddsCardList.tsx
1082
- import { useState as useState4 } from "react";
1083
- import { jsx as jsx11, jsxs as jsxs8 } from "react/jsx-runtime";
1121
+ import { useState as useState5 } from "react";
1122
+ import { Fragment as Fragment2, jsx as jsx12, jsxs as jsxs8 } from "react/jsx-runtime";
1084
1123
  function OddsRow({ vm, row, ui, slip }) {
1085
1124
  const selection = vm.addToSlipEnabled ? marketOddsRowToSlipSelection(vm, row, slip.conversationId) : null;
1086
1125
  return /* @__PURE__ */ jsxs8("div", { className: "ss-w-mo-row", children: [
1087
- /* @__PURE__ */ jsx11("span", { className: "ss-w-mo-sel", children: row.selection }),
1088
- /* @__PURE__ */ jsx11(OddsMovementBadge, { previous: row.previousOdds, current: row.odds, direction: row.movementDirection, ui }),
1089
- /* @__PURE__ */ jsx11("span", { className: "ss-w-mo-price", children: row.odds }),
1090
- /* @__PURE__ */ jsx11(SlipButton, { selection, shownOdds: row.odds, marketName: vm.market, slip, ui })
1126
+ /* @__PURE__ */ jsx12("span", { className: "ss-w-mo-sel", children: row.selection }),
1127
+ /* @__PURE__ */ jsx12(OddsMovementBadge, { previous: row.previousOdds, current: row.odds, direction: row.movementDirection, ui }),
1128
+ /* @__PURE__ */ jsx12("span", { className: "ss-w-mo-price", children: row.odds }),
1129
+ /* @__PURE__ */ jsx12(SlipButton, { selection, shownOdds: row.odds, marketName: vm.market, slip, ui })
1091
1130
  ] });
1092
1131
  }
1093
1132
  function MarketOddsCard({ vm, ui, slip }) {
1094
- const [open, setOpen] = useState4(false);
1133
+ const [open, setOpen] = useState5(false);
1134
+ const kickoff = vm.kickoffUtc && ui?.bettingKickoff ? ui.bettingKickoff(vm.kickoffUtc) : void 0;
1135
+ const hasNamedTeams = !!vm.homeTeamName && !!vm.awayTeamName;
1136
+ const showHeader = !!vm.competition || vm.isLive || !!kickoff;
1095
1137
  return /* @__PURE__ */ jsxs8("div", { className: "ss-w-mocard", children: [
1096
- /* @__PURE__ */ jsxs8("div", { className: "ss-w-mo-head", children: [
1097
- /* @__PURE__ */ jsx11("span", { className: "ss-w-mo-market", children: vm.market }),
1098
- /* @__PURE__ */ jsx11("span", { className: "ss-w-mo-tag", children: ui?.marketPrices ?? "Market prices" })
1099
- ] }),
1100
- vm.fixture || vm.competition ? /* @__PURE__ */ jsxs8("div", { className: "ss-w-mo-fixture", children: [
1101
- vm.fixture ? /* @__PURE__ */ jsx11("span", { children: vm.fixture }) : null,
1102
- vm.competition ? /* @__PURE__ */ jsx11("span", { className: "ss-w-mo-comp", children: vm.competition }) : null
1138
+ showHeader ? /* @__PURE__ */ jsxs8("div", { className: "ss-w-bi-header", children: [
1139
+ /* @__PURE__ */ jsx12("span", { className: "ss-w-bi-header-left", children: vm.competition ? /* @__PURE__ */ jsxs8(Fragment2, { children: [
1140
+ /* @__PURE__ */ jsx12(Crest2, { kind: "comp", url: vm.competitionCrest, name: vm.competition }),
1141
+ /* @__PURE__ */ jsx12("span", { className: "ss-w-bi-comp", children: vm.competition })
1142
+ ] }) : null }),
1143
+ /* @__PURE__ */ jsxs8("span", { className: "ss-w-bi-header-right", children: [
1144
+ vm.isLive ? /* @__PURE__ */ jsxs8("span", { className: "ss-w-bi-live", children: [
1145
+ /* @__PURE__ */ jsx12("span", { className: "ss-w-bi-live-dot", "aria-hidden": "true" }),
1146
+ ui?.liveLabel ?? "Live"
1147
+ ] }) : null,
1148
+ kickoff ? /* @__PURE__ */ jsxs8("span", { className: "ss-w-bi-kick", children: [
1149
+ kickoff.dayLabel,
1150
+ " ",
1151
+ kickoff.time
1152
+ ] }) : null
1153
+ ] })
1154
+ ] }) : null,
1155
+ hasNamedTeams || vm.fixture || vm.market ? /* @__PURE__ */ jsxs8("div", { className: "ss-w-mo-sub", children: [
1156
+ hasNamedTeams ? /* @__PURE__ */ jsxs8("div", { className: "ss-w-bi-fixture", children: [
1157
+ /* @__PURE__ */ jsx12(Crest2, { kind: "team", url: vm.homeCrest, name: vm.homeTeamName }),
1158
+ /* @__PURE__ */ jsx12("span", { className: "ss-w-bi-team", children: vm.homeTeamName }),
1159
+ /* @__PURE__ */ jsx12("span", { className: "ss-w-bi-vs", "aria-hidden": "true", children: "vs" }),
1160
+ /* @__PURE__ */ jsx12("span", { className: "ss-w-bi-team", children: vm.awayTeamName }),
1161
+ /* @__PURE__ */ jsx12(Crest2, { kind: "team", url: vm.awayCrest, name: vm.awayTeamName })
1162
+ ] }) : vm.fixture ? /* @__PURE__ */ jsx12("span", { className: "ss-w-mo-teams", children: vm.fixture }) : null,
1163
+ vm.market ? /* @__PURE__ */ jsx12("span", { className: "ss-w-mo-market", children: vm.market }) : null
1103
1164
  ] }) : null,
1104
1165
  /* @__PURE__ */ jsxs8("div", { className: "ss-w-mo-rows", children: [
1105
- vm.mainRows.map((row) => /* @__PURE__ */ jsx11(OddsRow, { vm, row, ui, slip }, row.key)),
1106
- open ? vm.moreRows.map((row) => /* @__PURE__ */ jsx11(OddsRow, { vm, row, ui, slip }, row.key)) : null
1166
+ vm.mainRows.map((row) => /* @__PURE__ */ jsx12(OddsRow, { vm, row, ui, slip }, row.key)),
1167
+ open ? vm.moreRows.map((row) => /* @__PURE__ */ jsx12(OddsRow, { vm, row, ui, slip }, row.key)) : null
1107
1168
  ] }),
1108
- vm.moreRows.length > 0 ? /* @__PURE__ */ jsxs8("button", { type: "button", className: "ss-w-bi-toggle", "aria-expanded": open, onClick: () => setOpen((v) => !v), children: [
1109
- /* @__PURE__ */ jsx11("span", { children: open ? ui?.hideLines ?? "Hide lines" : `${ui?.moreLines ?? "More lines"} (${vm.moreRows.length})` }),
1110
- /* @__PURE__ */ jsx11("span", { className: `ss-w-bi-chev${open ? " ss-w-bi-chev-up" : ""}`, "aria-hidden": "true" })
1169
+ vm.moreRows.length > 0 ? /* @__PURE__ */ jsxs8("button", { type: "button", className: "ss-w-mo-more", "aria-expanded": open, onClick: () => setOpen((v) => !v), children: [
1170
+ /* @__PURE__ */ jsx12("span", { children: open ? ui?.hideLines ?? "Hide lines" : `${ui?.moreLines ?? "More lines"} (${vm.moreRows.length})` }),
1171
+ /* @__PURE__ */ jsx12("span", { className: `ss-w-mo-more-chev${open ? " ss-w-mo-more-chev-up" : ""}`, "aria-hidden": "true" })
1111
1172
  ] }) : null,
1112
- vm.bookmaker ? /* @__PURE__ */ jsx11("div", { className: "ss-w-mo-foot", children: /* @__PURE__ */ jsx11("span", { children: vm.bookmaker }) }) : null,
1113
- vm.disclaimer ? /* @__PURE__ */ jsx11("p", { className: "ss-w-bi-disc", children: vm.disclaimer }) : null
1173
+ vm.bookmaker ? /* @__PURE__ */ jsx12("div", { className: "ss-w-mo-foot", children: /* @__PURE__ */ jsx12("span", { children: vm.bookmaker }) }) : null,
1174
+ vm.disclaimer ? /* @__PURE__ */ jsx12("p", { className: "ss-w-bi-disc", children: vm.disclaimer }) : null
1114
1175
  ] });
1115
1176
  }
1116
1177
  function MarketOddsCardList({ cards, ui, slip }) {
1117
1178
  if (!cards || cards.length === 0) return null;
1118
- return /* @__PURE__ */ jsx11("div", { className: "ss-w-mocards", children: cards.map((vm) => /* @__PURE__ */ jsx11(MarketOddsCard, { vm, ui, slip }, vm.id)) });
1179
+ return /* @__PURE__ */ jsx12("div", { className: "ss-w-mocards", children: cards.map((vm) => /* @__PURE__ */ jsx12(MarketOddsCard, { vm, ui, slip }, vm.id)) });
1119
1180
  }
1120
1181
 
1121
1182
  // src/components/OddsMovementCard.tsx
1122
- import { Fragment as Fragment2, jsx as jsx12, jsxs as jsxs9 } from "react/jsx-runtime";
1183
+ import { Fragment as Fragment3, jsx as jsx13, jsxs as jsxs9 } from "react/jsx-runtime";
1123
1184
  function directionWord(vm, ui) {
1124
1185
  if (vm.direction === "shortened") return ui?.oddsShortened ?? "shortened";
1125
1186
  if (vm.direction === "drifted") return ui?.oddsDrifted ?? "drifted";
@@ -1131,16 +1192,16 @@ function OddsMovementCard({ vm, ui }) {
1131
1192
  const aria = ui?.chartAria ? ui.chartAria(vm.opening, vm.current, word, vm.windowHours) : `Price movement over the last ${vm.windowHours} hours: opened at ${vm.opening}, currently ${vm.current}${drift}.`;
1132
1193
  return /* @__PURE__ */ jsxs9("div", { className: "ss-w-omcard", children: [
1133
1194
  /* @__PURE__ */ jsxs9("div", { className: "ss-w-om-head", children: [
1134
- /* @__PURE__ */ jsx12("span", { className: "ss-w-om-market", children: vm.market }),
1135
- /* @__PURE__ */ jsx12("span", { className: "ss-w-om-tag", children: ui?.priceMovement ?? "Price movement" })
1195
+ /* @__PURE__ */ jsx13("span", { className: "ss-w-om-market", children: vm.market }),
1196
+ /* @__PURE__ */ jsx13("span", { className: "ss-w-om-tag", children: ui?.priceMovement ?? "Price movement" })
1136
1197
  ] }),
1137
1198
  vm.fixture ? /* @__PURE__ */ jsxs9("div", { className: "ss-w-om-fixture", children: [
1138
- /* @__PURE__ */ jsx12("span", { children: vm.fixture }),
1139
- vm.competition ? /* @__PURE__ */ jsx12("span", { className: "ss-w-om-comp", children: vm.competition }) : null
1199
+ /* @__PURE__ */ jsx13("span", { children: vm.fixture }),
1200
+ vm.competition ? /* @__PURE__ */ jsx13("span", { className: "ss-w-om-comp", children: vm.competition }) : null
1140
1201
  ] }) : null,
1141
- /* @__PURE__ */ jsx12("div", { className: "ss-w-om-sel", children: vm.selection }),
1142
- /* @__PURE__ */ jsx12(OddsMovementBadge, { previous: vm.opening, current: vm.current, direction: vm.direction, ui }),
1143
- vm.geometry === null ? /* @__PURE__ */ jsx12("div", { className: "ss-w-om-empty", children: vm.observationCount === 1 ? ui?.singlePricePoint ?? "Only one price recorded so far for this selection." : ui?.notEnoughHistory ?? "No price history recorded yet for this selection." }) : /* @__PURE__ */ jsxs9(
1202
+ /* @__PURE__ */ jsx13("div", { className: "ss-w-om-sel", children: vm.selection }),
1203
+ /* @__PURE__ */ jsx13(OddsMovementBadge, { previous: vm.opening, current: vm.current, direction: vm.direction, ui }),
1204
+ vm.geometry === null ? /* @__PURE__ */ jsx13("div", { className: "ss-w-om-empty", children: vm.observationCount === 1 ? ui?.singlePricePoint ?? "Only one price recorded so far for this selection." : ui?.notEnoughHistory ?? "No price history recorded yet for this selection." }) : /* @__PURE__ */ jsxs9(
1144
1205
  "svg",
1145
1206
  {
1146
1207
  className: "ss-w-om-chart",
@@ -1150,15 +1211,15 @@ function OddsMovementCard({ vm, ui }) {
1150
1211
  "aria-label": aria,
1151
1212
  children: [
1152
1213
  vm.geometry.gridlines.map((g) => /* @__PURE__ */ jsxs9("g", { children: [
1153
- /* @__PURE__ */ jsx12("line", { className: "ss-w-om-grid", x1: 40, y1: g.y, x2: CHART_VIEW_W - 8, y2: g.y }),
1154
- /* @__PURE__ */ jsx12("text", { className: "ss-w-om-ylab", x: 34, y: g.y + 3, textAnchor: "end", children: g.price.toFixed(2) })
1214
+ /* @__PURE__ */ jsx13("line", { className: "ss-w-om-grid", x1: 40, y1: g.y, x2: CHART_VIEW_W - 8, y2: g.y }),
1215
+ /* @__PURE__ */ jsx13("text", { className: "ss-w-om-ylab", x: 34, y: g.y + 3, textAnchor: "end", children: g.price.toFixed(2) })
1155
1216
  ] }, g.price)),
1156
- /* @__PURE__ */ jsx12("path", { className: "ss-w-om-line", d: vm.geometry.path, fill: "none" }),
1157
- vm.geometry.markers.map((m, i) => /* @__PURE__ */ jsx12("circle", { className: "ss-w-om-dot", cx: m.x, cy: m.y, r: 2.5 }, i)),
1158
- ui?.chartTimeLabel ? /* @__PURE__ */ jsxs9(Fragment2, { children: [
1159
- /* @__PURE__ */ jsx12("text", { className: "ss-w-om-xlab", x: 40, y: CHART_VIEW_H - 8, textAnchor: "start", children: ui.chartTimeLabel(vm.geometry.startMs) }),
1160
- /* @__PURE__ */ jsx12("text", { className: "ss-w-om-xlab", x: CHART_VIEW_W - 8, y: CHART_VIEW_H - 8, textAnchor: "end", children: ui.chartTimeLabel(vm.geometry.endMs) })
1161
- ] }) : /* @__PURE__ */ jsx12("text", { className: "ss-w-om-xlab", x: 40, y: CHART_VIEW_H - 8, children: ui?.movementWindow ? ui.movementWindow(vm.windowHours) : `Last ${vm.windowHours}h` })
1217
+ /* @__PURE__ */ jsx13("path", { className: "ss-w-om-line", d: vm.geometry.path, fill: "none" }),
1218
+ vm.geometry.markers.map((m, i) => /* @__PURE__ */ jsx13("circle", { className: "ss-w-om-dot", cx: m.x, cy: m.y, r: 2.5 }, i)),
1219
+ ui?.chartTimeLabel ? /* @__PURE__ */ jsxs9(Fragment3, { children: [
1220
+ /* @__PURE__ */ jsx13("text", { className: "ss-w-om-xlab", x: 40, y: CHART_VIEW_H - 8, textAnchor: "start", children: ui.chartTimeLabel(vm.geometry.startMs) }),
1221
+ /* @__PURE__ */ jsx13("text", { className: "ss-w-om-xlab", x: CHART_VIEW_W - 8, y: CHART_VIEW_H - 8, textAnchor: "end", children: ui.chartTimeLabel(vm.geometry.endMs) })
1222
+ ] }) : /* @__PURE__ */ jsx13("text", { className: "ss-w-om-xlab", x: 40, y: CHART_VIEW_H - 8, children: ui?.movementWindow ? ui.movementWindow(vm.windowHours) : `Last ${vm.windowHours}h` })
1162
1223
  ]
1163
1224
  }
1164
1225
  ),
@@ -1166,66 +1227,66 @@ function OddsMovementCard({ vm, ui }) {
1166
1227
  /* @__PURE__ */ jsxs9("span", { children: [
1167
1228
  ui?.openingLabel ?? "Opening",
1168
1229
  " ",
1169
- /* @__PURE__ */ jsx12("b", { children: vm.opening })
1230
+ /* @__PURE__ */ jsx13("b", { children: vm.opening })
1170
1231
  ] }),
1171
1232
  /* @__PURE__ */ jsxs9("span", { children: [
1172
1233
  ui?.currentLabel ?? "Current",
1173
1234
  " ",
1174
- /* @__PURE__ */ jsx12("b", { children: vm.current })
1235
+ /* @__PURE__ */ jsx13("b", { children: vm.current })
1175
1236
  ] })
1176
1237
  ] }),
1177
- vm.bookmaker ? /* @__PURE__ */ jsx12("div", { className: "ss-w-om-foot", children: /* @__PURE__ */ jsx12("span", { children: vm.bookmaker }) }) : null,
1178
- vm.excludedCount > 0 ? /* @__PURE__ */ jsx12("div", { className: "ss-w-om-excl", children: ui?.excludedSuspended ? ui.excludedSuspended(vm.excludedCount) : `${vm.excludedCount} suspended price(s) excluded from this chart.` }) : null,
1179
- vm.isSuspended ? /* @__PURE__ */ jsx12("div", { className: "ss-w-om-susp", children: ui?.selectionSuspended ?? "This selection is not currently available to bet." }) : null,
1180
- vm.disclaimer ? /* @__PURE__ */ jsx12("div", { className: "ss-w-om-disc", children: vm.disclaimer }) : null
1238
+ vm.bookmaker ? /* @__PURE__ */ jsx13("div", { className: "ss-w-om-foot", children: /* @__PURE__ */ jsx13("span", { children: vm.bookmaker }) }) : null,
1239
+ vm.excludedCount > 0 ? /* @__PURE__ */ jsx13("div", { className: "ss-w-om-excl", children: ui?.excludedSuspended ? ui.excludedSuspended(vm.excludedCount) : `${vm.excludedCount} suspended price(s) excluded from this chart.` }) : null,
1240
+ vm.isSuspended ? /* @__PURE__ */ jsx13("div", { className: "ss-w-om-susp", children: ui?.selectionSuspended ?? "This selection is not currently available to bet." }) : null,
1241
+ vm.disclaimer ? /* @__PURE__ */ jsx13("div", { className: "ss-w-om-disc", children: vm.disclaimer }) : null
1181
1242
  ] });
1182
1243
  }
1183
1244
  function OddsMovementCardList({ cards, ui }) {
1184
1245
  if (!cards || cards.length === 0) return null;
1185
- return /* @__PURE__ */ jsx12("div", { className: "ss-w-omcards", children: cards.map((vm) => /* @__PURE__ */ jsx12(OddsMovementCard, { vm, ui }, vm.id)) });
1246
+ return /* @__PURE__ */ jsx13("div", { className: "ss-w-omcards", children: cards.map((vm) => /* @__PURE__ */ jsx13(OddsMovementCard, { vm, ui }, vm.id)) });
1186
1247
  }
1187
1248
 
1188
1249
  // src/components/ActionButtons.tsx
1189
- import { Fragment as Fragment3, jsx as jsx13, jsxs as jsxs10 } from "react/jsx-runtime";
1250
+ import { Fragment as Fragment4, jsx as jsx14, jsxs as jsxs10 } from "react/jsx-runtime";
1190
1251
  function ActionButtons({ actions, loading, disabled, onAct }) {
1191
1252
  if (actions.length === 0 && !loading) return null;
1192
1253
  return /* @__PURE__ */ jsxs10("div", { className: "ss-w-actions", children: [
1193
- actions.map((a, i) => /* @__PURE__ */ jsx13("button", { type: "button", className: "ss-w-abtn", disabled, onClick: () => onAct(a), children: a.label }, `${a.label}-${i}`)),
1194
- actions.length === 0 && loading ? /* @__PURE__ */ jsxs10(Fragment3, { children: [
1195
- /* @__PURE__ */ jsx13("span", { className: "ss-w-abtn-shimmer" }),
1196
- /* @__PURE__ */ jsx13("span", { className: "ss-w-abtn-shimmer" })
1254
+ actions.map((a, i) => /* @__PURE__ */ jsx14("button", { type: "button", className: "ss-w-abtn", disabled, onClick: () => onAct(a), children: a.label }, `${a.label}-${i}`)),
1255
+ actions.length === 0 && loading ? /* @__PURE__ */ jsxs10(Fragment4, { children: [
1256
+ /* @__PURE__ */ jsx14("span", { className: "ss-w-abtn-shimmer" }),
1257
+ /* @__PURE__ */ jsx14("span", { className: "ss-w-abtn-shimmer" })
1197
1258
  ] }) : null
1198
1259
  ] });
1199
1260
  }
1200
1261
 
1201
1262
  // src/components/dashboard/DashboardTile.tsx
1202
- import { jsx as jsx14, jsxs as jsxs11 } from "react/jsx-runtime";
1263
+ import { jsx as jsx15, jsxs as jsxs11 } from "react/jsx-runtime";
1203
1264
  var MAX_CRESTS = 3;
1204
1265
  function DashboardTile({ tile, onTap }) {
1205
1266
  const spanClass = tile.span === "full" ? "ss-w-dash-tile--full" : "ss-w-dash-tile--half";
1206
1267
  const crests = (tile.imageUrls ?? []).slice(0, MAX_CRESTS);
1207
1268
  const hasMeta = Boolean(tile.badgeText) || crests.length > 0;
1208
1269
  return /* @__PURE__ */ jsxs11("button", { type: "button", className: `ss-w-dash-tile ${spanClass}`, onClick: onTap, children: [
1209
- /* @__PURE__ */ jsx14("span", { className: "ss-w-dash-tile-icon", children: /* @__PURE__ */ jsx14(DashboardIcon, { name: tile.icon }) }),
1270
+ /* @__PURE__ */ jsx15("span", { className: "ss-w-dash-tile-icon", children: /* @__PURE__ */ jsx15(DashboardIcon, { name: tile.icon }) }),
1210
1271
  /* @__PURE__ */ jsxs11("span", { className: "ss-w-dash-tile-body", children: [
1211
- /* @__PURE__ */ jsx14("span", { className: "ss-w-dash-tile-title", children: tile.title }),
1212
- tile.subtitle ? /* @__PURE__ */ jsx14("span", { className: "ss-w-dash-tile-subtitle", children: tile.subtitle }) : null,
1272
+ /* @__PURE__ */ jsx15("span", { className: "ss-w-dash-tile-title", children: tile.title }),
1273
+ tile.subtitle ? /* @__PURE__ */ jsx15("span", { className: "ss-w-dash-tile-subtitle", children: tile.subtitle }) : null,
1213
1274
  hasMeta ? (
1214
1275
  // Badge + crests sit on their own row, below the title, instead of sharing the
1215
1276
  // icon/chevron row -- at the half-span tile's ~180px width a nowrap badge alone
1216
1277
  // (~70px) left the title column at ~22px and forced character-by-character wrapping.
1217
1278
  /* @__PURE__ */ jsxs11("span", { className: "ss-w-dash-tile-meta", children: [
1218
- tile.badgeText ? /* @__PURE__ */ jsx14("span", { className: "ss-w-dash-tile-badge", children: tile.badgeText }) : null,
1219
- crests.length > 0 ? /* @__PURE__ */ jsx14("span", { className: "ss-w-dash-tile-crests", children: crests.map((url, i) => /* @__PURE__ */ jsx14("span", { className: "ss-w-dash-tile-crest", style: { backgroundImage: `url(${url})` }, "aria-hidden": "true" }, `${url}-${i}`)) }) : null
1279
+ tile.badgeText ? /* @__PURE__ */ jsx15("span", { className: "ss-w-dash-tile-badge", children: tile.badgeText }) : null,
1280
+ crests.length > 0 ? /* @__PURE__ */ jsx15("span", { className: "ss-w-dash-tile-crests", children: crests.map((url, i) => /* @__PURE__ */ jsx15("span", { className: "ss-w-dash-tile-crest", style: { backgroundImage: `url(${url})` }, "aria-hidden": "true" }, `${url}-${i}`)) }) : null
1220
1281
  ] })
1221
1282
  ) : null
1222
1283
  ] }),
1223
- /* @__PURE__ */ jsx14("span", { className: "ss-w-dash-tile-chevron", children: /* @__PURE__ */ jsx14(ChevronIcon, {}) })
1284
+ /* @__PURE__ */ jsx15("span", { className: "ss-w-dash-tile-chevron", children: /* @__PURE__ */ jsx15(ChevronIcon, {}) })
1224
1285
  ] });
1225
1286
  }
1226
1287
 
1227
1288
  // src/components/dashboard/GreetingDashboard.tsx
1228
- import { jsx as jsx15, jsxs as jsxs12 } from "react/jsx-runtime";
1289
+ import { jsx as jsx16, jsxs as jsxs12 } from "react/jsx-runtime";
1229
1290
  var LAYOUT_CLASSES = {
1230
1291
  market: "ss-w-dash--market",
1231
1292
  goal: "ss-w-dash--goal",
@@ -1237,35 +1298,35 @@ function layoutClass(layout) {
1237
1298
  function GreetingDashboard({ dashboard, onTap }) {
1238
1299
  return /* @__PURE__ */ jsxs12("div", { className: `ss-w-dash ${layoutClass(dashboard.layout)}`, children: [
1239
1300
  /* @__PURE__ */ jsxs12("div", { className: "ss-w-dash-head", children: [
1240
- /* @__PURE__ */ jsx15("span", { className: "ss-w-dash-title", children: dashboard.title }),
1241
- dashboard.subtitle ? /* @__PURE__ */ jsx15("span", { className: "ss-w-dash-subtitle", children: dashboard.subtitle }) : null
1301
+ /* @__PURE__ */ jsx16("span", { className: "ss-w-dash-title", children: dashboard.title }),
1302
+ dashboard.subtitle ? /* @__PURE__ */ jsx16("span", { className: "ss-w-dash-subtitle", children: dashboard.subtitle }) : null
1242
1303
  ] }),
1243
- /* @__PURE__ */ jsx15("div", { className: "ss-w-dash-grid", children: dashboard.tiles.map((t, i) => /* @__PURE__ */ jsx15(DashboardTile, { tile: t, onTap: () => onTap(t, i) }, `${t.title}-${i}`)) }),
1244
- dashboard.footerTip ? /* @__PURE__ */ jsx15("span", { className: "ss-w-dash-foot", children: dashboard.footerTip }) : null
1304
+ /* @__PURE__ */ jsx16("div", { className: "ss-w-dash-grid", children: dashboard.tiles.map((t, i) => /* @__PURE__ */ jsx16(DashboardTile, { tile: t, onTap: () => onTap(t, i) }, `${t.title}-${i}`)) }),
1305
+ dashboard.footerTip ? /* @__PURE__ */ jsx16("span", { className: "ss-w-dash-foot", children: dashboard.footerTip }) : null
1245
1306
  ] });
1246
1307
  }
1247
1308
 
1248
1309
  // src/components/club-dashboard/GreetingHeaderBlock.tsx
1249
- import { jsx as jsx16, jsxs as jsxs13 } from "react/jsx-runtime";
1310
+ import { jsx as jsx17, jsxs as jsxs13 } from "react/jsx-runtime";
1250
1311
  function GreetingHeaderBlock({ block }) {
1251
1312
  return /* @__PURE__ */ jsxs13("div", { className: "ss-w-cdash-greeting", children: [
1252
- /* @__PURE__ */ jsx16("div", { className: "ss-w-cdash-greeting-title", children: block.title }),
1253
- /* @__PURE__ */ jsx16("div", { className: "ss-w-cdash-greeting-subtitle", children: block.subtitle })
1313
+ /* @__PURE__ */ jsx17("div", { className: "ss-w-cdash-greeting-title", children: block.title }),
1314
+ /* @__PURE__ */ jsx17("div", { className: "ss-w-cdash-greeting-subtitle", children: block.subtitle })
1254
1315
  ] });
1255
1316
  }
1256
1317
 
1257
1318
  // src/components/club-dashboard/FixtureHeroBlock.tsx
1258
- import { useState as useState5 } from "react";
1259
- import { jsx as jsx17, jsxs as jsxs14 } from "react/jsx-runtime";
1319
+ import { useState as useState6 } from "react";
1320
+ import { jsx as jsx18, jsxs as jsxs14 } from "react/jsx-runtime";
1260
1321
  function initials3(name) {
1261
1322
  return name.split(/\s+/).filter(Boolean).slice(0, 2).map((w) => w[0].toUpperCase()).join("");
1262
1323
  }
1263
1324
  function Crest3({ url, name }) {
1264
- const [broken, setBroken] = useState5(false);
1325
+ const [broken, setBroken] = useState6(false);
1265
1326
  if (!url || broken) {
1266
- return /* @__PURE__ */ jsx17("span", { className: "ss-w-cdash-crest ss-w-cdash-crest--fallback", "aria-hidden": "true", children: initials3(name) });
1327
+ return /* @__PURE__ */ jsx18("span", { className: "ss-w-cdash-crest ss-w-cdash-crest--fallback", "aria-hidden": "true", children: initials3(name) });
1267
1328
  }
1268
- return /* @__PURE__ */ jsx17("img", { className: "ss-w-cdash-crest", src: url, alt: "", "aria-hidden": "true", onError: () => setBroken(true) });
1329
+ return /* @__PURE__ */ jsx18("img", { className: "ss-w-cdash-crest", src: url, alt: "", "aria-hidden": "true", onError: () => setBroken(true) });
1269
1330
  }
1270
1331
  function FixtureHeroBlock({
1271
1332
  block,
@@ -1274,36 +1335,36 @@ function FixtureHeroBlock({
1274
1335
  }) {
1275
1336
  return /* @__PURE__ */ jsxs14("div", { className: "ss-w-cdash-fixture", children: [
1276
1337
  /* @__PURE__ */ jsxs14("div", { className: "ss-w-cdash-tie", children: [
1277
- /* @__PURE__ */ jsx17(Crest3, { url: block.homeCrestUrl, name: block.homeName }),
1278
- /* @__PURE__ */ jsx17("span", { className: "ss-w-cdash-team", children: block.homeName }),
1279
- /* @__PURE__ */ jsx17("span", { className: "ss-w-cdash-vs", children: "v" }),
1280
- /* @__PURE__ */ jsx17("span", { className: "ss-w-cdash-team", children: block.awayName }),
1281
- /* @__PURE__ */ jsx17(Crest3, { url: block.awayCrestUrl, name: block.awayName })
1338
+ /* @__PURE__ */ jsx18(Crest3, { url: block.homeCrestUrl, name: block.homeName }),
1339
+ /* @__PURE__ */ jsx18("span", { className: "ss-w-cdash-team", children: block.homeName }),
1340
+ /* @__PURE__ */ jsx18("span", { className: "ss-w-cdash-vs", children: "v" }),
1341
+ /* @__PURE__ */ jsx18("span", { className: "ss-w-cdash-team", children: block.awayName }),
1342
+ /* @__PURE__ */ jsx18(Crest3, { url: block.awayCrestUrl, name: block.awayName })
1282
1343
  ] }),
1283
1344
  /* @__PURE__ */ jsxs14("div", { className: "ss-w-cdash-fixture-meta", children: [
1284
- /* @__PURE__ */ jsx17("span", { className: "ss-w-cdash-kickoff", children: ui.clubKickoff(block.kickoffUnix * 1e3) }),
1285
- block.venue ? /* @__PURE__ */ jsx17("span", { className: "ss-w-cdash-venue", children: block.venue }) : null,
1286
- block.competition ? /* @__PURE__ */ jsx17("span", { className: "ss-w-cdash-comp", children: block.competition }) : null
1345
+ /* @__PURE__ */ jsx18("span", { className: "ss-w-cdash-kickoff", children: ui.clubKickoff(block.kickoffUnix * 1e3) }),
1346
+ block.venue ? /* @__PURE__ */ jsx18("span", { className: "ss-w-cdash-venue", children: block.venue }) : null,
1347
+ block.competition ? /* @__PURE__ */ jsx18("span", { className: "ss-w-cdash-comp", children: block.competition }) : null
1287
1348
  ] }),
1288
- /* @__PURE__ */ jsx17("button", { type: "button", className: "ss-w-cdash-cta", onClick: () => onAction(block.ctaActionValue, void 0, block.ctaLabel), children: block.ctaLabel })
1349
+ /* @__PURE__ */ jsx18("button", { type: "button", className: "ss-w-cdash-cta", onClick: () => onAction(block.ctaLabel, { actionRef: block.ctaActionRef }), children: block.ctaLabel })
1289
1350
  ] });
1290
1351
  }
1291
1352
 
1292
1353
  // src/components/club-dashboard/FormGuideBlock.tsx
1293
- import { jsx as jsx18, jsxs as jsxs15 } from "react/jsx-runtime";
1354
+ import { jsx as jsx19, jsxs as jsxs15 } from "react/jsx-runtime";
1294
1355
  function FormGuideBlock({ block }) {
1295
1356
  if (block.rows.length === 0) return null;
1296
- return /* @__PURE__ */ jsx18("div", { className: "ss-w-cdash-form", children: block.rows.map((row) => /* @__PURE__ */ jsxs15("div", { className: "ss-w-cdash-form-row", children: [
1357
+ return /* @__PURE__ */ jsx19("div", { className: "ss-w-cdash-form", children: block.rows.map((row) => /* @__PURE__ */ jsxs15("div", { className: "ss-w-cdash-form-row", children: [
1297
1358
  /* @__PURE__ */ jsxs15("span", { className: "ss-w-cdash-form-name", children: [
1298
1359
  row.name,
1299
1360
  row.position != null ? ` #${row.position}` : ""
1300
1361
  ] }),
1301
- /* @__PURE__ */ jsx18("span", { className: "ss-w-cdash-form-pills", children: row.last5.map((r, i) => /* @__PURE__ */ jsx18("span", { className: `ss-w-cdash-pill ss-w-cdash-pill--${r.toLowerCase()}`, children: r }, i)) })
1362
+ /* @__PURE__ */ jsx19("span", { className: "ss-w-cdash-form-pills", children: row.last5.map((r, i) => /* @__PURE__ */ jsx19("span", { className: `ss-w-cdash-pill ss-w-cdash-pill--${r.toLowerCase()}`, children: r }, i)) })
1302
1363
  ] }, row.name)) });
1303
1364
  }
1304
1365
 
1305
1366
  // src/components/club-dashboard/MatchPreviewBlock.tsx
1306
- import { jsx as jsx19, jsxs as jsxs16 } from "react/jsx-runtime";
1367
+ import { jsx as jsx20, jsxs as jsxs16 } from "react/jsx-runtime";
1307
1368
  function MatchPreviewBlock({
1308
1369
  block,
1309
1370
  ui,
@@ -1312,14 +1373,14 @@ function MatchPreviewBlock({
1312
1373
  if (block.facts.length === 0) return null;
1313
1374
  const heading = ui.clubMatchPreviewHeading;
1314
1375
  return /* @__PURE__ */ jsxs16("div", { className: "ss-w-cdash-lines", children: [
1315
- /* @__PURE__ */ jsx19("span", { className: "ss-w-cdash-lines-heading", children: heading }),
1376
+ /* @__PURE__ */ jsx20("span", { className: "ss-w-cdash-lines-heading", children: heading }),
1316
1377
  block.facts.map(
1317
- (f, i) => f.entityRef ? /* @__PURE__ */ jsxs16(
1378
+ (f, i) => f.actionRef ? /* @__PURE__ */ jsxs16(
1318
1379
  "button",
1319
1380
  {
1320
1381
  type: "button",
1321
1382
  className: "ss-w-cdash-fact",
1322
- onClick: () => onAction(f.question ?? f.sentence, { ...f.entityRef, subject: f.sentence, personaHint: "club" }),
1383
+ onClick: () => onAction(f.label ?? f.sentence, { actionRef: f.actionRef }),
1323
1384
  children: [
1324
1385
  f.sentence,
1325
1386
  f.label ? /* @__PURE__ */ jsxs16("span", { className: "ss-w-cdash-fact-label", children: [
@@ -1329,13 +1390,13 @@ function MatchPreviewBlock({
1329
1390
  ]
1330
1391
  },
1331
1392
  i
1332
- ) : /* @__PURE__ */ jsx19("p", { children: f.sentence }, i)
1393
+ ) : /* @__PURE__ */ jsx20("p", { children: f.sentence }, i)
1333
1394
  )
1334
1395
  ] });
1335
1396
  }
1336
1397
 
1337
1398
  // src/components/club-dashboard/TeamDnaBlock.tsx
1338
- import { jsx as jsx20, jsxs as jsxs17 } from "react/jsx-runtime";
1399
+ import { jsx as jsx21, jsxs as jsxs17 } from "react/jsx-runtime";
1339
1400
  function TeamDnaBlock({
1340
1401
  block,
1341
1402
  ui,
@@ -1344,14 +1405,14 @@ function TeamDnaBlock({
1344
1405
  if (block.facts.length === 0) return null;
1345
1406
  const heading = ui.clubTeamDnaHeading;
1346
1407
  return /* @__PURE__ */ jsxs17("div", { className: "ss-w-cdash-lines", children: [
1347
- /* @__PURE__ */ jsx20("span", { className: "ss-w-cdash-lines-heading", children: heading }),
1408
+ /* @__PURE__ */ jsx21("span", { className: "ss-w-cdash-lines-heading", children: heading }),
1348
1409
  block.facts.map(
1349
- (f, i) => f.entityRef ? /* @__PURE__ */ jsxs17(
1410
+ (f, i) => f.actionRef ? /* @__PURE__ */ jsxs17(
1350
1411
  "button",
1351
1412
  {
1352
1413
  type: "button",
1353
1414
  className: "ss-w-cdash-fact",
1354
- onClick: () => onAction(f.question ?? f.sentence, { ...f.entityRef, subject: f.sentence, personaHint: "club" }),
1415
+ onClick: () => onAction(f.label ?? f.sentence, { actionRef: f.actionRef }),
1355
1416
  children: [
1356
1417
  f.sentence,
1357
1418
  f.label ? /* @__PURE__ */ jsxs17("span", { className: "ss-w-cdash-fact-label", children: [
@@ -1361,14 +1422,14 @@ function TeamDnaBlock({
1361
1422
  ]
1362
1423
  },
1363
1424
  i
1364
- ) : /* @__PURE__ */ jsx20("p", { children: f.sentence }, i)
1425
+ ) : /* @__PURE__ */ jsx21("p", { children: f.sentence }, i)
1365
1426
  )
1366
1427
  ] });
1367
1428
  }
1368
1429
 
1369
1430
  // src/components/club-dashboard/CountdownBlock.tsx
1370
- import { useEffect as useEffect3, useState as useState6 } from "react";
1371
- import { jsx as jsx21, jsxs as jsxs18 } from "react/jsx-runtime";
1431
+ import { useEffect as useEffect3, useState as useState7 } from "react";
1432
+ import { jsx as jsx22, jsxs as jsxs18 } from "react/jsx-runtime";
1372
1433
  var DAY_MS = 24 * 3600 * 1e3;
1373
1434
  function pad2(n) {
1374
1435
  return String(n).padStart(2, "0");
@@ -1381,7 +1442,7 @@ function splitRemaining(remainingMs) {
1381
1442
  return { h: pad2(h), m: pad2(m), s: pad2(s) };
1382
1443
  }
1383
1444
  function CountdownBlock({ block, ui }) {
1384
- const [remainingMs, setRemainingMs] = useState6(() => block.kickoffUnix * 1e3 - Date.now());
1445
+ const [remainingMs, setRemainingMs] = useState7(() => block.kickoffUnix * 1e3 - Date.now());
1385
1446
  useEffect3(() => {
1386
1447
  setRemainingMs(block.kickoffUnix * 1e3 - Date.now());
1387
1448
  if (window.matchMedia("(prefers-reduced-motion: reduce)").matches) return;
@@ -1395,28 +1456,28 @@ function CountdownBlock({ block, ui }) {
1395
1456
  const { h, m, s } = splitRemaining(remainingMs);
1396
1457
  return /* @__PURE__ */ jsxs18("div", { className: "ss-w-cdash-cd", "aria-label": ui.clubKickoffAria(block.kickoffUnix * 1e3), children: [
1397
1458
  /* @__PURE__ */ jsxs18("div", { className: "ss-w-cdash-cd-clock", "aria-hidden": "true", children: [
1398
- /* @__PURE__ */ jsx21("span", { className: "ss-w-cdash-cd-h", children: h }),
1459
+ /* @__PURE__ */ jsx22("span", { className: "ss-w-cdash-cd-h", children: h }),
1399
1460
  "h",
1400
1461
  " ",
1401
- /* @__PURE__ */ jsx21("span", { className: "ss-w-cdash-cd-m", children: m }),
1462
+ /* @__PURE__ */ jsx22("span", { className: "ss-w-cdash-cd-m", children: m }),
1402
1463
  "m",
1403
1464
  " ",
1404
- /* @__PURE__ */ jsx21("span", { className: "ss-w-cdash-cd-s", children: s }),
1465
+ /* @__PURE__ */ jsx22("span", { className: "ss-w-cdash-cd-s", children: s }),
1405
1466
  "s"
1406
1467
  ] }),
1407
- /* @__PURE__ */ jsx21("div", { className: "ss-w-cdash-cd-label", children: ui.clubCountdownLabel })
1468
+ /* @__PURE__ */ jsx22("div", { className: "ss-w-cdash-cd-label", children: ui.clubCountdownLabel })
1408
1469
  ] });
1409
1470
  }
1410
1471
 
1411
1472
  // src/components/club-dashboard/ClubDashboard.tsx
1412
- import { jsx as jsx22 } from "react/jsx-runtime";
1473
+ import { jsx as jsx23 } from "react/jsx-runtime";
1413
1474
  var CLUB_BLOCK_REGISTRY = {
1414
- greetingHeader: ({ block }) => block.type === "greetingHeader" ? /* @__PURE__ */ jsx22(GreetingHeaderBlock, { block }) : null,
1415
- fixtureHero: ({ block, ui, onAction }) => block.type === "fixtureHero" ? /* @__PURE__ */ jsx22(FixtureHeroBlock, { block, ui, onAction }) : null,
1416
- formGuide: ({ block, ui }) => block.type === "formGuide" ? /* @__PURE__ */ jsx22(FormGuideBlock, { block, ui }) : null,
1417
- matchPreview: ({ block, ui, onAction }) => block.type === "matchPreview" ? /* @__PURE__ */ jsx22(MatchPreviewBlock, { block, ui, onAction }) : null,
1418
- teamDna: ({ block, ui, onAction }) => block.type === "teamDna" ? /* @__PURE__ */ jsx22(TeamDnaBlock, { block, ui, onAction }) : null,
1419
- countdown: ({ block, ui }) => block.type === "countdown" ? /* @__PURE__ */ jsx22(CountdownBlock, { block, ui }) : null
1475
+ greetingHeader: ({ block }) => block.type === "greetingHeader" ? /* @__PURE__ */ jsx23(GreetingHeaderBlock, { block }) : null,
1476
+ fixtureHero: ({ block, ui, onAction }) => block.type === "fixtureHero" ? /* @__PURE__ */ jsx23(FixtureHeroBlock, { block, ui, onAction }) : null,
1477
+ formGuide: ({ block, ui }) => block.type === "formGuide" ? /* @__PURE__ */ jsx23(FormGuideBlock, { block, ui }) : null,
1478
+ matchPreview: ({ block, ui, onAction }) => block.type === "matchPreview" ? /* @__PURE__ */ jsx23(MatchPreviewBlock, { block, ui, onAction }) : null,
1479
+ teamDna: ({ block, ui, onAction }) => block.type === "teamDna" ? /* @__PURE__ */ jsx23(TeamDnaBlock, { block, ui, onAction }) : null,
1480
+ countdown: ({ block, ui }) => block.type === "countdown" ? /* @__PURE__ */ jsx23(CountdownBlock, { block, ui }) : null
1420
1481
  };
1421
1482
  function ClubDashboard({
1422
1483
  dashboard,
@@ -1424,39 +1485,39 @@ function ClubDashboard({
1424
1485
  onAction
1425
1486
  }) {
1426
1487
  if (!dashboard.blocks.length) return null;
1427
- return /* @__PURE__ */ jsx22("div", { className: "ss-w-cdash", children: dashboard.blocks.map((block, i) => {
1488
+ return /* @__PURE__ */ jsx23("div", { className: "ss-w-cdash", children: dashboard.blocks.map((block, i) => {
1428
1489
  const Comp = Object.prototype.hasOwnProperty.call(CLUB_BLOCK_REGISTRY, block.type) ? CLUB_BLOCK_REGISTRY[block.type] : void 0;
1429
1490
  if (!Comp) {
1430
1491
  console.warn(`[club-dashboard] no renderer for block type "${block.type}"`);
1431
1492
  return null;
1432
1493
  }
1433
- return /* @__PURE__ */ jsx22("div", { className: "ss-w-cdash-block", children: /* @__PURE__ */ jsx22(Comp, { block, ui, onAction }) }, i);
1494
+ return /* @__PURE__ */ jsx23("div", { className: "ss-w-cdash-block", children: /* @__PURE__ */ jsx23(Comp, { block, ui, onAction }) }, i);
1434
1495
  }) });
1435
1496
  }
1436
1497
 
1437
1498
  // src/components/ChatMessage.tsx
1438
- import { jsx as jsx23, jsxs as jsxs19 } from "react/jsx-runtime";
1499
+ import { jsx as jsx24, jsxs as jsxs19 } from "react/jsx-runtime";
1439
1500
  function ChatMessage({ message, disabled, onAct, onTap, onClubAction, ui, slip }) {
1440
1501
  if (message.role === "user") {
1441
- return /* @__PURE__ */ jsx23("div", { className: "ss-w-msg ss-w-user", children: /* @__PURE__ */ jsx23("div", { className: "ss-w-bubble", children: message.text }) });
1502
+ return /* @__PURE__ */ jsx24("div", { className: "ss-w-msg ss-w-user", children: /* @__PURE__ */ jsx24("div", { className: "ss-w-bubble", children: message.text }) });
1442
1503
  }
1443
1504
  return /* @__PURE__ */ jsxs19("div", { className: "ss-w-msg ss-w-bot", children: [
1444
- message.text && !message.greetingDashboard && !message.clubDashboard ? /* @__PURE__ */ jsx23(Markdown, { text: message.text }) : null,
1445
- message.greetingDashboard ? /* @__PURE__ */ jsx23(GreetingDashboard, { dashboard: message.greetingDashboard, onTap }) : null,
1446
- message.clubDashboard && ui ? /* @__PURE__ */ jsx23(ClubDashboard, { dashboard: message.clubDashboard, ui, onAction: onClubAction }) : null,
1447
- /* @__PURE__ */ jsx23(EntityCardList, { cards: message.cards, disabled, showLessLabel: ui?.showLess, showMoreLabel: ui?.showMore }),
1448
- /* @__PURE__ */ jsx23(BettingInsightList, { insights: message.insights, ui, slip }),
1449
- /* @__PURE__ */ jsx23(MarketOddsCardList, { cards: message.marketOdds, ui, slip }),
1450
- /* @__PURE__ */ jsx23(OddsMovementCardList, { cards: message.oddsMovement, ui }),
1451
- /* @__PURE__ */ jsx23(ActionButtons, { actions: message.actions, loading: false, disabled, onAct })
1505
+ message.text && !message.greetingDashboard && !message.clubDashboard ? /* @__PURE__ */ jsx24(Markdown, { text: message.text }) : null,
1506
+ message.greetingDashboard ? /* @__PURE__ */ jsx24(GreetingDashboard, { dashboard: message.greetingDashboard, onTap }) : null,
1507
+ message.clubDashboard && ui ? /* @__PURE__ */ jsx24(ClubDashboard, { dashboard: message.clubDashboard, ui, onAction: onClubAction }) : null,
1508
+ /* @__PURE__ */ jsx24(EntityCardList, { cards: message.cards, disabled, showLessLabel: ui?.showLess, showMoreLabel: ui?.showMore }),
1509
+ /* @__PURE__ */ jsx24(BettingInsightList, { insights: message.insights, ui, slip }),
1510
+ /* @__PURE__ */ jsx24(MarketOddsCardList, { cards: message.marketOdds, ui, slip }),
1511
+ /* @__PURE__ */ jsx24(OddsMovementCardList, { cards: message.oddsMovement, ui }),
1512
+ /* @__PURE__ */ jsx24(ActionButtons, { actions: message.actions, loading: false, disabled, onAct })
1452
1513
  ] });
1453
1514
  }
1454
1515
 
1455
1516
  // src/components/IntentChips.tsx
1456
- import { jsx as jsx24 } from "react/jsx-runtime";
1517
+ import { jsx as jsx25 } from "react/jsx-runtime";
1457
1518
  function IntentChips({ chips, onPick }) {
1458
1519
  if (chips.length === 0) return null;
1459
- return /* @__PURE__ */ jsx24("div", { className: "ss-w-chips", children: chips.map((c, i) => /* @__PURE__ */ jsx24("button", { type: "button", className: "ss-w-chip", onClick: () => onPick(c.text), children: c.text }, `${c.text}-${i}`)) });
1520
+ return /* @__PURE__ */ jsx25("div", { className: "ss-w-chips", children: chips.map((c, i) => /* @__PURE__ */ jsx25("button", { type: "button", className: "ss-w-chip", onClick: () => onPick(c.text), children: c.text }, `${c.text}-${i}`)) });
1460
1521
  }
1461
1522
 
1462
1523
  // src/scroll.ts
@@ -1466,7 +1527,7 @@ function isPinnedToBottom(el, threshold = PIN_THRESHOLD) {
1466
1527
  }
1467
1528
 
1468
1529
  // src/components/MessageList.tsx
1469
- import { jsx as jsx25, jsxs as jsxs20 } from "react/jsx-runtime";
1530
+ import { jsx as jsx26, jsxs as jsxs20 } from "react/jsx-runtime";
1470
1531
  function MessageList({ controller, ui, slip, onDashboardTap }) {
1471
1532
  const { state, send } = controller;
1472
1533
  const listRef = useRef2(null);
@@ -1487,7 +1548,7 @@ function MessageList({ controller, ui, slip, onDashboardTap }) {
1487
1548
  if (listRef.current) pinnedRef.current = isPinnedToBottom(listRef.current);
1488
1549
  },
1489
1550
  children: [
1490
- state.messages.map((m) => /* @__PURE__ */ jsx25(
1551
+ state.messages.map((m) => /* @__PURE__ */ jsx26(
1491
1552
  ChatMessage,
1492
1553
  {
1493
1554
  message: m,
@@ -1497,31 +1558,31 @@ function MessageList({ controller, ui, slip, onDashboardTap }) {
1497
1558
  send(tile.actionValue, { displayText: tile.title });
1498
1559
  if (m.role === "assistant" && m.greetingDashboard) onDashboardTap?.(tile, index, m.greetingDashboard.layout);
1499
1560
  },
1500
- onClubAction: (message, grounding, displayText) => send(message, { grounding, displayText: displayText ?? message }),
1561
+ onClubAction: (label, opts) => send(label, { actionRef: opts.actionRef }),
1501
1562
  ui,
1502
1563
  slip
1503
1564
  },
1504
1565
  m.id
1505
1566
  )),
1506
- state.isStreaming && state.actionsLoading ? /* @__PURE__ */ jsx25(ActionButtons, { actions: [], loading: true, onAct: () => {
1567
+ state.isStreaming && state.actionsLoading ? /* @__PURE__ */ jsx26(ActionButtons, { actions: [], loading: true, onAct: () => {
1507
1568
  } }) : null,
1508
- state.status ? /* @__PURE__ */ jsx25("div", { className: "ss-w-status", children: state.status }) : null,
1569
+ state.status ? /* @__PURE__ */ jsx26("div", { className: "ss-w-status", children: state.status }) : null,
1509
1570
  state.error ? /* @__PURE__ */ jsxs20("div", { className: "ss-w-error", role: "alert", children: [
1510
- /* @__PURE__ */ jsx25("span", { children: "message" in state.error ? state.error.message : ui.somethingWentWrong }),
1511
- /* @__PURE__ */ jsx25("button", { type: "button", className: "ss-w-retry", onClick: () => controller.retry(), children: ui.retry })
1571
+ /* @__PURE__ */ jsx26("span", { children: "message" in state.error ? state.error.message : ui.somethingWentWrong }),
1572
+ /* @__PURE__ */ jsx26("button", { type: "button", className: "ss-w-retry", onClick: () => controller.retry(), children: ui.retry })
1512
1573
  ] }) : null,
1513
- !hasUserMessage && !hasDashboard ? /* @__PURE__ */ jsx25(IntentChips, { chips: state.starters, onPick: (t) => send(t) }) : null,
1514
- /* @__PURE__ */ jsx25("div", { ref: endRef })
1574
+ !hasUserMessage && !hasDashboard ? /* @__PURE__ */ jsx26(IntentChips, { chips: state.starters, onPick: (t) => send(t) }) : null,
1575
+ /* @__PURE__ */ jsx26("div", { ref: endRef })
1515
1576
  ]
1516
1577
  }
1517
1578
  );
1518
1579
  }
1519
1580
 
1520
1581
  // src/components/ChatInput.tsx
1521
- import { useState as useState7 } from "react";
1522
- import { jsx as jsx26, jsxs as jsxs21 } from "react/jsx-runtime";
1582
+ import { useState as useState8 } from "react";
1583
+ import { jsx as jsx27, jsxs as jsxs21 } from "react/jsx-runtime";
1523
1584
  function ChatInput({ placeholder, streaming, onSend, onStop, stopLabel = "Stop response", sendLabel = "Send message" }) {
1524
- const [value, setValue] = useState7("");
1585
+ const [value, setValue] = useState8("");
1525
1586
  const submit = () => {
1526
1587
  const t = value.trim();
1527
1588
  if (!t) return;
@@ -1535,16 +1596,16 @@ function ChatInput({ placeholder, streaming, onSend, onStop, stopLabel = "Stop r
1535
1596
  }
1536
1597
  };
1537
1598
  return /* @__PURE__ */ jsxs21("div", { className: "ss-w-input", children: [
1538
- /* @__PURE__ */ jsx26("textarea", { className: "ss-w-textarea", rows: 1, placeholder, value, disabled: streaming, onChange: (e) => setValue(e.target.value), onKeyDown }),
1539
- streaming ? /* @__PURE__ */ jsx26("button", { type: "button", className: "ss-w-send ss-w-stop", "aria-label": stopLabel, onClick: onStop, children: /* @__PURE__ */ jsx26("span", { className: "ss-w-stop-glyph" }) }) : /* @__PURE__ */ jsx26("button", { type: "button", className: "ss-w-send", "aria-label": sendLabel, onClick: submit, children: /* @__PURE__ */ jsx26(SendIcon, {}) })
1599
+ /* @__PURE__ */ jsx27("textarea", { className: "ss-w-textarea", rows: 1, placeholder, value, disabled: streaming, onChange: (e) => setValue(e.target.value), onKeyDown }),
1600
+ streaming ? /* @__PURE__ */ jsx27("button", { type: "button", className: "ss-w-send ss-w-stop", "aria-label": stopLabel, onClick: onStop, children: /* @__PURE__ */ jsx27("span", { className: "ss-w-stop-glyph" }) }) : /* @__PURE__ */ jsx27("button", { type: "button", className: "ss-w-send", "aria-label": sendLabel, onClick: submit, children: /* @__PURE__ */ jsx27(SendIcon, {}) })
1540
1601
  ] });
1541
1602
  }
1542
1603
 
1543
1604
  // src/components/ChatPanel.tsx
1544
- import { jsx as jsx27, jsxs as jsxs22 } from "react/jsx-runtime";
1605
+ import { jsx as jsx28, jsxs as jsxs22 } from "react/jsx-runtime";
1545
1606
  var FULLSCREEN_QUERY = "(max-width: 639px)";
1546
1607
  function useIsModal() {
1547
- const [modal, setModal] = useState8(false);
1608
+ const [modal, setModal] = useState9(false);
1548
1609
  useEffect5(() => {
1549
1610
  if (typeof window === "undefined" || typeof window.matchMedia !== "function") return;
1550
1611
  const mql = window.matchMedia(FULLSCREEN_QUERY);
@@ -1566,9 +1627,9 @@ function ChatPanel({ controller, ui, slip, onDashboardTap }) {
1566
1627
  return () => window.removeEventListener("keydown", onKey);
1567
1628
  }, [close]);
1568
1629
  return /* @__PURE__ */ jsxs22("div", { className: "ss-w-panel", role: "dialog", "aria-modal": isModal, "aria-label": ui.chatDialogLabel, children: [
1569
- /* @__PURE__ */ jsx27(ChatHeader, { onClose: close, closeLabel: ui.closeChat }),
1570
- /* @__PURE__ */ jsx27(MessageList, { controller, ui, slip, onDashboardTap }),
1571
- /* @__PURE__ */ jsx27(ChatInput, { placeholder: ui.placeholder, streaming: state.isStreaming, onSend: (t) => send(t), onStop: stop, stopLabel: ui.stopResponse, sendLabel: ui.sendMessage })
1630
+ /* @__PURE__ */ jsx28(ChatHeader, { onClose: close, closeLabel: ui.closeChat }),
1631
+ /* @__PURE__ */ jsx28(MessageList, { controller, ui, slip, onDashboardTap }),
1632
+ /* @__PURE__ */ jsx28(ChatInput, { placeholder: ui.placeholder, streaming: state.isStreaming, onSend: (t) => send(t), onStop: stop, stopLabel: ui.stopResponse, sendLabel: ui.sendMessage })
1572
1633
  ] });
1573
1634
  }
1574
1635
 
@@ -1616,7 +1677,7 @@ var WIDGET_CSS = `.ss-w-root { all: revert; }
1616
1677
  .ss-w-greeting-x { flex: none; background: none; border: none; color: var(--ss-w-faint); cursor: pointer; font-size: 17px; line-height: 1; padding: 7px 9px 0 2px; }
1617
1678
 
1618
1679
  /* panel */
1619
- .ss-w-panel { position: fixed; bottom: calc(var(--ss-w-offset-y) + 72px); z-index: 2147483000; display: flex; flex-direction: column; width: min(400px, calc(100vw - 40px)); height: min(620px, calc(100vh - 120px)); background: var(--ss-w-panel); border: 1px solid var(--ss-w-line); border-radius: var(--ss-w-radius); overflow: hidden; box-shadow: 0 12px 40px rgba(0,0,0,.24); }
1680
+ .ss-w-panel { position: fixed; bottom: calc(var(--ss-w-offset-y) + 72px); z-index: 2147483000; display: flex; flex-direction: column; width: min(460px, calc(100vw - 40px)); height: min(640px, calc(100vh - 120px)); background: var(--ss-w-panel); border: 1px solid var(--ss-w-line); border-radius: var(--ss-w-radius); overflow: hidden; box-shadow: 0 12px 40px rgba(0,0,0,.24); }
1620
1681
  .ss-w-root[data-ss-w-pos="right"] .ss-w-panel { right: var(--ss-w-offset-x); }
1621
1682
  .ss-w-root[data-ss-w-pos="left"] .ss-w-panel { left: var(--ss-w-offset-x); }
1622
1683
  @media (max-width: 639px) {
@@ -1719,9 +1780,9 @@ var WIDGET_CSS = `.ss-w-root { all: revert; }
1719
1780
  .ss-w-bi-floor { font-size: 8.5px; font-weight: 700; letter-spacing: .04em; text-transform: uppercase; color: var(--ss-w-faint); background: var(--ss-w-bubble); border: 1px solid var(--ss-w-line); border-radius: 4px; padding: 1px 5px; display: inline-flex; align-items: center; align-self: flex-start; flex: none; }
1720
1781
 
1721
1782
  /* crests -- CSS background-image on a span, never <img> (matches .ss-w-ecard-crest precedent) */
1722
- .ss-w-bi-comp-logo { width: 15px; height: 15px; flex: none; display: block; border-radius: 50%; background-size: contain; background-repeat: no-repeat; background-position: center; }
1723
- .ss-w-bi-crest { width: 22px; height: 22px; flex: none; display: block; border-radius: 50%; background-size: contain; background-repeat: no-repeat; background-position: center; background-color: var(--ss-w-bubble); border: 1px solid var(--ss-w-line); }
1724
- .ss-w-bi-crest-mono { display: grid; place-items: center; font-size: 9px; font-weight: 700; color: var(--ss-w-accent); background: color-mix(in srgb, var(--ss-w-accent) 16%, var(--ss-w-panel)); }
1783
+ .ss-w-bi-comp-logo { width: 16px; height: 16px; flex: none; display: block; border-radius: 50%; object-fit: contain; }
1784
+ .ss-w-bi-crest { width: 24px; height: 24px; flex: none; display: block; border-radius: 50%; object-fit: contain; }
1785
+ .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)); }
1725
1786
 
1726
1787
  /* row 2: crest-flanked fixture on the left, boxed action panel on the right */
1727
1788
  .ss-w-bi-body { display: flex; align-items: center; gap: 10px; }
@@ -1729,16 +1790,16 @@ var WIDGET_CSS = `.ss-w-root { all: revert; }
1729
1790
  .ss-w-bi-team { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
1730
1791
  .ss-w-bi-vs { font-size: 10px; font-weight: 600; color: var(--ss-w-faint); flex: none; }
1731
1792
  .ss-w-bi-fixture-txt { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
1732
- .ss-w-bi-action { margin-left: auto; flex: none; display: flex; flex-direction: column; align-items: stretch; gap: 6px; background: var(--ss-w-bubble); border: 1px solid var(--ss-w-line); border-radius: 12px; padding: 8px 10px; }
1733
- .ss-w-bi-action .ss-w-slip > button { flex: 1; text-align: center; }
1793
+ .ss-w-bi-action { margin-left: auto; flex: none; display: flex; flex-direction: column; align-items: flex-end; gap: 4px; min-width: 0; }
1794
+ .ss-w-bi-action .ss-w-slip { width: 100%; }
1795
+ .ss-w-bi-action .ss-w-slip > button { flex: 1; width: 100%; text-align: center; border-radius: 8px; }
1734
1796
  /* The action column is narrow; let its toast size to its own text and anchor to the card's right edge instead of being clipped to the button width. */
1735
1797
  .ss-w-bi-action .ss-w-toast { left: auto; right: 0; width: max-content; max-width: 210px; white-space: normal; }
1736
- .ss-w-bi-mkt { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
1737
- .ss-w-bi-market { font-size: 13.5px; font-weight: 700; line-height: 1.25; color: var(--ss-w-ink); }
1738
- .ss-w-bi-sel { font-size: 11.5px; line-height: 1.3; color: var(--ss-w-faint); }
1739
- .ss-w-bi-odds { flex: none; display: flex; flex-direction: column; align-items: center; justify-content: center; text-align: center; gap: 1px; background: color-mix(in srgb, var(--ss-w-accent) 12%, var(--ss-w-panel)); border: 1px solid color-mix(in srgb, var(--ss-w-accent) 30%, var(--ss-w-line)); border-radius: 10px; padding: 4px 11px; min-width: 62px; }
1740
- .ss-w-bi-odds-cap { font-size: 9px; font-weight: 700; letter-spacing: .09em; text-transform: uppercase; color: var(--ss-w-faint); }
1741
- .ss-w-bi-odds b { font-size: 19px; font-family: ui-monospace, monospace; font-weight: 700; font-variant-numeric: tabular-nums; color: var(--ss-w-accent); line-height: 1.1; }
1798
+ .ss-w-bi-mkt { display: flex; flex-direction: column; gap: 1px; min-width: 0; align-items: flex-end; text-align: right; }
1799
+ .ss-w-bi-market { font-size: 12px; font-weight: 600; line-height: 1.2; color: var(--ss-w-faint); white-space: nowrap; }
1800
+ .ss-w-bi-sel { font-size: 11px; line-height: 1.25; color: var(--ss-w-faint); }
1801
+ .ss-w-bi-odds { flex: none; display: flex; align-items: baseline; justify-content: flex-end; gap: 5px; }
1802
+ .ss-w-bi-odds b { font-size: 23px; font-family: ui-monospace, monospace; font-weight: 800; font-variant-numeric: tabular-nums; color: var(--ss-w-accent); line-height: 1.05; }
1742
1803
  .ss-w-bi-book { font-size: 9.5px; color: var(--ss-w-faint); }
1743
1804
 
1744
1805
  /* row 3: bar-chart glyph + reasoning prose + stats-expander chevron, in a subtle strip */
@@ -1752,11 +1813,11 @@ var WIDGET_CSS = `.ss-w-root { all: revert; }
1752
1813
  }
1753
1814
  .ss-w-bi-reason-body { display: flex; flex-direction: column; gap: 8px; min-width: 0; flex: 1; }
1754
1815
  .ss-w-bi-text { font-size: 12px; line-height: 1.45; color: var(--ss-w-ink); }
1755
- .ss-w-bi-stats { display: flex; flex-direction: column; gap: 8px; }
1756
- .ss-w-bi-toggle { align-self: flex-start; display: inline-flex; align-items: center; gap: 5px; background: none; border: none; color: var(--ss-w-accent); font: inherit; font-size: 11.5px; font-weight: 600; padding: 2px 0; cursor: pointer; }
1757
- .ss-w-bi-toggle:hover { text-decoration: underline; }
1758
- .ss-w-bi-chev { width: 7px; height: 7px; border-right: 1.5px solid currentColor; border-bottom: 1.5px solid currentColor; transform: translateY(-2px) rotate(45deg); transition: transform .15s ease; }
1759
- .ss-w-bi-chev-up { transform: translateY(1px) rotate(-135deg); }
1816
+ .ss-w-bi-text-clamp { display: -webkit-box; -webkit-line-clamp: 2; line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden; }
1817
+ .ss-w-bi-expand { flex: none; align-self: center; display: grid; place-items: center; width: 22px; height: 22px; margin: -2px -4px -2px 0; background: none; border: none; color: var(--ss-w-faint); cursor: pointer; }
1818
+ .ss-w-bi-expand:hover { color: var(--ss-w-ink); }
1819
+ .ss-w-bi-arrow { width: 7px; height: 7px; border-right: 1.6px solid currentColor; border-top: 1.6px solid currentColor; transform: rotate(45deg); transition: transform .15s ease; }
1820
+ .ss-w-bi-arrow-open { transform: rotate(135deg); }
1760
1821
  .ss-w-bi-rows { display: flex; flex-direction: column; }
1761
1822
  .ss-w-bi-row { display: flex; align-items: baseline; justify-content: space-between; gap: 12px; padding: 5px 0; border-top: 1px solid var(--ss-w-line); }
1762
1823
  .ss-w-bi-row:first-child { border-top: none; }
@@ -1771,11 +1832,13 @@ var WIDGET_CSS = `.ss-w-root { all: revert; }
1771
1832
  /* market odds card */
1772
1833
  .ss-w-mocards { display: flex; flex-direction: column; gap: 7px; }
1773
1834
  .ss-w-mocard { background: var(--ss-w-panel); border: 1px solid var(--ss-w-line); border-radius: 12px; border-left: 3px solid var(--ss-w-accent); padding: 9px 11px; display: flex; flex-direction: column; gap: 6px; }
1774
- .ss-w-mo-head { display: flex; align-items: center; gap: 8px; }
1775
- .ss-w-mo-market { font-size: 13px; font-weight: 700; color: var(--ss-w-ink); }
1776
- .ss-w-mo-tag { margin-left: auto; flex: none; font-size: 9px; font-weight: 700; letter-spacing: .09em; text-transform: uppercase; color: var(--ss-w-faint); border: 1px solid var(--ss-w-line); border-radius: 8px; padding: 2px 7px; }
1777
- .ss-w-mo-fixture { display: flex; align-items: center; gap: 6px; font-size: 11.5px; color: var(--ss-w-faint); min-width: 0; }
1778
- .ss-w-mo-comp { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
1835
+ .ss-w-mo-sub { display: flex; flex-direction: column; gap: 5px; min-width: 0; }
1836
+ .ss-w-mo-teams { font-size: 13px; font-weight: 650; color: var(--ss-w-ink); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
1837
+ .ss-w-mo-market { font-size: 11.5px; font-weight: 600; color: var(--ss-w-faint); }
1838
+ .ss-w-mo-more { align-self: flex-start; display: inline-flex; align-items: center; gap: 5px; background: none; border: none; color: var(--ss-w-accent); font: inherit; font-size: 11.5px; font-weight: 600; padding: 2px 0; cursor: pointer; }
1839
+ .ss-w-mo-more:hover { text-decoration: underline; }
1840
+ .ss-w-mo-more-chev { width: 7px; height: 7px; border-right: 1.5px solid currentColor; border-bottom: 1.5px solid currentColor; transform: translateY(-2px) rotate(45deg); transition: transform .15s ease; }
1841
+ .ss-w-mo-more-chev-up { transform: translateY(1px) rotate(-135deg); }
1779
1842
  .ss-w-mo-rows { display: flex; flex-direction: column; gap: 4px; }
1780
1843
  .ss-w-mo-row { display: flex; align-items: center; gap: 10px; padding: 4px 0; border-bottom: 1px dashed var(--ss-w-line); }
1781
1844
  .ss-w-mo-row:last-child { border-bottom: none; }
@@ -1961,7 +2024,7 @@ var WIDGET_CSS = `.ss-w-root { all: revert; }
1961
2024
  `;
1962
2025
 
1963
2026
  // src/StatsWidget.tsx
1964
- import { jsx as jsx28 } from "react/jsx-runtime";
2027
+ import { jsx as jsx29 } from "react/jsx-runtime";
1965
2028
  function buildClient(props) {
1966
2029
  try {
1967
2030
  if ("client" in props && props.client) {
@@ -2014,7 +2077,7 @@ function StatsWidget(props) {
2014
2077
  }, [built, ui.injectStyles]);
2015
2078
  if (!built) return null;
2016
2079
  const attrs = themeAttrs(ui);
2017
- return /* @__PURE__ */ jsx28("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__ */ jsx28(ChatPanel, { controller, ui, slip, onDashboardTap }) : /* @__PURE__ */ jsx28(
2080
+ return /* @__PURE__ */ jsx29("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__ */ jsx29(ChatPanel, { controller, ui, slip, onDashboardTap }) : /* @__PURE__ */ jsx29(
2018
2081
  ChatFab,
2019
2082
  {
2020
2083
  label: ui.launcherLabel,