@sensiblestats/widget-react 0.14.1 → 0.15.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,16 @@
1
1
  # @sensiblestats/widget-react
2
2
 
3
+ ## 0.15.0
4
+
5
+ ### Minor Changes
6
+
7
+ - Render the Club Match-Day dashboard (typed blocks: greeting header, fixture hero, form guide, match preview, team DNA, countdown) delivered on `turn_complete.clubDashboard`.
8
+
9
+ ### Patch Changes
10
+
11
+ - Updated dependencies
12
+ - @sensiblestats/widget-sdk@0.13.0
13
+
3
14
  ## 0.14.1
4
15
 
5
16
  ### Patch Changes
package/dist/index.cjs CHANGED
@@ -36,7 +36,7 @@ __export(index_exports, {
36
36
  module.exports = __toCommonJS(index_exports);
37
37
 
38
38
  // src/StatsWidget.tsx
39
- var import_react10 = require("react");
39
+ var import_react12 = require("react");
40
40
  var import_widget_sdk5 = require("@sensiblestats/widget-sdk");
41
41
 
42
42
  // src/i18n.ts
@@ -79,6 +79,12 @@ var STRINGS = {
79
79
  const drift = direction ? `, ${direction}` : "";
80
80
  return `Price movement over the last ${span}: opened at ${opening}, currently ${current}${drift}.`;
81
81
  },
82
+ clubMatchPreviewHeading: "Match preview",
83
+ clubTeamDnaHeading: "How they play",
84
+ clubFormHeading: "Recent form",
85
+ clubCountdownLabel: "Countdown to kick-off",
86
+ clubKickoff: (ms) => new Intl.DateTimeFormat("en-GB", { weekday: "short", day: "numeric", month: "short", hour: "2-digit", minute: "2-digit" }).format(new Date(ms)),
87
+ 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))}`,
82
88
  inSlip: "In slip",
83
89
  slipAdded: (n) => n === void 0 ? "Added to slip" : `Added to slip (${n} selection${n === 1 ? "" : "s"})`,
84
90
  slipDuplicate: "Already in your slip",
@@ -126,6 +132,12 @@ var STRINGS = {
126
132
  const drift = direction ? `, ${direction}` : "";
127
133
  return `Son ${span} i\xE7indeki oran hareketi: ${opening} ile a\xE7\u0131ld\u0131, \u015Fu anda ${current}${drift}.`;
128
134
  },
135
+ clubMatchPreviewHeading: "Ma\xE7 \xF6nizlemesi",
136
+ clubTeamDnaHeading: "Nas\u0131l oynuyorlar",
137
+ clubFormHeading: "Son form",
138
+ clubCountdownLabel: "Ba\u015Flama vuru\u015Funa kalan",
139
+ clubKickoff: (ms) => new Intl.DateTimeFormat("tr-TR", { weekday: "short", day: "numeric", month: "short", hour: "2-digit", minute: "2-digit" }).format(new Date(ms)),
140
+ 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))}`,
129
141
  inSlip: "Kuponda",
130
142
  slipAdded: (n) => n === void 0 ? "Kupona eklendi" : `Kupona eklendi (${n} se\xE7im)`,
131
143
  slipDuplicate: "Bu se\xE7im kuponunuzda zaten var",
@@ -185,6 +197,12 @@ function normalizeUi(cfg) {
185
197
  excludedSuspended: strings.excludedSuspended,
186
198
  chartTimeLabel: strings.chartTimeLabel,
187
199
  chartAria: strings.chartAria,
200
+ clubMatchPreviewHeading: strings.clubMatchPreviewHeading,
201
+ clubTeamDnaHeading: strings.clubTeamDnaHeading,
202
+ clubFormHeading: strings.clubFormHeading,
203
+ clubCountdownLabel: strings.clubCountdownLabel,
204
+ clubKickoff: strings.clubKickoff,
205
+ clubKickoffAria: strings.clubKickoffAria,
188
206
  addToSlip: strings.addToSlip,
189
207
  inSlip: strings.inSlip,
190
208
  slipAdded: strings.slipAdded,
@@ -531,7 +549,8 @@ function applyEvent(state, event) {
531
549
  messages: mapActive(state, (t) => ({
532
550
  ...t,
533
551
  done: true,
534
- greetingDashboard: event.data.dashboard ?? void 0
552
+ greetingDashboard: event.data.dashboard ?? void 0,
553
+ clubDashboard: event.data.clubDashboard ?? void 0
535
554
  }))
536
555
  };
537
556
  }
