@sensiblestats/widget-react 0.10.1 → 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,29 @@
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
+
16
+ ## 0.11.0
17
+
18
+ ### Minor Changes
19
+
20
+ - Neutral odds price-movement display on market_odds card rows.
21
+
22
+ ### Patch Changes
23
+
24
+ - Updated dependencies
25
+ - @sensiblestats/widget-sdk@0.9.0
26
+
3
27
  ## 0.10.1
4
28
 
5
29
  ### Patch Changes
package/dist/index.cjs CHANGED
@@ -61,6 +61,9 @@ var STRINGS = {
61
61
  marketPrices: "Market prices",
62
62
  moreLines: "More lines",
63
63
  hideLines: "Hide lines",
64
+ oddsMoved: (was, now, direction) => `was ${was}, now ${now}, ${direction}`,
65
+ oddsShortened: "shortened",
66
+ oddsDrifted: "drifted",
64
67
  updatedLabel: "updated",
65
68
  inSlip: "In slip",
66
69
  slipAdded: (n) => n === void 0 ? "Added to slip" : `Added to slip (${n} selection${n === 1 ? "" : "s"})`,
@@ -91,6 +94,9 @@ var STRINGS = {
91
94
  marketPrices: "Piyasa fiyatlar\u0131",
92
95
  moreLines: "Di\u011Fer \xE7izgiler",
93
96
  hideLines: "\xC7izgileri gizle",
97
+ oddsMoved: (was, now, direction) => `\xF6nce ${was}, \u015Fimdi ${now}, ${direction}`,
98
+ oddsShortened: "d\xFC\u015Ft\xFC",
99
+ oddsDrifted: "y\xFCkseldi",
94
100
  updatedLabel: "g\xFCncellendi",
95
101
  inSlip: "Kuponda",
96
102
  slipAdded: (n) => n === void 0 ? "Kupona eklendi" : `Kupona eklendi (${n} se\xE7im)`,
@@ -137,6 +143,9 @@ function normalizeUi(cfg) {
137
143
  marketPrices: strings.marketPrices,
138
144
  moreLines: strings.moreLines,
139
145
  hideLines: strings.hideLines,
146
+ oddsMoved: strings.oddsMoved,
147
+ oddsShortened: strings.oddsShortened,
148
+ oddsDrifted: strings.oddsDrifted,
140
149
  updatedLabel: strings.updatedLabel,
141
150
  addToSlip: strings.addToSlip,
142
151
  inSlip: strings.inSlip,
@@ -241,6 +250,9 @@ function toInsightVM(insight) {
241
250
  const fixture = home && away ? `${home} vs ${away}` : void 0;
242
251
  const competition = str(insight.competitionName);
243
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;
244
256
  return {
245
257
  id: str(insight.insightId) ?? `${str(insight.ruleId) ?? "insight"}:${num(insight.matchId) ?? ""}`,
246
258
  market,
@@ -260,7 +272,9 @@ function toInsightVM(insight) {
260
272
  matchOddsId,
261
273
  addToSlipEnabled: insight.addToSlipEnabled === true,
262
274
  oddsDecimal: typeof oddsValue === "number" && Number.isFinite(oddsValue) ? oddsValue : void 0,
263
- 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
264
278
  };
265
279
  }
266
280
  function toSlipSelection(vm, conversationId) {
@@ -280,6 +294,8 @@ function toMarketOddsVM(card) {
280
294
  const toRow = (r, i) => {
281
295
  const selection = [str(r.selectionLabel), str(r.line)].filter(Boolean).join(" ");
282
296
  const price = typeof r.price === "number" && Number.isFinite(r.price) ? r.price : void 0;
297
+ const previous = typeof r.previousPrice === "number" && Number.isFinite(r.previousPrice) ? r.previousPrice : void 0;
298
+ const direction = r.movementDirection === "shortened" || r.movementDirection === "drifted" ? r.movementDirection : void 0;
283
299
  return {
284
300
  key: `${selection}:${str(r.line) ?? ""}:${i}`,
285
301
  selection,
@@ -287,7 +303,9 @@ function toMarketOddsVM(card) {
287
303
  oddsDecimal: price,
288
304
  matchOddsId: typeof r.matchOddsId === "number" ? r.matchOddsId : void 0,
289
305
  quotedAtUtc: str(r.quotedAtUtc),
290
- isMain: r.isMainLine !== false
306
+ isMain: r.isMainLine !== false,
307
+ previousOdds: previous !== void 0 && direction !== void 0 ? previous.toFixed(2) : void 0,
308
+ movementDirection: previous !== void 0 && direction !== void 0 ? direction : void 0
291
309
  };
292
310
  };
293
311
  const vms = rows.map(toRow).filter((r) => r.selection && r.odds);
@@ -745,20 +763,41 @@ function AddToSlipButton({ it, slip, ui }) {
745
763
  return /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(SlipButton, { selection, shownOdds: it.odds ?? "", marketName: it.market, slip, ui });
746
764
  }
747
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
+
748
787
  // src/components/BettingInsightList.tsx
749
- var import_jsx_runtime9 = require("react/jsx-runtime");
788
+ var import_jsx_runtime10 = require("react/jsx-runtime");
750
789
  function StatRows2({ stats }) {
751
- 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: [
752
- /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("span", { className: "ss-w-bi-row-lbl", children: s.label }),
753
- /* @__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 })
754
793
  ] }, `${s.label}:${s.value}`)) });
755
794
  }
756
795
  function ScopedStats({ scopes }) {
757
796
  const [active, setActive] = (0, import_react5.useState)(0);
758
797
  const current = scopes[active] ?? scopes[0] ?? { key: "", label: "", stats: [] };
759
- return /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { className: "ss-w-bi-scoped", children: [
760
- /* @__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)) }),
761
- /* @__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)
762
801
  ] });
763
802
  }
764
803
  function StatsDisclosure({ it, ui }) {
@@ -766,8 +805,8 @@ function StatsDisclosure({ it, ui }) {
766
805
  const hasScopes = !!it.scopes && it.scopes.length >= 2;
767
806
  const hasStats = hasScopes || it.stats.length > 0;
768
807
  if (!hasStats) return null;
769
- return /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { className: "ss-w-bi-stats", children: [
770
- /* @__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)(
771
810
  "button",
772
811
  {
773
812
  type: "button",
@@ -775,121 +814,123 @@ function StatsDisclosure({ it, ui }) {
775
814
  "aria-expanded": open,
776
815
  onClick: () => setOpen((v) => !v),
777
816
  children: [
778
- /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("span", { children: open ? ui?.hideStats ?? "Hide stats" : ui?.showStats ?? "Show stats" }),
779
- /* @__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" })
780
819
  ]
781
820
  }
782
821
  ),
783
- 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
784
823
  ] });
785
824
  }
786
825
  function InsightCard({ it, ui, slip }) {
787
826
  const live = it.isLive || it.phase === "live";
788
- return /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { className: "ss-w-binsight", children: [
789
- 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: [
790
- /* @__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" }),
791
830
  ui?.liveLabel ?? "Live"
792
831
  ] }) }) : null,
793
- /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { className: "ss-w-bi-head", children: [
794
- /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("span", { className: "ss-w-bi-mkt", children: [
795
- /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("span", { className: "ss-w-bi-market", children: it.market }),
796
- it.selection ? /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("span", { className: "ss-w-bi-sel", children: it.selection }) : null,
797
- it.fixture || it.competition ? /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("span", { className: "ss-w-bi-match", children: [
798
- it.fixture ? /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("span", { className: "ss-w-bi-fixture", children: it.fixture }) : null,
799
- 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
800
839
  ] }) : null
801
840
  ] }),
802
- /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { className: "ss-w-bi-action", children: [
803
- it.odds ? /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("span", { className: "ss-w-bi-odds", children: [
804
- /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("span", { className: "ss-w-bi-odds-cap", children: ui?.oddsLabel ?? "Odds" }),
805
- /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("b", { children: it.odds }),
806
- 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
807
847
  ] }) : null,
808
- /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(AddToSlipButton, { it, slip, ui })
848
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(AddToSlipButton, { it, slip, ui })
809
849
  ] })
810
850
  ] }),
811
- it.text ? /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("p", { className: "ss-w-bi-text", children: it.text }) : null,
812
- /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(StatsDisclosure, { it, ui }),
813
- 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
814
854
  ] });
815
855
  }
816
856
  function BettingInsightList({ insights, ui, slip }) {
817
857
  if (!insights || insights.length === 0) return null;
818
- 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)) });
819
859
  }
820
860
 
821
861
  // src/components/MarketOddsCardList.tsx
822
862
  var import_react6 = require("react");
823
- var import_jsx_runtime10 = require("react/jsx-runtime");
863
+ var import_jsx_runtime11 = require("react/jsx-runtime");
824
864
  function OddsRow({ vm, row, ui, slip }) {
825
865
  const selection = vm.addToSlipEnabled ? marketOddsRowToSlipSelection(vm, row, slip.conversationId) : null;
826
- return /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { className: "ss-w-mo-row", children: [
827
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("span", { className: "ss-w-mo-sel", children: row.selection }),
828
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("span", { className: "ss-w-mo-price", children: row.odds }),
829
- /* @__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 })
830
871
  ] });
831
872
  }
832
873
  function MarketOddsCard({ vm, ui, slip }) {
833
874
  const [open, setOpen] = (0, import_react6.useState)(false);
834
- return /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { className: "ss-w-mocard", children: [
835
- /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { className: "ss-w-mo-head", children: [
836
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("span", { className: "ss-w-mo-market", children: vm.market }),
837
- /* @__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" })
838
879
  ] }),
839
- vm.fixture || vm.competition ? /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { className: "ss-w-mo-fixture", children: [
840
- vm.fixture ? /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("span", { children: vm.fixture }) : null,
841
- 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
842
883
  ] }) : null,
843
- /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { className: "ss-w-mo-rows", children: [
844
- vm.mainRows.map((row) => /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(OddsRow, { vm, row, ui, slip }, row.key)),
845
- 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
846
887
  ] }),
847
- 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: [
848
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("span", { children: open ? ui?.hideLines ?? "Hide lines" : `${ui?.moreLines ?? "More lines"} (${vm.moreRows.length})` }),
849
- /* @__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" })
850
891
  ] }) : null,
851
- 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,
852
- 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
853
894
  ] });
854
895
  }
855
896
  function MarketOddsCardList({ cards, ui, slip }) {
856
897
  if (!cards || cards.length === 0) return null;
857
- 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)) });
858
899
  }
859
900
 
860
901
  // src/components/ActionButtons.tsx
861
- var import_jsx_runtime11 = require("react/jsx-runtime");
902
+ var import_jsx_runtime12 = require("react/jsx-runtime");
862
903
  function ActionButtons({ actions, loading, disabled, onAct }) {
863
904
  if (actions.length === 0 && !loading) return null;
864
- return /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("div", { className: "ss-w-actions", children: [
865
- 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}`)),
866
- actions.length === 0 && loading ? /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(import_jsx_runtime11.Fragment, { children: [
867
- /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("span", { className: "ss-w-abtn-shimmer" }),
868
- /* @__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" })
869
910
  ] }) : null
870
911
  ] });
871
912
  }
872
913
 
873
914
  // src/components/ChatMessage.tsx
874
- var import_jsx_runtime12 = require("react/jsx-runtime");
915
+ var import_jsx_runtime13 = require("react/jsx-runtime");
875
916
  function ChatMessage({ message, disabled, onAct, ui, slip }) {
876
917
  if (message.role === "user") {
877
- 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 }) });
878
919
  }
879
- return /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("div", { className: "ss-w-msg ss-w-bot", children: [
880
- message.text ? /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(Markdown, { text: message.text }) : null,
881
- /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(EntityCardList, { cards: message.cards, disabled, showLessLabel: ui?.showLess, showMoreLabel: ui?.showMore }),
882
- /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(BettingInsightList, { insights: message.insights, ui, slip }),
883
- /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(MarketOddsCardList, { cards: message.marketOdds, ui, slip }),
884
- /* @__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 })
885
926
  ] });
886
927
  }
887
928
 
888
929
  // src/components/IntentChips.tsx
889
- var import_jsx_runtime13 = require("react/jsx-runtime");
930
+ var import_jsx_runtime14 = require("react/jsx-runtime");
890
931
  function IntentChips({ chips, onPick }) {
891
932
  if (chips.length === 0) return null;
892
- 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}`)) });
893
934
  }
894
935
 
895
936
  // src/scroll.ts
@@ -899,7 +940,7 @@ function isPinnedToBottom(el, threshold = PIN_THRESHOLD) {
899
940
  }
900
941
 
901
942
  // src/components/MessageList.tsx
902
- var import_jsx_runtime14 = require("react/jsx-runtime");
943
+ var import_jsx_runtime15 = require("react/jsx-runtime");
903
944
  function MessageList({ controller, ui, slip }) {
904
945
  const { state, send } = controller;
905
946
  const listRef = (0, import_react7.useRef)(null);
@@ -909,7 +950,7 @@ function MessageList({ controller, ui, slip }) {
909
950
  if (pinnedRef.current) endRef.current?.scrollIntoView({ block: "end" });
910
951
  }, [state.messages, state.status]);
911
952
  const empty = state.messages.length === 0;
912
- return /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)(
953
+ return /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)(
913
954
  "div",
914
955
  {
915
956
  className: "ss-w-list",
@@ -919,7 +960,7 @@ function MessageList({ controller, ui, slip }) {
919
960
  if (listRef.current) pinnedRef.current = isPinnedToBottom(listRef.current);
920
961
  },
921
962
  children: [
922
- state.messages.map((m) => /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
963
+ state.messages.map((m) => /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
923
964
  ChatMessage,
924
965
  {
925
966
  message: m,
@@ -930,15 +971,15 @@ function MessageList({ controller, ui, slip }) {
930
971
  },
931
972
  m.id
932
973
  )),
933
- 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: () => {
934
975
  } }) : null,
935
- state.status ? /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("div", { className: "ss-w-status", children: state.status }) : null,
936
- state.error ? /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)("div", { className: "ss-w-error", role: "alert", children: [
937
- /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("span", { children: "message" in state.error ? state.error.message : ui.somethingWentWrong }),
938
- /* @__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 })
939
980
  ] }) : null,
940
- empty ? /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(IntentChips, { chips: state.starters, onPick: (t) => send(t) }) : null,
941
- /* @__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 })
942
983
  ]
943
984
  }
944
985
  );
@@ -946,7 +987,7 @@ function MessageList({ controller, ui, slip }) {
946
987
 
947
988
  // src/components/ChatInput.tsx
948
989
  var import_react8 = require("react");
949
- var import_jsx_runtime15 = require("react/jsx-runtime");
990
+ var import_jsx_runtime16 = require("react/jsx-runtime");
950
991
  function ChatInput({ placeholder, streaming, onSend, onStop, stopLabel = "Stop response", sendLabel = "Send message" }) {
951
992
  const [value, setValue] = (0, import_react8.useState)("");
952
993
  const submit = () => {
@@ -961,14 +1002,14 @@ function ChatInput({ placeholder, streaming, onSend, onStop, stopLabel = "Stop r
961
1002
  submit();
962
1003
  }
963
1004
  };
964
- return /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)("div", { className: "ss-w-input", children: [
965
- /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("textarea", { className: "ss-w-textarea", rows: 1, placeholder, value, disabled: streaming, onChange: (e) => setValue(e.target.value), onKeyDown }),
966
- 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, {}) })
967
1008
  ] });
968
1009
  }
969
1010
 
970
1011
  // src/components/ChatPanel.tsx
971
- var import_jsx_runtime16 = require("react/jsx-runtime");
1012
+ var import_jsx_runtime17 = require("react/jsx-runtime");
972
1013
  var FULLSCREEN_QUERY = "(max-width: 639px)";
973
1014
  function useIsModal() {
974
1015
  const [modal, setModal] = (0, import_react9.useState)(false);
@@ -992,10 +1033,10 @@ function ChatPanel({ controller, ui, slip }) {
992
1033
  window.addEventListener("keydown", onKey);
993
1034
  return () => window.removeEventListener("keydown", onKey);
994
1035
  }, [close]);
995
- return /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)("div", { className: "ss-w-panel", role: "dialog", "aria-modal": isModal, "aria-label": ui.chatDialogLabel, children: [
996
- /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(ChatHeader, { onClose: close, closeLabel: ui.closeChat }),
997
- /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(MessageList, { controller, ui, slip }),
998
- /* @__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 })
999
1040
  ] });
1000
1041
  }
1001
1042
 
@@ -1182,6 +1223,15 @@ var WIDGET_CSS = `.ss-w-root { all: revert; }
1182
1223
  .ss-w-mo-row:last-child { border-bottom: none; }
1183
1224
  .ss-w-mo-sel { font-size: 12.5px; font-weight: 600; color: var(--ss-w-ink); }
1184
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); }
1226
+ /* Movement: BOTH directions share --ss-w-faint on purpose. Green-good/red-bad would frame a
1227
+ shortening price as a discount, which inverts what it actually means for the bettor. */
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; }
1185
1235
  .ss-w-mo-row .ss-w-slip { margin: 0; }
1186
1236
  .ss-w-mo-foot { display: flex; align-items: center; gap: 8px; font-size: 10.5px; color: var(--ss-w-faint); }
1187
1237
 
@@ -1222,7 +1272,7 @@ var WIDGET_CSS = `.ss-w-root { all: revert; }
1222
1272
  `;
1223
1273
 
1224
1274
  // src/StatsWidget.tsx
1225
- var import_jsx_runtime17 = require("react/jsx-runtime");
1275
+ var import_jsx_runtime18 = require("react/jsx-runtime");
1226
1276
  function buildClient(props) {
1227
1277
  try {
1228
1278
  if ("client" in props && props.client) {
@@ -1271,7 +1321,7 @@ function StatsWidget(props) {
1271
1321
  }, [built, ui.injectStyles]);
1272
1322
  if (!built) return null;
1273
1323
  const attrs = themeAttrs(ui);
1274
- 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)(
1275
1325
  ChatFab,
1276
1326
  {
1277
1327
  label: ui.launcherLabel,