@sensiblestats/widget-react 0.7.2 → 0.9.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
@@ -21,6 +21,10 @@ var STRINGS = {
21
21
  oddsLabel: "Odds",
22
22
  liveLabel: "Live",
23
23
  addToSlip: "Add to slip",
24
+ marketPrices: "Market prices",
25
+ moreLines: "More lines",
26
+ hideLines: "Hide lines",
27
+ updatedLabel: "updated",
24
28
  inSlip: "In slip",
25
29
  slipAdded: (n) => n === void 0 ? "Added to slip" : `Added to slip (${n} selection${n === 1 ? "" : "s"})`,
26
30
  slipDuplicate: "Already in your slip",
@@ -47,6 +51,10 @@ var STRINGS = {
47
51
  oddsLabel: "Oran",
48
52
  liveLabel: "Canl\u0131",
49
53
  addToSlip: "Kupona ekle",
54
+ marketPrices: "Piyasa fiyatlar\u0131",
55
+ moreLines: "Di\u011Fer \xE7izgiler",
56
+ hideLines: "\xC7izgileri gizle",
57
+ updatedLabel: "g\xFCncellendi",
50
58
  inSlip: "Kuponda",
51
59
  slipAdded: (n) => n === void 0 ? "Kupona eklendi" : `Kupona eklendi (${n} se\xE7im)`,
52
60
  slipDuplicate: "Bu se\xE7im kuponunuzda zaten var",
@@ -89,6 +97,10 @@ function normalizeUi(cfg) {
89
97
  hideStats: strings.hideStats,
90
98
  oddsLabel: strings.oddsLabel,
91
99
  liveLabel: strings.liveLabel,
100
+ marketPrices: strings.marketPrices,
101
+ moreLines: strings.moreLines,
102
+ hideLines: strings.hideLines,
103
+ updatedLabel: strings.updatedLabel,
92
104
  addToSlip: strings.addToSlip,
93
105
  inSlip: strings.inSlip,
94
106
  slipAdded: strings.slipAdded,
@@ -144,19 +156,27 @@ function readStatCells(raw) {
144
156
  }
145
157
  return cells;
146
158
  }
147
- function readStats(card) {
148
- return readStatCells(card.stats);
149
- }
150
159
  function toCardVM(card) {
151
160
  const entityType = str(card.entityType)?.toLowerCase();
152
161
  const kind = entityType === "player" ? "player" : entityType === "team" ? "team" : "generic";
162
+ const scopes = Array.isArray(card.scopes) ? card.scopes.map((s) => {
163
+ const label = str(s.label);
164
+ if (!label) return null;
165
+ return {
166
+ competitionId: typeof s.competitionId === "number" ? s.competitionId : null,
167
+ label,
168
+ image: str(s.imagePath),
169
+ stats: readStatCells(s.stats)
170
+ };
171
+ }).filter((s) => s !== null) : [];
153
172
  return {
154
173
  id: String(card.entityId),
155
174
  kind,
156
175
  name: card.canonicalName,
157
176
  image: str(card.imagePath) ?? str(card.imageUrl) ?? str(card.image_url) ?? str(card.logoUrl) ?? str(card.logo),
158
177
  sub: str(card.subtitle) ?? str(card.teamName) ?? str(card.leagueName) ?? str(card.position),
159
- stats: readStats(card)
178
+ season: str(card.seasonName),
179
+ scopes
160
180
  };
161
181
  }
162
182
  function toActionVM(action) {
@@ -218,6 +238,47 @@ function toSlipSelection(vm, conversationId) {
218
238
  if (conversationId !== void 0) selection.conversationId = conversationId;
219
239
  return selection;
220
240
  }
241
+ function toMarketOddsVM(card) {
242
+ const rows = Array.isArray(card.rows) ? card.rows : [];
243
+ const toRow = (r, i) => {
244
+ const selection = [str(r.selectionLabel), str(r.line)].filter(Boolean).join(" ");
245
+ const price = typeof r.price === "number" && Number.isFinite(r.price) ? r.price : void 0;
246
+ return {
247
+ key: `${selection}:${str(r.line) ?? ""}:${i}`,
248
+ selection,
249
+ odds: price !== void 0 ? price.toFixed(2) : "",
250
+ oddsDecimal: price,
251
+ matchOddsId: typeof r.matchOddsId === "number" ? r.matchOddsId : void 0,
252
+ quotedAtUtc: str(r.quotedAtUtc),
253
+ isMain: r.isMainLine !== false
254
+ };
255
+ };
256
+ const vms = rows.map(toRow).filter((r) => r.selection && r.odds);
257
+ const home = str(card.homeTeamName);
258
+ const away = str(card.awayTeamName);
259
+ const updated = rows.map((r) => str(r.quotedAtUtc)).filter((v) => !!v).sort();
260
+ return {
261
+ id: `${typeof card.matchId === "number" ? card.matchId : 0}:${str(card.marketDeveloperName) ?? ""}`,
262
+ market: str(card.marketLabel) ?? str(card.marketDeveloperName) ?? "",
263
+ fixture: home && away ? `${home} vs ${away}` : void 0,
264
+ competition: str(card.competitionName),
265
+ bookmaker: rows.map((r) => str(r.bookmakerName)).find(Boolean),
266
+ updatedAt: updated.length > 0 ? updated[updated.length - 1] : void 0,
267
+ isLive: rows.some((r) => r.isLive === true),
268
+ matchId: typeof card.matchId === "number" ? card.matchId : 0,
269
+ mainRows: vms.filter((r) => r.isMain),
270
+ moreRows: vms.filter((r) => !r.isMain),
271
+ disclaimer: str(card.disclaimer),
272
+ addToSlipEnabled: card.addToSlipEnabled === true
273
+ };
274
+ }
275
+ function marketOddsRowToSlipSelection(vm, row, conversationId) {
276
+ if (row.matchOddsId === void 0 || row.oddsDecimal === void 0) return null;
277
+ const selection = { matchOddsId: row.matchOddsId, matchId: vm.matchId, oddsDecimal: row.oddsDecimal };
278
+ if (row.quotedAtUtc !== void 0) selection.quotedAtUtc = row.quotedAtUtc;
279
+ if (conversationId !== void 0) selection.conversationId = conversationId;
280
+ return selection;
281
+ }
221
282
 
222
283
  // src/reducer.ts
223
284
  var POPUP_MAX = 160;
@@ -249,6 +310,8 @@ function applyEvent(state, event) {
249
310
  return { ...state, actionsLoading: false, messages: mapActive(state, (t) => ({ ...t, actions: [...t.actions, toActionVM(event.data.action)] })) };
250
311
  case "betting_insight":
251
312
  return { ...state, messages: mapActive(state, (t) => ({ ...t, insights: [...t.insights, toInsightVM(event.data.insight)] })) };
313
+ case "market_odds":
314
+ return { ...state, messages: mapActive(state, (t) => ({ ...t, marketOdds: [...t.marketOdds, toMarketOddsVM(event.data.card)] })) };
252
315
  case "intent_chip": {
253
316
  const chip = toChipVM(event.data.intent);
254
317
  return chip ? { ...state, starters: [...state.starters, chip] } : state;
@@ -284,11 +347,11 @@ function reducer(state, action) {
284
347
  return { ...state, isOpen: false, isStreaming: false, status: null, actionsLoading: false };
285
348
  case "USER_SENT": {
286
349
  const user = { role: "user", id: action.userId, text: action.displayText ?? action.input.message };
287
- const assistant = { role: "assistant", id: action.assistantId, text: "", cards: [], actions: [], insights: [], done: false };
350
+ const assistant = { role: "assistant", id: action.assistantId, text: "", cards: [], actions: [], insights: [], marketOdds: [], done: false };
288
351
  return { ...state, messages: [...state.messages, user, assistant], isStreaming: true, status: null, actionsLoading: false, starters: [], error: null, lastUserInput: action.input, lastDisplayText: action.displayText ?? null };
289
352
  }
290
353
  case "GREETING_SENT": {
291
- const assistant = { role: "assistant", id: action.assistantId, text: "", cards: [], actions: [], insights: [], done: false, isGreeting: true };
354
+ const assistant = { role: "assistant", id: action.assistantId, text: "", cards: [], actions: [], insights: [], marketOdds: [], done: false, isGreeting: true };
292
355
  return { ...state, messages: [...state.messages, assistant], isStreaming: true, status: null, error: null };
293
356
  }
294
357
  case "DISMISS_POPUP":
@@ -410,7 +473,7 @@ function ChatFab({ label, greeting, popupText, onOpen, onDismissPopup, dismissLa
410
473
  }
411
474
 
412
475
  // src/components/ChatPanel.tsx
413
- import { useEffect as useEffect4, useState as useState5 } from "react";
476
+ import { useEffect as useEffect4, useState as useState6 } from "react";
414
477
 
415
478
  // src/components/ChatHeader.tsx
416
479
  import { jsx as jsx4, jsxs as jsxs3 } from "react/jsx-runtime";
@@ -442,18 +505,58 @@ function Markdown({ text }) {
442
505
 
443
506
  // src/components/EntityCardList.tsx
444
507
  import { useState } from "react";
445
- import { jsx as jsx6, jsxs as jsxs4 } from "react/jsx-runtime";
508
+ import { Fragment, jsx as jsx6, jsxs as jsxs4 } from "react/jsx-runtime";
446
509
  var MAX_VISIBLE = 3;
447
510
  function initials(name) {
448
511
  return name.split(/\s+/).slice(0, 2).map((w) => w[0] ?? "").join("").toUpperCase();
449
512
  }
450
- function EntityCardList({ cards, disabled, showLessLabel = "Show less", showMoreLabel = (n) => `Show ${n} more` }) {
451
- const [expanded, setExpanded] = useState(false);
452
- if (cards.length === 0) return null;
453
- const visible = expanded ? cards : cards.slice(0, MAX_VISIBLE);
454
- const overflow = cards.length - MAX_VISIBLE;
455
- return /* @__PURE__ */ jsxs4("div", { className: "ss-w-cards", children: [
456
- visible.map((c) => /* @__PURE__ */ jsxs4("div", { className: "ss-w-ecard", "data-kind": c.kind, children: [
513
+ function Crest({ scope }) {
514
+ return /* @__PURE__ */ jsx6("span", { className: "ss-w-ecard-crest", style: { backgroundImage: `url(${scope.image})` }, "aria-hidden": "true" });
515
+ }
516
+ function StatRow({ stats }) {
517
+ return /* @__PURE__ */ jsx6("div", { className: "ss-w-ecard-stats", children: stats.map((s) => /* @__PURE__ */ jsxs4("span", { className: "ss-w-stat", children: [
518
+ /* @__PURE__ */ jsx6("b", { children: s.value }),
519
+ /* @__PURE__ */ jsx6("span", { children: s.label })
520
+ ] }, s.label)) });
521
+ }
522
+ function ScopeChips({ scopes }) {
523
+ const [active, setActive] = useState(0);
524
+ const current = scopes[active] ?? scopes[0];
525
+ return /* @__PURE__ */ jsxs4(Fragment, { children: [
526
+ /* @__PURE__ */ jsx6("div", { className: "ss-w-ecard-scopes", role: "group", children: scopes.map((s, i) => /* @__PURE__ */ jsx6(
527
+ "button",
528
+ {
529
+ type: "button",
530
+ className: "ss-w-ecard-chip",
531
+ "aria-pressed": i === active,
532
+ "aria-label": s.label,
533
+ title: s.label,
534
+ onClick: () => setActive(i),
535
+ children: s.image ? /* @__PURE__ */ jsx6(Crest, { scope: s }) : s.label
536
+ },
537
+ String(s.competitionId ?? "all")
538
+ )) }),
539
+ /* @__PURE__ */ jsx6(StatRow, { stats: current.stats }, String(current.competitionId ?? "all"))
540
+ ] });
541
+ }
542
+ function CardScopes({ scopes }) {
543
+ if (scopes.length === 0) return null;
544
+ if (scopes.length === 1) {
545
+ const only = scopes[0];
546
+ return /* @__PURE__ */ jsxs4(Fragment, { children: [
547
+ /* @__PURE__ */ jsxs4("span", { className: "ss-w-ecard-tag", children: [
548
+ only.image ? /* @__PURE__ */ jsx6(Crest, { scope: only }) : null,
549
+ only.label
550
+ ] }),
551
+ /* @__PURE__ */ jsx6(StatRow, { stats: only.stats })
552
+ ] });
553
+ }
554
+ return /* @__PURE__ */ jsx6(ScopeChips, { scopes });
555
+ }
556
+ function EntityCardItem({ c }) {
557
+ const season = c.scopes.length > 0 ? c.season : void 0;
558
+ return /* @__PURE__ */ jsxs4("div", { className: "ss-w-ecard", "data-kind": c.kind, children: [
559
+ /* @__PURE__ */ jsxs4("div", { className: "ss-w-ecard-head", children: [
457
560
  /* @__PURE__ */ jsx6(
458
561
  "span",
459
562
  {
@@ -465,13 +568,23 @@ function EntityCardList({ cards, disabled, showLessLabel = "Show less", showMore
465
568
  /* @__PURE__ */ jsxs4("span", { className: "ss-w-meta", children: [
466
569
  c.kind !== "generic" ? /* @__PURE__ */ jsx6("span", { className: "ss-w-kind", children: c.kind }) : null,
467
570
  /* @__PURE__ */ jsx6("span", { className: "ss-w-nm", children: c.name }),
468
- c.sub ? /* @__PURE__ */ jsx6("span", { className: "ss-w-sub", children: c.sub }) : null
469
- ] }),
470
- c.stats.map((s) => /* @__PURE__ */ jsxs4("span", { className: "ss-w-stat", children: [
471
- /* @__PURE__ */ jsx6("b", { children: s.value }),
472
- /* @__PURE__ */ jsx6("span", { children: s.label })
473
- ] }, s.label))
474
- ] }, c.id)),
571
+ c.sub || season ? /* @__PURE__ */ jsxs4("span", { className: "ss-w-sub", children: [
572
+ c.sub,
573
+ c.sub && season ? " \xB7 " : "",
574
+ season ? /* @__PURE__ */ jsx6("span", { className: "ss-w-season", children: season }) : null
575
+ ] }) : null
576
+ ] })
577
+ ] }),
578
+ /* @__PURE__ */ jsx6(CardScopes, { scopes: c.scopes })
579
+ ] });
580
+ }
581
+ function EntityCardList({ cards, disabled, showLessLabel = "Show less", showMoreLabel = (n) => `Show ${n} more` }) {
582
+ const [expanded, setExpanded] = useState(false);
583
+ if (cards.length === 0) return null;
584
+ const visible = expanded ? cards : cards.slice(0, MAX_VISIBLE);
585
+ const overflow = cards.length - MAX_VISIBLE;
586
+ return /* @__PURE__ */ jsxs4("div", { className: "ss-w-cards", children: [
587
+ visible.map((c) => /* @__PURE__ */ jsx6(EntityCardItem, { c }, c.id)),
475
588
  overflow > 0 ? /* @__PURE__ */ jsx6("button", { type: "button", className: "ss-w-cards-more", disabled, onClick: () => setExpanded((v) => !v), children: expanded ? showLessLabel : showMoreLabel(overflow) }) : null
476
589
  ] });
477
590
  }
@@ -533,13 +646,12 @@ function withTimeout(value) {
533
646
  new Promise((_, reject) => setTimeout(() => reject(new Error("add-to-slip timed out")), ADD_TO_SLIP_TIMEOUT_MS))
534
647
  ]);
535
648
  }
536
- function AddToSlipButton({ it, slip, ui }) {
649
+ function SlipButton({ selection, shownOdds, marketName, slip, ui }) {
537
650
  const [pending, setPending] = useState2(false);
538
651
  const [settled, setSettled] = useState2(false);
539
652
  const [toast, setToast] = useState2(null);
540
653
  const dismiss = useCallback2(() => setToast(null), []);
541
654
  const handler = slip.onAddToSlip;
542
- const selection = it.addToSlipEnabled ? toSlipSelection(it, slip.conversationId) : null;
543
655
  if (!ui || !handler || !selection) return null;
544
656
  const onClick = async () => {
545
657
  setPending(true);
@@ -547,11 +659,11 @@ function AddToSlipButton({ it, slip, ui }) {
547
659
  const raw = await withTimeout(Promise.resolve(handler(selection)));
548
660
  if (raw != null && !isAddToSlipResult(raw)) throw new Error("malformed add-to-slip result");
549
661
  const result = raw ?? void 0;
550
- setToast(resultToToast(result, it.odds ?? selection.oddsDecimal.toFixed(2), ui));
662
+ setToast(resultToToast(result, shownOdds || selection.oddsDecimal.toFixed(2), ui));
551
663
  setSettled(isTerminal(result));
552
664
  if (wasAdded(result) && slip.onMarketClicked) {
553
665
  try {
554
- slip.onMarketClicked(it.market);
666
+ slip.onMarketClicked(marketName);
555
667
  } catch {
556
668
  }
557
669
  }
@@ -575,6 +687,10 @@ function AddToSlipButton({ it, slip, ui }) {
575
687
  /* @__PURE__ */ jsx8(Toast, { toast, onDismiss: dismiss })
576
688
  ] });
577
689
  }
690
+ function AddToSlipButton({ it, slip, ui }) {
691
+ const selection = it.addToSlipEnabled ? toSlipSelection(it, slip.conversationId) : null;
692
+ return /* @__PURE__ */ jsx8(SlipButton, { selection, shownOdds: it.odds ?? "", marketName: it.market, slip, ui });
693
+ }
578
694
 
579
695
  // src/components/BettingInsightList.tsx
580
696
  import { jsx as jsx9, jsxs as jsxs6 } from "react/jsx-runtime";
@@ -647,38 +763,88 @@ function BettingInsightList({ insights, ui, slip }) {
647
763
  return /* @__PURE__ */ jsx9("div", { className: "ss-w-binsights", children: insights.map((it) => /* @__PURE__ */ jsx9(InsightCard, { it, ui, slip }, it.id)) });
648
764
  }
649
765
 
766
+ // src/components/MarketOddsCardList.tsx
767
+ import { useState as useState4 } from "react";
768
+ import { jsx as jsx10, jsxs as jsxs7 } from "react/jsx-runtime";
769
+ function OddsRow({ vm, row, ui, slip }) {
770
+ const selection = vm.addToSlipEnabled ? marketOddsRowToSlipSelection(vm, row, slip.conversationId) : null;
771
+ return /* @__PURE__ */ jsxs7("div", { className: "ss-w-mo-row", children: [
772
+ /* @__PURE__ */ jsx10("span", { className: "ss-w-mo-sel", children: row.selection }),
773
+ /* @__PURE__ */ jsx10("span", { className: "ss-w-mo-price", children: row.odds }),
774
+ /* @__PURE__ */ jsx10(SlipButton, { selection, shownOdds: row.odds, marketName: vm.market, slip, ui })
775
+ ] });
776
+ }
777
+ function formatUpdated(iso) {
778
+ if (!iso) return void 0;
779
+ const date = new Date(iso);
780
+ if (Number.isNaN(date.getTime())) return void 0;
781
+ return date.toLocaleTimeString(void 0, { hour: "2-digit", minute: "2-digit" });
782
+ }
783
+ function MarketOddsCard({ vm, ui, slip }) {
784
+ const [open, setOpen] = useState4(false);
785
+ const updated = formatUpdated(vm.updatedAt);
786
+ return /* @__PURE__ */ jsxs7("div", { className: "ss-w-mocard", children: [
787
+ /* @__PURE__ */ jsxs7("div", { className: "ss-w-mo-head", children: [
788
+ /* @__PURE__ */ jsx10("span", { className: "ss-w-mo-market", children: vm.market }),
789
+ /* @__PURE__ */ jsx10("span", { className: "ss-w-mo-tag", children: ui?.marketPrices ?? "Market prices" })
790
+ ] }),
791
+ vm.fixture || vm.competition ? /* @__PURE__ */ jsxs7("div", { className: "ss-w-mo-fixture", children: [
792
+ vm.fixture ? /* @__PURE__ */ jsx10("span", { children: vm.fixture }) : null,
793
+ vm.competition ? /* @__PURE__ */ jsx10("span", { className: "ss-w-mo-comp", children: vm.competition }) : null
794
+ ] }) : null,
795
+ /* @__PURE__ */ jsxs7("div", { className: "ss-w-mo-rows", children: [
796
+ vm.mainRows.map((row) => /* @__PURE__ */ jsx10(OddsRow, { vm, row, ui, slip }, row.key)),
797
+ open ? vm.moreRows.map((row) => /* @__PURE__ */ jsx10(OddsRow, { vm, row, ui, slip }, row.key)) : null
798
+ ] }),
799
+ vm.moreRows.length > 0 ? /* @__PURE__ */ jsxs7("button", { type: "button", className: "ss-w-bi-toggle", "aria-expanded": open, onClick: () => setOpen((v) => !v), children: [
800
+ /* @__PURE__ */ jsx10("span", { children: open ? ui?.hideLines ?? "Hide lines" : `${ui?.moreLines ?? "More lines"} (${vm.moreRows.length})` }),
801
+ /* @__PURE__ */ jsx10("span", { className: `ss-w-bi-chev${open ? " ss-w-bi-chev-up" : ""}`, "aria-hidden": "true" })
802
+ ] }) : null,
803
+ vm.bookmaker || updated ? /* @__PURE__ */ jsxs7("div", { className: "ss-w-mo-foot", children: [
804
+ vm.bookmaker ? /* @__PURE__ */ jsx10("span", { children: vm.bookmaker }) : null,
805
+ updated ? /* @__PURE__ */ jsx10("span", { children: `${ui?.updatedLabel ?? "updated"} ${updated}` }) : null
806
+ ] }) : null,
807
+ vm.disclaimer ? /* @__PURE__ */ jsx10("p", { className: "ss-w-bi-disc", children: vm.disclaimer }) : null
808
+ ] });
809
+ }
810
+ function MarketOddsCardList({ cards, ui, slip }) {
811
+ if (!cards || cards.length === 0) return null;
812
+ return /* @__PURE__ */ jsx10("div", { className: "ss-w-mocards", children: cards.map((vm) => /* @__PURE__ */ jsx10(MarketOddsCard, { vm, ui, slip }, vm.id)) });
813
+ }
814
+
650
815
  // src/components/ActionButtons.tsx
651
- import { Fragment, jsx as jsx10, jsxs as jsxs7 } from "react/jsx-runtime";
816
+ import { Fragment as Fragment2, jsx as jsx11, jsxs as jsxs8 } from "react/jsx-runtime";
652
817
  function ActionButtons({ actions, loading, disabled, onAct }) {
653
818
  if (actions.length === 0 && !loading) return null;
654
- return /* @__PURE__ */ jsxs7("div", { className: "ss-w-actions", children: [
655
- actions.map((a, i) => /* @__PURE__ */ jsx10("button", { type: "button", className: "ss-w-abtn", disabled, onClick: () => onAct(a), children: a.label }, `${a.label}-${i}`)),
656
- actions.length === 0 && loading ? /* @__PURE__ */ jsxs7(Fragment, { children: [
657
- /* @__PURE__ */ jsx10("span", { className: "ss-w-abtn-shimmer" }),
658
- /* @__PURE__ */ jsx10("span", { className: "ss-w-abtn-shimmer" })
819
+ return /* @__PURE__ */ jsxs8("div", { className: "ss-w-actions", children: [
820
+ actions.map((a, i) => /* @__PURE__ */ jsx11("button", { type: "button", className: "ss-w-abtn", disabled, onClick: () => onAct(a), children: a.label }, `${a.label}-${i}`)),
821
+ actions.length === 0 && loading ? /* @__PURE__ */ jsxs8(Fragment2, { children: [
822
+ /* @__PURE__ */ jsx11("span", { className: "ss-w-abtn-shimmer" }),
823
+ /* @__PURE__ */ jsx11("span", { className: "ss-w-abtn-shimmer" })
659
824
  ] }) : null
660
825
  ] });
661
826
  }
662
827
 
663
828
  // src/components/ChatMessage.tsx
664
- import { jsx as jsx11, jsxs as jsxs8 } from "react/jsx-runtime";
829
+ import { jsx as jsx12, jsxs as jsxs9 } from "react/jsx-runtime";
665
830
  function ChatMessage({ message, disabled, onAct, ui, slip }) {
666
831
  if (message.role === "user") {
667
- return /* @__PURE__ */ jsx11("div", { className: "ss-w-msg ss-w-user", children: /* @__PURE__ */ jsx11("div", { className: "ss-w-bubble", children: message.text }) });
832
+ return /* @__PURE__ */ jsx12("div", { className: "ss-w-msg ss-w-user", children: /* @__PURE__ */ jsx12("div", { className: "ss-w-bubble", children: message.text }) });
668
833
  }
669
- return /* @__PURE__ */ jsxs8("div", { className: "ss-w-msg ss-w-bot", children: [
670
- message.text ? /* @__PURE__ */ jsx11(Markdown, { text: message.text }) : null,
671
- /* @__PURE__ */ jsx11(EntityCardList, { cards: message.cards, disabled, showLessLabel: ui?.showLess, showMoreLabel: ui?.showMore }),
672
- /* @__PURE__ */ jsx11(BettingInsightList, { insights: message.insights, ui, slip }),
673
- /* @__PURE__ */ jsx11(ActionButtons, { actions: message.actions, loading: false, disabled, onAct })
834
+ return /* @__PURE__ */ jsxs9("div", { className: "ss-w-msg ss-w-bot", children: [
835
+ message.text ? /* @__PURE__ */ jsx12(Markdown, { text: message.text }) : null,
836
+ /* @__PURE__ */ jsx12(EntityCardList, { cards: message.cards, disabled, showLessLabel: ui?.showLess, showMoreLabel: ui?.showMore }),
837
+ /* @__PURE__ */ jsx12(BettingInsightList, { insights: message.insights, ui, slip }),
838
+ /* @__PURE__ */ jsx12(MarketOddsCardList, { cards: message.marketOdds, ui, slip }),
839
+ /* @__PURE__ */ jsx12(ActionButtons, { actions: message.actions, loading: false, disabled, onAct })
674
840
  ] });
675
841
  }
676
842
 
677
843
  // src/components/IntentChips.tsx
678
- import { jsx as jsx12 } from "react/jsx-runtime";
844
+ import { jsx as jsx13 } from "react/jsx-runtime";
679
845
  function IntentChips({ chips, onPick }) {
680
846
  if (chips.length === 0) return null;
681
- return /* @__PURE__ */ jsx12("div", { className: "ss-w-chips", children: chips.map((c, i) => /* @__PURE__ */ jsx12("button", { type: "button", className: "ss-w-chip", onClick: () => onPick(c.text), children: c.text }, `${c.text}-${i}`)) });
847
+ return /* @__PURE__ */ jsx13("div", { className: "ss-w-chips", children: chips.map((c, i) => /* @__PURE__ */ jsx13("button", { type: "button", className: "ss-w-chip", onClick: () => onPick(c.text), children: c.text }, `${c.text}-${i}`)) });
682
848
  }
683
849
 
684
850
  // src/scroll.ts
@@ -688,7 +854,7 @@ function isPinnedToBottom(el, threshold = PIN_THRESHOLD) {
688
854
  }
689
855
 
690
856
  // src/components/MessageList.tsx
691
- import { jsx as jsx13, jsxs as jsxs9 } from "react/jsx-runtime";
857
+ import { jsx as jsx14, jsxs as jsxs10 } from "react/jsx-runtime";
692
858
  function MessageList({ controller, ui, slip }) {
693
859
  const { state, send } = controller;
694
860
  const listRef = useRef2(null);
@@ -698,7 +864,7 @@ function MessageList({ controller, ui, slip }) {
698
864
  if (pinnedRef.current) endRef.current?.scrollIntoView({ block: "end" });
699
865
  }, [state.messages, state.status]);
700
866
  const empty = state.messages.length === 0;
701
- return /* @__PURE__ */ jsxs9(
867
+ return /* @__PURE__ */ jsxs10(
702
868
  "div",
703
869
  {
704
870
  className: "ss-w-list",
@@ -708,7 +874,7 @@ function MessageList({ controller, ui, slip }) {
708
874
  if (listRef.current) pinnedRef.current = isPinnedToBottom(listRef.current);
709
875
  },
710
876
  children: [
711
- state.messages.map((m) => /* @__PURE__ */ jsx13(
877
+ state.messages.map((m) => /* @__PURE__ */ jsx14(
712
878
  ChatMessage,
713
879
  {
714
880
  message: m,
@@ -719,25 +885,25 @@ function MessageList({ controller, ui, slip }) {
719
885
  },
720
886
  m.id
721
887
  )),
722
- state.isStreaming && state.actionsLoading ? /* @__PURE__ */ jsx13(ActionButtons, { actions: [], loading: true, onAct: () => {
888
+ state.isStreaming && state.actionsLoading ? /* @__PURE__ */ jsx14(ActionButtons, { actions: [], loading: true, onAct: () => {
723
889
  } }) : null,
724
- state.status ? /* @__PURE__ */ jsx13("div", { className: "ss-w-status", children: state.status }) : null,
725
- state.error ? /* @__PURE__ */ jsxs9("div", { className: "ss-w-error", role: "alert", children: [
726
- /* @__PURE__ */ jsx13("span", { children: "message" in state.error ? state.error.message : ui.somethingWentWrong }),
727
- /* @__PURE__ */ jsx13("button", { type: "button", className: "ss-w-retry", onClick: () => controller.retry(), children: ui.retry })
890
+ state.status ? /* @__PURE__ */ jsx14("div", { className: "ss-w-status", children: state.status }) : null,
891
+ state.error ? /* @__PURE__ */ jsxs10("div", { className: "ss-w-error", role: "alert", children: [
892
+ /* @__PURE__ */ jsx14("span", { children: "message" in state.error ? state.error.message : ui.somethingWentWrong }),
893
+ /* @__PURE__ */ jsx14("button", { type: "button", className: "ss-w-retry", onClick: () => controller.retry(), children: ui.retry })
728
894
  ] }) : null,
729
- empty ? /* @__PURE__ */ jsx13(IntentChips, { chips: state.starters, onPick: (t) => send(t) }) : null,
730
- /* @__PURE__ */ jsx13("div", { ref: endRef })
895
+ empty ? /* @__PURE__ */ jsx14(IntentChips, { chips: state.starters, onPick: (t) => send(t) }) : null,
896
+ /* @__PURE__ */ jsx14("div", { ref: endRef })
731
897
  ]
732
898
  }
733
899
  );
734
900
  }
735
901
 
736
902
  // src/components/ChatInput.tsx
737
- import { useState as useState4 } from "react";
738
- import { jsx as jsx14, jsxs as jsxs10 } from "react/jsx-runtime";
903
+ import { useState as useState5 } from "react";
904
+ import { jsx as jsx15, jsxs as jsxs11 } from "react/jsx-runtime";
739
905
  function ChatInput({ placeholder, streaming, onSend, onStop, stopLabel = "Stop response", sendLabel = "Send message" }) {
740
- const [value, setValue] = useState4("");
906
+ const [value, setValue] = useState5("");
741
907
  const submit = () => {
742
908
  const t = value.trim();
743
909
  if (!t) return;
@@ -750,17 +916,17 @@ function ChatInput({ placeholder, streaming, onSend, onStop, stopLabel = "Stop r
750
916
  submit();
751
917
  }
752
918
  };
753
- return /* @__PURE__ */ jsxs10("div", { className: "ss-w-input", children: [
754
- /* @__PURE__ */ jsx14("textarea", { className: "ss-w-textarea", rows: 1, placeholder, value, disabled: streaming, onChange: (e) => setValue(e.target.value), onKeyDown }),
755
- streaming ? /* @__PURE__ */ jsx14("button", { type: "button", className: "ss-w-send ss-w-stop", "aria-label": stopLabel, onClick: onStop, children: /* @__PURE__ */ jsx14("span", { className: "ss-w-stop-glyph" }) }) : /* @__PURE__ */ jsx14("button", { type: "button", className: "ss-w-send", "aria-label": sendLabel, onClick: submit, children: /* @__PURE__ */ jsx14(SendIcon, {}) })
919
+ return /* @__PURE__ */ jsxs11("div", { className: "ss-w-input", children: [
920
+ /* @__PURE__ */ jsx15("textarea", { className: "ss-w-textarea", rows: 1, placeholder, value, disabled: streaming, onChange: (e) => setValue(e.target.value), onKeyDown }),
921
+ streaming ? /* @__PURE__ */ jsx15("button", { type: "button", className: "ss-w-send ss-w-stop", "aria-label": stopLabel, onClick: onStop, children: /* @__PURE__ */ jsx15("span", { className: "ss-w-stop-glyph" }) }) : /* @__PURE__ */ jsx15("button", { type: "button", className: "ss-w-send", "aria-label": sendLabel, onClick: submit, children: /* @__PURE__ */ jsx15(SendIcon, {}) })
756
922
  ] });
757
923
  }
758
924
 
759
925
  // src/components/ChatPanel.tsx
760
- import { jsx as jsx15, jsxs as jsxs11 } from "react/jsx-runtime";
926
+ import { jsx as jsx16, jsxs as jsxs12 } from "react/jsx-runtime";
761
927
  var FULLSCREEN_QUERY = "(max-width: 639px)";
762
928
  function useIsModal() {
763
- const [modal, setModal] = useState5(false);
929
+ const [modal, setModal] = useState6(false);
764
930
  useEffect4(() => {
765
931
  if (typeof window === "undefined" || typeof window.matchMedia !== "function") return;
766
932
  const mql = window.matchMedia(FULLSCREEN_QUERY);
@@ -781,10 +947,10 @@ function ChatPanel({ controller, ui, slip }) {
781
947
  window.addEventListener("keydown", onKey);
782
948
  return () => window.removeEventListener("keydown", onKey);
783
949
  }, [close]);
784
- return /* @__PURE__ */ jsxs11("div", { className: "ss-w-panel", role: "dialog", "aria-modal": isModal, "aria-label": ui.chatDialogLabel, children: [
785
- /* @__PURE__ */ jsx15(ChatHeader, { onClose: close, closeLabel: ui.closeChat }),
786
- /* @__PURE__ */ jsx15(MessageList, { controller, ui, slip }),
787
- /* @__PURE__ */ jsx15(ChatInput, { placeholder: ui.placeholder, streaming: state.isStreaming, onSend: (t) => send(t), onStop: stop, stopLabel: ui.stopResponse, sendLabel: ui.sendMessage })
950
+ return /* @__PURE__ */ jsxs12("div", { className: "ss-w-panel", role: "dialog", "aria-modal": isModal, "aria-label": ui.chatDialogLabel, children: [
951
+ /* @__PURE__ */ jsx16(ChatHeader, { onClose: close, closeLabel: ui.closeChat }),
952
+ /* @__PURE__ */ jsx16(MessageList, { controller, ui, slip }),
953
+ /* @__PURE__ */ jsx16(ChatInput, { placeholder: ui.placeholder, streaming: state.isStreaming, onSend: (t) => send(t), onStop: stop, stopLabel: ui.stopResponse, sendLabel: ui.sendMessage })
788
954
  ] });
789
955
  }
790
956
 
@@ -872,7 +1038,8 @@ var WIDGET_CSS = `.ss-w-root { all: revert; }
872
1038
 
873
1039
  /* entity cards */
874
1040
  .ss-w-cards { display: flex; flex-direction: column; gap: 7px; }
875
- .ss-w-ecard { display: flex; align-items: center; gap: 11px; width: 100%; background: var(--ss-w-panel); border: 1px solid var(--ss-w-line); border-radius: 12px; padding: 8px 11px; text-align: left; }
1041
+ .ss-w-ecard { display: flex; flex-direction: column; align-items: stretch; gap: 8px; width: 100%; background: var(--ss-w-panel); border: 1px solid var(--ss-w-line); border-radius: 12px; padding: 8px 11px; text-align: left; }
1042
+ .ss-w-ecard-head { display: flex; align-items: center; gap: 11px; }
876
1043
  .ss-w-thumb { width: 40px; height: 40px; flex: none; border-radius: 50%; display: grid; place-items: center; font-size: 13px; font-weight: 700; color: #fff; background-color: var(--ss-w-nav); background-size: cover; background-position: center; }
877
1044
  .ss-w-thumb.ss-w-team { border-radius: 10px; }
878
1045
  .ss-w-ecard[data-kind="player"] .ss-w-thumb { background-color: var(--ss-w-accent); }
@@ -880,7 +1047,14 @@ var WIDGET_CSS = `.ss-w-root { all: revert; }
880
1047
  .ss-w-kind { font-size: 8.5px; font-weight: 700; letter-spacing: .05em; text-transform: uppercase; color: var(--ss-w-faint); line-height: 1.3; }
881
1048
  .ss-w-nm { font-weight: 660; }
882
1049
  .ss-w-sub { font-size: 11px; color: var(--ss-w-faint); }
883
- .ss-w-stat { margin-left: auto; text-align: center; }
1050
+ .ss-w-season { color: var(--ss-w-accent); font-weight: 660; }
1051
+ .ss-w-ecard-scopes { display: inline-flex; align-items: center; gap: 2px; flex-wrap: wrap; align-self: flex-start; background: var(--ss-w-bubble); border: 1px solid var(--ss-w-line); border-radius: 9px; padding: 2px; }
1052
+ .ss-w-ecard-chip { display: inline-flex; align-items: center; font-size: 11px; font-weight: 600; border: none; border-radius: 7px; padding: 4px 8px; color: var(--ss-w-faint); background: none; cursor: pointer; }
1053
+ .ss-w-ecard-chip[aria-pressed="true"] { color: var(--ss-w-ink); background: var(--ss-w-panel); box-shadow: 0 1px 2px rgba(0,0,0,.08); }
1054
+ .ss-w-ecard-crest { width: 16px; height: 16px; display: block; border-radius: 4px; background-size: cover; background-position: center; }
1055
+ .ss-w-ecard-tag { display: inline-flex; align-items: center; gap: 6px; align-self: flex-start; font-size: 11px; font-weight: 600; color: var(--ss-w-faint); }
1056
+ .ss-w-ecard-stats { display: flex; gap: 16px; }
1057
+ .ss-w-stat { text-align: center; }
884
1058
  .ss-w-stat b { display: block; font-variant-numeric: tabular-nums; }
885
1059
  .ss-w-stat span { font-size: 9px; text-transform: uppercase; color: var(--ss-w-faint); }
886
1060
  .ss-w-cards-more { align-self: flex-start; font-size: 11.5px; font-weight: 560; color: var(--ss-w-accent); background: none; border: none; cursor: pointer; padding: 3px 2px; }
@@ -920,6 +1094,22 @@ var WIDGET_CSS = `.ss-w-root { all: revert; }
920
1094
  .ss-w-bi-chip { font-size: 11px; font-weight: 600; border: none; border-radius: 7px; padding: 4px 10px; color: var(--ss-w-faint); background: none; cursor: pointer; }
921
1095
  .ss-w-bi-chip[aria-pressed="true"] { color: var(--ss-w-ink); background: var(--ss-w-panel); box-shadow: 0 1px 2px rgba(0,0,0,.08); }
922
1096
 
1097
+ /* market odds card */
1098
+ .ss-w-mocards { display: flex; flex-direction: column; gap: 7px; }
1099
+ .ss-w-mocard { background: var(--ss-w-panel); border: 1px solid var(--ss-w-line); border-radius: 12px; border-left: 3px solid var(--ss-w-accent); padding: 9px 11px; display: flex; flex-direction: column; gap: 6px; }
1100
+ .ss-w-mo-head { display: flex; align-items: center; gap: 8px; }
1101
+ .ss-w-mo-market { font-size: 13px; font-weight: 700; color: var(--ss-w-ink); }
1102
+ .ss-w-mo-tag { margin-left: auto; flex: none; font-size: 9px; font-weight: 700; letter-spacing: .09em; text-transform: uppercase; color: var(--ss-w-faint); border: 1px solid var(--ss-w-line); border-radius: 8px; padding: 2px 7px; }
1103
+ .ss-w-mo-fixture { display: flex; align-items: center; gap: 6px; font-size: 11.5px; color: var(--ss-w-faint); min-width: 0; }
1104
+ .ss-w-mo-comp { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
1105
+ .ss-w-mo-rows { display: flex; flex-direction: column; gap: 4px; }
1106
+ .ss-w-mo-row { display: flex; align-items: center; gap: 10px; padding: 4px 0; border-bottom: 1px dashed var(--ss-w-line); }
1107
+ .ss-w-mo-row:last-child { border-bottom: none; }
1108
+ .ss-w-mo-sel { font-size: 12.5px; font-weight: 600; color: var(--ss-w-ink); }
1109
+ .ss-w-mo-price { margin-left: auto; font-family: ui-monospace, monospace; font-variant-numeric: tabular-nums; font-size: 14px; font-weight: 700; color: var(--ss-w-accent); }
1110
+ .ss-w-mo-row .ss-w-slip { margin: 0; }
1111
+ .ss-w-mo-foot { display: flex; align-items: center; gap: 8px; font-size: 10.5px; color: var(--ss-w-faint); }
1112
+
923
1113
  /* action buttons */
924
1114
  .ss-w-actions { display: flex; flex-direction: column; align-items: flex-start; gap: 6px; }
925
1115
  .ss-w-abtn { font-size: 12px; font-weight: 560; color: var(--ss-w-accent); background: var(--ss-w-panel); border: 1px solid var(--ss-w-accent); border-radius: 16px; padding: 6px 12px; cursor: pointer; }
@@ -957,7 +1147,7 @@ var WIDGET_CSS = `.ss-w-root { all: revert; }
957
1147
  `;
958
1148
 
959
1149
  // src/StatsWidget.tsx
960
- import { jsx as jsx16 } from "react/jsx-runtime";
1150
+ import { jsx as jsx17 } from "react/jsx-runtime";
961
1151
  function buildClient(props) {
962
1152
  try {
963
1153
  if ("client" in props && props.client) {
@@ -1006,7 +1196,7 @@ function StatsWidget(props) {
1006
1196
  }, [built, ui.injectStyles]);
1007
1197
  if (!built) return null;
1008
1198
  const attrs = themeAttrs(ui);
1009
- return /* @__PURE__ */ jsx16("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__ */ jsx16(ChatPanel, { controller, ui, slip }) : /* @__PURE__ */ jsx16(
1199
+ return /* @__PURE__ */ jsx17("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__ */ jsx17(ChatPanel, { controller, ui, slip }) : /* @__PURE__ */ jsx17(
1010
1200
  ChatFab,
1011
1201
  {
1012
1202
  label: ui.launcherLabel,