@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/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
@@ -42,6 +42,12 @@ var STRINGS = {
42
42
  const drift = direction ? `, ${direction}` : "";
43
43
  return `Price movement over the last ${span}: opened at ${opening}, currently ${current}${drift}.`;
44
44
  },
45
+ clubMatchPreviewHeading: "Match preview",
46
+ clubTeamDnaHeading: "How they play",
47
+ clubFormHeading: "Recent form",
48
+ clubCountdownLabel: "Countdown to kick-off",
49
+ clubKickoff: (ms) => new Intl.DateTimeFormat("en-GB", { weekday: "short", day: "numeric", month: "short", hour: "2-digit", minute: "2-digit" }).format(new Date(ms)),
50
+ 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
51
  inSlip: "In slip",
46
52
  slipAdded: (n) => n === void 0 ? "Added to slip" : `Added to slip (${n} selection${n === 1 ? "" : "s"})`,
47
53
  slipDuplicate: "Already in your slip",
@@ -89,6 +95,12 @@ var STRINGS = {
89
95
  const drift = direction ? `, ${direction}` : "";
90
96
  return `Son ${span} i\xE7indeki oran hareketi: ${opening} ile a\xE7\u0131ld\u0131, \u015Fu anda ${current}${drift}.`;
91
97
  },
98
+ clubMatchPreviewHeading: "Ma\xE7 \xF6nizlemesi",
99
+ clubTeamDnaHeading: "Nas\u0131l oynuyorlar",
100
+ clubFormHeading: "Son form",
101
+ clubCountdownLabel: "Ba\u015Flama vuru\u015Funa kalan",
102
+ clubKickoff: (ms) => new Intl.DateTimeFormat("tr-TR", { weekday: "short", day: "numeric", month: "short", hour: "2-digit", minute: "2-digit" }).format(new Date(ms)),
103
+ 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
104
  inSlip: "Kuponda",
93
105
  slipAdded: (n) => n === void 0 ? "Kupona eklendi" : `Kupona eklendi (${n} se\xE7im)`,
94
106
  slipDuplicate: "Bu se\xE7im kuponunuzda zaten var",
@@ -148,6 +160,12 @@ function normalizeUi(cfg) {
148
160
  excludedSuspended: strings.excludedSuspended,
149
161
  chartTimeLabel: strings.chartTimeLabel,
150
162
  chartAria: strings.chartAria,
163
+ clubMatchPreviewHeading: strings.clubMatchPreviewHeading,
164
+ clubTeamDnaHeading: strings.clubTeamDnaHeading,
165
+ clubFormHeading: strings.clubFormHeading,
166
+ clubCountdownLabel: strings.clubCountdownLabel,
167
+ clubKickoff: strings.clubKickoff,
168
+ clubKickoffAria: strings.clubKickoffAria,
151
169
  addToSlip: strings.addToSlip,
152
170
  inSlip: strings.inSlip,
153
171
  slipAdded: strings.slipAdded,
@@ -494,7 +512,8 @@ function applyEvent(state, event) {
494
512
  messages: mapActive(state, (t) => ({
495
513
  ...t,
496
514
  done: true,
497
- greetingDashboard: event.data.dashboard ?? void 0
515
+ greetingDashboard: event.data.dashboard ?? void 0,
516
+ clubDashboard: event.data.clubDashboard ?? void 0
498
517
  }))
499
518
  };
500
519
  }
@@ -661,7 +680,7 @@ function ChatFab({ label, greeting, popupText, onOpen, onDismissPopup, dismissLa
661
680
  }
662
681
 
663
682
  // src/components/ChatPanel.tsx
664
- import { useEffect as useEffect4, useState as useState6 } from "react";
683
+ import { useEffect as useEffect5, useState as useState8 } from "react";
665
684
 
666
685
  // src/components/ChatHeader.tsx
667
686
  import { jsx as jsx4, jsxs as jsxs3 } from "react/jsx-runtime";
@@ -674,7 +693,7 @@ function ChatHeader({ onClose, closeLabel = "Close chat" }) {
674
693
  }
675
694
 
676
695
  // src/components/MessageList.tsx
677
- import { useEffect as useEffect3, useRef as useRef2 } from "react";
696
+ import { useEffect as useEffect4, useRef as useRef2 } from "react";
678
697
 
679
698
  // src/components/Markdown.tsx
680
699
  import ReactMarkdown from "react-markdown";
@@ -1158,28 +1177,184 @@ function GreetingDashboard({ dashboard, onTap }) {
1158
1177
  ] });
