@sensiblestats/widget-react 0.16.0 → 0.18.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
@@ -2,6 +2,18 @@
2
2
  import { useEffect as useEffect6, useMemo as useMemo2, useRef as useRef3 } from "react";
3
3
  import { StatsWidgetClient } from "@sensiblestats/widget-sdk";
4
4
 
5
+ // src/kickoff.ts
6
+ function formatBettingKickoff(kickoffUtc, ui, locale, now = /* @__PURE__ */ new Date()) {
7
+ const at = new Date(kickoffUtc);
8
+ const intlLocale = locale === "tr" ? "tr-TR" : "en-GB";
9
+ const time = new Intl.DateTimeFormat(intlLocale, { hour: "2-digit", minute: "2-digit" }).format(at);
10
+ const atLocalDate = new Date(at.getFullYear(), at.getMonth(), at.getDate());
11
+ const nowLocalDate = new Date(now.getFullYear(), now.getMonth(), now.getDate());
12
+ const dayDiff = Math.round((atLocalDate.getTime() - nowLocalDate.getTime()) / 864e5);
13
+ const dayLabel = dayDiff === 0 ? ui.today : dayDiff === 1 ? ui.tomorrow : new Intl.DateTimeFormat(intlLocale, { weekday: "short", day: "numeric", month: "short" }).format(at);
14
+ return { dayLabel, time };
15
+ }
16
+
5
17
  // src/i18n.ts