@@ -698,7 +717,7 @@ function ChatFab({ label, greeting, popupText, onOpen, onDismissPopup, dismissLa
698
717
  }
699
718
 
700
719
  // src/components/ChatPanel.tsx
701
- var import_react9 = require("react");
720
+ var import_react11 = require("react");
702
721
 
703
722
  // src/components/ChatHeader.tsx
704
723
  var import_jsx_runtime4 = require("react/jsx-runtime");
@@ -711,7 +730,7 @@ function ChatHeader({ onClose, closeLabel = "Close chat" }) {
711
730
  }
712
731
 
713
732
  // src/components/MessageList.tsx
714
- var import_react7 = require("react");
733
+ var import_react9 = require("react");
715
734
 
716
735
  // src/components/Markdown.tsx
717
736
  var import_react_markdown = __toESM(require("react-markdown"), 1);
@@ -1195,28 +1214,184 @@ function GreetingDashboard({ dashboard, onTap }) {
1195
1214
  ] });
1196
1215
  }
1197
1216
 
1198
- // src/components/ChatMessage.tsx
1217
+ // src/components/club-dashboard/GreetingHeaderBlock.tsx
1199
1218
  var import_jsx_runtime16 = require("react/jsx-runtime");
1200
- function ChatMessage({ message, disabled, onAct, onTap, ui, slip }) {
1219
+ function GreetingHeaderBlock({ block }) {
1220
+ return /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)("div", { className: "ss-w-cdash-greeting", children: [
1221
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("div", { className: "ss-w-cdash-greeting-title", children: block.title }),
1222
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("div", { className: "ss-w-cdash-greeting-subtitle", children: block.subtitle })
1223
+ ] });
1224
+ }
1225
+
1226
+ // src/components/club-dashboard/FixtureHeroBlock.tsx
1227
+ var import_react7 = require("react");
1228
+ var import_jsx_runtime17 = require("react/jsx-runtime");
1229
+ function initials2(name) {
1230
+ return name.split(/\s+/).filter(Boolean).slice(0, 2).map((w) => w[0].toUpperCase()).join("");
1231
+ }
1232
+ function Crest2({ url, name }) {
1233
+ const [broken, setBroken] = (0, import_react7.useState)(false);
1234
+ if (!url || broken) {
1235
+ return /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("span", { className: "ss-w-cdash-crest ss-w-cdash-crest--fallback", "aria-hidden": "true", children: initials2(name) });
1236
+ }
1237
+ return /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("img", { className: "ss-w-cdash-crest", src: url, alt: "", "aria-hidden": "true", onError: () => setBroken(true) });
1238
+ }
1239
+ function FixtureHeroBlock({
1240
+ block,
1241
+ ui,
1242
+ onAction
1243
+ }) {
1244
+ return /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)("div", { className: "ss-w-cdash-fixture", children: [
1245
+ /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)("div", { className: "ss-w-cdash-tie", children: [
1246
+ /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(Crest2, { url: block.homeCrestUrl, name: block.homeName }),
1247
+ /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("span", { className: "ss-w-cdash-team", children: block.homeName }),
1248
+ /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("span", { className: "ss-w-cdash-vs", children: "v" }),
1249
+ /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("span", { className: "ss-w-cdash-team", children: block.awayName }),
1250
+ /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(Crest2, { url: block.awayCrestUrl, name: block.awayName })
1251
+ ] }),
1252
+ /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)("div", { className: "ss-w-cdash-fixture-meta", children: [
1253
+ /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("span", { className: "ss-w-cdash-kickoff", children: ui.clubKickoff(block.kickoffUnix * 1e3) }),
1254
+ block.venue ? /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("span", { className: "ss-w-cdash-venue", children: block.venue }) : null,
1255
+ block.competition ? /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("span", { className: "ss-w-cdash-comp", children: block.competition }) : null
1256
+ ] }),
1257
+ /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("button", { type: "button", className: "ss-w-cdash-cta", onClick: () => onAction(block.ctaActionValue, block.ctaLabel), children: block.ctaLabel })
1258
+ ] });
1259
+ }
1260
+
1261
+ // src/components/club-dashboard/FormGuideBlock.tsx
1262
+ var import_jsx_runtime18 = require("react/jsx-runtime");
1263
+ function FormGuideBlock({ block }) {
1264
+ if (block.rows.length === 0) return null;
1265
+ return /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("div", { className: "ss-w-cdash-form", children: block.rows.map((row) => /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("div", { className: "ss-w-cdash-form-row", children: [
1266
+ /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("span", { className: "ss-w-cdash-form-name", children: [
1267
+ row.name,
1268
+ row.position != null ? ` #${row.position}` : ""
1269
+ ] }),
1270
+ /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("span", { className: "ss-w-cdash-form-pills", children: row.last5.map((r, i) => /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("span", { className: `ss-w-cdash-pill ss-w-cdash-pill--${r.toLowerCase()}`, children: r }, i)) })
1271
+ ] }, row.name)) });
1272
+ }
1273
+
1274
+ // src/components/club-dashboard/MatchPreviewBlock.tsx
1275
+ var import_jsx_runtime19 = require("react/jsx-runtime");
1276
+ function MatchPreviewBlock({
1277
+ block,
1278
+ ui,
1279
+ onAction
1280
+ }) {
1281
+ if (block.lines.length === 0) return null;
1282
+ const heading = ui.clubMatchPreviewHeading;
1283
+ return /* @__PURE__ */ (0, import_jsx_runtime19.jsxs)("button", { type: "button", className: "ss-w-cdash-lines", onClick: () => onAction(block.actionValue, heading), children: [
1284
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsx)("span", { className: "ss-w-cdash-lines-heading", children: heading }),
1285
+ block.lines.map((line, i) => /* @__PURE__ */ (0, import_jsx_runtime19.jsx)("p", { children: line }, i))
1286
+ ] });
1287
+ }
1288
+
1289
+ // src/components/club-dashboard/TeamDnaBlock.tsx
1290
+ var import_jsx_runtime20 = require("react/jsx-runtime");
1291
+ function TeamDnaBlock({
1292
+ block,
1293
+ ui,
1294
+ onAction
1295
+ }) {
1296
+ if (block.lines.length === 0) return null;
1297
+ const heading = ui.clubTeamDnaHeading;
1298
+ return /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)("button", { type: "button", className: "ss-w-cdash-lines", onClick: () => onAction(block.actionValue, heading), children: [
1299
+ /* @__PURE__ */ (0, import_jsx_runtime20.jsx)("span", { className: "ss-w-cdash-lines-heading", children: heading }),
1300
+ block.lines.map((line, i) => /* @__PURE__ */ (0, import_jsx_runtime20.jsx)("p", { children: line }, i))
1301
+ ] });
1302
+ }
1303
+
1304
+ // src/components/club-dashboard/CountdownBlock.tsx
1305
+ var import_react8 = require("react");
1306
+ var import_jsx_runtime21 = require("react/jsx-runtime");
1307
+ var DAY_MS = 24 * 3600 * 1e3;
1308
+ function pad2(n) {
1309
+ return String(n).padStart(2, "0");
1310
+ }
1311
+ function splitRemaining(remainingMs) {
1312
+ const totalSeconds = Math.max(0, Math.floor(remainingMs / 1e3));
1313
+ const h = Math.floor(totalSeconds / 3600);
1314
+ const m = Math.floor(totalSeconds % 3600 / 60);
1315
+ const s = totalSeconds % 60;
1316
+ return { h: pad2(h), m: pad2(m), s: pad2(s) };
1317
+ }
1318
+ function CountdownBlock({ block, ui }) {
1319
+ const [remainingMs, setRemainingMs] = (0, import_react8.useState)(() => block.kickoffUnix * 1e3 - Date.now());
1320
+ (0, import_react8.useEffect)(() => {
1321
+ setRemainingMs(block.kickoffUnix * 1e3 - Date.now());
1322
+ if (window.matchMedia("(prefers-reduced-motion: reduce)").matches) return;
1323
+ const id = setInterval(() => {
1324
+ setRemainingMs(block.kickoffUnix * 1e3 - Date.now());
1325
+ }, 1e3);
1326
+ return () => clearInterval(id);
1327
+ }, [block.kickoffUnix]);
1328
+ if (remainingMs <= 0) return null;
1329
+ if (remainingMs > DAY_MS) return null;
1330
+ const { h, m, s } = splitRemaining(remainingMs);
1331
+ return /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)("div", { className: "ss-w-cdash-cd", "aria-label": ui.clubKickoffAria(block.kickoffUnix * 1e3), children: [
1332
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)("div", { className: "ss-w-cdash-cd-clock", "aria-hidden": "true", children: [
1333
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("span", { className: "ss-w-cdash-cd-h", children: h }),
1334
+ "h",
1335
+ " ",
1336
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("span", { className: "ss-w-cdash-cd-m", children: m }),
1337
+ "m",
1338
+ " ",
1339
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("span", { className: "ss-w-cdash-cd-s", children: s }),
1340
+ "s"
1341
+ ] }),
1342
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("div", { className: "ss-w-cdash-cd-label", children: ui.clubCountdownLabel })
1343
+ ] });
1344
+ }
1345
+
1346
+ // src/components/club-dashboard/ClubDashboard.tsx
1347
+ var import_jsx_runtime22 = require("react/jsx-runtime");
1348
+ var CLUB_BLOCK_REGISTRY = {
1349
+ greetingHeader: ({ block }) => block.type === "greetingHeader" ? /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(GreetingHeaderBlock, { block }) : null,
1350
+ fixtureHero: ({ block, ui, onAction }) => block.type === "fixtureHero" ? /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(FixtureHeroBlock, { block, ui, onAction }) : null,
1351
+ formGuide: ({ block, ui }) => block.type === "formGuide" ? /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(FormGuideBlock, { block, ui }) : null,
1352
+ matchPreview: ({ block, ui, onAction }) => block.type === "matchPreview" ? /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(MatchPreviewBlock, { block, ui, onAction }) : null,
1353
+ teamDna: ({ block, ui, onAction }) => block.type === "teamDna" ? /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(TeamDnaBlock, { block, ui, onAction }) : null,
1354
+ countdown: ({ block, ui }) => block.type === "countdown" ? /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(CountdownBlock, { block, ui }) : null
1355
+ };
1356
+ function ClubDashboard({
1357
+ dashboard,
1358
+ ui,
1359
+ onAction
1360
+ }) {
1361
+ if (!dashboard.blocks.length) return null;
1362
+ return /* @__PURE__ */ (0, import_jsx_runtime22.jsx)("div", { className: "ss-w-cdash", children: dashboard.blocks.map((block, i) => {
1363
+ const Comp = Object.prototype.hasOwnProperty.call(CLUB_BLOCK_REGISTRY, block.type) ? CLUB_BLOCK_REGISTRY[block.type] : void 0;
1364
+ if (!Comp) {
1365
+ console.warn(`[club-dashboard] no renderer for block type "${block.type}"`);
1366
+ return null;
1367
+ }
1368
+ return /* @__PURE__ */ (0, import_jsx_runtime22.jsx)("div", { className: "ss-w-cdash-block", children: /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(Comp, { block, ui, onAction }) }, i);
1369
+ }) });
1370
+ }
1371
+
1372
+ // src/components/ChatMessage.tsx
1373
+ var import_jsx_runtime23 = require("react/jsx-runtime");
1374
+ function ChatMessage({ message, disabled, onAct, onTap, onClubAction, ui, slip }) {
1201
1375
  if (message.role === "user") {
1202
- return /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("div", { className: "ss-w-msg ss-w-user", children: /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("div", { className: "ss-w-bubble", children: message.text }) });
1376
+ return /* @__PURE__ */ (0, import_jsx_runtime23.jsx)("div", { className: "ss-w-msg ss-w-user", children: /* @__PURE__ */ (0, import_jsx_runtime23.jsx)("div", { className: "ss-w-bubble", children: message.text }) });
1203
1377
  }
1204
- return /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)("div", { className: "ss-w-msg ss-w-bot", children: [
1205
- message.text && !message.greetingDashboard ? /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(Markdown, { text: message.text }) : null,
1206
- message.greetingDashboard ? /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(GreetingDashboard, { dashboard: message.greetingDashboard, onTap }) : null,
1207
- /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(EntityCardList, { cards: message.cards, disabled, showLessLabel: ui?.showLess, showMoreLabel: ui?.showMore }),
1208
- /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(BettingInsightList, { insights: message.insights, ui, slip }),
1209
- /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(MarketOddsCardList, { cards: message.marketOdds, ui, slip }),
1210
- /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(OddsMovementCardList, { cards: message.oddsMovement, ui }),
1211
- /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(ActionButtons, { actions: message.actions, loading: false, disabled, onAct })
1378
+ return /* @__PURE__ */ (0, import_jsx_runtime23.jsxs)("div", { className: "ss-w-msg ss-w-bot", children: [
1379
+ message.text && !message.greetingDashboard && !message.clubDashboard ? /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(Markdown, { text: message.text }) : null,
1380
+ message.greetingDashboard ? /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(GreetingDashboard, { dashboard: message.greetingDashboard, onTap }) : null,
1381
+ message.clubDashboard && ui ? /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(ClubDashboard, { dashboard: message.clubDashboard, ui, onAction: onClubAction }) : null,
1382
+ /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(EntityCardList, { cards: message.cards, disabled, showLessLabel: ui?.showLess, showMoreLabel: ui?.showMore }),
1383
+ /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(BettingInsightList, { insights: message.insights, ui, slip }),
1384
+ /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(MarketOddsCardList, { cards: message.marketOdds, ui, slip }),
1385
+ /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(OddsMovementCardList, { cards: message.oddsMovement, ui }),
1386
+ /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(ActionButtons, { actions: message.actions, loading: false, disabled, onAct })
1212
1387
  ] });
1213
1388
  }
1214
1389
 
1215
1390
  // src/components/IntentChips.tsx
1216
- var import_jsx_runtime17 = require("react/jsx-runtime");
1391
+ var import_jsx_runtime24 = require("react/jsx-runtime");
1217
1392
  function IntentChips({ chips, onPick }) {
1218
1393
  if (chips.length === 0) return null;
1219
- return /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("div", { className: "ss-w-chips", children: chips.map((c, i) => /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("button", { type: "button", className: "ss-w-chip", onClick: () => onPick(c.text), children: c.text }, `${c.text}-${i}`)) });
1394
+ return /* @__PURE__ */ (0, import_jsx_runtime24.jsx)("div", { className: "ss-w-chips", children: chips.map((c, i) => /* @__PURE__ */ (0, import_jsx_runtime24.jsx)("button", { type: "button", className: "ss-w-chip", onClick: () => onPick(c.text), children: c.text }, `${c.text}-${i}`)) });
1220
1395
  }
1221
1396
 
1222
1397
  // src/scroll.ts
@@ -1226,18 +1401,18 @@ function isPinnedToBottom(el, threshold = PIN_THRESHOLD) {
1226
1401
  }
1227
1402
 
1228
1403
  // src/components/MessageList.tsx
1229
- var import_jsx_runtime18 = require("react/jsx-runtime");
1404
+ var import_jsx_runtime25 = require("react/jsx-runtime");
1230
1405
  function MessageList({ controller, ui, slip, onDashboardTap }) {
1231
1406
  const { state, send } = controller;
1232
- const listRef = (0, import_react7.useRef)(null);
1233
- const endRef = (0, import_react7.useRef)(null);
1234
- const pinnedRef = (0, import_react7.useRef)(true);
1235
- (0, import_react7.useEffect)(() => {
1407
+ const listRef = (0, import_react9.useRef)(null);
1408
+ const endRef = (0, import_react9.useRef)(null);
1409
+ const pinnedRef = (0, import_react9.useRef)(true);
1410
+ (0, import_react9.useEffect)(() => {
1236
1411
  if (pinnedRef.current) endRef.current?.scrollIntoView({ block: "end" });
1237
1412
  }, [state.messages, state.status]);
1238
1413
  const hasUserMessage = state.messages.some((m) => m.role === "user");
1239
1414
  const hasDashboard = state.messages.some((m) => m.role === "assistant" && m.greetingDashboard);
1240
- return /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)(
1415
+ return /* @__PURE__ */ (0, import_jsx_runtime25.jsxs)(
1241
1416
  "div",
1242
1417
  {
1243
1418
  className: "ss-w-list",
@@ -1247,7 +1422,7 @@ function MessageList({ controller, ui, slip, onDashboardTap }) {
1247
1422
  if (listRef.current) pinnedRef.current = isPinnedToBottom(listRef.current);
1248
1423
  },
1249
1424
  children: [
1250
- state.messages.map((m) => /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
1425
+ state.messages.map((m) => /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(
1251
1426
  ChatMessage,
1252
1427
  {
1253
1428
  message: m,
@@ -1257,30 +1432,31 @@ function MessageList({ controller, ui, slip, onDashboardTap }) {
1257
1432
  send(tile.actionValue, { displayText: tile.title });
1258
1433
  if (m.role === "assistant" && m.greetingDashboard) onDashboardTap?.(tile, index, m.greetingDashboard.layout);
1259
1434
  },
1435
+ onClubAction: (actionValue, displayText) => send(actionValue, { displayText }),
1260
1436
  ui,
1261
1437
  slip
1262
1438
  },
1263
1439
  m.id
1264
1440
  )),
1265
- state.isStreaming && state.actionsLoading ? /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(ActionButtons, { actions: [], loading: true, onAct: () => {
1441
+ state.isStreaming && state.actionsLoading ? /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(ActionButtons, { actions: [], loading: true, onAct: () => {
1266
1442
  } }) : null,
1267
- state.status ? /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("div", { className: "ss-w-status", children: state.status }) : null,
1268
- state.error ? /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("div", { className: "ss-w-error", role: "alert", children: [
1269
- /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("span", { children: "message" in state.error ? state.error.message : ui.somethingWentWrong }),
1270
- /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("button", { type: "button", className: "ss-w-retry", onClick: () => controller.retry(), children: ui.retry })
1443
+ state.status ? /* @__PURE__ */ (0, import_jsx_runtime25.jsx)("div", { className: "ss-w-status", children: state.status }) : null,
1444
+ state.error ? /* @__PURE__ */ (0, import_jsx_runtime25.jsxs)("div", { className: "ss-w-error", role: "alert", children: [
1445
+ /* @__PURE__ */ (0, import_jsx_runtime25.jsx)("span", { children: "message" in state.error ? state.error.message : ui.somethingWentWrong }),
1446
+ /* @__PURE__ */ (0, import_jsx_runtime25.jsx)("button", { type: "button", className: "ss-w-retry", onClick: () => controller.retry(), children: ui.retry })
1271
1447
  ] }) : null,
1272
- !hasUserMessage && !hasDashboard ? /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(IntentChips, { chips: state.starters, onPick: (t) => send(t) }) : null,
1273
- /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("div", { ref: endRef })
1448
+ !hasUserMessage && !hasDashboard ? /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(IntentChips, { chips: state.starters, onPick: (t) => send(t) }) : null,
1449
+ /* @__PURE__ */ (0, import_jsx_runtime25.jsx)("div", { ref: endRef })
1274
1450
  ]
1275
1451
  }
1276
1452
  );
1277
1453
  }
1278
1454
 
1279
1455
  // src/components/ChatInput.tsx
1280
- var import_react8 = require("react");
1281
- var import_jsx_runtime19 = require("react/jsx-runtime");
1456
+ var import_react10 = require("react");
1457
+ var import_jsx_runtime26 = require("react/jsx-runtime");
1282
1458
  function ChatInput({ placeholder, streaming, onSend, onStop, stopLabel = "Stop response", sendLabel = "Send message" }) {
1283
- const [value, setValue] = (0, import_react8.useState)("");
1459
+ const [value, setValue] = (0, import_react10.useState)("");
1284
1460
  const submit = () => {
1285
1461
  const t = value.trim();
1286
1462
  if (!t) return;
@@ -1293,18 +1469,18 @@ function ChatInput({ placeholder, streaming, onSend, onStop, stopLabel = "Stop r
1293
1469
  submit();
1294
1470
  }
1295
1471
  };
1296
- return /* @__PURE__ */ (0, import_jsx_runtime19.jsxs)("div", { className: "ss-w-input", children: [
1297
- /* @__PURE__ */ (0, import_jsx_runtime19.jsx)("textarea", { className: "ss-w-textarea", rows: 1, placeholder, value, disabled: streaming, onChange: (e) => setValue(e.target.value), onKeyDown }),
1298
- streaming ? /* @__PURE__ */ (0, import_jsx_runtime19.jsx)("button", { type: "button", className: "ss-w-send ss-w-stop", "aria-label": stopLabel, onClick: onStop, children: /* @__PURE__ */ (0, import_jsx_runtime19.jsx)("span", { className: "ss-w-stop-glyph" }) }) : /* @__PURE__ */ (0, import_jsx_runtime19.jsx)("button", { type: "button", className: "ss-w-send", "aria-label": sendLabel, onClick: submit, children: /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(SendIcon, {}) })
1472
+ return /* @__PURE__ */ (0, import_jsx_runtime26.jsxs)("div", { className: "ss-w-input", children: [
1473
+ /* @__PURE__ */ (0, import_jsx_runtime26.jsx)("textarea", { className: "ss-w-textarea", rows: 1, placeholder, value, disabled: streaming, onChange: (e) => setValue(e.target.value), onKeyDown }),
1474
+ streaming ? /* @__PURE__ */ (0, import_jsx_runtime26.jsx)("button", { type: "button", className: "ss-w-send ss-w-stop", "aria-label": stopLabel, onClick: onStop, children: /* @__PURE__ */ (0, import_jsx_runtime26.jsx)("span", { className: "ss-w-stop-glyph" }) }) : /* @__PURE__ */ (0, import_jsx_runtime26.jsx)("button", { type: "button", className: "ss-w-send", "aria-label": sendLabel, onClick: submit, children: /* @__PURE__ */ (0, import_jsx_runtime26.jsx)(SendIcon, {}) })
1299
1475
  ] });
1300
1476
  }
1301
1477
 
1302
1478
  // src/components/ChatPanel.tsx
1303
- var import_jsx_runtime20 = require("react/jsx-runtime");
1479
+ var import_jsx_runtime27 = require("react/jsx-runtime");
1304
1480
  var FULLSCREEN_QUERY = "(max-width: 639px)";
1305
1481
  function useIsModal() {
1306
- const [modal, setModal] = (0, import_react9.useState)(false);
1307
- (0, import_react9.useEffect)(() => {
1482
+ const [modal, setModal] = (0, import_react11.useState)(false);
1483
+ (0, import_react11.useEffect)(() => {
1308
1484
  if (typeof window === "undefined" || typeof window.matchMedia !== "function") return;
1309
1485
  const mql = window.matchMedia(FULLSCREEN_QUERY);
1310
1486
  const sync = () => setModal(mql.matches);
@@ -1317,17 +1493,17 @@ function useIsModal() {
1317
1493
  function ChatPanel({ controller, ui, slip, onDashboardTap }) {
1318
1494
  const { state, close, stop, send } = controller;
1319
1495
  const isModal = useIsModal();
1320
- (0, import_react9.useEffect)(() => {
1496
+ (0, import_react11.useEffect)(() => {
1321
1497
  const onKey = (e) => {
1322
1498
  if (e.key === "Escape") close();
1323
1499
  };
1324
1500
  window.addEventListener("keydown", onKey);
1325
1501
  return () => window.removeEventListener("keydown", onKey);
1326
1502
  }, [close]);
1327
- return /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)("div", { className: "ss-w-panel", role: "dialog", "aria-modal": isModal, "aria-label": ui.chatDialogLabel, children: [
1328
- /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(ChatHeader, { onClose: close, closeLabel: ui.closeChat }),
1329
- /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(MessageList, { controller, ui, slip, onDashboardTap }),
1330
- /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(ChatInput, { placeholder: ui.placeholder, streaming: state.isStreaming, onSend: (t) => send(t), onStop: stop, stopLabel: ui.stopResponse, sendLabel: ui.sendMessage })
1503
+ return /* @__PURE__ */ (0, import_jsx_runtime27.jsxs)("div", { className: "ss-w-panel", role: "dialog", "aria-modal": isModal, "aria-label": ui.chatDialogLabel, children: [
1504
+ /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(ChatHeader, { onClose: close, closeLabel: ui.closeChat }),
1505
+ /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(MessageList, { controller, ui, slip, onDashboardTap }),
1506
+ /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(ChatInput, { placeholder: ui.placeholder, streaming: state.isStreaming, onSend: (t) => send(t), onStop: stop, stopLabel: ui.stopResponse, sendLabel: ui.sendMessage })
1331
1507
  ] });
1332
1508
  }
1333
1509
 
@@ -1652,10 +1828,46 @@ var WIDGET_CSS = `.ss-w-root { all: revert; }
1652
1828
  .ss-w-dash--discovery .ss-w-dash-tile:nth-child(2) { background: #eef3fb; }
1653
1829
  .ss-w-dash--discovery .ss-w-dash-tile:nth-child(3) { background: #fdf6ec; }
1654
1830
  .ss-w-dash--discovery .ss-w-dash-tile:nth-child(4) { background: #f5eefb; }
1831
+
1832
+ /* club match-day dashboard */
1833
+ .ss-w-cdash { display: flex; flex-direction: column; gap: 10px; padding: 4px 0; }
1834
+ .ss-w-cdash-block { display: flex; flex-direction: column; }
1835
+
1836
+ .ss-w-cdash-greeting { display: flex; flex-direction: column; gap: 2px; }
1837
+ .ss-w-cdash-greeting-title { font-weight: 700; font-size: 14px; color: var(--ss-w-ink); }
1838
+ .ss-w-cdash-greeting-subtitle { font-size: 11.5px; color: var(--ss-w-faint); }
1839
+
1840
+ .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; }
1841
+ .ss-w-cdash-tie { display: flex; align-items: center; justify-content: center; gap: 8px; width: 100%; }
1842
+ .ss-w-cdash-team { font-weight: 650; font-size: 13px; color: var(--ss-w-ink); min-width: 0; overflow-wrap: anywhere; }
1843
+ .ss-w-cdash-vs { font-size: 11px; color: var(--ss-w-faint); font-weight: 600; }
1844
+ .ss-w-cdash-crest { flex: none; width: 28px; height: 28px; border-radius: 50%; object-fit: cover; background: var(--ss-w-bubble); }
1845
+ .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)); }
1846
+ .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); }
1847
+ .ss-w-cdash-kickoff { font-weight: 700; color: var(--ss-w-ink); }
1848
+ .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; }
1849
+
1850
+ .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; }
1851
+ .ss-w-cdash-form-row { display: flex; align-items: center; justify-content: space-between; gap: 8px; }
1852
+ .ss-w-cdash-form-name { font-size: 12px; font-weight: 600; color: var(--ss-w-ink); min-width: 0; overflow-wrap: anywhere; }
1853
+ .ss-w-cdash-form-pills { flex: none; display: flex; align-items: center; gap: 3px; }
1854
+ .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; }
1855
+ .ss-w-cdash-pill--w { background: color-mix(in srgb, var(--ss-w-pos) 88%, black); }
1856
+ .ss-w-cdash-pill--d { background: var(--ss-w-faint); }
1857
+ .ss-w-cdash-pill--l { background: color-mix(in srgb, var(--ss-w-neg) 88%, black); }
1858
+
1859
+ .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; }
1860
+ .ss-w-cdash-lines-heading { font-size: 12px; font-weight: 700; color: var(--ss-w-accent); }
1861
+ .ss-w-cdash-lines p { font-size: 12px; line-height: 1.4; color: var(--ss-w-ink); }
1862
+
1863
+ .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; }
1864
+ .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); }
1865
+ .ss-w-cdash-cd-h, .ss-w-cdash-cd-m, .ss-w-cdash-cd-s { color: var(--ss-w-accent); }
1866
+ .ss-w-cdash-cd-label { font-size: 11px; font-weight: 600; color: var(--ss-w-faint); }
1655
1867
  `;
1656
1868
 
1657
1869
  // src/StatsWidget.tsx
1658
- var import_jsx_runtime21 = require("react/jsx-runtime");
1870
+ var import_jsx_runtime28 = require("react/jsx-runtime");
1659
1871
  function buildClient(props) {
1660
1872
  try {
1661
1873
  if ("client" in props && props.client) {
@@ -1680,11 +1892,11 @@ function injectStyles(root) {
1680
1892
  function StatsWidget(props) {
1681
1893
  const injectedClient = "client" in props ? props.client : void 0;
1682
1894
  const cfg = props.config;
1683
- const built = (0, import_react10.useMemo)(() => buildClient(props), [injectedClient, cfg?.operatorId, cfg?.publicKey, cfg?.baseUrl]);
1684
- const ui = (0, import_react10.useMemo)(() => normalizeUi(props.config ?? { operatorId: "", publicKey: "" }), [props.config]);
1685
- const rootRef = (0, import_react10.useRef)(null);
1895
+ const built = (0, import_react12.useMemo)(() => buildClient(props), [injectedClient, cfg?.operatorId, cfg?.publicKey, cfg?.baseUrl]);
1896
+ const ui = (0, import_react12.useMemo)(() => normalizeUi(props.config ?? { operatorId: "", publicKey: "" }), [props.config]);
1897
+ const rootRef = (0, import_react12.useRef)(null);
1686
1898
  const controller = useChatStream(built?.conversation ?? emptyConversation(), ui.starters, built ? ui.greetingMessage : void 0);
1687
- const slip = (0, import_react10.useMemo)(
1899
+ const slip = (0, import_react12.useMemo)(
1688
1900
  () => ({
1689
1901
  onAddToSlip: props.config?.onAddToSlip,
1690
1902
  conversationId: controller.state.conversationId ?? void 0,
@@ -1697,18 +1909,18 @@ function StatsWidget(props) {
1697
1909
  }),
1698
1910
  [props.config?.onAddToSlip, controller.state.conversationId, built]
1699
1911
  );
1700
- const onDashboardTap = (0, import_react10.useMemo)(
1912
+ const onDashboardTap = (0, import_react12.useMemo)(
1701
1913
  () => built ? createDashboardTapSignal((input) => built.conversation.sendSignal(input)) : void 0,
1702
1914
  [built]
1703
1915
  );
1704
- (0, import_react10.useEffect)(() => {
1916
+ (0, import_react12.useEffect)(() => {
1705
1917
  if (!built || !ui.injectStyles) return;
1706
1918
  const node = rootRef.current?.getRootNode();
1707
1919
  if (node) injectStyles(node);
1708
1920
  }, [built, ui.injectStyles]);
1709
1921
  if (!built) return null;
1710
1922
  const attrs = themeAttrs(ui);
1711
- return /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("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__ */ (0, import_jsx_runtime21.jsx)(ChatPanel, { controller, ui, slip, onDashboardTap }) : /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
1923
+ return /* @__PURE__ */ (0, import_jsx_runtime28.jsx)("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__ */ (0, import_jsx_runtime28.jsx)(ChatPanel, { controller, ui, slip, onDashboardTap }) : /* @__PURE__ */ (0, import_jsx_runtime28.jsx)(
1712
1924
  ChatFab,
1713
1925
  {
1714
1926
  label: ui.launcherLabel,