1159
1178
  }
1160
1179
 
1161
- // src/components/ChatMessage.tsx
1180
+ // src/components/club-dashboard/GreetingHeaderBlock.tsx
1162
1181
  import { jsx as jsx16, jsxs as jsxs13 } from "react/jsx-runtime";
1163
- function ChatMessage({ message, disabled, onAct, onTap, ui, slip }) {
1182
+ function GreetingHeaderBlock({ block }) {
1183
+ return /* @__PURE__ */ jsxs13("div", { className: "ss-w-cdash-greeting", children: [
1184
+ /* @__PURE__ */ jsx16("div", { className: "ss-w-cdash-greeting-title", children: block.title }),
1185
+ /* @__PURE__ */ jsx16("div", { className: "ss-w-cdash-greeting-subtitle", children: block.subtitle })
1186
+ ] });
1187
+ }
1188
+
1189
+ // src/components/club-dashboard/FixtureHeroBlock.tsx
1190
+ import { useState as useState5 } from "react";
1191
+ import { jsx as jsx17, jsxs as jsxs14 } from "react/jsx-runtime";
1192
+ function initials2(name) {
1193
+ return name.split(/\s+/).filter(Boolean).slice(0, 2).map((w) => w[0].toUpperCase()).join("");
1194
+ }
1195
+ function Crest2({ url, name }) {
1196
+ const [broken, setBroken] = useState5(false);
1197
+ if (!url || broken) {
1198
+ return /* @__PURE__ */ jsx17("span", { className: "ss-w-cdash-crest ss-w-cdash-crest--fallback", "aria-hidden": "true", children: initials2(name) });
1199
+ }
1200
+ return /* @__PURE__ */ jsx17("img", { className: "ss-w-cdash-crest", src: url, alt: "", "aria-hidden": "true", onError: () => setBroken(true) });
1201
+ }
1202
+ function FixtureHeroBlock({
1203
+ block,
1204
+ ui,
1205
+ onAction
1206
+ }) {
1207
+ return /* @__PURE__ */ jsxs14("div", { className: "ss-w-cdash-fixture", children: [
1208
+ /* @__PURE__ */ jsxs14("div", { className: "ss-w-cdash-tie", children: [
1209
+ /* @__PURE__ */ jsx17(Crest2, { url: block.homeCrestUrl, name: block.homeName }),
1210
+ /* @__PURE__ */ jsx17("span", { className: "ss-w-cdash-team", children: block.homeName }),
1211
+ /* @__PURE__ */ jsx17("span", { className: "ss-w-cdash-vs", children: "v" }),
1212
+ /* @__PURE__ */ jsx17("span", { className: "ss-w-cdash-team", children: block.awayName }),
1213
+ /* @__PURE__ */ jsx17(Crest2, { url: block.awayCrestUrl, name: block.awayName })
1214
+ ] }),
1215
+ /* @__PURE__ */ jsxs14("div", { className: "ss-w-cdash-fixture-meta", children: [
1216
+ /* @__PURE__ */ jsx17("span", { className: "ss-w-cdash-kickoff", children: ui.clubKickoff(block.kickoffUnix * 1e3) }),
1217
+ block.venue ? /* @__PURE__ */ jsx17("span", { className: "ss-w-cdash-venue", children: block.venue }) : null,
1218
+ block.competition ? /* @__PURE__ */ jsx17("span", { className: "ss-w-cdash-comp", children: block.competition }) : null
1219
+ ] }),
1220
+ /* @__PURE__ */ jsx17("button", { type: "button", className: "ss-w-cdash-cta", onClick: () => onAction(block.ctaActionValue, block.ctaLabel), children: block.ctaLabel })
1221
+ ] });
1222
+ }
1223
+
1224
+ // src/components/club-dashboard/FormGuideBlock.tsx
1225
+ import { jsx as jsx18, jsxs as jsxs15 } from "react/jsx-runtime";
1226
+ function FormGuideBlock({ block }) {
1227
+ if (block.rows.length === 0) return null;
1228
+ return /* @__PURE__ */ jsx18("div", { className: "ss-w-cdash-form", children: block.rows.map((row) => /* @__PURE__ */ jsxs15("div", { className: "ss-w-cdash-form-row", children: [
1229
+ /* @__PURE__ */ jsxs15("span", { className: "ss-w-cdash-form-name", children: [
1230
+ row.name,
1231
+ row.position != null ? ` #${row.position}` : ""
1232
+ ] }),
1233
+ /* @__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)) })
1234
+ ] }, row.name)) });
1235
+ }
1236
+
1237
+ // src/components/club-dashboard/MatchPreviewBlock.tsx
1238
+ import { jsx as jsx19, jsxs as jsxs16 } from "react/jsx-runtime";
1239
+ function MatchPreviewBlock({
1240
+ block,
1241
+ ui,
1242
+ onAction
1243
+ }) {
1244
+ if (block.lines.length === 0) return null;
1245
+ const heading = ui.clubMatchPreviewHeading;
1246
+ return /* @__PURE__ */ jsxs16("button", { type: "button", className: "ss-w-cdash-lines", onClick: () => onAction(block.actionValue, heading), children: [
1247
+ /* @__PURE__ */ jsx19("span", { className: "ss-w-cdash-lines-heading", children: heading }),
1248
+ block.lines.map((line, i) => /* @__PURE__ */ jsx19("p", { children: line }, i))
1249
+ ] });
1250
+ }
1251
+
1252
+ // src/components/club-dashboard/TeamDnaBlock.tsx
1253
+ import { jsx as jsx20, jsxs as jsxs17 } from "react/jsx-runtime";
1254
+ function TeamDnaBlock({
1255
+ block,
1256
+ ui,
1257
+ onAction
1258
+ }) {
1259
+ if (block.lines.length === 0) return null;
1260
+ const heading = ui.clubTeamDnaHeading;
1261
+ return /* @__PURE__ */ jsxs17("button", { type: "button", className: "ss-w-cdash-lines", onClick: () => onAction(block.actionValue, heading), children: [
1262
+ /* @__PURE__ */ jsx20("span", { className: "ss-w-cdash-lines-heading", children: heading }),
1263
+ block.lines.map((line, i) => /* @__PURE__ */ jsx20("p", { children: line }, i))
1264
+ ] });
1265
+ }
1266
+
1267
+ // src/components/club-dashboard/CountdownBlock.tsx
1268
+ import { useEffect as useEffect3, useState as useState6 } from "react";
1269
+ import { jsx as jsx21, jsxs as jsxs18 } from "react/jsx-runtime";
1270
+ var DAY_MS = 24 * 3600 * 1e3;
1271
+ function pad2(n) {
1272
+ return String(n).padStart(2, "0");
1273
+ }
1274
+ function splitRemaining(remainingMs) {
1275
+ const totalSeconds = Math.max(0, Math.floor(remainingMs / 1e3));
1276
+ const h = Math.floor(totalSeconds / 3600);
1277
+ const m = Math.floor(totalSeconds % 3600 / 60);
1278
+ const s = totalSeconds % 60;
1279
+ return { h: pad2(h), m: pad2(m), s: pad2(s) };
1280
+ }
1281
+ function CountdownBlock({ block, ui }) {
1282
+ const [remainingMs, setRemainingMs] = useState6(() => block.kickoffUnix * 1e3 - Date.now());
1283
+ useEffect3(() => {
1284
+ setRemainingMs(block.kickoffUnix * 1e3 - Date.now());
1285
+ if (window.matchMedia("(prefers-reduced-motion: reduce)").matches) return;
1286
+ const id = setInterval(() => {
1287
+ setRemainingMs(block.kickoffUnix * 1e3 - Date.now());
1288
+ }, 1e3);
1289
+ return () => clearInterval(id);
1290
+ }, [block.kickoffUnix]);
1291
+ if (remainingMs <= 0) return null;
1292
+ if (remainingMs > DAY_MS) return null;
1293
+ const { h, m, s } = splitRemaining(remainingMs);
1294
+ return /* @__PURE__ */ jsxs18("div", { className: "ss-w-cdash-cd", "aria-label": ui.clubKickoffAria(block.kickoffUnix * 1e3), children: [
1295
+ /* @__PURE__ */ jsxs18("div", { className: "ss-w-cdash-cd-clock", "aria-hidden": "true", children: [
1296
+ /* @__PURE__ */ jsx21("span", { className: "ss-w-cdash-cd-h", children: h }),
1297
+ "h",
1298
+ " ",
1299
+ /* @__PURE__ */ jsx21("span", { className: "ss-w-cdash-cd-m", children: m }),
1300
+ "m",
1301
+ " ",
1302
+ /* @__PURE__ */ jsx21("span", { className: "ss-w-cdash-cd-s", children: s }),
1303
+ "s"
1304
+ ] }),
1305
+ /* @__PURE__ */ jsx21("div", { className: "ss-w-cdash-cd-label", children: ui.clubCountdownLabel })
1306
+ ] });
1307
+ }
1308
+
1309
+ // src/components/club-dashboard/ClubDashboard.tsx
1310
+ import { jsx as jsx22 } from "react/jsx-runtime";
1311
+ var CLUB_BLOCK_REGISTRY = {
1312
+ greetingHeader: ({ block }) => block.type === "greetingHeader" ? /* @__PURE__ */ jsx22(GreetingHeaderBlock, { block }) : null,
1313
+ fixtureHero: ({ block, ui, onAction }) => block.type === "fixtureHero" ? /* @__PURE__ */ jsx22(FixtureHeroBlock, { block, ui, onAction }) : null,
1314
+ formGuide: ({ block, ui }) => block.type === "formGuide" ? /* @__PURE__ */ jsx22(FormGuideBlock, { block, ui }) : null,
1315
+ matchPreview: ({ block, ui, onAction }) => block.type === "matchPreview" ? /* @__PURE__ */ jsx22(MatchPreviewBlock, { block, ui, onAction }) : null,
1316
+ teamDna: ({ block, ui, onAction }) => block.type === "teamDna" ? /* @__PURE__ */ jsx22(TeamDnaBlock, { block, ui, onAction }) : null,
1317
+ countdown: ({ block, ui }) => block.type === "countdown" ? /* @__PURE__ */ jsx22(CountdownBlock, { block, ui }) : null
1318
+ };
1319
+ function ClubDashboard({
1320
+ dashboard,
1321
+ ui,
1322
+ onAction
1323
+ }) {
1324
+ if (!dashboard.blocks.length) return null;
1325
+ return /* @__PURE__ */ jsx22("div", { className: "ss-w-cdash", children: dashboard.blocks.map((block, i) => {
1326
+ const Comp = Object.prototype.hasOwnProperty.call(CLUB_BLOCK_REGISTRY, block.type) ? CLUB_BLOCK_REGISTRY[block.type] : void 0;
1327
+ if (!Comp) {
1328
+ console.warn(`[club-dashboard] no renderer for block type "${block.type}"`);
1329
+ return null;
1330
+ }
1331
+ return /* @__PURE__ */ jsx22("div", { className: "ss-w-cdash-block", children: /* @__PURE__ */ jsx22(Comp, { block, ui, onAction }) }, i);
1332
+ }) });
1333
+ }
1334
+
1335
+ // src/components/ChatMessage.tsx
1336
+ import { jsx as jsx23, jsxs as jsxs19 } from "react/jsx-runtime";
1337
+ function ChatMessage({ message, disabled, onAct, onTap, onClubAction, ui, slip }) {
1164
1338
  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 }) });
1339
+ return /* @__PURE__ */ jsx23("div", { className: "ss-w-msg ss-w-user", children: /* @__PURE__ */ jsx23("div", { className: "ss-w-bubble", children: message.text }) });
1166
1340
  }
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 })
1341
+ return /* @__PURE__ */ jsxs19("div", { className: "ss-w-msg ss-w-bot", children: [
1342
+ message.text && !message.greetingDashboard && !message.clubDashboard ? /* @__PURE__ */ jsx23(Markdown, { text: message.text }) : null,
1343
+ message.greetingDashboard ? /* @__PURE__ */ jsx23(GreetingDashboard, { dashboard: message.greetingDashboard, onTap }) : null,
1344
+ message.clubDashboard && ui ? /* @__PURE__ */ jsx23(ClubDashboard, { dashboard: message.clubDashboard, ui, onAction: onClubAction }) : null,
1345
+ /* @__PURE__ */ jsx23(EntityCardList, { cards: message.cards, disabled, showLessLabel: ui?.showLess, showMoreLabel: ui?.showMore }),
1346
+ /* @__PURE__ */ jsx23(BettingInsightList, { insights: message.insights, ui, slip }),
1347
+ /* @__PURE__ */ jsx23(MarketOddsCardList, { cards: message.marketOdds, ui, slip }),
1348
+ /* @__PURE__ */ jsx23(OddsMovementCardList, { cards: message.oddsMovement, ui }),
1349
+ /* @__PURE__ */ jsx23(ActionButtons, { actions: message.actions, loading: false, disabled, onAct })
1175
1350
  ] });
1176
1351
  }