6
18
  var STRINGS = {
7
19
  en: {
@@ -56,7 +68,10 @@ var STRINGS = {
56
68
  slipPriceNotAccepted: (from, to) => `Price moved to ${to} (was ${from}). Tap to add.`,
57
69
  slipLoginRequired: "Sign in to add to your slip",
58
70
  slipSuspended: "Market suspended",
59
- slipFailed: "Couldn't add to slip. Try again."
71
+ slipFailed: "Couldn't add to slip. Try again.",
72
+ today: "Today",
73
+ tomorrow: "Tomorrow",
74
+ bettingKickoff: (kickoffUtc, now) => formatBettingKickoff(kickoffUtc, STRINGS.en, "en", now)
60
75
  },
61
76
  tr: {
62
77
  launcherLabel: "SensibleStats ile sohbet et",
@@ -110,7 +125,10 @@ var STRINGS = {
110
125
  slipPriceNotAccepted: (from, to) => `Oran ${to} oldu (\xF6nceki ${from}). Eklemek i\xE7in dokunun.`,
111
126
  slipLoginRequired: "Kupona eklemek i\xE7in giri\u015F yap\u0131n",
112
127
  slipSuspended: "Bu market ask\u0131ya al\u0131nd\u0131",
113
- slipFailed: "Kupona eklenemedi. Tekrar deneyin."
128
+ slipFailed: "Kupona eklenemedi. Tekrar deneyin.",
129
+ today: "Bug\xFCn",
130
+ tomorrow: "Yar\u0131n",
131
+ bettingKickoff: (kickoffUtc, now) => formatBettingKickoff(kickoffUtc, STRINGS.tr, "tr", now)
114
132
  }
115
133
  };
116
134
  function resolveLocaleStrings(locale) {
@@ -177,7 +195,10 @@ function normalizeUi(cfg) {
177
195
  slipPriceNotAccepted: strings.slipPriceNotAccepted,
178
196
  slipLoginRequired: strings.slipLoginRequired,
179
197
  slipSuspended: strings.slipSuspended,
180
- slipFailed: strings.slipFailed
198
+ slipFailed: strings.slipFailed,
199
+ today: strings.today,
200
+ tomorrow: strings.tomorrow,
201
+ bettingKickoff: strings.bettingKickoff
181
202
  };
182
203
  }
183
204
  function toClientOptions(cfg) {
@@ -366,7 +387,13 @@ function toInsightVM(insight) {
366
387
  oddsDecimal: typeof oddsValue === "number" && Number.isFinite(oddsValue) ? oddsValue : void 0,
367
388
  quotedAtUtc: str(insight.odds?.quotedAtUtc),
368
389
  previousOdds: previous !== void 0 && direction !== void 0 ? previous.toFixed(2) : void 0,
369
- movementDirection: previous !== void 0 && direction !== void 0 ? direction : void 0
390
+ movementDirection: previous !== void 0 && direction !== void 0 ? direction : void 0,
391
+ homeTeamName: home,
392
+ awayTeamName: away,
393
+ homeCrest: str(insight.homeTeamImagePath),
394
+ awayCrest: str(insight.awayTeamImagePath),
395
+ competitionCrest: str(insight.competitionImagePath),
396
+ kickoffUtc: str(insight.kickoffUtc)
370
397
  };
371
398
  }
372
399
  function toSlipSelection(vm, conversationId) {
@@ -588,7 +615,7 @@ function useChatStream(conversation, starterTexts, greetingMessage) {
588
615
  const send = useCallback((text, opts) => {
589
616
  const trimmed = text.trim();
590
617
  if (!trimmed) return;
591
- run({ message: trimmed, actionId: opts?.actionId }, opts?.displayText);
618
+ run({ message: trimmed, actionId: opts?.actionId, grounding: opts?.grounding }, opts?.displayText);
592
619
  }, [run]);
593
620
  const retry = useCallback(() => {
594
621
  const last = stateRef.current.lastUserInput;
@@ -941,7 +968,7 @@ function OddsMovementBadge({
941
968
  }
942
969
 
943
970
  // src/components/BettingInsightList.tsx
944
- import { jsx as jsx10, jsxs as jsxs7 } from "react/jsx-runtime";
971
+ import { Fragment, jsx as jsx10, jsxs as jsxs7 } from "react/jsx-runtime";
945
972
  function StatRows2({ stats }) {
946
973
  return /* @__PURE__ */ jsx10("div", { className: "ss-w-bi-rows", children: stats.map((s) => /* @__PURE__ */ jsxs7("div", { className: "ss-w-bi-row", children: [
947
974
  /* @__PURE__ */ jsx10("span", { className: "ss-w-bi-row-lbl", children: s.label }),
@@ -956,10 +983,21 @@ function ScopedStats({ scopes }) {
956
983
  /* @__PURE__ */ jsx10(StatRows2, { stats: current.stats }, current.key)
957
984
  ] });
958
985
  }
986
+ function hasStatsContent(it) {
987
+ return !!it.scopes && it.scopes.length >= 2 || it.stats.length > 0;
988
+ }
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
+ }
959
997
  function StatsDisclosure({ it, ui }) {
960
998
  const [open, setOpen] = useState3(false);
961
999
  const hasScopes = !!it.scopes && it.scopes.length >= 2;
962
- const hasStats = hasScopes || it.stats.length > 0;
1000
+ const hasStats = hasStatsContent(it);
963
1001
  if (!hasStats) return null;
964
1002
  return /* @__PURE__ */ jsxs7("div", { className: "ss-w-bi-stats", children: [
965
1003
  /* @__PURE__ */ jsxs7(
@@ -980,24 +1018,42 @@ function StatsDisclosure({ it, ui }) {
980
1018
  }
981
1019
  function InsightCard({ it, ui, slip }) {
982
1020
  const live = it.isLive || it.phase === "live";
1021
+ const kickoff = it.kickoffUtc && ui?.bettingKickoff ? ui.bettingKickoff(it.kickoffUtc) : void 0;
1022
+ const hasNamedTeams = !!it.homeTeamName && !!it.awayTeamName;
1023
+ const showHeader = !!it.competition || live || !!kickoff;
1024
+ const showReason = !!it.text || hasStatsContent(it);
983
1025
  return /* @__PURE__ */ jsxs7("div", { className: "ss-w-binsight", children: [
984
- live || it.isMarketFloor ? /* @__PURE__ */ jsxs7("div", { className: "ss-w-bi-status", children: [
985
- live ? /* @__PURE__ */ jsxs7("span", { className: "ss-w-bi-live", children: [
986
- /* @__PURE__ */ jsx10("span", { className: "ss-w-bi-live-dot", "aria-hidden": "true" }),
987
- ui?.liveLabel ?? "Live"
988
- ] }) : null,
989
- it.isMarketFloor ? /* @__PURE__ */ jsx10("span", { className: "ss-w-bi-floor", children: ui?.marketFloorLabel ?? "Market price \xB7 not a value pick" }) : null
990
- ] }) : null,
991
- /* @__PURE__ */ jsxs7("div", { className: "ss-w-bi-head", children: [
992
- /* @__PURE__ */ jsxs7("span", { className: "ss-w-bi-mkt", children: [
993
- /* @__PURE__ */ jsx10("span", { className: "ss-w-bi-market", children: it.market }),
994
- it.selection ? /* @__PURE__ */ jsx10("span", { className: "ss-w-bi-sel", children: it.selection }) : null,
995
- it.fixture || it.competition ? /* @__PURE__ */ jsxs7("span", { className: "ss-w-bi-match", children: [
996
- it.fixture ? /* @__PURE__ */ jsx10("span", { className: "ss-w-bi-fixture", children: it.fixture }) : null,
997
- it.competition ? /* @__PURE__ */ jsx10("span", { className: "ss-w-bi-comp", children: it.competition }) : null
1026
+ 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 })
1030
+ ] }) : null }),
1031
+ /* @__PURE__ */ jsxs7("span", { className: "ss-w-bi-header-right", children: [
1032
+ live ? /* @__PURE__ */ jsxs7("span", { className: "ss-w-bi-live", children: [
1033
+ /* @__PURE__ */ jsx10("span", { className: "ss-w-bi-live-dot", "aria-hidden": "true" }),
1034
+ ui?.liveLabel ?? "Live"
1035
+ ] }) : null,
1036
+ kickoff ? /* @__PURE__ */ jsxs7("span", { className: "ss-w-bi-kick", children: [
1037
+ kickoff.dayLabel,
1038
+ " ",
1039
+ kickoff.time
998
1040
  ] }) : null
999
- ] }),
1041
+ ] })
1042
+ ] }) : null,
1043
+ /* @__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 }),
1000
1051
  /* @__PURE__ */ jsxs7("div", { className: "ss-w-bi-action", children: [
1052
+ /* @__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
1055
+ ] }),
1056
+ it.isMarketFloor ? /* @__PURE__ */ jsx10("span", { className: "ss-w-bi-floor", children: ui?.marketFloorLabel ?? "Market price \xB7 not a value pick" }) : null,
1001
1057
  it.odds ? /* @__PURE__ */ jsxs7("span", { className: "ss-w-bi-odds", children: [
1002
1058
  /* @__PURE__ */ jsx10("span", { className: "ss-w-bi-odds-cap", children: ui?.oddsLabel ?? "Odds" }),
1003
1059
  /* @__PURE__ */ jsx10("b", { children: it.odds }),
@@ -1007,8 +1063,13 @@ function InsightCard({ it, ui, slip }) {
1007
1063
  /* @__PURE__ */ jsx10(AddToSlipButton, { it, slip, ui })
1008
1064
  ] })
1009
1065
  ] }),
1010
- it.text ? /* @__PURE__ */ jsx10("p", { className: "ss-w-bi-text", children: it.text }) : null,
1011
- /* @__PURE__ */ jsx10(StatsDisclosure, { it, ui }),
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,
1012
1073
  it.disclaimer ? /* @__PURE__ */ jsx10("p", { className: "ss-w-bi-disc", children: it.disclaimer }) : null
1013
1074
  ] });
1014
1075
  }
@@ -1058,7 +1119,7 @@ function MarketOddsCardList({ cards, ui, slip }) {
1058
1119
  }
1059
1120
 
1060
1121
  // src/components/OddsMovementCard.tsx
1061
- import { Fragment, jsx as jsx12, jsxs as jsxs9 } from "react/jsx-runtime";
1122
+ import { Fragment as Fragment2, jsx as jsx12, jsxs as jsxs9 } from "react/jsx-runtime";
1062
1123
  function directionWord(vm, ui) {
1063
1124
  if (vm.direction === "shortened") return ui?.oddsShortened ?? "shortened";
1064
1125
  if (vm.direction === "drifted") return ui?.oddsDrifted ?? "drifted";
@@ -1094,7 +1155,7 @@ function OddsMovementCard({ vm, ui }) {
1094
1155
  ] }, g.price)),
1095
1156
  /* @__PURE__ */ jsx12("path", { className: "ss-w-om-line", d: vm.geometry.path, fill: "none" }),
1096
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)),
1097
- ui?.chartTimeLabel ? /* @__PURE__ */ jsxs9(Fragment, { children: [
1158
+ ui?.chartTimeLabel ? /* @__PURE__ */ jsxs9(Fragment2, { children: [
1098
1159
  /* @__PURE__ */ jsx12("text", { className: "ss-w-om-xlab", x: 40, y: CHART_VIEW_H - 8, textAnchor: "start", children: ui.chartTimeLabel(vm.geometry.startMs) }),
1099
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) })
1100
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` })
@@ -1125,12 +1186,12 @@ function OddsMovementCardList({ cards, ui }) {
1125
1186
  }
1126
1187
 
1127
1188
  // src/components/ActionButtons.tsx
1128
- import { Fragment as Fragment2, jsx as jsx13, jsxs as jsxs10 } from "react/jsx-runtime";
1189
+ import { Fragment as Fragment3, jsx as jsx13, jsxs as jsxs10 } from "react/jsx-runtime";
1129
1190
  function ActionButtons({ actions, loading, disabled, onAct }) {
1130
1191
  if (actions.length === 0 && !loading) return null;
1131
1192
  return /* @__PURE__ */ jsxs10("div", { className: "ss-w-actions", children: [
1132
1193
  actions.map((a, i) => /* @__PURE__ */ jsx13("button", { type: "button", className: "ss-w-abtn", disabled, onClick: () => onAct(a), children: a.label }, `${a.label}-${i}`)),
1133
- actions.length === 0 && loading ? /* @__PURE__ */ jsxs10(Fragment2, { children: [
1194
+ actions.length === 0 && loading ? /* @__PURE__ */ jsxs10(Fragment3, { children: [
1134
1195
  /* @__PURE__ */ jsx13("span", { className: "ss-w-abtn-shimmer" }),
1135
1196
  /* @__PURE__ */ jsx13("span", { className: "ss-w-abtn-shimmer" })
1136
1197
  ] }) : null
@@ -1196,13 +1257,13 @@ function GreetingHeaderBlock({ block }) {
1196
1257
  // src/components/club-dashboard/FixtureHeroBlock.tsx
1197
1258
  import { useState as useState5 } from "react";
1198
1259
  import { jsx as jsx17, jsxs as jsxs14 } from "react/jsx-runtime";
1199
- function initials2(name) {
1260
+ function initials3(name) {
1200
1261
  return name.split(/\s+/).filter(Boolean).slice(0, 2).map((w) => w[0].toUpperCase()).join("");
1201
1262
  }
1202
- function Crest2({ url, name }) {
1263
+ function Crest3({ url, name }) {
1203
1264
  const [broken, setBroken] = useState5(false);
1204
1265
  if (!url || broken) {
1205
- return /* @__PURE__ */ jsx17("span", { className: "ss-w-cdash-crest ss-w-cdash-crest--fallback", "aria-hidden": "true", children: initials2(name) });
1266
+ return /* @__PURE__ */ jsx17("span", { className: "ss-w-cdash-crest ss-w-cdash-crest--fallback", "aria-hidden": "true", children: initials3(name) });
1206
1267
  }
1207
1268
  return /* @__PURE__ */ jsx17("img", { className: "ss-w-cdash-crest", src: url, alt: "", "aria-hidden": "true", onError: () => setBroken(true) });
1208
1269
  }
@@ -1213,18 +1274,18 @@ function FixtureHeroBlock({
1213
1274
  }) {
1214
1275
  return /* @__PURE__ */ jsxs14("div", { className: "ss-w-cdash-fixture", children: [
1215
1276
  /* @__PURE__ */ jsxs14("div", { className: "ss-w-cdash-tie", children: [
1216
- /* @__PURE__ */ jsx17(Crest2, { url: block.homeCrestUrl, name: block.homeName }),
1277
+ /* @__PURE__ */ jsx17(Crest3, { url: block.homeCrestUrl, name: block.homeName }),
1217
1278
  /* @__PURE__ */ jsx17("span", { className: "ss-w-cdash-team", children: block.homeName }),
1218
1279
  /* @__PURE__ */ jsx17("span", { className: "ss-w-cdash-vs", children: "v" }),
1219
1280
  /* @__PURE__ */ jsx17("span", { className: "ss-w-cdash-team", children: block.awayName }),
1220
- /* @__PURE__ */ jsx17(Crest2, { url: block.awayCrestUrl, name: block.awayName })
1281
+ /* @__PURE__ */ jsx17(Crest3, { url: block.awayCrestUrl, name: block.awayName })
1221
1282
  ] }),
1222
1283
  /* @__PURE__ */ jsxs14("div", { className: "ss-w-cdash-fixture-meta", children: [
1223
1284
  /* @__PURE__ */ jsx17("span", { className: "ss-w-cdash-kickoff", children: ui.clubKickoff(block.kickoffUnix * 1e3) }),
1224
1285
  block.venue ? /* @__PURE__ */ jsx17("span", { className: "ss-w-cdash-venue", children: block.venue }) : null,
1225
1286
  block.competition ? /* @__PURE__ */ jsx17("span", { className: "ss-w-cdash-comp", children: block.competition }) : null
1226
1287
  ] }),
1227
- /* @__PURE__ */ jsx17("button", { type: "button", className: "ss-w-cdash-cta", onClick: () => onAction(block.ctaActionValue, block.ctaLabel), children: block.ctaLabel })
1288
+ /* @__PURE__ */ jsx17("button", { type: "button", className: "ss-w-cdash-cta", onClick: () => onAction(block.ctaActionValue, void 0, block.ctaLabel), children: block.ctaLabel })
1228
1289
  ] });
1229
1290
  }
1230
1291
 
@@ -1248,11 +1309,28 @@ function MatchPreviewBlock({
1248
1309
  ui,
1249
1310
  onAction
1250
1311
  }) {
1251
- if (block.lines.length === 0) return null;
1312
+ if (block.facts.length === 0) return null;
1252
1313
  const heading = ui.clubMatchPreviewHeading;
1253
- return /* @__PURE__ */ jsxs16("button", { type: "button", className: "ss-w-cdash-lines", onClick: () => onAction(block.actionValue, heading), children: [
1314
+ return /* @__PURE__ */ jsxs16("div", { className: "ss-w-cdash-lines", children: [
1254
1315
  /* @__PURE__ */ jsx19("span", { className: "ss-w-cdash-lines-heading", children: heading }),
1255
- block.lines.map((line, i) => /* @__PURE__ */ jsx19("p", { children: line }, i))
1316
+ block.facts.map(
1317
+ (f, i) => f.entityRef ? /* @__PURE__ */ jsxs16(
1318
+ "button",
1319
+ {
1320
+ type: "button",
1321
+ className: "ss-w-cdash-fact",
1322
+ onClick: () => onAction(f.question ?? f.sentence, { ...f.entityRef, subject: f.sentence, personaHint: "club" }),
1323
+ children: [
1324
+ f.sentence,
1325
+ f.label ? /* @__PURE__ */ jsxs16("span", { className: "ss-w-cdash-fact-label", children: [
1326
+ " ",
1327
+ f.label
1328
+ ] }) : null
1329
+ ]
1330
+ },
1331
+ i
1332
+ ) : /* @__PURE__ */ jsx19("p", { children: f.sentence }, i)
1333
+ )
1256
1334
  ] });
1257
1335
  }
1258
1336
 
@@ -1263,11 +1341,28 @@ function TeamDnaBlock({
1263
1341
  ui,
1264
1342
  onAction
1265
1343
  }) {
1266
- if (block.lines.length === 0) return null;
1344
+ if (block.facts.length === 0) return null;
1267
1345
  const heading = ui.clubTeamDnaHeading;
1268
- return /* @__PURE__ */ jsxs17("button", { type: "button", className: "ss-w-cdash-lines", onClick: () => onAction(block.actionValue, heading), children: [
1346
+ return /* @__PURE__ */ jsxs17("div", { className: "ss-w-cdash-lines", children: [
1269
1347
  /* @__PURE__ */ jsx20("span", { className: "ss-w-cdash-lines-heading", children: heading }),
1270
- block.lines.map((line, i) => /* @__PURE__ */ jsx20("p", { children: line }, i))
1348
+ block.facts.map(
1349
+ (f, i) => f.entityRef ? /* @__PURE__ */ jsxs17(
1350
+ "button",
1351
+ {
1352
+ type: "button",
1353
+ className: "ss-w-cdash-fact",
1354
+ onClick: () => onAction(f.question ?? f.sentence, { ...f.entityRef, subject: f.sentence, personaHint: "club" }),
1355
+ children: [
1356
+ f.sentence,
1357
+ f.label ? /* @__PURE__ */ jsxs17("span", { className: "ss-w-cdash-fact-label", children: [
1358
+ " ",
1359
+ f.label
1360
+ ] }) : null
1361
+ ]
1362
+ },
1363
+ i
1364
+ ) : /* @__PURE__ */ jsx20("p", { children: f.sentence }, i)
1365
+ )
1271
1366
  ] });
1272
1367
  }
1273
1368
 
@@ -1402,7 +1497,7 @@ function MessageList({ controller, ui, slip, onDashboardTap }) {
1402
1497
  send(tile.actionValue, { displayText: tile.title });
1403
1498
  if (m.role === "assistant" && m.greetingDashboard) onDashboardTap?.(tile, index, m.greetingDashboard.layout);
1404
1499
  },
1405
- onClubAction: (actionValue, displayText) => send(actionValue, { displayText }),
1500
+ onClubAction: (message, grounding, displayText) => send(message, { grounding, displayText: displayText ?? message }),
1406
1501
  ui,
1407
1502
  slip
1408
1503
  },
@@ -1611,26 +1706,51 @@ var WIDGET_CSS = `.ss-w-root { all: revert; }
1611
1706
 
1612
1707
  /* betting insights */
1613
1708
  .ss-w-binsights { display: flex; flex-direction: column; gap: 7px; }
1614
- .ss-w-binsight { 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; }
1615
- .ss-w-bi-status { display: flex; align-items: center; gap: 6px; min-width: 0; }
1616
- .ss-w-bi-fixture { font-size: 11px; color: var(--ss-w-ink); font-weight: 600; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
1617
- .ss-w-bi-comp { font-size: 10.5px; color: var(--ss-w-faint); font-weight: 500; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
1709
+ .ss-w-binsight { 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: 8px; }
1710
+
1711
+ /* row 1: competition logo + name on the left, live badge + kickoff on the right */
1712
+ .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); }
1713
+ .ss-w-bi-header-left { display: inline-flex; align-items: center; gap: 6px; min-width: 0; }
1714
+ .ss-w-bi-header-right { display: inline-flex; align-items: center; gap: 8px; flex: none; }
1715
+ .ss-w-bi-comp { font-size: 10.5px; color: var(--ss-w-faint); font-weight: 600; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
1716
+ .ss-w-bi-kick { font-size: 10.5px; font-weight: 600; color: var(--ss-w-faint); white-space: nowrap; flex: none; }
1618
1717
  .ss-w-bi-live { font-size: 8.5px; font-weight: 700; letter-spacing: .04em; text-transform: uppercase; color: #fff; background: #c0392b; border-radius: 4px; padding: 1px 5px; display: inline-flex; align-items: center; gap: 4px; flex: none; }
1619
1718
  .ss-w-bi-live-dot { width: 5px; height: 5px; border-radius: 50%; background: #fff; }
1620
- .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; flex: none; }
1621
- .ss-w-bi-head { display: flex; align-items: flex-start; gap: 10px; }
1622
- .ss-w-bi-mkt { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
1623
- .ss-w-bi-market { font-size: 15px; font-weight: 700; line-height: 1.25; color: var(--ss-w-ink); }
1624
- .ss-w-bi-sel { font-size: 12px; line-height: 1.3; color: var(--ss-w-faint); }
1625
- .ss-w-bi-match { display: flex; flex-direction: column; gap: 1px; min-width: 0; margin-top: 5px; }
1626
- .ss-w-bi-action { margin-left: auto; flex: none; display: flex; flex-direction: column; align-items: stretch; gap: 6px; }
1719
+ .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
+
1721
+ /* 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)); }
1725
+
1726
+ /* row 2: crest-flanked fixture on the left, boxed action panel on the right */
1727
+ .ss-w-bi-body { display: flex; align-items: center; gap: 10px; }
1728
+ .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); }
1729
+ .ss-w-bi-team { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
1730
+ .ss-w-bi-vs { font-size: 10px; font-weight: 600; color: var(--ss-w-faint); flex: none; }
1731
+ .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; }
1627
1733
  .ss-w-bi-action .ss-w-slip > button { flex: 1; text-align: center; }
1628
1734
  /* 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. */
1629
1735
  .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); }
1630
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; }
1631
1740
  .ss-w-bi-odds-cap { font-size: 9px; font-weight: 700; letter-spacing: .09em; text-transform: uppercase; color: var(--ss-w-faint); }
1632
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; }
1633
1742
  .ss-w-bi-book { font-size: 9.5px; color: var(--ss-w-faint); }
1743
+
1744
+ /* row 3: bar-chart glyph + reasoning prose + stats-expander chevron, in a subtle strip */
1745
+ .ss-w-bi-reason { display: flex; align-items: flex-start; gap: 8px; background: var(--ss-w-bubble); border-radius: 10px; padding: 8px 10px; }
1746
+ .ss-w-bi-chart { flex: none; width: 13px; height: 12px; margin-top: 2px; opacity: .55;
1747
+ background:
1748
+ linear-gradient(var(--ss-w-accent), var(--ss-w-accent)) left / 3px 55% no-repeat,
1749
+ linear-gradient(var(--ss-w-accent), var(--ss-w-accent)) center / 3px 100% no-repeat,
1750
+ linear-gradient(var(--ss-w-accent), var(--ss-w-accent)) right / 3px 75% no-repeat;
1751
+ background-position: left bottom, center bottom, right bottom;
1752
+ }
1753
+ .ss-w-bi-reason-body { display: flex; flex-direction: column; gap: 8px; min-width: 0; flex: 1; }
1634
1754
  .ss-w-bi-text { font-size: 12px; line-height: 1.45; color: var(--ss-w-ink); }
1635
1755
  .ss-w-bi-stats { display: flex; flex-direction: column; gap: 8px; }
1636
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; }
@@ -1827,9 +1947,12 @@ var WIDGET_CSS = `.ss-w-root { all: revert; }
1827
1947
  .ss-w-cdash-pill--d { background: var(--ss-w-faint); }
1828
1948
  .ss-w-cdash-pill--l { background: color-mix(in srgb, var(--ss-w-neg) 88%, black); }
1829
1949
 
1830
- .ss-w-cdash-lines { display: flex; flex-direction: column; align-items: flex-start; gap: 4px; width: 100%; text-align: left; background: var(--ss-w-panel); border: 1px solid var(--ss-w-line); border-left: 3px solid var(--ss-w-accent); border-radius: 12px; padding: 10px 12px; font: inherit; cursor: pointer; }
1950
+ .ss-w-cdash-lines { display: flex; flex-direction: column; align-items: flex-start; gap: 4px; width: 100%; text-align: left; background: var(--ss-w-panel); border: 1px solid var(--ss-w-line); border-left: 3px solid var(--ss-w-accent); border-radius: 12px; padding: 10px 12px; }
1831
1951
  .ss-w-cdash-lines-heading { font-size: 12px; font-weight: 700; color: var(--ss-w-accent); }
1832
- .ss-w-cdash-lines p { font-size: 12px; line-height: 1.4; color: var(--ss-w-ink); }
1952
+ .ss-w-cdash-lines p { font-size: 12px; line-height: 1.4; color: var(--ss-w-ink); margin: 0; }
1953
+ .ss-w-cdash-fact { display: block; width: 100%; text-align: left; font: inherit; font-size: 12px; line-height: 1.4; color: var(--ss-w-ink); background: none; border: none; padding: 0; cursor: pointer; }
1954
+ .ss-w-cdash-fact:hover, .ss-w-cdash-fact:focus-visible { color: var(--ss-w-accent); text-decoration: underline; }
1955
+ .ss-w-cdash-fact-label { font-size: 11px; font-weight: 700; color: var(--ss-w-accent); }
1833
1956
 
1834
1957
  .ss-w-cdash-cd { display: flex; flex-direction: column; align-items: center; gap: 4px; background: var(--ss-w-panel); border: 1px solid var(--ss-w-line); border-radius: 12px; padding: 10px 12px; text-align: center; }
1835
1958
  .ss-w-cdash-cd-clock { display: flex; align-items: baseline; justify-content: center; gap: 2px; font-variant-numeric: tabular-nums; font-family: ui-monospace, "SFMono-Regular", Menlo, monospace; font-weight: 700; font-size: 18px; color: var(--ss-w-ink); }