@sensiblestats/widget-react 0.7.1 → 0.8.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
@@ -16,9 +16,15 @@ var STRINGS = {
16
16
  retry: "Retry",
17
17
  showLess: "Show less",
18
18
  showMore: (n) => `Show ${n} more`,
19
+ showStats: "Show stats",
20
+ hideStats: "Hide stats",
19
21
  oddsLabel: "Odds",
20
22
  liveLabel: "Live",
21
23
  addToSlip: "Add to slip",
24
+ marketPrices: "Market prices",
25
+ moreLines: "More lines",
26
+ hideLines: "Hide lines",
27
+ updatedLabel: "updated",
22
28
  inSlip: "In slip",
23
29
  slipAdded: (n) => n === void 0 ? "Added to slip" : `Added to slip (${n} selection${n === 1 ? "" : "s"})`,
24
30
  slipDuplicate: "Already in your slip",
@@ -40,9 +46,15 @@ var STRINGS = {
40
46
  retry: "Tekrar dene",
41
47
  showLess: "Daha az g\xF6ster",
42
48
  showMore: (n) => `${n} tane daha g\xF6ster`,
49
+ showStats: "\u0130statistikleri g\xF6ster",
50
+ hideStats: "\u0130statistikleri gizle",
43
51
  oddsLabel: "Oran",
44
52
  liveLabel: "Canl\u0131",
45
53
  addToSlip: "Kupona ekle",
54
+ marketPrices: "Piyasa fiyatlar\u0131",
55
+ moreLines: "Di\u011Fer \xE7izgiler",
56
+ hideLines: "\xC7izgileri gizle",
57
+ updatedLabel: "g\xFCncellendi",
46
58
  inSlip: "Kuponda",
47
59
  slipAdded: (n) => n === void 0 ? "Kupona eklendi" : `Kupona eklendi (${n} se\xE7im)`,
48
60
  slipDuplicate: "Bu se\xE7im kuponunuzda zaten var",
@@ -81,8 +93,14 @@ function normalizeUi(cfg) {
81
93
  retry: strings.retry,
82
94
  showLess: strings.showLess,
83
95
  showMore: strings.showMore,
96
+ showStats: strings.showStats,
97
+ hideStats: strings.hideStats,
84
98
  oddsLabel: strings.oddsLabel,
85
99
  liveLabel: strings.liveLabel,
100
+ marketPrices: strings.marketPrices,
101
+ moreLines: strings.moreLines,
102
+ hideLines: strings.hideLines,
103
+ updatedLabel: strings.updatedLabel,
86
104
  addToSlip: strings.addToSlip,
87
105
  inSlip: strings.inSlip,
88
106
  slipAdded: strings.slipAdded,
@@ -212,6 +230,47 @@ function toSlipSelection(vm, conversationId) {
212
230
  if (conversationId !== void 0) selection.conversationId = conversationId;
213
231
  return selection;
214
232
  }
233
+ function toMarketOddsVM(card) {
234
+ const rows = Array.isArray(card.rows) ? card.rows : [];
235
+ const toRow = (r, i) => {
236
+ const selection = [str(r.selectionLabel), str(r.line)].filter(Boolean).join(" ");
237
+ const price = typeof r.price === "number" && Number.isFinite(r.price) ? r.price : void 0;
238
+ return {
239
+ key: `${selection}:${str(r.line) ?? ""}:${i}`,
240
+ selection,
241
+ odds: price !== void 0 ? price.toFixed(2) : "",
242
+ oddsDecimal: price,
243
+ matchOddsId: typeof r.matchOddsId === "number" ? r.matchOddsId : void 0,
244
+ quotedAtUtc: str(r.quotedAtUtc),
245
+ isMain: r.isMainLine !== false
246
+ };
247
+ };
248
+ const vms = rows.map(toRow).filter((r) => r.selection && r.odds);
249
+ const home = str(card.homeTeamName);
250
+ const away = str(card.awayTeamName);
251
+ const updated = rows.map((r) => str(r.quotedAtUtc)).filter((v) => !!v).sort();
252
+ return {
253
+ id: `${typeof card.matchId === "number" ? card.matchId : 0}:${str(card.marketDeveloperName) ?? ""}`,
254
+ market: str(card.marketLabel) ?? str(card.marketDeveloperName) ?? "",
255
+ fixture: home && away ? `${home} vs ${away}` : void 0,
256
+ competition: str(card.competitionName),
257
+ bookmaker: rows.map((r) => str(r.bookmakerName)).find(Boolean),
258
+ updatedAt: updated.length > 0 ? updated[updated.length - 1] : void 0,
259
+ isLive: rows.some((r) => r.isLive === true),
260
+ matchId: typeof card.matchId === "number" ? card.matchId : 0,
261
+ mainRows: vms.filter((r) => r.isMain),
262
+ moreRows: vms.filter((r) => !r.isMain),
263
+ disclaimer: str(card.disclaimer),
264
+ addToSlipEnabled: card.addToSlipEnabled === true
265
+ };
266
+ }
267
+ function marketOddsRowToSlipSelection(vm, row, conversationId) {
268
+ if (row.matchOddsId === void 0 || row.oddsDecimal === void 0) return null;
269
+ const selection = { matchOddsId: row.matchOddsId, matchId: vm.matchId, oddsDecimal: row.oddsDecimal };
270
+ if (row.quotedAtUtc !== void 0) selection.quotedAtUtc = row.quotedAtUtc;
271
+ if (conversationId !== void 0) selection.conversationId = conversationId;
272
+ return selection;
273
+ }
215
274
 
216
275
  // src/reducer.ts
217
276
  var POPUP_MAX = 160;
@@ -243,6 +302,8 @@ function applyEvent(state, event) {
243
302
  return { ...state, actionsLoading: false, messages: mapActive(state, (t) => ({ ...t, actions: [...t.actions, toActionVM(event.data.action)] })) };
244
303
  case "betting_insight":
245
304
  return { ...state, messages: mapActive(state, (t) => ({ ...t, insights: [...t.insights, toInsightVM(event.data.insight)] })) };
305
+ case "market_odds":
306
+ return { ...state, messages: mapActive(state, (t) => ({ ...t, marketOdds: [...t.marketOdds, toMarketOddsVM(event.data.card)] })) };
246
307
  case "intent_chip": {
247
308
  const chip = toChipVM(event.data.intent);
248
309
  return chip ? { ...state, starters: [...state.starters, chip] } : state;
@@ -278,11 +339,11 @@ function reducer(state, action) {
278
339
  return { ...state, isOpen: false, isStreaming: false, status: null, actionsLoading: false };
279
340
  case "USER_SENT": {
280
341
  const user = { role: "user", id: action.userId, text: action.displayText ?? action.input.message };
281
- const assistant = { role: "assistant", id: action.assistantId, text: "", cards: [], actions: [], insights: [], done: false };
342
+ const assistant = { role: "assistant", id: action.assistantId, text: "", cards: [], actions: [], insights: [], marketOdds: [], done: false };
282
343
  return { ...state, messages: [...state.messages, user, assistant], isStreaming: true, status: null, actionsLoading: false, starters: [], error: null, lastUserInput: action.input, lastDisplayText: action.displayText ?? null };
283
344
  }
284
345
  case "GREETING_SENT": {
285
- const assistant = { role: "assistant", id: action.assistantId, text: "", cards: [], actions: [], insights: [], done: false, isGreeting: true };
346
+ const assistant = { role: "assistant", id: action.assistantId, text: "", cards: [], actions: [], insights: [], marketOdds: [], done: false, isGreeting: true };
286
347
  return { ...state, messages: [...state.messages, assistant], isStreaming: true, status: null, error: null };
287
348
  }
288
349
  case "DISMISS_POPUP":
@@ -404,7 +465,7 @@ function ChatFab({ label, greeting, popupText, onOpen, onDismissPopup, dismissLa
404
465
  }
405
466
 
406
467
  // src/components/ChatPanel.tsx
407
- import { useEffect as useEffect4, useState as useState5 } from "react";
468
+ import { useEffect as useEffect4, useState as useState6 } from "react";
408
469
 
409
470
  // src/components/ChatHeader.tsx
410
471
  import { jsx as jsx4, jsxs as jsxs3 } from "react/jsx-runtime";
@@ -527,13 +588,12 @@ function withTimeout(value) {
527
588
  new Promise((_, reject) => setTimeout(() => reject(new Error("add-to-slip timed out")), ADD_TO_SLIP_TIMEOUT_MS))
528
589
  ]);
529
590
  }
530
- function AddToSlipButton({ it, slip, ui }) {
591
+ function SlipButton({ selection, shownOdds, marketName, slip, ui }) {
531
592
  const [pending, setPending] = useState2(false);
532
593
  const [settled, setSettled] = useState2(false);
533
594
  const [toast, setToast] = useState2(null);
534
595
  const dismiss = useCallback2(() => setToast(null), []);
535
596
  const handler = slip.onAddToSlip;
536
- const selection = it.addToSlipEnabled ? toSlipSelection(it, slip.conversationId) : null;
537
597
  if (!ui || !handler || !selection) return null;
538
598
  const onClick = async () => {
539
599
  setPending(true);
@@ -541,11 +601,11 @@ function AddToSlipButton({ it, slip, ui }) {
541
601
  const raw = await withTimeout(Promise.resolve(handler(selection)));
542
602
  if (raw != null && !isAddToSlipResult(raw)) throw new Error("malformed add-to-slip result");
543
603
  const result = raw ?? void 0;
544
- setToast(resultToToast(result, it.odds ?? selection.oddsDecimal.toFixed(2), ui));
604
+ setToast(resultToToast(result, shownOdds || selection.oddsDecimal.toFixed(2), ui));
545
605
  setSettled(isTerminal(result));
546
606
  if (wasAdded(result) && slip.onMarketClicked) {
547
607
  try {
548
- slip.onMarketClicked(it.market);
608
+ slip.onMarketClicked(marketName);
549
609
  } catch {
550
610
  }
551
611
  }
@@ -569,29 +629,47 @@ function AddToSlipButton({ it, slip, ui }) {
569
629
  /* @__PURE__ */ jsx8(Toast, { toast, onDismiss: dismiss })
570
630
  ] });
571
631
  }
632
+ function AddToSlipButton({ it, slip, ui }) {
633
+ const selection = it.addToSlipEnabled ? toSlipSelection(it, slip.conversationId) : null;
634
+ return /* @__PURE__ */ jsx8(SlipButton, { selection, shownOdds: it.odds ?? "", marketName: it.market, slip, ui });
635
+ }
572
636
 
573
637
  // src/components/BettingInsightList.tsx
574
638
  import { jsx as jsx9, jsxs as jsxs6 } from "react/jsx-runtime";
575
- var MAX_ROWS = 3;
576
- function StatRows({ stats, ui }) {
577
- const [expanded, setExpanded] = useState3(false);
578
- const shown = expanded ? stats : stats.slice(0, MAX_ROWS);
579
- const overflow = stats.length - MAX_ROWS;
580
- return /* @__PURE__ */ jsxs6("div", { className: "ss-w-bi-rows", children: [
581
- shown.map((s) => /* @__PURE__ */ jsxs6("div", { className: "ss-w-bi-row", children: [
582
- /* @__PURE__ */ jsx9("span", { className: "ss-w-bi-row-lbl", children: s.label }),
583
- /* @__PURE__ */ jsx9("span", { className: "ss-w-bi-row-dots", "aria-hidden": "true" }),
584
- /* @__PURE__ */ jsx9("span", { className: "ss-w-bi-row-num", children: s.value })
585
- ] }, `${s.label}:${s.value}`)),
586
- overflow > 0 ? /* @__PURE__ */ jsx9("button", { type: "button", className: "ss-w-bi-more", onClick: () => setExpanded((v) => !v), children: expanded ? ui?.showLess ?? "Show less" : (ui?.showMore ?? ((n) => `Show ${n} more`))(overflow) }) : null
587
- ] });
639
+ function StatRows({ stats }) {
640
+ return /* @__PURE__ */ jsx9("div", { className: "ss-w-bi-rows", children: stats.map((s) => /* @__PURE__ */ jsxs6("div", { className: "ss-w-bi-row", children: [
641
+ /* @__PURE__ */ jsx9("span", { className: "ss-w-bi-row-lbl", children: s.label }),
642
+ /* @__PURE__ */ jsx9("span", { className: "ss-w-bi-row-num", children: s.value })
643
+ ] }, `${s.label}:${s.value}`)) });
588
644
  }
589
- function ScopedStats({ scopes, ui }) {
645
+ function ScopedStats({ scopes }) {
590
646
  const [active, setActive] = useState3(0);
591
647
  const current = scopes[active] ?? scopes[0] ?? { key: "", label: "", stats: [] };
592
648
  return /* @__PURE__ */ jsxs6("div", { className: "ss-w-bi-scoped", children: [
593
649
  /* @__PURE__ */ jsx9("div", { className: "ss-w-bi-scopes", role: "group", children: scopes.map((s, i) => /* @__PURE__ */ jsx9("button", { type: "button", className: "ss-w-bi-chip", "aria-pressed": i === active, onClick: () => setActive(i), children: s.label }, s.key)) }),
594
- /* @__PURE__ */ jsx9(StatRows, { stats: current.stats, ui }, current.key)
650
+ /* @__PURE__ */ jsx9(StatRows, { stats: current.stats }, current.key)
651
+ ] });
652
+ }
653
+ function StatsDisclosure({ it, ui }) {
654
+ const [open, setOpen] = useState3(false);
655
+ const hasScopes = !!it.scopes && it.scopes.length >= 2;
656
+ const hasStats = hasScopes || it.stats.length > 0;
657
+ if (!hasStats) return null;
658
+ return /* @__PURE__ */ jsxs6("div", { className: "ss-w-bi-stats", children: [
659
+ /* @__PURE__ */ jsxs6(
660
+ "button",
661
+ {
662
+ type: "button",
663
+ className: "ss-w-bi-toggle",
664
+ "aria-expanded": open,
665
+ onClick: () => setOpen((v) => !v),
666
+ children: [
667
+ /* @__PURE__ */ jsx9("span", { children: open ? ui?.hideStats ?? "Hide stats" : ui?.showStats ?? "Show stats" }),
668
+ /* @__PURE__ */ jsx9("span", { className: `ss-w-bi-chev${open ? " ss-w-bi-chev-up" : ""}`, "aria-hidden": "true" })
669
+ ]
670
+ }
671
+ ),
672
+ open ? hasScopes ? /* @__PURE__ */ jsx9(ScopedStats, { scopes: it.scopes }) : /* @__PURE__ */ jsx9(StatRows, { stats: it.stats }) : null
595
673
  ] });
596
674
  }
597
675
  function InsightCard({ it, ui, slip }) {
@@ -617,7 +695,7 @@ function InsightCard({ it, ui, slip }) {
617
695
  ] }) : null
618
696
  ] }),
619
697
  it.text ? /* @__PURE__ */ jsx9("p", { className: "ss-w-bi-text", children: it.text }) : null,
620
- it.scopes && it.scopes.length >= 2 ? /* @__PURE__ */ jsx9(ScopedStats, { scopes: it.scopes, ui }) : it.stats.length > 0 ? /* @__PURE__ */ jsx9(StatRows, { stats: it.stats, ui }) : null,
698
+ /* @__PURE__ */ jsx9(StatsDisclosure, { it, ui }),
621
699
  it.disclaimer ? /* @__PURE__ */ jsx9("p", { className: "ss-w-bi-disc", children: it.disclaimer }) : null,
622
700
  /* @__PURE__ */ jsx9(AddToSlipButton, { it, slip, ui })
623
701
  ] });
@@ -627,38 +705,88 @@ function BettingInsightList({ insights, ui, slip }) {
627
705
  return /* @__PURE__ */ jsx9("div", { className: "ss-w-binsights", children: insights.map((it) => /* @__PURE__ */ jsx9(InsightCard, { it, ui, slip }, it.id)) });
628
706
  }
629
707
 
708
+ // src/components/MarketOddsCardList.tsx
709
+ import { useState as useState4 } from "react";
710
+ import { jsx as jsx10, jsxs as jsxs7 } from "react/jsx-runtime";
711
+ function OddsRow({ vm, row, ui, slip }) {
712
+ const selection = vm.addToSlipEnabled ? marketOddsRowToSlipSelection(vm, row, slip.conversationId) : null;
713
+ return /* @__PURE__ */ jsxs7("div", { className: "ss-w-mo-row", children: [
714
+ /* @__PURE__ */ jsx10("span", { className: "ss-w-mo-sel", children: row.selection }),
715
+ /* @__PURE__ */ jsx10("span", { className: "ss-w-mo-price", children: row.odds }),
716
+ /* @__PURE__ */ jsx10(SlipButton, { selection, shownOdds: row.odds, marketName: vm.market, slip, ui })
717
+ ] });
718
+ }
719
+ function formatUpdated(iso) {
720
+ if (!iso) return void 0;
721
+ const date = new Date(iso);
722
+ if (Number.isNaN(date.getTime())) return void 0;
723
+ return date.toLocaleTimeString(void 0, { hour: "2-digit", minute: "2-digit" });
724
+ }
725
+ function MarketOddsCard({ vm, ui, slip }) {
726
+ const [open, setOpen] = useState4(false);
727
+ const updated = formatUpdated(vm.updatedAt);
728
+ return /* @__PURE__ */ jsxs7("div", { className: "ss-w-mocard", children: [
729
+ /* @__PURE__ */ jsxs7("div", { className: "ss-w-mo-head", children: [
730
+ /* @__PURE__ */ jsx10("span", { className: "ss-w-mo-market", children: vm.market }),
731
+ /* @__PURE__ */ jsx10("span", { className: "ss-w-mo-tag", children: ui?.marketPrices ?? "Market prices" })
732
+ ] }),
733
+ vm.fixture || vm.competition ? /* @__PURE__ */ jsxs7("div", { className: "ss-w-mo-fixture", children: [
734
+ vm.fixture ? /* @__PURE__ */ jsx10("span", { children: vm.fixture }) : null,
735
+ vm.competition ? /* @__PURE__ */ jsx10("span", { className: "ss-w-mo-comp", children: vm.competition }) : null
736
+ ] }) : null,
737
+ /* @__PURE__ */ jsxs7("div", { className: "ss-w-mo-rows", children: [
738
+ vm.mainRows.map((row) => /* @__PURE__ */ jsx10(OddsRow, { vm, row, ui, slip }, row.key)),
739
+ open ? vm.moreRows.map((row) => /* @__PURE__ */ jsx10(OddsRow, { vm, row, ui, slip }, row.key)) : null
740
+ ] }),
741
+ vm.moreRows.length > 0 ? /* @__PURE__ */ jsxs7("button", { type: "button", className: "ss-w-bi-toggle", "aria-expanded": open, onClick: () => setOpen((v) => !v), children: [
742
+ /* @__PURE__ */ jsx10("span", { children: open ? ui?.hideLines ?? "Hide lines" : `${ui?.moreLines ?? "More lines"} (${vm.moreRows.length})` }),
743
+ /* @__PURE__ */ jsx10("span", { className: `ss-w-bi-chev${open ? " ss-w-bi-chev-up" : ""}`, "aria-hidden": "true" })
744
+ ] }) : null,
745
+ vm.bookmaker || updated ? /* @__PURE__ */ jsxs7("div", { className: "ss-w-mo-foot", children: [
746
+ vm.bookmaker ? /* @__PURE__ */ jsx10("span", { children: vm.bookmaker }) : null,
747
+ updated ? /* @__PURE__ */ jsx10("span", { children: `${ui?.updatedLabel ?? "updated"} ${updated}` }) : null
748
+ ] }) : null,
749
+ vm.disclaimer ? /* @__PURE__ */ jsx10("p", { className: "ss-w-bi-disc", children: vm.disclaimer }) : null
750
+ ] });
751
+ }
752
+ function MarketOddsCardList({ cards, ui, slip }) {
753
+ if (!cards || cards.length === 0) return null;
754
+ return /* @__PURE__ */ jsx10("div", { className: "ss-w-mocards", children: cards.map((vm) => /* @__PURE__ */ jsx10(MarketOddsCard, { vm, ui, slip }, vm.id)) });
755
+ }
756
+
630
757
  // src/components/ActionButtons.tsx
631
- import { Fragment, jsx as jsx10, jsxs as jsxs7 } from "react/jsx-runtime";
758
+ import { Fragment, jsx as jsx11, jsxs as jsxs8 } from "react/jsx-runtime";
632
759
  function ActionButtons({ actions, loading, disabled, onAct }) {
633
760
  if (actions.length === 0 && !loading) return null;
634
- return /* @__PURE__ */ jsxs7("div", { className: "ss-w-actions", children: [
635
- actions.map((a, i) => /* @__PURE__ */ jsx10("button", { type: "button", className: "ss-w-abtn", disabled, onClick: () => onAct(a), children: a.label }, `${a.label}-${i}`)),
636
- actions.length === 0 && loading ? /* @__PURE__ */ jsxs7(Fragment, { children: [
637
- /* @__PURE__ */ jsx10("span", { className: "ss-w-abtn-shimmer" }),
638
- /* @__PURE__ */ jsx10("span", { className: "ss-w-abtn-shimmer" })
761
+ return /* @__PURE__ */ jsxs8("div", { className: "ss-w-actions", children: [
762
+ actions.map((a, i) => /* @__PURE__ */ jsx11("button", { type: "button", className: "ss-w-abtn", disabled, onClick: () => onAct(a), children: a.label }, `${a.label}-${i}`)),
763
+ actions.length === 0 && loading ? /* @__PURE__ */ jsxs8(Fragment, { children: [
764
+ /* @__PURE__ */ jsx11("span", { className: "ss-w-abtn-shimmer" }),
765
+ /* @__PURE__ */ jsx11("span", { className: "ss-w-abtn-shimmer" })
639
766
  ] }) : null
640
767
  ] });
641
768
  }
642
769
 
643
770
  // src/components/ChatMessage.tsx
644
- import { jsx as jsx11, jsxs as jsxs8 } from "react/jsx-runtime";
771
+ import { jsx as jsx12, jsxs as jsxs9 } from "react/jsx-runtime";
645
772
  function ChatMessage({ message, disabled, onAct, ui, slip }) {
646
773
  if (message.role === "user") {
647
- return /* @__PURE__ */ jsx11("div", { className: "ss-w-msg ss-w-user", children: /* @__PURE__ */ jsx11("div", { className: "ss-w-bubble", children: message.text }) });
774
+ return /* @__PURE__ */ jsx12("div", { className: "ss-w-msg ss-w-user", children: /* @__PURE__ */ jsx12("div", { className: "ss-w-bubble", children: message.text }) });
648
775
  }
649
- return /* @__PURE__ */ jsxs8("div", { className: "ss-w-msg ss-w-bot", children: [
650
- message.text ? /* @__PURE__ */ jsx11(Markdown, { text: message.text }) : null,
651
- /* @__PURE__ */ jsx11(EntityCardList, { cards: message.cards, disabled, showLessLabel: ui?.showLess, showMoreLabel: ui?.showMore }),
652
- /* @__PURE__ */ jsx11(BettingInsightList, { insights: message.insights, ui, slip }),
653
- /* @__PURE__ */ jsx11(ActionButtons, { actions: message.actions, loading: false, disabled, onAct })
776
+ return /* @__PURE__ */ jsxs9("div", { className: "ss-w-msg ss-w-bot", children: [
777
+ message.text ? /* @__PURE__ */ jsx12(Markdown, { text: message.text }) : null,
778
+ /* @__PURE__ */ jsx12(EntityCardList, { cards: message.cards, disabled, showLessLabel: ui?.showLess, showMoreLabel: ui?.showMore }),
779
+ /* @__PURE__ */ jsx12(BettingInsightList, { insights: message.insights, ui, slip }),
780
+ /* @__PURE__ */ jsx12(MarketOddsCardList, { cards: message.marketOdds, ui, slip }),
781
+ /* @__PURE__ */ jsx12(ActionButtons, { actions: message.actions, loading: false, disabled, onAct })
654
782
  ] });
655
783
  }
656
784
 
657
785
  // src/components/IntentChips.tsx
658
- import { jsx as jsx12 } from "react/jsx-runtime";
786
+ import { jsx as jsx13 } from "react/jsx-runtime";
659
787
  function IntentChips({ chips, onPick }) {
660
788
  if (chips.length === 0) return null;
661
- 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}`)) });
789
+ 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}`)) });
662
790
  }
663
791
 
664
792
  // src/scroll.ts
@@ -668,7 +796,7 @@ function isPinnedToBottom(el, threshold = PIN_THRESHOLD) {
668
796
  }
669
797
 
670
798
  // src/components/MessageList.tsx
671
- import { jsx as jsx13, jsxs as jsxs9 } from "react/jsx-runtime";
799
+ import { jsx as jsx14, jsxs as jsxs10 } from "react/jsx-runtime";
672
800
  function MessageList({ controller, ui, slip }) {
673
801
  const { state, send } = controller;
674
802
  const listRef = useRef2(null);
@@ -678,7 +806,7 @@ function MessageList({ controller, ui, slip }) {
678
806
  if (pinnedRef.current) endRef.current?.scrollIntoView({ block: "end" });
679
807
  }, [state.messages, state.status]);
680
808
  const empty = state.messages.length === 0;
681
- return /* @__PURE__ */ jsxs9(
809
+ return /* @__PURE__ */ jsxs10(
682
810
  "div",
683
811
  {
684
812
  className: "ss-w-list",
@@ -688,7 +816,7 @@ function MessageList({ controller, ui, slip }) {
688
816
  if (listRef.current) pinnedRef.current = isPinnedToBottom(listRef.current);
689
817
  },
690
818
  children: [
691
- state.messages.map((m) => /* @__PURE__ */ jsx13(
819
+ state.messages.map((m) => /* @__PURE__ */ jsx14(
692
820
  ChatMessage,
693
821
  {
694
822
  message: m,
@@ -699,25 +827,25 @@ function MessageList({ controller, ui, slip }) {
699
827
  },
700
828
  m.id
701
829
  )),
702
- state.isStreaming && state.actionsLoading ? /* @__PURE__ */ jsx13(ActionButtons, { actions: [], loading: true, onAct: () => {
830
+ state.isStreaming && state.actionsLoading ? /* @__PURE__ */ jsx14(ActionButtons, { actions: [], loading: true, onAct: () => {
703
831
  } }) : null,
704
- state.status ? /* @__PURE__ */ jsx13("div", { className: "ss-w-status", children: state.status }) : null,
705
- state.error ? /* @__PURE__ */ jsxs9("div", { className: "ss-w-error", role: "alert", children: [
706
- /* @__PURE__ */ jsx13("span", { children: "message" in state.error ? state.error.message : ui.somethingWentWrong }),
707
- /* @__PURE__ */ jsx13("button", { type: "button", className: "ss-w-retry", onClick: () => controller.retry(), children: ui.retry })
832
+ state.status ? /* @__PURE__ */ jsx14("div", { className: "ss-w-status", children: state.status }) : null,
833
+ state.error ? /* @__PURE__ */ jsxs10("div", { className: "ss-w-error", role: "alert", children: [
834
+ /* @__PURE__ */ jsx14("span", { children: "message" in state.error ? state.error.message : ui.somethingWentWrong }),
835
+ /* @__PURE__ */ jsx14("button", { type: "button", className: "ss-w-retry", onClick: () => controller.retry(), children: ui.retry })
708
836
  ] }) : null,
709
- empty ? /* @__PURE__ */ jsx13(IntentChips, { chips: state.starters, onPick: (t) => send(t) }) : null,
710
- /* @__PURE__ */ jsx13("div", { ref: endRef })
837
+ empty ? /* @__PURE__ */ jsx14(IntentChips, { chips: state.starters, onPick: (t) => send(t) }) : null,
838
+ /* @__PURE__ */ jsx14("div", { ref: endRef })
711
839
  ]
712
840
  }
713
841
  );
714
842
  }
715
843
 
716
844
  // src/components/ChatInput.tsx
717
- import { useState as useState4 } from "react";
718
- import { jsx as jsx14, jsxs as jsxs10 } from "react/jsx-runtime";
845
+ import { useState as useState5 } from "react";
846
+ import { jsx as jsx15, jsxs as jsxs11 } from "react/jsx-runtime";
719
847
  function ChatInput({ placeholder, streaming, onSend, onStop, stopLabel = "Stop response", sendLabel = "Send message" }) {
720
- const [value, setValue] = useState4("");
848
+ const [value, setValue] = useState5("");
721
849
  const submit = () => {
722
850
  const t = value.trim();
723
851
  if (!t) return;
@@ -730,17 +858,17 @@ function ChatInput({ placeholder, streaming, onSend, onStop, stopLabel = "Stop r
730
858
  submit();
731
859
  }
732
860
  };
733
- return /* @__PURE__ */ jsxs10("div", { className: "ss-w-input", children: [
734
- /* @__PURE__ */ jsx14("textarea", { className: "ss-w-textarea", rows: 1, placeholder, value, disabled: streaming, onChange: (e) => setValue(e.target.value), onKeyDown }),
735
- 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, {}) })
861
+ return /* @__PURE__ */ jsxs11("div", { className: "ss-w-input", children: [
862
+ /* @__PURE__ */ jsx15("textarea", { className: "ss-w-textarea", rows: 1, placeholder, value, disabled: streaming, onChange: (e) => setValue(e.target.value), onKeyDown }),
863
+ 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, {}) })
736
864
  ] });
737
865
  }
738
866
 
739
867
  // src/components/ChatPanel.tsx
740
- import { jsx as jsx15, jsxs as jsxs11 } from "react/jsx-runtime";
868
+ import { jsx as jsx16, jsxs as jsxs12 } from "react/jsx-runtime";
741
869
  var FULLSCREEN_QUERY = "(max-width: 639px)";
742
870
  function useIsModal() {
743
- const [modal, setModal] = useState5(false);
871
+ const [modal, setModal] = useState6(false);
744
872
  useEffect4(() => {
745
873
  if (typeof window === "undefined" || typeof window.matchMedia !== "function") return;
746
874
  const mql = window.matchMedia(FULLSCREEN_QUERY);
@@ -761,10 +889,10 @@ function ChatPanel({ controller, ui, slip }) {
761
889
  window.addEventListener("keydown", onKey);
762
890
  return () => window.removeEventListener("keydown", onKey);
763
891
  }, [close]);
764
- return /* @__PURE__ */ jsxs11("div", { className: "ss-w-panel", role: "dialog", "aria-modal": isModal, "aria-label": ui.chatDialogLabel, children: [
765
- /* @__PURE__ */ jsx15(ChatHeader, { onClose: close, closeLabel: ui.closeChat }),
766
- /* @__PURE__ */ jsx15(MessageList, { controller, ui, slip }),
767
- /* @__PURE__ */ jsx15(ChatInput, { placeholder: ui.placeholder, streaming: state.isStreaming, onSend: (t) => send(t), onStop: stop, stopLabel: ui.stopResponse, sendLabel: ui.sendMessage })
892
+ return /* @__PURE__ */ jsxs12("div", { className: "ss-w-panel", role: "dialog", "aria-modal": isModal, "aria-label": ui.chatDialogLabel, children: [
893
+ /* @__PURE__ */ jsx16(ChatHeader, { onClose: close, closeLabel: ui.closeChat }),
894
+ /* @__PURE__ */ jsx16(MessageList, { controller, ui, slip }),
895
+ /* @__PURE__ */ jsx16(ChatInput, { placeholder: ui.placeholder, streaming: state.isStreaming, onSend: (t) => send(t), onStop: stop, stopLabel: ui.stopResponse, sendLabel: ui.sendMessage })
768
896
  ] });
769
897
  }
770
898
 
@@ -876,27 +1004,45 @@ var WIDGET_CSS = `.ss-w-root { all: revert; }
876
1004
  .ss-w-bi-live { font-size: 8.5px; font-weight: 700; letter-spacing: .04em; text-transform: uppercase; color: #fff; background: #c0392b; border-radius: 4px; padding: 1px 5px; display: inline-flex; align-items: center; gap: 4px; flex: none; }
877
1005
  .ss-w-bi-live-dot { width: 5px; height: 5px; border-radius: 50%; background: #fff; }
878
1006
  .ss-w-bi-head { display: flex; align-items: flex-start; gap: 10px; }
879
- .ss-w-bi-mkt { display: flex; flex-direction: column; gap: 1px; min-width: 0; }
880
- .ss-w-bi-market { font-size: 13px; font-weight: 660; line-height: 1.3; color: var(--ss-w-ink); }
881
- .ss-w-bi-sel { font-size: 11px; line-height: 1.3; color: var(--ss-w-faint); }
882
- .ss-w-bi-odds { margin-left: auto; flex: none; display: flex; flex-direction: column; align-items: center; justify-content: center; background: color-mix(in srgb, var(--ss-w-accent) 12%, var(--ss-w-panel)); border: 1px solid color-mix(in srgb, var(--ss-w-accent) 24%, var(--ss-w-line)); border-radius: 8px; padding: 3px 9px; min-width: 56px; }
883
- .ss-w-bi-odds-cap { font-size: 8px; font-weight: 700; letter-spacing: .07em; text-transform: uppercase; color: var(--ss-w-faint); }
884
- .ss-w-bi-odds b { font-size: 15px; font-family: ui-monospace, monospace; font-weight: 700; font-variant-numeric: tabular-nums; color: var(--ss-w-accent); line-height: 1.15; }
1007
+ .ss-w-bi-mkt { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
1008
+ .ss-w-bi-market { font-size: 15px; font-weight: 700; line-height: 1.25; color: var(--ss-w-ink); }
1009
+ .ss-w-bi-sel { font-size: 12px; line-height: 1.3; color: var(--ss-w-faint); }
1010
+ .ss-w-bi-odds { margin-left: auto; flex: none; display: flex; flex-direction: column; align-items: center; justify-content: center; text-align: center; gap: 1px; background: color-mix(in srgb, var(--ss-w-accent) 12%, var(--ss-w-panel)); border: 1px solid color-mix(in srgb, var(--ss-w-accent) 30%, var(--ss-w-line)); border-radius: 10px; padding: 4px 11px; min-width: 62px; }
1011
+ .ss-w-bi-odds-cap { font-size: 9px; font-weight: 700; letter-spacing: .09em; text-transform: uppercase; color: var(--ss-w-faint); }
1012
+ .ss-w-bi-odds b { font-size: 19px; font-family: ui-monospace, monospace; font-weight: 700; font-variant-numeric: tabular-nums; color: var(--ss-w-accent); line-height: 1.1; }
885
1013
  .ss-w-bi-book { font-size: 9.5px; color: var(--ss-w-faint); }
886
- .ss-w-bi-text { font-size: 12px; color: var(--ss-w-ink); }
1014
+ .ss-w-bi-text { font-size: 12px; line-height: 1.45; color: var(--ss-w-ink); }
1015
+ .ss-w-bi-stats { display: flex; flex-direction: column; gap: 8px; }
1016
+ .ss-w-bi-toggle { align-self: flex-start; display: inline-flex; align-items: center; gap: 5px; background: none; border: none; color: var(--ss-w-accent); font: inherit; font-size: 11.5px; font-weight: 600; padding: 2px 0; cursor: pointer; }
1017
+ .ss-w-bi-toggle:hover { text-decoration: underline; }
1018
+ .ss-w-bi-chev { width: 7px; height: 7px; border-right: 1.5px solid currentColor; border-bottom: 1.5px solid currentColor; transform: translateY(-2px) rotate(45deg); transition: transform .15s ease; }
1019
+ .ss-w-bi-chev-up { transform: translateY(1px) rotate(-135deg); }
887
1020
  .ss-w-bi-rows { display: flex; flex-direction: column; }
888
- .ss-w-bi-row { display: flex; align-items: baseline; gap: 8px; padding: 4px 0; border-top: 1px solid var(--ss-w-line); }
1021
+ .ss-w-bi-row { display: flex; align-items: baseline; justify-content: space-between; gap: 12px; padding: 5px 0; border-top: 1px solid var(--ss-w-line); }
889
1022
  .ss-w-bi-row:first-child { border-top: none; }
890
- .ss-w-bi-row-lbl { font-size: 11px; color: var(--ss-w-faint); min-width: 0; }
891
- .ss-w-bi-row-dots { flex: 1; border-bottom: 1px dotted var(--ss-w-line); transform: translateY(-3px); min-width: 12px; }
892
- .ss-w-bi-row-num { font-family: ui-monospace, monospace; font-weight: 700; font-variant-numeric: tabular-nums; font-size: 12.5px; flex: none; color: var(--ss-w-ink); }
893
- .ss-w-bi-more { align-self: flex-start; background: none; border: none; color: var(--ss-w-accent); font: inherit; font-size: 11px; font-weight: 560; padding: 2px 0; cursor: pointer; }
894
- .ss-w-bi-more:hover { text-decoration: underline; }
1023
+ .ss-w-bi-row-lbl { font-size: 11.5px; color: var(--ss-w-faint); min-width: 0; }
1024
+ .ss-w-bi-row-num { font-family: ui-monospace, monospace; font-weight: 700; font-variant-numeric: tabular-nums; font-size: 13px; flex: none; color: var(--ss-w-ink); }
895
1025
  .ss-w-bi-disc { font-size: 10px; font-style: italic; color: var(--ss-w-faint); }
896
- .ss-w-bi-scoped { display: flex; flex-direction: column; gap: 6px; }
897
- .ss-w-bi-scopes { display: flex; align-items: center; gap: 5px; flex-wrap: wrap; }
898
- .ss-w-bi-chip { font-size: 11px; font-weight: 550; border: 1px solid var(--ss-w-line); border-radius: 14px; padding: 3px 10px; color: var(--ss-w-ink); background: var(--ss-w-bubble); cursor: pointer; }
899
- .ss-w-bi-chip[aria-pressed="true"] { color: #fff; background: var(--ss-w-accent); border-color: var(--ss-w-accent); }
1026
+ .ss-w-bi-scoped { display: flex; flex-direction: column; gap: 8px; }
1027
+ .ss-w-bi-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; }
1028
+ .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; }
1029
+ .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); }
1030
+
1031
+ /* market odds card */
1032
+ .ss-w-mocards { display: flex; flex-direction: column; gap: 7px; }
1033
+ .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; }
1034
+ .ss-w-mo-head { display: flex; align-items: center; gap: 8px; }
1035
+ .ss-w-mo-market { font-size: 13px; font-weight: 700; color: var(--ss-w-ink); }
1036
+ .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; }
1037
+ .ss-w-mo-fixture { display: flex; align-items: center; gap: 6px; font-size: 11.5px; color: var(--ss-w-faint); min-width: 0; }
1038
+ .ss-w-mo-comp { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
1039
+ .ss-w-mo-rows { display: flex; flex-direction: column; gap: 4px; }
1040
+ .ss-w-mo-row { display: flex; align-items: center; gap: 10px; padding: 4px 0; border-bottom: 1px dashed var(--ss-w-line); }
1041
+ .ss-w-mo-row:last-child { border-bottom: none; }
1042
+ .ss-w-mo-sel { font-size: 12.5px; font-weight: 600; color: var(--ss-w-ink); }
1043
+ .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); }
1044
+ .ss-w-mo-row .ss-w-slip { margin: 0; }
1045
+ .ss-w-mo-foot { display: flex; align-items: center; gap: 8px; font-size: 10.5px; color: var(--ss-w-faint); }
900
1046
 
901
1047
  /* action buttons */
902
1048
  .ss-w-actions { display: flex; flex-direction: column; align-items: flex-start; gap: 6px; }
@@ -935,7 +1081,7 @@ var WIDGET_CSS = `.ss-w-root { all: revert; }
935
1081
  `;
936
1082
 
937
1083
  // src/StatsWidget.tsx
938
- import { jsx as jsx16 } from "react/jsx-runtime";
1084
+ import { jsx as jsx17 } from "react/jsx-runtime";
939
1085
  function buildClient(props) {
940
1086
  try {
941
1087
  if ("client" in props && props.client) {
@@ -984,7 +1130,7 @@ function StatsWidget(props) {
984
1130
  }, [built, ui.injectStyles]);
985
1131
  if (!built) return null;
986
1132
  const attrs = themeAttrs(ui);
987
- 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(
1133
+ 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(
988
1134
  ChatFab,
989
1135
  {
990
1136
  label: ui.launcherLabel,