1177
1352
 
1178
1353
  // src/components/IntentChips.tsx
1179
- import { jsx as jsx17 } from "react/jsx-runtime";
1354
+ import { jsx as jsx24 } from "react/jsx-runtime";
1180
1355
  function IntentChips({ chips, onPick }) {
1181
1356
  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}`)) });
1357
+ 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
1358
  }
1184
1359
 
1185
1360
  // src/scroll.ts
@@ -1189,18 +1364,18 @@ function isPinnedToBottom(el, threshold = PIN_THRESHOLD) {
1189
1364
  }
1190
1365
 
1191
1366
  // src/components/MessageList.tsx
1192
- import { jsx as jsx18, jsxs as jsxs14 } from "react/jsx-runtime";
1367
+ import { jsx as jsx25, jsxs as jsxs20 } from "react/jsx-runtime";
1193
1368
  function MessageList({ controller, ui, slip, onDashboardTap }) {
1194
1369
  const { state, send } = controller;
1195
1370
  const listRef = useRef2(null);
1196
1371
  const endRef = useRef2(null);
1197
1372
  const pinnedRef = useRef2(true);
1198
- useEffect3(() => {
1373
+ useEffect4(() => {
1199
1374
  if (pinnedRef.current) endRef.current?.scrollIntoView({ block: "end" });
1200
1375
  }, [state.messages, state.status]);
1201
1376
  const hasUserMessage = state.messages.some((m) => m.role === "user");
1202
1377
  const hasDashboard = state.messages.some((m) => m.role === "assistant" && m.greetingDashboard);
1203
- return /* @__PURE__ */ jsxs14(
1378
+ return /* @__PURE__ */ jsxs20(
1204
1379
  "div",
1205
1380
  {
1206
1381
  className: "ss-w-list",
@@ -1210,7 +1385,7 @@ function MessageList({ controller, ui, slip, onDashboardTap }) {
1210
1385
  if (listRef.current) pinnedRef.current = isPinnedToBottom(listRef.current);
1211
1386
  },
1212
1387
  children: [
1213
- state.messages.map((m) => /* @__PURE__ */ jsx18(
1388
+ state.messages.map((m) => /* @__PURE__ */ jsx25(
1214
1389
  ChatMessage,
1215
1390
  {
1216
1391
  message: m,
@@ -1220,30 +1395,31 @@ function MessageList({ controller, ui, slip, onDashboardTap }) {
1220
1395
  send(tile.actionValue, { displayText: tile.title });
1221
1396
  if (m.role === "assistant" && m.greetingDashboard) onDashboardTap?.(tile, index, m.greetingDashboard.layout);
1222
1397
  },
1398
+ onClubAction: (actionValue, displayText) => send(actionValue, { displayText }),
1223
1399
  ui,
1224
1400
  slip
1225
1401
  },
1226
1402
  m.id
1227
1403
  )),
1228
- state.isStreaming && state.actionsLoading ? /* @__PURE__ */ jsx18(ActionButtons, { actions: [], loading: true, onAct: () => {
1404
+ state.isStreaming && state.actionsLoading ? /* @__PURE__ */ jsx25(ActionButtons, { actions: [], loading: true, onAct: () => {
1229
1405
  } }) : 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 })
1406
+ state.status ? /* @__PURE__ */ jsx25("div", { className: "ss-w-status", children: state.status }) : null,
1407
+ state.error ? /* @__PURE__ */ jsxs20("div", { className: "ss-w-error", role: "alert", children: [
1408
+ /* @__PURE__ */ jsx25("span", { children: "message" in state.error ? state.error.message : ui.somethingWentWrong }),
1409
+ /* @__PURE__ */ jsx25("button", { type: "button", className: "ss-w-retry", onClick: () => controller.retry(), children: ui.retry })
1234
1410
  ] }) : null,
1235
- !hasUserMessage && !hasDashboard ? /* @__PURE__ */ jsx18(IntentChips, { chips: state.starters, onPick: (t) => send(t) }) : null,
1236
- /* @__PURE__ */ jsx18("div", { ref: endRef })
1411
+ !hasUserMessage && !hasDashboard ? /* @__PURE__ */ jsx25(IntentChips, { chips: state.starters, onPick: (t) => send(t) }) : null,
1412
+ /* @__PURE__ */ jsx25("div", { ref: endRef })
1237
1413
  ]
1238
1414
  }
1239
1415
  );
1240
1416
  }
1241
1417
 
1242
1418
  // src/components/ChatInput.tsx
1243
- import { useState as useState5 } from "react";
1244
- import { jsx as jsx19, jsxs as jsxs15 } from "react/jsx-runtime";
1419
+ import { useState as useState7 } from "react";
1420
+ import { jsx as jsx26, jsxs as jsxs21 } from "react/jsx-runtime";
1245
1421
  function ChatInput({ placeholder, streaming, onSend, onStop, stopLabel = "Stop response", sendLabel = "Send message" }) {
1246
- const [value, setValue] = useState5("");
1422
+ const [value, setValue] = useState7("");
1247
1423
  const submit = () => {
1248
1424
  const t = value.trim();
1249
1425
  if (!t) return;
@@ -1256,18 +1432,18 @@ function ChatInput({ placeholder, streaming, onSend, onStop, stopLabel = "Stop r
1256
1432
  submit();
1257
1433
  }
1258
1434
  };
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, {}) })
1435
+ return /* @__PURE__ */ jsxs21("div", { className: "ss-w-input", children: [
1436
+ /* @__PURE__ */ jsx26("textarea", { className: "ss-w-textarea", rows: 1, placeholder, value, disabled: streaming, onChange: (e) => setValue(e.target.value), onKeyDown }),
1437
+ 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
1438
  ] });
1263
1439
  }
1264
1440
 
1265
1441
  // src/components/ChatPanel.tsx
1266
- import { jsx as jsx20, jsxs as jsxs16 } from "react/jsx-runtime";
1442
+ import { jsx as jsx27, jsxs as jsxs22 } from "react/jsx-runtime";
1267
1443
  var FULLSCREEN_QUERY = "(max-width: 639px)";
1268
1444
  function useIsModal() {
1269
- const [modal, setModal] = useState6(false);
1270
- useEffect4(() => {
1445
+ const [modal, setModal] = useState8(false);
1446
+ useEffect5(() => {
1271
1447
  if (typeof window === "undefined" || typeof window.matchMedia !== "function") return;
1272
1448
  const mql = window.matchMedia(FULLSCREEN_QUERY);
1273
1449
  const sync = () => setModal(mql.matches);
@@ -1280,17 +1456,17 @@ function useIsModal() {
1280
1456
  function ChatPanel({ controller, ui, slip, onDashboardTap }) {
1281
1457
  const { state, close, stop, send } = controller;
1282
1458
  const isModal = useIsModal();
1283
- useEffect4(() => {
1459
+ useEffect5(() => {
1284
1460
  const onKey = (e) => {
1285
1461
  if (e.key === "Escape") close();
1286
1462
  };
1287
1463
  window.addEventListener("keydown", onKey);
1288
1464
  return () => window.removeEventListener("keydown", onKey);
1289
1465
  }, [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 })
1466
+ return /* @__PURE__ */ jsxs22("div", { className: "ss-w-panel", role: "dialog", "aria-modal": isModal, "aria-label": ui.chatDialogLabel, children: [
1467
+ /* @__PURE__ */ jsx27(ChatHeader, { onClose: close, closeLabel: ui.closeChat }),
1468
+ /* @__PURE__ */ jsx27(MessageList, { controller, ui, slip, onDashboardTap }),
1469
+ /* @__PURE__ */ jsx27(ChatInput, { placeholder: ui.placeholder, streaming: state.isStreaming, onSend: (t) => send(t), onStop: stop, stopLabel: ui.stopResponse, sendLabel: ui.sendMessage })
1294
1470
  ] });
1295
1471
  }
1296
1472
 
@@ -1615,10 +1791,46 @@ var WIDGET_CSS = `.ss-w-root { all: revert; }
1615
1791
  .ss-w-dash--discovery .ss-w-dash-tile:nth-child(2) { background: #eef3fb; }
1616
1792
  .ss-w-dash--discovery .ss-w-dash-tile:nth-child(3) { background: #fdf6ec; }
1617
1793
  .ss-w-dash--discovery .ss-w-dash-tile:nth-child(4) { background: #f5eefb; }
1794
+
1795
+ /* club match-day dashboard */
1796
+ .ss-w-cdash { display: flex; flex-direction: column; gap: 10px; padding: 4px 0; }
1797
+ .ss-w-cdash-block { display: flex; flex-direction: column; }
1798
+
1799
+ .ss-w-cdash-greeting { display: flex; flex-direction: column; gap: 2px; }
1800
+ .ss-w-cdash-greeting-title { font-weight: 700; font-size: 14px; color: var(--ss-w-ink); }
1801
+ .ss-w-cdash-greeting-subtitle { font-size: 11.5px; color: var(--ss-w-faint); }
1802
+
1803
+ .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; }
1804
+ .ss-w-cdash-tie { display: flex; align-items: center; justify-content: center; gap: 8px; width: 100%; }
1805
+ .ss-w-cdash-team { font-weight: 650; font-size: 13px; color: var(--ss-w-ink); min-width: 0; overflow-wrap: anywhere; }
1806
+ .ss-w-cdash-vs { font-size: 11px; color: var(--ss-w-faint); font-weight: 600; }
1807
+ .ss-w-cdash-crest { flex: none; width: 28px; height: 28px; border-radius: 50%; object-fit: cover; background: var(--ss-w-bubble); }
1808
+ .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)); }
1809
+ .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); }
1810
+ .ss-w-cdash-kickoff { font-weight: 700; color: var(--ss-w-ink); }
1811
+ .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; }
1812
+
1813
+ .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; }
1814
+ .ss-w-cdash-form-row { display: flex; align-items: center; justify-content: space-between; gap: 8px; }
1815
+ .ss-w-cdash-form-name { font-size: 12px; font-weight: 600; color: var(--ss-w-ink); min-width: 0; overflow-wrap: anywhere; }
1816
+ .ss-w-cdash-form-pills { flex: none; display: flex; align-items: center; gap: 3px; }
1817
+ .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; }
1818
+ .ss-w-cdash-pill--w { background: color-mix(in srgb, var(--ss-w-pos) 88%, black); }
1819
+ .ss-w-cdash-pill--d { background: var(--ss-w-faint); }
1820
+ .ss-w-cdash-pill--l { background: color-mix(in srgb, var(--ss-w-neg) 88%, black); }
1821
+
1822
+ .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; }
1823
+ .ss-w-cdash-lines-heading { font-size: 12px; font-weight: 700; color: var(--ss-w-accent); }
1824
+ .ss-w-cdash-lines p { font-size: 12px; line-height: 1.4; color: var(--ss-w-ink); }
1825
+
1826
+ .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; }
1827
+ .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); }
1828
+ .ss-w-cdash-cd-h, .ss-w-cdash-cd-m, .ss-w-cdash-cd-s { color: var(--ss-w-accent); }
1829
+ .ss-w-cdash-cd-label { font-size: 11px; font-weight: 600; color: var(--ss-w-faint); }
1618
1830
  `;
1619
1831
 
1620
1832
  // src/StatsWidget.tsx
1621
- import { jsx as jsx21 } from "react/jsx-runtime";
1833
+ import { jsx as jsx28 } from "react/jsx-runtime";
1622
1834
  function buildClient(props) {
1623
1835
  try {
1624
1836
  if ("client" in props && props.client) {
@@ -1664,14 +1876,14 @@ function StatsWidget(props) {
1664
1876
  () => built ? createDashboardTapSignal((input) => built.conversation.sendSignal(input)) : void 0,
1665
1877
  [built]
1666
1878
  );
1667
- useEffect5(() => {
1879
+ useEffect6(() => {
1668
1880
  if (!built || !ui.injectStyles) return;
1669
1881
  const node = rootRef.current?.getRootNode();
1670
1882
  if (node) injectStyles(node);
1671
1883
  }, [built, ui.injectStyles]);
1672
1884
  if (!built) return null;
1673
1885
  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(
1886
+ 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
1887
  ChatFab,
1676
1888
  {
1677
1889
  label: ui.launcherLabel,