@sensiblestats/widget-react 0.11.0 → 0.12.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,18 @@
1
1
  # @sensiblestats/widget-react
2
2
 
3
+ ## 0.12.0
4
+
5
+ ### Minor Changes
6
+
7
+ - 3c93b88: Show odds movement on betting insight cards, using the same markup and neutral styling as market-odds rows.
8
+
9
+ The movement markup moves into a shared component and its CSS classes are renamed from `ss-w-mo-move` / `ss-w-mo-prev` / `ss-w-mo-arrow` to `ss-w-move` / `ss-w-move-prev` / `ss-w-move-arrow`, since the same element is now rendered by both card types. Operator stylesheets targeting the old names need updating.
10
+
11
+ ### Patch Changes
12
+
13
+ - Updated dependencies [3c93b88]
14
+ - @sensiblestats/widget-sdk@0.10.0
15
+
3
16
  ## 0.11.0
4
17
 
5
18
  ### Minor Changes
package/dist/index.cjs CHANGED
@@ -250,6 +250,9 @@ function toInsightVM(insight) {
250
250
  const fixture = home && away ? `${home} vs ${away}` : void 0;
251
251
  const competition = str(insight.competitionName);
252
252
  const scopes = Array.isArray(insight.scopes) ? insight.scopes.map((s) => ({ key: str(s.key), label: str(s.label), stats: readStatCells(s.stats) })).filter((s) => !!s.key && !!s.label) : void 0;
253
+ const previousValue = insight.odds?.previousValue;
254
+ const previous = typeof previousValue === "number" && Number.isFinite(previousValue) ? previousValue : void 0;
255
+ const direction = insight.odds?.movementDirection === "shortened" || insight.odds?.movementDirection === "drifted" ? insight.odds.movementDirection : void 0;
253
256
  return {
254
257
  id: str(insight.insightId) ?? `${str(insight.ruleId) ?? "insight"}:${num(insight.matchId) ?? ""}`,
255
258
  market,
@@ -269,7 +272,9 @@ function toInsightVM(insight) {
269
272
  matchOddsId,
270
273
  addToSlipEnabled: insight.addToSlipEnabled === true,
271
274
  oddsDecimal: typeof oddsValue === "number" && Number.isFinite(oddsValue) ? oddsValue : void 0,
272
- quotedAtUtc: str(insight.odds?.quotedAtUtc)
275
+ quotedAtUtc: str(insight.odds?.quotedAtUtc),
276
+ previousOdds: previous !== void 0 && direction !== void 0 ? previous.toFixed(2) : void 0,
277
+ movementDirection: previous !== void 0 && direction !== void 0 ? direction : void 0
273
278
  };
274
279
  }
275
280
  function toSlipSelection(vm, conversationId) {
@@ -758,20 +763,41 @@ function AddToSlipButton({ it, slip, ui }) {
758
763
  return /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(SlipButton, { selection, shownOdds: it.odds ?? "", marketName: it.market, slip, ui });
759
764
  }
760
765
 
766
+ // src/components/OddsMovementBadge.tsx
767
+ var import_jsx_runtime9 = (
768
+ // Strikethrough is invisible to screen readers, so the whole was/now/direction sentence rides
769
+ // on one aria-label and the visual glyphs are hidden from the accessibility tree.
770
+ require("react/jsx-runtime")
771
+ );
772
+ function OddsMovementBadge({
773
+ previous,
774
+ current,
775
+ direction,
776
+ ui
777
+ }) {
778
+ if (previous === void 0 || direction === void 0) return null;
779
+ const directionWord = direction === "shortened" ? ui?.oddsShortened ?? "shortened" : ui?.oddsDrifted ?? "drifted";
780
+ const label = ui?.oddsMoved?.(previous, current, directionWord) ?? `was ${previous}, now ${current}, ${directionWord}`;
781
+ return /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("span", { className: "ss-w-move", role: "img", "aria-label": label, children: [
782
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("span", { className: "ss-w-move-prev", "aria-hidden": "true", children: previous }),
783
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("span", { className: "ss-w-move-arrow", "aria-hidden": "true", children: direction === "shortened" ? "\u25BC" : "\u25B2" })
784
+ ] });
785
+ }
786
+
761
787
  // src/components/BettingInsightList.tsx
762
- var import_jsx_runtime9 = require("react/jsx-runtime");
788
+ var import_jsx_runtime10 = require("react/jsx-runtime");
763
789
  function StatRows2({ stats }) {
764
- return /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("div", { className: "ss-w-bi-rows", children: stats.map((s) => /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { className: "ss-w-bi-row", children: [
765
- /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("span", { className: "ss-w-bi-row-lbl", children: s.label }),
766
- /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("span", { className: "ss-w-bi-row-num", children: s.value })
790
+ return /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("div", { className: "ss-w-bi-rows", children: stats.map((s) => /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { className: "ss-w-bi-row", children: [
791
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("span", { className: "ss-w-bi-row-lbl", children: s.label }),
792
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("span", { className: "ss-w-bi-row-num", children: s.value })
767
793
  ] }, `${s.label}:${s.value}`)) });
768
794
  }
769
795
  function ScopedStats({ scopes }) {
770
796
  const [active, setActive] = (0, import_react5.useState)(0);
771
797
  const current = scopes[active] ?? scopes[0] ?? { key: "", label: "", stats: [] };
772
- return /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { className: "ss-w-bi-scoped", children: [
773
- /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("div", { className: "ss-w-bi-scopes", role: "group", children: scopes.map((s, i) => /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("button", { type: "button", className: "ss-w-bi-chip", "aria-pressed": i === active, onClick: () => setActive(i), children: s.label }, s.key)) }),
774
- /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(StatRows2, { stats: current.stats }, current.key)
798
+ return /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { className: "ss-w-bi-scoped", children: [
799
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("div", { className: "ss-w-bi-scopes", role: "group", children: scopes.map((s, i) => /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("button", { type: "button", className: "ss-w-bi-chip", "aria-pressed": i === active, onClick: () => setActive(i), children: s.label }, s.key)) }),
800
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(StatRows2, { stats: current.stats }, current.key)
775
801
  ] });
776
802
  }
777
803
  function StatsDisclosure({ it, ui }) {
@@ -779,8 +805,8 @@ function StatsDisclosure({ it, ui }) {
779
805
  const hasScopes = !!it.scopes && it.scopes.length >= 2;
780
806
  const hasStats = hasScopes || it.stats.length > 0;
781
807
  if (!hasStats) return null;
782
- return /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { className: "ss-w-bi-stats", children: [
783
- /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(
808
+ return /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { className: "ss-w-bi-stats", children: [
809
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(
784
810
  "button",
785
811
  {
786
812
  type: "button",
@@ -788,131 +814,123 @@ function StatsDisclosure({ it, ui }) {
788
814
  "aria-expanded": open,
789
815
  onClick: () => setOpen((v) => !v),
790
816
  children: [
791
- /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("span", { children: open ? ui?.hideStats ?? "Hide stats" : ui?.showStats ?? "Show stats" }),
792
- /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("span", { className: `ss-w-bi-chev${open ? " ss-w-bi-chev-up" : ""}`, "aria-hidden": "true" })
817
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("span", { children: open ? ui?.hideStats ?? "Hide stats" : ui?.showStats ?? "Show stats" }),
818
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("span", { className: `ss-w-bi-chev${open ? " ss-w-bi-chev-up" : ""}`, "aria-hidden": "true" })
793
819
  ]
794
820
  }
795
821
  ),
796
- open ? hasScopes ? /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(ScopedStats, { scopes: it.scopes }) : /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(StatRows2, { stats: it.stats }) : null
822
+ open ? hasScopes ? /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(ScopedStats, { scopes: it.scopes }) : /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(StatRows2, { stats: it.stats }) : null
797
823
  ] });
798
824
  }
799
825
  function InsightCard({ it, ui, slip }) {
800
826
  const live = it.isLive || it.phase === "live";
801
- return /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { className: "ss-w-binsight", children: [
802
- live ? /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("div", { className: "ss-w-bi-status", children: /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("span", { className: "ss-w-bi-live", children: [
803
- /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("span", { className: "ss-w-bi-live-dot", "aria-hidden": "true" }),
827
+ return /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { className: "ss-w-binsight", children: [
828
+ live ? /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("div", { className: "ss-w-bi-status", children: /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("span", { className: "ss-w-bi-live", children: [
829
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("span", { className: "ss-w-bi-live-dot", "aria-hidden": "true" }),
804
830
  ui?.liveLabel ?? "Live"
805
831
  ] }) }) : null,
806
- /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { className: "ss-w-bi-head", children: [
807
- /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("span", { className: "ss-w-bi-mkt", children: [
808
- /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("span", { className: "ss-w-bi-market", children: it.market }),
809
- it.selection ? /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("span", { className: "ss-w-bi-sel", children: it.selection }) : null,
810
- it.fixture || it.competition ? /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("span", { className: "ss-w-bi-match", children: [
811
- it.fixture ? /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("span", { className: "ss-w-bi-fixture", children: it.fixture }) : null,
812
- it.competition ? /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("span", { className: "ss-w-bi-comp", children: it.competition }) : null
832
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { className: "ss-w-bi-head", children: [
833
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("span", { className: "ss-w-bi-mkt", children: [
834
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("span", { className: "ss-w-bi-market", children: it.market }),
835
+ it.selection ? /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("span", { className: "ss-w-bi-sel", children: it.selection }) : null,
836
+ it.fixture || it.competition ? /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("span", { className: "ss-w-bi-match", children: [
837
+ it.fixture ? /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("span", { className: "ss-w-bi-fixture", children: it.fixture }) : null,
838
+ it.competition ? /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("span", { className: "ss-w-bi-comp", children: it.competition }) : null
813
839
  ] }) : null
814
840
  ] }),
815
- /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { className: "ss-w-bi-action", children: [
816
- it.odds ? /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("span", { className: "ss-w-bi-odds", children: [
817
- /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("span", { className: "ss-w-bi-odds-cap", children: ui?.oddsLabel ?? "Odds" }),
818
- /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("b", { children: it.odds }),
819
- it.bookmaker ? /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("span", { className: "ss-w-bi-book", children: it.bookmaker }) : null
841
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { className: "ss-w-bi-action", children: [
842
+ it.odds ? /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("span", { className: "ss-w-bi-odds", children: [
843
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("span", { className: "ss-w-bi-odds-cap", children: ui?.oddsLabel ?? "Odds" }),
844
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("b", { children: it.odds }),
845
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(OddsMovementBadge, { previous: it.previousOdds, current: it.odds, direction: it.movementDirection, ui }),
846
+ it.bookmaker ? /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("span", { className: "ss-w-bi-book", children: it.bookmaker }) : null
820
847
  ] }) : null,
821
- /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(AddToSlipButton, { it, slip, ui })
848
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(AddToSlipButton, { it, slip, ui })
822
849
  ] })
823
850
  ] }),
824
- it.text ? /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("p", { className: "ss-w-bi-text", children: it.text }) : null,
825
- /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(StatsDisclosure, { it, ui }),
826
- it.disclaimer ? /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("p", { className: "ss-w-bi-disc", children: it.disclaimer }) : null
851
+ it.text ? /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("p", { className: "ss-w-bi-text", children: it.text }) : null,
852
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(StatsDisclosure, { it, ui }),
853
+ it.disclaimer ? /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("p", { className: "ss-w-bi-disc", children: it.disclaimer }) : null
827
854
  ] });
828
855
  }
829
856
  function BettingInsightList({ insights, ui, slip }) {
830
857
  if (!insights || insights.length === 0) return null;
831
- return /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("div", { className: "ss-w-binsights", children: insights.map((it) => /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(InsightCard, { it, ui, slip }, it.id)) });
858
+ return /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("div", { className: "ss-w-binsights", children: insights.map((it) => /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(InsightCard, { it, ui, slip }, it.id)) });
832
859
  }
833
860
 
834
861
  // src/components/MarketOddsCardList.tsx
835
862
  var import_react6 = require("react");
836
- var import_jsx_runtime10 = require("react/jsx-runtime");
863
+ var import_jsx_runtime11 = require("react/jsx-runtime");
837
864
  function OddsRow({ vm, row, ui, slip }) {
838
865
  const selection = vm.addToSlipEnabled ? marketOddsRowToSlipSelection(vm, row, slip.conversationId) : null;
839
- const directionWord = row.movementDirection === "shortened" ? ui?.oddsShortened ?? "shortened" : ui?.oddsDrifted ?? "drifted";
840
- const movementLabel = row.previousOdds !== void 0 ? ui?.oddsMoved?.(row.previousOdds, row.odds, directionWord) ?? `was ${row.previousOdds}, now ${row.odds}, ${directionWord}` : void 0;
841
- return /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { className: "ss-w-mo-row", children: [
842
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("span", { className: "ss-w-mo-sel", children: row.selection }),
843
- row.previousOdds !== void 0 ? (
844
- // Strikethrough is invisible to screen readers, so the whole was/now/direction sentence
845
- // rides on one aria-label and the visual glyphs are hidden from the accessibility tree.
846
- /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("span", { className: "ss-w-mo-move", role: "img", "aria-label": movementLabel, children: [
847
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("span", { className: "ss-w-mo-prev", "aria-hidden": "true", children: row.previousOdds }),
848
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("span", { className: "ss-w-mo-arrow", "aria-hidden": "true", children: row.movementDirection === "shortened" ? "\u25BC" : "\u25B2" })
849
- ] })
850
- ) : null,
851
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("span", { className: "ss-w-mo-price", children: row.odds }),
852
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(SlipButton, { selection, shownOdds: row.odds, marketName: vm.market, slip, ui })
866
+ return /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("div", { className: "ss-w-mo-row", children: [
867
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("span", { className: "ss-w-mo-sel", children: row.selection }),
868
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(OddsMovementBadge, { previous: row.previousOdds, current: row.odds, direction: row.movementDirection, ui }),
869
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("span", { className: "ss-w-mo-price", children: row.odds }),
870
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(SlipButton, { selection, shownOdds: row.odds, marketName: vm.market, slip, ui })
853
871
  ] });
854
872
  }
855
873
  function MarketOddsCard({ vm, ui, slip }) {
856
874
  const [open, setOpen] = (0, import_react6.useState)(false);
857
- return /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { className: "ss-w-mocard", children: [
858
- /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { className: "ss-w-mo-head", children: [
859
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("span", { className: "ss-w-mo-market", children: vm.market }),
860
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("span", { className: "ss-w-mo-tag", children: ui?.marketPrices ?? "Market prices" })
875
+ return /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("div", { className: "ss-w-mocard", children: [
876
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("div", { className: "ss-w-mo-head", children: [
877
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("span", { className: "ss-w-mo-market", children: vm.market }),
878
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("span", { className: "ss-w-mo-tag", children: ui?.marketPrices ?? "Market prices" })
861
879
  ] }),
862
- vm.fixture || vm.competition ? /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { className: "ss-w-mo-fixture", children: [
863
- vm.fixture ? /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("span", { children: vm.fixture }) : null,
864
- vm.competition ? /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("span", { className: "ss-w-mo-comp", children: vm.competition }) : null
880
+ vm.fixture || vm.competition ? /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("div", { className: "ss-w-mo-fixture", children: [
881
+ vm.fixture ? /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("span", { children: vm.fixture }) : null,
882
+ vm.competition ? /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("span", { className: "ss-w-mo-comp", children: vm.competition }) : null
865
883
  ] }) : null,
866
- /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { className: "ss-w-mo-rows", children: [
867
- vm.mainRows.map((row) => /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(OddsRow, { vm, row, ui, slip }, row.key)),
868
- open ? vm.moreRows.map((row) => /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(OddsRow, { vm, row, ui, slip }, row.key)) : null
884
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("div", { className: "ss-w-mo-rows", children: [
885
+ vm.mainRows.map((row) => /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(OddsRow, { vm, row, ui, slip }, row.key)),
886
+ open ? vm.moreRows.map((row) => /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(OddsRow, { vm, row, ui, slip }, row.key)) : null
869
887
  ] }),
870
- vm.moreRows.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("button", { type: "button", className: "ss-w-bi-toggle", "aria-expanded": open, onClick: () => setOpen((v) => !v), children: [
871
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("span", { children: open ? ui?.hideLines ?? "Hide lines" : `${ui?.moreLines ?? "More lines"} (${vm.moreRows.length})` }),
872
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("span", { className: `ss-w-bi-chev${open ? " ss-w-bi-chev-up" : ""}`, "aria-hidden": "true" })
888
+ vm.moreRows.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("button", { type: "button", className: "ss-w-bi-toggle", "aria-expanded": open, onClick: () => setOpen((v) => !v), children: [
889
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("span", { children: open ? ui?.hideLines ?? "Hide lines" : `${ui?.moreLines ?? "More lines"} (${vm.moreRows.length})` }),
890
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("span", { className: `ss-w-bi-chev${open ? " ss-w-bi-chev-up" : ""}`, "aria-hidden": "true" })
873
891
  ] }) : null,
874
- vm.bookmaker ? /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("div", { className: "ss-w-mo-foot", children: /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("span", { children: vm.bookmaker }) }) : null,
875
- vm.disclaimer ? /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("p", { className: "ss-w-bi-disc", children: vm.disclaimer }) : null
892
+ vm.bookmaker ? /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("div", { className: "ss-w-mo-foot", children: /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("span", { children: vm.bookmaker }) }) : null,
893
+ vm.disclaimer ? /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("p", { className: "ss-w-bi-disc", children: vm.disclaimer }) : null
876
894
  ] });
877
895
  }
878
896
  function MarketOddsCardList({ cards, ui, slip }) {
879
897
  if (!cards || cards.length === 0) return null;
880
- return /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("div", { className: "ss-w-mocards", children: cards.map((vm) => /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(MarketOddsCard, { vm, ui, slip }, vm.id)) });
898
+ return /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("div", { className: "ss-w-mocards", children: cards.map((vm) => /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(MarketOddsCard, { vm, ui, slip }, vm.id)) });
881
899
  }
882
900
 
883
901
  // src/components/ActionButtons.tsx
884
- var import_jsx_runtime11 = require("react/jsx-runtime");
902
+ var import_jsx_runtime12 = require("react/jsx-runtime");
885
903
  function ActionButtons({ actions, loading, disabled, onAct }) {
886
904
  if (actions.length === 0 && !loading) return null;
887
- return /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("div", { className: "ss-w-actions", children: [
888
- actions.map((a, i) => /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("button", { type: "button", className: "ss-w-abtn", disabled, onClick: () => onAct(a), children: a.label }, `${a.label}-${i}`)),
889
- actions.length === 0 && loading ? /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(import_jsx_runtime11.Fragment, { children: [
890
- /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("span", { className: "ss-w-abtn-shimmer" }),
891
- /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("span", { className: "ss-w-abtn-shimmer" })
905
+ return /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("div", { className: "ss-w-actions", children: [
906
+ actions.map((a, i) => /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("button", { type: "button", className: "ss-w-abtn", disabled, onClick: () => onAct(a), children: a.label }, `${a.label}-${i}`)),
907
+ actions.length === 0 && loading ? /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)(import_jsx_runtime12.Fragment, { children: [
908
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("span", { className: "ss-w-abtn-shimmer" }),
909
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("span", { className: "ss-w-abtn-shimmer" })
892
910
  ] }) : null
893
911
  ] });
894
912
  }
895
913
 
896
914
  // src/components/ChatMessage.tsx
897
- var import_jsx_runtime12 = require("react/jsx-runtime");
915
+ var import_jsx_runtime13 = require("react/jsx-runtime");
898
916
  function ChatMessage({ message, disabled, onAct, ui, slip }) {
899
917
  if (message.role === "user") {
900
- return /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("div", { className: "ss-w-msg ss-w-user", children: /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("div", { className: "ss-w-bubble", children: message.text }) });
918
+ return /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("div", { className: "ss-w-msg ss-w-user", children: /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("div", { className: "ss-w-bubble", children: message.text }) });
901
919
  }
902
- return /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("div", { className: "ss-w-msg ss-w-bot", children: [
903
- message.text ? /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(Markdown, { text: message.text }) : null,
904
- /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(EntityCardList, { cards: message.cards, disabled, showLessLabel: ui?.showLess, showMoreLabel: ui?.showMore }),
905
- /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(BettingInsightList, { insights: message.insights, ui, slip }),
906
- /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(MarketOddsCardList, { cards: message.marketOdds, ui, slip }),
907
- /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(ActionButtons, { actions: message.actions, loading: false, disabled, onAct })
920
+ return /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("div", { className: "ss-w-msg ss-w-bot", children: [
921
+ message.text ? /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(Markdown, { text: message.text }) : null,
922
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(EntityCardList, { cards: message.cards, disabled, showLessLabel: ui?.showLess, showMoreLabel: ui?.showMore }),
923
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(BettingInsightList, { insights: message.insights, ui, slip }),
924
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(MarketOddsCardList, { cards: message.marketOdds, ui, slip }),
925
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(ActionButtons, { actions: message.actions, loading: false, disabled, onAct })
908
926
  ] });
909
927
  }
910
928
 
911
929
  // src/components/IntentChips.tsx
912
- var import_jsx_runtime13 = require("react/jsx-runtime");
930
+ var import_jsx_runtime14 = require("react/jsx-runtime");
913
931
  function IntentChips({ chips, onPick }) {
914
932
  if (chips.length === 0) return null;
915
- return /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("div", { className: "ss-w-chips", children: chips.map((c, i) => /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("button", { type: "button", className: "ss-w-chip", onClick: () => onPick(c.text), children: c.text }, `${c.text}-${i}`)) });
933
+ return /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("div", { className: "ss-w-chips", children: chips.map((c, i) => /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("button", { type: "button", className: "ss-w-chip", onClick: () => onPick(c.text), children: c.text }, `${c.text}-${i}`)) });
916
934
  }
917
935
 
918
936
  // src/scroll.ts
@@ -922,7 +940,7 @@ function isPinnedToBottom(el, threshold = PIN_THRESHOLD) {
922
940
  }
923
941
 
924
942
  // src/components/MessageList.tsx
925
- var import_jsx_runtime14 = require("react/jsx-runtime");
943
+ var import_jsx_runtime15 = require("react/jsx-runtime");
926
944
  function MessageList({ controller, ui, slip }) {
927
945
  const { state, send } = controller;
928
946
  const listRef = (0, import_react7.useRef)(null);
@@ -932,7 +950,7 @@ function MessageList({ controller, ui, slip }) {
932
950
  if (pinnedRef.current) endRef.current?.scrollIntoView({ block: "end" });
933
951
  }, [state.messages, state.status]);
934
952
  const empty = state.messages.length === 0;
935
- return /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)(
953
+ return /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)(
936
954
  "div",
937
955
  {
938
956
  className: "ss-w-list",
@@ -942,7 +960,7 @@ function MessageList({ controller, ui, slip }) {
942
960
  if (listRef.current) pinnedRef.current = isPinnedToBottom(listRef.current);
943
961
  },
944
962
  children: [
945
- state.messages.map((m) => /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
963
+ state.messages.map((m) => /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
946
964
  ChatMessage,
947
965
  {
948
966
  message: m,
@@ -953,15 +971,15 @@ function MessageList({ controller, ui, slip }) {
953
971
  },
954
972
  m.id
955
973
  )),
956
- state.isStreaming && state.actionsLoading ? /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(ActionButtons, { actions: [], loading: true, onAct: () => {
974
+ state.isStreaming && state.actionsLoading ? /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(ActionButtons, { actions: [], loading: true, onAct: () => {
957
975
  } }) : null,
958
- state.status ? /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("div", { className: "ss-w-status", children: state.status }) : null,
959
- state.error ? /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)("div", { className: "ss-w-error", role: "alert", children: [
960
- /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("span", { children: "message" in state.error ? state.error.message : ui.somethingWentWrong }),
961
- /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("button", { type: "button", className: "ss-w-retry", onClick: () => controller.retry(), children: ui.retry })
976
+ state.status ? /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("div", { className: "ss-w-status", children: state.status }) : null,
977
+ state.error ? /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)("div", { className: "ss-w-error", role: "alert", children: [
978
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("span", { children: "message" in state.error ? state.error.message : ui.somethingWentWrong }),
979
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("button", { type: "button", className: "ss-w-retry", onClick: () => controller.retry(), children: ui.retry })
962
980
  ] }) : null,
963
- empty ? /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(IntentChips, { chips: state.starters, onPick: (t) => send(t) }) : null,
964
- /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("div", { ref: endRef })
981
+ empty ? /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(IntentChips, { chips: state.starters, onPick: (t) => send(t) }) : null,
982
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("div", { ref: endRef })
965
983
  ]
966
984
  }
967
985
  );
@@ -969,7 +987,7 @@ function MessageList({ controller, ui, slip }) {
969
987
 
970
988
  // src/components/ChatInput.tsx
971
989
  var import_react8 = require("react");
972
- var import_jsx_runtime15 = require("react/jsx-runtime");
990
+ var import_jsx_runtime16 = require("react/jsx-runtime");
973
991
  function ChatInput({ placeholder, streaming, onSend, onStop, stopLabel = "Stop response", sendLabel = "Send message" }) {
974
992
  const [value, setValue] = (0, import_react8.useState)("");
975
993
  const submit = () => {
@@ -984,14 +1002,14 @@ function ChatInput({ placeholder, streaming, onSend, onStop, stopLabel = "Stop r
984
1002
  submit();
985
1003
  }
986
1004
  };
987
- return /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)("div", { className: "ss-w-input", children: [
988
- /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("textarea", { className: "ss-w-textarea", rows: 1, placeholder, value, disabled: streaming, onChange: (e) => setValue(e.target.value), onKeyDown }),
989
- streaming ? /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("button", { type: "button", className: "ss-w-send ss-w-stop", "aria-label": stopLabel, onClick: onStop, children: /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("span", { className: "ss-w-stop-glyph" }) }) : /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("button", { type: "button", className: "ss-w-send", "aria-label": sendLabel, onClick: submit, children: /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(SendIcon, {}) })
1005
+ return /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)("div", { className: "ss-w-input", children: [
1006
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("textarea", { className: "ss-w-textarea", rows: 1, placeholder, value, disabled: streaming, onChange: (e) => setValue(e.target.value), onKeyDown }),
1007
+ streaming ? /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("button", { type: "button", className: "ss-w-send ss-w-stop", "aria-label": stopLabel, onClick: onStop, children: /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("span", { className: "ss-w-stop-glyph" }) }) : /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("button", { type: "button", className: "ss-w-send", "aria-label": sendLabel, onClick: submit, children: /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(SendIcon, {}) })
990
1008
  ] });
991
1009
  }
992
1010
 
993
1011
  // src/components/ChatPanel.tsx
994
- var import_jsx_runtime16 = require("react/jsx-runtime");
1012
+ var import_jsx_runtime17 = require("react/jsx-runtime");
995
1013
  var FULLSCREEN_QUERY = "(max-width: 639px)";
996
1014
  function useIsModal() {
997
1015
  const [modal, setModal] = (0, import_react9.useState)(false);
@@ -1015,10 +1033,10 @@ function ChatPanel({ controller, ui, slip }) {
1015
1033
  window.addEventListener("keydown", onKey);
1016
1034
  return () => window.removeEventListener("keydown", onKey);
1017
1035
  }, [close]);
1018
- return /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)("div", { className: "ss-w-panel", role: "dialog", "aria-modal": isModal, "aria-label": ui.chatDialogLabel, children: [
1019
- /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(ChatHeader, { onClose: close, closeLabel: ui.closeChat }),
1020
- /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(MessageList, { controller, ui, slip }),
1021
- /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(ChatInput, { placeholder: ui.placeholder, streaming: state.isStreaming, onSend: (t) => send(t), onStop: stop, stopLabel: ui.stopResponse, sendLabel: ui.sendMessage })
1036
+ return /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)("div", { className: "ss-w-panel", role: "dialog", "aria-modal": isModal, "aria-label": ui.chatDialogLabel, children: [
1037
+ /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(ChatHeader, { onClose: close, closeLabel: ui.closeChat }),
1038
+ /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(MessageList, { controller, ui, slip }),
1039
+ /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(ChatInput, { placeholder: ui.placeholder, streaming: state.isStreaming, onSend: (t) => send(t), onStop: stop, stopLabel: ui.stopResponse, sendLabel: ui.sendMessage })
1022
1040
  ] });
1023
1041
  }
1024
1042
 
@@ -1207,10 +1225,13 @@ var WIDGET_CSS = `.ss-w-root { all: revert; }
1207
1225
  .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); }
1208
1226
  /* Movement: BOTH directions share --ss-w-faint on purpose. Green-good/red-bad would frame a
1209
1227
  shortening price as a discount, which inverts what it actually means for the bettor. */
1210
- .ss-w-mo-move { margin-left: auto; display: inline-flex; align-items: baseline; gap: 4px; color: var(--ss-w-faint); }
1211
- .ss-w-mo-prev { font-family: ui-monospace, monospace; font-variant-numeric: tabular-nums; font-size: 12px; text-decoration: line-through; }
1212
- .ss-w-mo-arrow { font-size: 9px; line-height: 1; }
1213
- .ss-w-mo-move + .ss-w-mo-price { margin-left: 0; }
1228
+ .ss-w-move { display: inline-flex; align-items: baseline; gap: 4px; color: var(--ss-w-faint); }
1229
+ .ss-w-move-prev { font-family: ui-monospace, monospace; font-variant-numeric: tabular-nums; font-size: 12px; text-decoration: line-through; }
1230
+ .ss-w-move-arrow { font-size: 9px; line-height: 1; }
1231
+ /* margin-left:auto is row layout, not badge styling -- it belongs to the market-odds row, which is
1232
+ a flex row, and must NOT follow the badge into the insight card's odds column. */
1233
+ .ss-w-mo-row .ss-w-move { margin-left: auto; }
1234
+ .ss-w-mo-row .ss-w-move + .ss-w-mo-price { margin-left: 0; }
1214
1235
  .ss-w-mo-row .ss-w-slip { margin: 0; }
1215
1236
  .ss-w-mo-foot { display: flex; align-items: center; gap: 8px; font-size: 10.5px; color: var(--ss-w-faint); }
1216
1237
 
@@ -1251,7 +1272,7 @@ var WIDGET_CSS = `.ss-w-root { all: revert; }
1251
1272
  `;
1252
1273
 
1253
1274
  // src/StatsWidget.tsx
1254
- var import_jsx_runtime17 = require("react/jsx-runtime");
1275
+ var import_jsx_runtime18 = require("react/jsx-runtime");
1255
1276
  function buildClient(props) {
1256
1277
  try {
1257
1278
  if ("client" in props && props.client) {
@@ -1300,7 +1321,7 @@ function StatsWidget(props) {
1300
1321
  }, [built, ui.injectStyles]);
1301
1322
  if (!built) return null;
1302
1323
  const attrs = themeAttrs(ui);
1303
- return /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("div", { ref: rootRef, className: "ss-w-root", "data-ss-w-theme": attrs["data-ss-w-theme"], "data-ss-w-pos": attrs["data-ss-w-pos"], style: attrs.style, children: controller.state.isOpen ? /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(ChatPanel, { controller, ui, slip }) : /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
1324
+ return /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("div", { ref: rootRef, className: "ss-w-root", "data-ss-w-theme": attrs["data-ss-w-theme"], "data-ss-w-pos": attrs["data-ss-w-pos"], style: attrs.style, children: controller.state.isOpen ? /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(ChatPanel, { controller, ui, slip }) : /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
1304
1325
  ChatFab,
1305
1326
  {
1306
1327
  label: ui.launcherLabel,