@sensiblestats/widget-react 0.14.1 → 0.16.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
@@ -1,5 +1,5 @@
1
1
  // src/StatsWidget.tsx
2
- import { useEffect as useEffect5, useMemo as useMemo2, useRef as useRef3 } from "react";
2
+ import { useEffect as useEffect6, useMemo as useMemo2, useRef as useRef3 } from "react";
3
3
  import { StatsWidgetClient } from "@sensiblestats/widget-sdk";
4
4
 
5
5
  // src/i18n.ts
@@ -20,6 +20,7 @@ var STRINGS = {
20
20
  hideStats: "Hide stats",
21
21
  oddsLabel: "Odds",
22
22
  liveLabel: "Live",
23
+ marketFloorLabel: "Market price \xB7 not a value pick",
23
24
  addToSlip: "Add to slip",
24
25
  marketPrices: "Market prices",
25
26
  moreLines: "More lines",
@@ -42,6 +43,12 @@ var STRINGS = {
42
43
  const drift = direction ? `, ${direction}` : "";
43
44
  return `Price movement over the last ${span}: opened at ${opening}, currently ${current}${drift}.`;
44
45
  },
46
+ clubMatchPreviewHeading: "Match preview",
47
+ clubTeamDnaHeading: "How they play",
48
+ clubFormHeading: "Recent form",
49
+ clubCountdownLabel: "Countdown to kick-off",
50
+ clubKickoff: (ms) => new Intl.DateTimeFormat("en-GB", { weekday: "short", day: "numeric", month: "short", hour: "2-digit", minute: "2-digit" }).format(new Date(ms)),
51
+ clubKickoffAria: (ms) => `Kick-off ${new Intl.DateTimeFormat("en-GB", { weekday: "long", day: "numeric", month: "long", hour: "2-digit", minute: "2-digit" }).format(new Date(ms))}`,
45
52
  inSlip: "In slip",
46
53
  slipAdded: (n) => n === void 0 ? "Added to slip" : `Added to slip (${n} selection${n === 1 ? "" : "s"})`,
47
54
  slipDuplicate: "Already in your slip",
@@ -67,6 +74,7 @@ var STRINGS = {
67
74
  hideStats: "\u0130statistikleri gizle",
68
75
  oddsLabel: "Oran",
69
76
  liveLabel: "Canl\u0131",
77
+ marketFloorLabel: "Piyasa oran\u0131 \xB7 de\u011Fer bahsi de\u011Fil",
70
78
  addToSlip: "Kupona ekle",
71
79
  marketPrices: "Piyasa fiyatlar\u0131",
72
80
  moreLines: "Di\u011Fer \xE7izgiler",
@@ -89,6 +97,12 @@ var STRINGS = {
89
97
  const drift = direction ? `, ${direction}` : "";
90
98
  return `Son ${span} i\xE7indeki oran hareketi: ${opening} ile a\xE7\u0131ld\u0131, \u015Fu anda ${current}${drift}.`;
91
99
  },
100
+ clubMatchPreviewHeading: "Ma\xE7 \xF6nizlemesi",
101
+ clubTeamDnaHeading: "Nas\u0131l oynuyorlar",
102
+ clubFormHeading: "Son form",
103
+ clubCountdownLabel: "Ba\u015Flama vuru\u015Funa kalan",
104
+ clubKickoff: (ms) => new Intl.DateTimeFormat("tr-TR", { weekday: "short", day: "numeric", month: "short", hour: "2-digit", minute: "2-digit" }).format(new Date(ms)),
105
+ clubKickoffAria: (ms) => `Ba\u015Flama vuru\u015Fu ${new Intl.DateTimeFormat("tr-TR", { weekday: "long", day: "numeric", month: "long", hour: "2-digit", minute: "2-digit" }).format(new Date(ms))}`,
92
106
  inSlip: "Kuponda",
93
107
  slipAdded: (n) => n === void 0 ? "Kupona eklendi" : `Kupona eklendi (${n} se\xE7im)`,
94
108
  slipDuplicate: "Bu se\xE7im kuponunuzda zaten var",
@@ -131,6 +145,7 @@ function normalizeUi(cfg) {
131
145
  hideStats: strings.hideStats,
132
146
  oddsLabel: strings.oddsLabel,
133
147
  liveLabel: strings.liveLabel,
148
+ marketFloorLabel: strings.marketFloorLabel,
134
149
  marketPrices: strings.marketPrices,
135
150
  moreLines: strings.moreLines,
136
151
  hideLines: strings.hideLines,
@@ -148,6 +163,12 @@ function normalizeUi(cfg) {
148
163
  excludedSuspended: strings.excludedSuspended,
149
164
  chartTimeLabel: strings.chartTimeLabel,
150
165
  chartAria: strings.chartAria,
166
+ clubMatchPreviewHeading: strings.clubMatchPreviewHeading,
167
+ clubTeamDnaHeading: strings.clubTeamDnaHeading,
168
+ clubFormHeading: strings.clubFormHeading,
169
+ clubCountdownLabel: strings.clubCountdownLabel,
170
+ clubKickoff: strings.clubKickoff,
171
+ clubKickoffAria: strings.clubKickoffAria,
151
172
  addToSlip: strings.addToSlip,
152
173
  inSlip: strings.inSlip,
153
174
  slipAdded: strings.slipAdded,
@@ -341,6 +362,7 @@ function toInsightVM(insight) {
341
362
  matchId: typeof insight.matchId === "number" ? insight.matchId : 0,
342
363
  matchOddsId,
343
364
  addToSlipEnabled: insight.addToSlipEnabled === true,
365
+ isMarketFloor: insight.isMarketFloor === true,
344
366
  oddsDecimal: typeof oddsValue === "number" && Number.isFinite(oddsValue) ? oddsValue : void 0,
345
367
  quotedAtUtc: str(insight.odds?.quotedAtUtc),
346
368
  previousOdds: previous !== void 0 && direction !== void 0 ? previous.toFixed(2) : void 0,
@@ -494,7 +516,8 @@ function applyEvent(state, event) {
494
516
  messages: mapActive(state, (t) => ({
495
517
  ...t,
496
518
  done: true,
497
- greetingDashboard: event.data.dashboard ?? void 0
519
+ greetingDashboard: event.data.dashboard ?? void 0,
520
+ clubDashboard: event.data.clubDashboard ?? void 0
498
521
  }))
499
522
  };
500
523
  }
@@ -661,7 +684,7 @@ function ChatFab({ label, greeting, popupText, onOpen, onDismissPopup, dismissLa
661
684
  }
662
685
 
663
686
  // src/components/ChatPanel.tsx
664
- import { useEffect as useEffect4, useState as useState6 } from "react";
687
+ import { useEffect as useEffect5, useState as useState8 } from "react";
665
688
 
666
689
  // src/components/ChatHeader.tsx
667
690
  import { jsx as jsx4, jsxs as jsxs3 } from "react/jsx-runtime";
@@ -674,7 +697,7 @@ function ChatHeader({ onClose, closeLabel = "Close chat" }) {
674
697
  }
675
698
 
676
699
  // src/components/MessageList.tsx
677
- import { useEffect as useEffect3, useRef as useRef2 } from "react";
700
+ import { useEffect as useEffect4, useRef as useRef2 } from "react";
678
701
 
679
702
  // src/components/Markdown.tsx
680
703
  import ReactMarkdown from "react-markdown";
@@ -958,10 +981,13 @@ function StatsDisclosure({ it, ui }) {
958
981
  function InsightCard({ it, ui, slip }) {
959
982
  const live = it.isLive || it.phase === "live";
960
983
  return /* @__PURE__ */ jsxs7("div", { className: "ss-w-binsight", children: [
961
- live ? /* @__PURE__ */ jsx10("div", { className: "ss-w-bi-status", children: /* @__PURE__ */ jsxs7("span", { className: "ss-w-bi-live", children: [
962
- /* @__PURE__ */ jsx10("span", { className: "ss-w-bi-live-dot", "aria-hidden": "true" }),
963
- ui?.liveLabel ?? "Live"
964
- ] }) }) : null,
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,
965
991
  /* @__PURE__ */ jsxs7("div", { className: "ss-w-bi-head", children: [
966
992
  /* @__PURE__ */ jsxs7("span", { className: "ss-w-bi-mkt", children: [
967
993
  /* @__PURE__ */ jsx10("span", { className: "ss-w-bi-market", children: it.market }),
@@ -1158,28 +1184,184 @@ function GreetingDashboard({ dashboard, onTap }) {
1158
1184
  ] });
1159
1185
  }
1160
1186
 
1161
- // src/components/ChatMessage.tsx
1187
+ // src/components/club-dashboard/GreetingHeaderBlock.tsx
1162
1188
  import { jsx as jsx16, jsxs as jsxs13 } from "react/jsx-runtime";
1163
- function ChatMessage({ message, disabled, onAct, onTap, ui, slip }) {
1189
+ function GreetingHeaderBlock({ block }) {
1190
+ return /* @__PURE__ */ jsxs13("div", { className: "ss-w-cdash-greeting", children: [
1191
+ /* @__PURE__ */ jsx16("div", { className: "ss-w-cdash-greeting-title", children: block.title }),
1192
+ /* @__PURE__ */ jsx16("div", { className: "ss-w-cdash-greeting-subtitle", children: block.subtitle })
1193
+ ] });
1194
+ }
1195
+
1196
+ // src/components/club-dashboard/FixtureHeroBlock.tsx
1197
+ import { useState as useState5 } from "react";
1198
+ import { jsx as jsx17, jsxs as jsxs14 } from "react/jsx-runtime";
1199
+ function initials2(name) {
1200
+ return name.split(/\s+/).filter(Boolean).slice(0, 2).map((w) => w[0].toUpperCase()).join("");
1201
+ }
1202
+ function Crest2({ url, name }) {
1203
+ const [broken, setBroken] = useState5(false);
1204
+ if (!url || broken) {
1205
+ return /* @__PURE__ */ jsx17("span", { className: "ss-w-cdash-crest ss-w-cdash-crest--fallback", "aria-hidden": "true", children: initials2(name) });
1206
+ }
1207
+ return /* @__PURE__ */ jsx17("img", { className: "ss-w-cdash-crest", src: url, alt: "", "aria-hidden": "true", onError: () => setBroken(true) });
1208
+ }
1209
+ function FixtureHeroBlock({
1210
+ block,
1211
+ ui,
1212
+ onAction
1213
+ }) {
1214
+ return /* @__PURE__ */ jsxs14("div", { className: "ss-w-cdash-fixture", children: [
1215
+ /* @__PURE__ */ jsxs14("div", { className: "ss-w-cdash-tie", children: [
1216
+ /* @__PURE__ */ jsx17(Crest2, { url: block.homeCrestUrl, name: block.homeName }),
1217
+ /* @__PURE__ */ jsx17("span", { className: "ss-w-cdash-team", children: block.homeName }),
1218
+ /* @__PURE__ */ jsx17("span", { className: "ss-w-cdash-vs", children: "v" }),
1219
+ /* @__PURE__ */ jsx17("span", { className: "ss-w-cdash-team", children: block.awayName }),
1220
+ /* @__PURE__ */ jsx17(Crest2, { url: block.awayCrestUrl, name: block.awayName })
1221
+ ] }),
1222
+ /* @__PURE__ */ jsxs14("div", { className: "ss-w-cdash-fixture-meta", children: [
1223
+ /* @__PURE__ */ jsx17("span", { className: "ss-w-cdash-kickoff", children: ui.clubKickoff(block.kickoffUnix * 1e3) }),
1224
+ block.venue ? /* @__PURE__ */ jsx17("span", { className: "ss-w-cdash-venue", children: block.venue }) : null,
1225
+ block.competition ? /* @__PURE__ */ jsx17("span", { className: "ss-w-cdash-comp", children: block.competition }) : null
1226
+ ] }),
1227
+ /* @__PURE__ */ jsx17("button", { type: "button", className: "ss-w-cdash-cta", onClick: () => onAction(block.ctaActionValue, block.ctaLabel), children: block.ctaLabel })
1228
+ ] });
1229
+ }
1230
+
1231
+ // src/components/club-dashboard/FormGuideBlock.tsx
1232
+ import { jsx as jsx18, jsxs as jsxs15 } from "react/jsx-runtime";
1233
+ function FormGuideBlock({ block }) {
1234
+ if (block.rows.length === 0) return null;
1235
+ return /* @__PURE__ */ jsx18("div", { className: "ss-w-cdash-form", children: block.rows.map((row) => /* @__PURE__ */ jsxs15("div", { className: "ss-w-cdash-form-row", children: [
1236
+ /* @__PURE__ */ jsxs15("span", { className: "ss-w-cdash-form-name", children: [
1237
+ row.name,
1238
+ row.position != null ? ` #${row.position}` : ""
1239
+ ] }),
1240
+ /* @__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)) })
1241
+ ] }, row.name)) });
1242
+ }
1243
+
1244
+ // src/components/club-dashboard/MatchPreviewBlock.tsx
1245
+ import { jsx as jsx19, jsxs as jsxs16 } from "react/jsx-runtime";
1246
+ function MatchPreviewBlock({
1247
+ block,
1248
+ ui,
1249
+ onAction
1250
+ }) {
1251
+ if (block.lines.length === 0) return null;
1252
+ const heading = ui.clubMatchPreviewHeading;
1253
+ return /* @__PURE__ */ jsxs16("button", { type: "button", className: "ss-w-cdash-lines", onClick: () => onAction(block.actionValue, heading), children: [
1254
+ /* @__PURE__ */ jsx19("span", { className: "ss-w-cdash-lines-heading", children: heading }),
1255
+ block.lines.map((line, i) => /* @__PURE__ */ jsx19("p", { children: line }, i))
1256
+ ] });
1257
+ }
1258
+
1259
+ // src/components/club-dashboard/TeamDnaBlock.tsx
1260
+ import { jsx as jsx20, jsxs as jsxs17 } from "react/jsx-runtime";
1261
+ function TeamDnaBlock({
1262
+ block,
1263
+ ui,
1264
+ onAction
1265
+ }) {
1266
+ if (block.lines.length === 0) return null;
1267
+ const heading = ui.clubTeamDnaHeading;
1268
+ return /* @__PURE__ */ jsxs17("button", { type: "button", className: "ss-w-cdash-lines", onClick: () => onAction(block.actionValue, heading), children: [
1269
+ /* @__PURE__ */ jsx20("span", { className: "ss-w-cdash-lines-heading", children: heading }),
1270
+ block.lines.map((line, i) => /* @__PURE__ */ jsx20("p", { children: line }, i))
1271
+ ] });
1272
+ }
1273
+
1274
+ // src/components/club-dashboard/CountdownBlock.tsx
1275
+ import { useEffect as useEffect3, useState as useState6 } from "react";
1276
+ import { jsx as jsx21, jsxs as jsxs18 } from "react/jsx-runtime";
1277
+ var DAY_MS = 24 * 3600 * 1e3;
1278
+ function pad2(n) {
1279
+ return String(n).padStart(2, "0");
1280
+ }
1281
+ function splitRemaining(remainingMs) {
1282
+ const totalSeconds = Math.max(0, Math.floor(remainingMs / 1e3));
1283
+ const h = Math.floor(totalSeconds / 3600);
1284
+ const m = Math.floor(totalSeconds % 3600 / 60);
1285
+ const s = totalSeconds % 60;
1286
+ return { h: pad2(h), m: pad2(m), s: pad2(s) };
1287
+ }
1288
+ function CountdownBlock({ block, ui }) {
1289
+ const [remainingMs, setRemainingMs] = useState6(() => block.kickoffUnix * 1e3 - Date.now());
1290
+ useEffect3(() => {
1291
+ setRemainingMs(block.kickoffUnix * 1e3 - Date.now());
1292
+ if (window.matchMedia("(prefers-reduced-motion: reduce)").matches) return;
1293
+ const id = setInterval(() => {
1294
+ setRemainingMs(block.kickoffUnix * 1e3 - Date.now());
1295
+ }, 1e3);
1296
+ return () => clearInterval(id);
1297
+ }, [block.kickoffUnix]);
1298
+ if (remainingMs <= 0) return null;
1299
+ if (remainingMs > DAY_MS) return null;
1300
+ const { h, m, s } = splitRemaining(remainingMs);
1301
+ return /* @__PURE__ */ jsxs18("div", { className: "ss-w-cdash-cd", "aria-label": ui.clubKickoffAria(block.kickoffUnix * 1e3), children: [
1302
+ /* @__PURE__ */ jsxs18("div", { className: "ss-w-cdash-cd-clock", "aria-hidden": "true", children: [
1303
+ /* @__PURE__ */ jsx21("span", { className: "ss-w-cdash-cd-h", children: h }),
1304
+ "h",
1305
+ " ",
1306
+ /* @__PURE__ */ jsx21("span", { className: "ss-w-cdash-cd-m", children: m }),
1307
+ "m",
1308
+ " ",
1309
+ /* @__PURE__ */ jsx21("span", { className: "ss-w-cdash-cd-s", children: s }),
1310
+ "s"
1311
+ ] }),
1312
+ /* @__PURE__ */ jsx21("div", { className: "ss-w-cdash-cd-label", children: ui.clubCountdownLabel })
1313
+ ] });
1314
+ }
1315
+
1316
+ // src/components/club-dashboard/ClubDashboard.tsx
1317
+ import { jsx as jsx22 } from "react/jsx-runtime";
1318
+ var CLUB_BLOCK_REGISTRY = {
1319
+ greetingHeader: ({ block }) => block.type === "greetingHeader" ? /* @__PURE__ */ jsx22(GreetingHeaderBlock, { block }) : null,
1320
+ fixtureHero: ({ block, ui, onAction }) => block.type === "fixtureHero" ? /* @__PURE__ */ jsx22(FixtureHeroBlock, { block, ui, onAction }) : null,
1321
+ formGuide: ({ block, ui }) => block.type === "formGuide" ? /* @__PURE__ */ jsx22(FormGuideBlock, { block, ui }) : null,
1322
+ matchPreview: ({ block, ui, onAction }) => block.type === "matchPreview" ? /* @__PURE__ */ jsx22(MatchPreviewBlock, { block, ui, onAction }) : null,
1323
+ teamDna: ({ block, ui, onAction }) => block.type === "teamDna" ? /* @__PURE__ */ jsx22(TeamDnaBlock, { block, ui, onAction }) : null,
1324
+ countdown: ({ block, ui }) => block.type === "countdown" ? /* @__PURE__ */ jsx22(CountdownBlock, { block, ui }) : null
1325
+ };
1326
+ function ClubDashboard({
1327
+ dashboard,
1328
+ ui,
1329
+ onAction
1330
+ }) {
1331
+ if (!dashboard.blocks.length) return null;
1332
+ return /* @__PURE__ */ jsx22("div", { className: "ss-w-cdash", children: dashboard.blocks.map((block, i) => {
1333
+ const Comp = Object.prototype.hasOwnProperty.call(CLUB_BLOCK_REGISTRY, block.type) ? CLUB_BLOCK_REGISTRY[block.type] : void 0;
1334
+ if (!Comp) {
1335
+ console.warn(`[club-dashboard] no renderer for block type "${block.type}"`);
1336
+ return null;
1337
+ }
1338
+ return /* @__PURE__ */ jsx22("div", { className: "ss-w-cdash-block", children: /* @__PURE__ */ jsx22(Comp, { block, ui, onAction }) }, i);
1339
+ }) });
1340
+ }
1341
+
1342
+ // src/components/ChatMessage.tsx
1343
+ import { jsx as jsx23, jsxs as jsxs19 } from "react/jsx-runtime";
1344
+ function ChatMessage({ message, disabled, onAct, onTap, onClubAction, ui, slip }) {
1164
1345
  if (message.role === "user") {
1165
- return /* @__PURE__ */ jsx16("div", { className: "ss-w-msg ss-w-user", children: /* @__PURE__ */ jsx16("div", { className: "ss-w-bubble", children: message.text }) });
1346
+ return /* @__PURE__ */ jsx23("div", { className: "ss-w-msg ss-w-user", children: /* @__PURE__ */ jsx23("div", { className: "ss-w-bubble", children: message.text }) });
1166
1347
  }
1167
- return /* @__PURE__ */ jsxs13("div", { className: "ss-w-msg ss-w-bot", children: [
1168
- message.text && !message.greetingDashboard ? /* @__PURE__ */ jsx16(Markdown, { text: message.text }) : null,
1169
- message.greetingDashboard ? /* @__PURE__ */ jsx16(GreetingDashboard, { dashboard: message.greetingDashboard, onTap }) : null,
1170
- /* @__PURE__ */ jsx16(EntityCardList, { cards: message.cards, disabled, showLessLabel: ui?.showLess, showMoreLabel: ui?.showMore }),
1171
- /* @__PURE__ */ jsx16(BettingInsightList, { insights: message.insights, ui, slip }),
1172
- /* @__PURE__ */ jsx16(MarketOddsCardList, { cards: message.marketOdds, ui, slip }),
1173
- /* @__PURE__ */ jsx16(OddsMovementCardList, { cards: message.oddsMovement, ui }),
1174
- /* @__PURE__ */ jsx16(ActionButtons, { actions: message.actions, loading: false, disabled, onAct })
1348
+ return /* @__PURE__ */ jsxs19("div", { className: "ss-w-msg ss-w-bot", children: [
1349
+ message.text && !message.greetingDashboard && !message.clubDashboard ? /* @__PURE__ */ jsx23(Markdown, { text: message.text }) : null,
1350
+ message.greetingDashboard ? /* @__PURE__ */ jsx23(GreetingDashboard, { dashboard: message.greetingDashboard, onTap }) : null,
1351
+ message.clubDashboard && ui ? /* @__PURE__ */ jsx23(ClubDashboard, { dashboard: message.clubDashboard, ui, onAction: onClubAction }) : null,
1352
+ /* @__PURE__ */ jsx23(EntityCardList, { cards: message.cards, disabled, showLessLabel: ui?.showLess, showMoreLabel: ui?.showMore }),
1353
+ /* @__PURE__ */ jsx23(BettingInsightList, { insights: message.insights, ui, slip }),
1354
+ /* @__PURE__ */ jsx23(MarketOddsCardList, { cards: message.marketOdds, ui, slip }),
1355
+ /* @__PURE__ */ jsx23(OddsMovementCardList, { cards: message.oddsMovement, ui }),
1356
+ /* @__PURE__ */ jsx23(ActionButtons, { actions: message.actions, loading: false, disabled, onAct })
1175
1357
  ] });
1176
1358
  }
1177
1359
 
1178
1360
  // src/components/IntentChips.tsx
1179
- import { jsx as jsx17 } from "react/jsx-runtime";
1361
+ import { jsx as jsx24 } from "react/jsx-runtime";
1180
1362
  function IntentChips({ chips, onPick }) {
1181
1363
  if (chips.length === 0) return null;
1182
- return /* @__PURE__ */ jsx17("div", { className: "ss-w-chips", children: chips.map((c, i) => /* @__PURE__ */ jsx17("button", { type: "button", className: "ss-w-chip", onClick: () => onPick(c.text), children: c.text }, `${c.text}-${i}`)) });
1364
+ 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}`)) });
1183
1365
  }
1184
1366
 
1185
1367
  // src/scroll.ts
@@ -1189,18 +1371,18 @@ function isPinnedToBottom(el, threshold = PIN_THRESHOLD) {
1189
1371
  }
1190
1372
 
1191
1373
  // src/components/MessageList.tsx
1192
- import { jsx as jsx18, jsxs as jsxs14 } from "react/jsx-runtime";
1374
+ import { jsx as jsx25, jsxs as jsxs20 } from "react/jsx-runtime";
1193
1375
  function MessageList({ controller, ui, slip, onDashboardTap }) {
1194
1376
  const { state, send } = controller;
1195
1377
  const listRef = useRef2(null);
1196
1378
  const endRef = useRef2(null);
1197
1379
  const pinnedRef = useRef2(true);
1198
- useEffect3(() => {
1380
+ useEffect4(() => {
1199
1381
  if (pinnedRef.current) endRef.current?.scrollIntoView({ block: "end" });
1200
1382
  }, [state.messages, state.status]);
1201
1383
  const hasUserMessage = state.messages.some((m) => m.role === "user");
1202
1384
  const hasDashboard = state.messages.some((m) => m.role === "assistant" && m.greetingDashboard);
1203
- return /* @__PURE__ */ jsxs14(
1385
+ return /* @__PURE__ */ jsxs20(
1204
1386
  "div",
1205
1387
  {
1206
1388
  className: "ss-w-list",
@@ -1210,7 +1392,7 @@ function MessageList({ controller, ui, slip, onDashboardTap }) {
1210
1392
  if (listRef.current) pinnedRef.current = isPinnedToBottom(listRef.current);
1211
1393
  },
1212
1394
  children: [
1213
- state.messages.map((m) => /* @__PURE__ */ jsx18(
1395
+ state.messages.map((m) => /* @__PURE__ */ jsx25(
1214
1396
  ChatMessage,
1215
1397
  {
1216
1398
  message: m,
@@ -1220,30 +1402,31 @@ function MessageList({ controller, ui, slip, onDashboardTap }) {
1220
1402
  send(tile.actionValue, { displayText: tile.title });
1221
1403
  if (m.role === "assistant" && m.greetingDashboard) onDashboardTap?.(tile, index, m.greetingDashboard.layout);
1222
1404
  },
1405
+ onClubAction: (actionValue, displayText) => send(actionValue, { displayText }),
1223
1406
  ui,
1224
1407
  slip
1225
1408
  },
1226
1409
  m.id
1227
1410
  )),
1228
- state.isStreaming && state.actionsLoading ? /* @__PURE__ */ jsx18(ActionButtons, { actions: [], loading: true, onAct: () => {
1411
+ state.isStreaming && state.actionsLoading ? /* @__PURE__ */ jsx25(ActionButtons, { actions: [], loading: true, onAct: () => {
1229
1412
  } }) : null,
1230
- state.status ? /* @__PURE__ */ jsx18("div", { className: "ss-w-status", children: state.status }) : null,
1231
- state.error ? /* @__PURE__ */ jsxs14("div", { className: "ss-w-error", role: "alert", children: [
1232
- /* @__PURE__ */ jsx18("span", { children: "message" in state.error ? state.error.message : ui.somethingWentWrong }),
1233
- /* @__PURE__ */ jsx18("button", { type: "button", className: "ss-w-retry", onClick: () => controller.retry(), children: ui.retry })
1413
+ state.status ? /* @__PURE__ */ jsx25("div", { className: "ss-w-status", children: state.status }) : null,
1414
+ state.error ? /* @__PURE__ */ jsxs20("div", { className: "ss-w-error", role: "alert", children: [
1415
+ /* @__PURE__ */ jsx25("span", { children: "message" in state.error ? state.error.message : ui.somethingWentWrong }),
1416
+ /* @__PURE__ */ jsx25("button", { type: "button", className: "ss-w-retry", onClick: () => controller.retry(), children: ui.retry })
1234
1417
  ] }) : null,
1235
- !hasUserMessage && !hasDashboard ? /* @__PURE__ */ jsx18(IntentChips, { chips: state.starters, onPick: (t) => send(t) }) : null,
1236
- /* @__PURE__ */ jsx18("div", { ref: endRef })
1418
+ !hasUserMessage && !hasDashboard ? /* @__PURE__ */ jsx25(IntentChips, { chips: state.starters, onPick: (t) => send(t) }) : null,
1419
+ /* @__PURE__ */ jsx25("div", { ref: endRef })
1237
1420
  ]
1238
1421
  }
1239
1422
  );
1240
1423
  }
1241
1424
 
1242
1425
  // src/components/ChatInput.tsx
1243
- import { useState as useState5 } from "react";
1244
- import { jsx as jsx19, jsxs as jsxs15 } from "react/jsx-runtime";
1426
+ import { useState as useState7 } from "react";
1427
+ import { jsx as jsx26, jsxs as jsxs21 } from "react/jsx-runtime";
1245
1428
  function ChatInput({ placeholder, streaming, onSend, onStop, stopLabel = "Stop response", sendLabel = "Send message" }) {
1246
- const [value, setValue] = useState5("");
1429
+ const [value, setValue] = useState7("");
1247
1430
  const submit = () => {
1248
1431
  const t = value.trim();
1249
1432
  if (!t) return;
@@ -1256,18 +1439,18 @@ function ChatInput({ placeholder, streaming, onSend, onStop, stopLabel = "Stop r
1256
1439
  submit();
1257
1440
  }
1258
1441
  };
1259
- return /* @__PURE__ */ jsxs15("div", { className: "ss-w-input", children: [
1260
- /* @__PURE__ */ jsx19("textarea", { className: "ss-w-textarea", rows: 1, placeholder, value, disabled: streaming, onChange: (e) => setValue(e.target.value), onKeyDown }),
1261
- streaming ? /* @__PURE__ */ jsx19("button", { type: "button", className: "ss-w-send ss-w-stop", "aria-label": stopLabel, onClick: onStop, children: /* @__PURE__ */ jsx19("span", { className: "ss-w-stop-glyph" }) }) : /* @__PURE__ */ jsx19("button", { type: "button", className: "ss-w-send", "aria-label": sendLabel, onClick: submit, children: /* @__PURE__ */ jsx19(SendIcon, {}) })
1442
+ return /* @__PURE__ */ jsxs21("div", { className: "ss-w-input", children: [
1443
+ /* @__PURE__ */ jsx26("textarea", { className: "ss-w-textarea", rows: 1, placeholder, value, disabled: streaming, onChange: (e) => setValue(e.target.value), onKeyDown }),
1444
+ 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, {}) })
1262
1445
  ] });
1263
1446
  }
1264
1447
 
1265
1448
  // src/components/ChatPanel.tsx
1266
- import { jsx as jsx20, jsxs as jsxs16 } from "react/jsx-runtime";
1449
+ import { jsx as jsx27, jsxs as jsxs22 } from "react/jsx-runtime";
1267
1450
  var FULLSCREEN_QUERY = "(max-width: 639px)";
1268
1451
  function useIsModal() {
1269
- const [modal, setModal] = useState6(false);
1270
- useEffect4(() => {
1452
+ const [modal, setModal] = useState8(false);
1453
+ useEffect5(() => {
1271
1454
  if (typeof window === "undefined" || typeof window.matchMedia !== "function") return;
1272
1455
  const mql = window.matchMedia(FULLSCREEN_QUERY);
1273
1456
  const sync = () => setModal(mql.matches);
@@ -1280,17 +1463,17 @@ function useIsModal() {
1280
1463
  function ChatPanel({ controller, ui, slip, onDashboardTap }) {
1281
1464
  const { state, close, stop, send } = controller;
1282
1465
  const isModal = useIsModal();
1283
- useEffect4(() => {
1466
+ useEffect5(() => {
1284
1467
  const onKey = (e) => {
1285
1468
  if (e.key === "Escape") close();
1286
1469
  };
1287
1470
  window.addEventListener("keydown", onKey);
1288
1471
  return () => window.removeEventListener("keydown", onKey);
1289
1472
  }, [close]);
1290
- return /* @__PURE__ */ jsxs16("div", { className: "ss-w-panel", role: "dialog", "aria-modal": isModal, "aria-label": ui.chatDialogLabel, children: [
1291
- /* @__PURE__ */ jsx20(ChatHeader, { onClose: close, closeLabel: ui.closeChat }),
1292
- /* @__PURE__ */ jsx20(MessageList, { controller, ui, slip, onDashboardTap }),
1293
- /* @__PURE__ */ jsx20(ChatInput, { placeholder: ui.placeholder, streaming: state.isStreaming, onSend: (t) => send(t), onStop: stop, stopLabel: ui.stopResponse, sendLabel: ui.sendMessage })
1473
+ return /* @__PURE__ */ jsxs22("div", { className: "ss-w-panel", role: "dialog", "aria-modal": isModal, "aria-label": ui.chatDialogLabel, children: [
1474
+ /* @__PURE__ */ jsx27(ChatHeader, { onClose: close, closeLabel: ui.closeChat }),
1475
+ /* @__PURE__ */ jsx27(MessageList, { controller, ui, slip, onDashboardTap }),
1476
+ /* @__PURE__ */ jsx27(ChatInput, { placeholder: ui.placeholder, streaming: state.isStreaming, onSend: (t) => send(t), onStop: stop, stopLabel: ui.stopResponse, sendLabel: ui.sendMessage })
1294
1477
  ] });
1295
1478
  }
1296
1479
 
@@ -1434,6 +1617,7 @@ var WIDGET_CSS = `.ss-w-root { all: revert; }
1434
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; }
1435
1618
  .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; }
1436
1619
  .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; }
1437
1621
  .ss-w-bi-head { display: flex; align-items: flex-start; gap: 10px; }
1438
1622
  .ss-w-bi-mkt { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
1439
1623
  .ss-w-bi-market { font-size: 15px; font-weight: 700; line-height: 1.25; color: var(--ss-w-ink); }
@@ -1615,10 +1799,46 @@ var WIDGET_CSS = `.ss-w-root { all: revert; }
1615
1799
  .ss-w-dash--discovery .ss-w-dash-tile:nth-child(2) { background: #eef3fb; }
1616
1800
  .ss-w-dash--discovery .ss-w-dash-tile:nth-child(3) { background: #fdf6ec; }
1617
1801
  .ss-w-dash--discovery .ss-w-dash-tile:nth-child(4) { background: #f5eefb; }
1802
+
1803
+ /* club match-day dashboard */
1804
+ .ss-w-cdash { display: flex; flex-direction: column; gap: 10px; padding: 4px 0; }
1805
+ .ss-w-cdash-block { display: flex; flex-direction: column; }
1806
+
1807
+ .ss-w-cdash-greeting { display: flex; flex-direction: column; gap: 2px; }
1808
+ .ss-w-cdash-greeting-title { font-weight: 700; font-size: 14px; color: var(--ss-w-ink); }
1809
+ .ss-w-cdash-greeting-subtitle { font-size: 11.5px; color: var(--ss-w-faint); }
1810
+
1811
+ .ss-w-cdash-fixture { display: flex; flex-direction: column; align-items: center; gap: 8px; background: var(--ss-w-panel); border: 1px solid var(--ss-w-line); border-radius: 12px; padding: 12px; text-align: center; }
1812
+ .ss-w-cdash-tie { display: flex; align-items: center; justify-content: center; gap: 8px; width: 100%; }
1813
+ .ss-w-cdash-team { font-weight: 650; font-size: 13px; color: var(--ss-w-ink); min-width: 0; overflow-wrap: anywhere; }
1814
+ .ss-w-cdash-vs { font-size: 11px; color: var(--ss-w-faint); font-weight: 600; }
1815
+ .ss-w-cdash-crest { flex: none; width: 28px; height: 28px; border-radius: 50%; object-fit: cover; background: var(--ss-w-bubble); }
1816
+ .ss-w-cdash-crest--fallback { display: grid; place-items: center; font-size: 10.5px; font-weight: 700; color: var(--ss-w-accent); background: color-mix(in srgb, var(--ss-w-accent) 16%, var(--ss-w-panel)); }
1817
+ .ss-w-cdash-fixture-meta { display: flex; align-items: center; justify-content: center; gap: 6px; flex-wrap: wrap; font-size: 11px; color: var(--ss-w-faint); }
1818
+ .ss-w-cdash-kickoff { font-weight: 700; color: var(--ss-w-ink); }
1819
+ .ss-w-cdash-cta { font: inherit; font-weight: 700; font-size: 12px; color: var(--ss-w-cta-ink); background: var(--ss-w-cta); border: none; border-radius: 999px; padding: 8px 18px; cursor: pointer; }
1820
+
1821
+ .ss-w-cdash-form { display: flex; flex-direction: column; gap: 6px; background: var(--ss-w-panel); border: 1px solid var(--ss-w-line); border-radius: 12px; padding: 10px 12px; }
1822
+ .ss-w-cdash-form-row { display: flex; align-items: center; justify-content: space-between; gap: 8px; }
1823
+ .ss-w-cdash-form-name { font-size: 12px; font-weight: 600; color: var(--ss-w-ink); min-width: 0; overflow-wrap: anywhere; }
1824
+ .ss-w-cdash-form-pills { flex: none; display: flex; align-items: center; gap: 3px; }
1825
+ .ss-w-cdash-pill { display: inline-flex; align-items: center; justify-content: center; width: 18px; height: 18px; border-radius: 50%; font-size: 9.5px; font-weight: 700; color: #fff; }
1826
+ .ss-w-cdash-pill--w { background: color-mix(in srgb, var(--ss-w-pos) 88%, black); }
1827
+ .ss-w-cdash-pill--d { background: var(--ss-w-faint); }
1828
+ .ss-w-cdash-pill--l { background: color-mix(in srgb, var(--ss-w-neg) 88%, black); }
1829
+
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; }
1831
+ .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); }
1833
+
1834
+ .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
+ .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); }
1836
+ .ss-w-cdash-cd-h, .ss-w-cdash-cd-m, .ss-w-cdash-cd-s { color: var(--ss-w-accent); }
1837
+ .ss-w-cdash-cd-label { font-size: 11px; font-weight: 600; color: var(--ss-w-faint); }
1618
1838
  `;
1619
1839
 
1620
1840
  // src/StatsWidget.tsx
1621
- import { jsx as jsx21 } from "react/jsx-runtime";
1841
+ import { jsx as jsx28 } from "react/jsx-runtime";
1622
1842
  function buildClient(props) {
1623
1843
  try {
1624
1844
  if ("client" in props && props.client) {
@@ -1664,14 +1884,14 @@ function StatsWidget(props) {
1664
1884
  () => built ? createDashboardTapSignal((input) => built.conversation.sendSignal(input)) : void 0,
1665
1885
  [built]
1666
1886
  );
1667
- useEffect5(() => {
1887
+ useEffect6(() => {
1668
1888
  if (!built || !ui.injectStyles) return;
1669
1889
  const node = rootRef.current?.getRootNode();
1670
1890
  if (node) injectStyles(node);
1671
1891
  }, [built, ui.injectStyles]);
1672
1892
  if (!built) return null;
1673
1893
  const attrs = themeAttrs(ui);
1674
- return /* @__PURE__ */ jsx21("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__ */ jsx21(ChatPanel, { controller, ui, slip, onDashboardTap }) : /* @__PURE__ */ jsx21(
1894
+ 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(
1675
1895
  ChatFab,
1676
1896
  {
1677
1897
  label: ui.launcherLabel,