@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/dist/index.js CHANGED
@@ -24,6 +24,9 @@ var STRINGS = {
24
24
  marketPrices: "Market prices",
25
25
  moreLines: "More lines",
26
26
  hideLines: "Hide lines",
27
+ oddsMoved: (was, now, direction) => `was ${was}, now ${now}, ${direction}`,
28
+ oddsShortened: "shortened",
29
+ oddsDrifted: "drifted",
27
30
  updatedLabel: "updated",
28
31
  inSlip: "In slip",
29
32
  slipAdded: (n) => n === void 0 ? "Added to slip" : `Added to slip (${n} selection${n === 1 ? "" : "s"})`,
@@ -54,6 +57,9 @@ var STRINGS = {
54
57
  marketPrices: "Piyasa fiyatlar\u0131",
55
58
  moreLines: "Di\u011Fer \xE7izgiler",
56
59
  hideLines: "\xC7izgileri gizle",
60
+ oddsMoved: (was, now, direction) => `\xF6nce ${was}, \u015Fimdi ${now}, ${direction}`,
61
+ oddsShortened: "d\xFC\u015Ft\xFC",
62
+ oddsDrifted: "y\xFCkseldi",
57
63
  updatedLabel: "g\xFCncellendi",
58
64
  inSlip: "Kuponda",
59
65
  slipAdded: (n) => n === void 0 ? "Kupona eklendi" : `Kupona eklendi (${n} se\xE7im)`,
@@ -100,6 +106,9 @@ function normalizeUi(cfg) {
100
106
  marketPrices: strings.marketPrices,
101
107
  moreLines: strings.moreLines,
102
108
  hideLines: strings.hideLines,
109
+ oddsMoved: strings.oddsMoved,
110
+ oddsShortened: strings.oddsShortened,
111
+ oddsDrifted: strings.oddsDrifted,
103
112
  updatedLabel: strings.updatedLabel,
104
113
  addToSlip: strings.addToSlip,
105
114
  inSlip: strings.inSlip,
@@ -204,6 +213,9 @@ function toInsightVM(insight) {
204
213
  const fixture = home && away ? `${home} vs ${away}` : void 0;
205
214
  const competition = str(insight.competitionName);
206
215
  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;
216
+ const previousValue = insight.odds?.previousValue;
217
+ const previous = typeof previousValue === "number" && Number.isFinite(previousValue) ? previousValue : void 0;
218
+ const direction = insight.odds?.movementDirection === "shortened" || insight.odds?.movementDirection === "drifted" ? insight.odds.movementDirection : void 0;
207
219
  return {
208
220
  id: str(insight.insightId) ?? `${str(insight.ruleId) ?? "insight"}:${num(insight.matchId) ?? ""}`,
209
221
  market,
@@ -223,7 +235,9 @@ function toInsightVM(insight) {
223
235
  matchOddsId,
224
236
  addToSlipEnabled: insight.addToSlipEnabled === true,
225
237
  oddsDecimal: typeof oddsValue === "number" && Number.isFinite(oddsValue) ? oddsValue : void 0,
226
- quotedAtUtc: str(insight.odds?.quotedAtUtc)
238
+ quotedAtUtc: str(insight.odds?.quotedAtUtc),
239
+ previousOdds: previous !== void 0 && direction !== void 0 ? previous.toFixed(2) : void 0,
240
+ movementDirection: previous !== void 0 && direction !== void 0 ? direction : void 0
227
241
  };
228
242
  }
229
243
  function toSlipSelection(vm, conversationId) {
@@ -243,6 +257,8 @@ function toMarketOddsVM(card) {
243
257
  const toRow = (r, i) => {
244
258
  const selection = [str(r.selectionLabel), str(r.line)].filter(Boolean).join(" ");
245
259
  const price = typeof r.price === "number" && Number.isFinite(r.price) ? r.price : void 0;
260
+ const previous = typeof r.previousPrice === "number" && Number.isFinite(r.previousPrice) ? r.previousPrice : void 0;
261
+ const direction = r.movementDirection === "shortened" || r.movementDirection === "drifted" ? r.movementDirection : void 0;
246
262
  return {
247
263
  key: `${selection}:${str(r.line) ?? ""}:${i}`,
248
264
  selection,
@@ -250,7 +266,9 @@ function toMarketOddsVM(card) {
250
266
  oddsDecimal: price,
251
267
  matchOddsId: typeof r.matchOddsId === "number" ? r.matchOddsId : void 0,
252
268
  quotedAtUtc: str(r.quotedAtUtc),
253
- isMain: r.isMainLine !== false
269
+ isMain: r.isMainLine !== false,
270
+ previousOdds: previous !== void 0 && direction !== void 0 ? previous.toFixed(2) : void 0,
271
+ movementDirection: previous !== void 0 && direction !== void 0 ? direction : void 0
254
272
  };
255
273
  };
256
274
  const vms = rows.map(toRow).filter((r) => r.selection && r.odds);
@@ -708,20 +726,41 @@ function AddToSlipButton({ it, slip, ui }) {
708
726
  return /* @__PURE__ */ jsx8(SlipButton, { selection, shownOdds: it.odds ?? "", marketName: it.market, slip, ui });
709
727
  }
710
728
 
711
- // src/components/BettingInsightList.tsx
729
+ // src/components/OddsMovementBadge.tsx
712
730
  import { jsx as jsx9, jsxs as jsxs6 } from "react/jsx-runtime";
731
+ function OddsMovementBadge({
732
+ previous,
733
+ current,
734
+ direction,
735
+ ui
736
+ }) {
737
+ if (previous === void 0 || direction === void 0) return null;
738
+ const directionWord = direction === "shortened" ? ui?.oddsShortened ?? "shortened" : ui?.oddsDrifted ?? "drifted";
739
+ const label = ui?.oddsMoved?.(previous, current, directionWord) ?? `was ${previous}, now ${current}, ${directionWord}`;
740
+ return (
741
+ // Strikethrough is invisible to screen readers, so the whole was/now/direction sentence rides
742
+ // on one aria-label and the visual glyphs are hidden from the accessibility tree.
743
+ /* @__PURE__ */ jsxs6("span", { className: "ss-w-move", role: "img", "aria-label": label, children: [
744
+ /* @__PURE__ */ jsx9("span", { className: "ss-w-move-prev", "aria-hidden": "true", children: previous }),
745
+ /* @__PURE__ */ jsx9("span", { className: "ss-w-move-arrow", "aria-hidden": "true", children: direction === "shortened" ? "\u25BC" : "\u25B2" })
746
+ ] })
747
+ );
748
+ }
749
+
750
+ // src/components/BettingInsightList.tsx
751
+ import { jsx as jsx10, jsxs as jsxs7 } from "react/jsx-runtime";
713
752
  function StatRows2({ stats }) {
714
- return /* @__PURE__ */ jsx9("div", { className: "ss-w-bi-rows", children: stats.map((s) => /* @__PURE__ */ jsxs6("div", { className: "ss-w-bi-row", children: [
715
- /* @__PURE__ */ jsx9("span", { className: "ss-w-bi-row-lbl", children: s.label }),
716
- /* @__PURE__ */ jsx9("span", { className: "ss-w-bi-row-num", children: s.value })
753
+ return /* @__PURE__ */ jsx10("div", { className: "ss-w-bi-rows", children: stats.map((s) => /* @__PURE__ */ jsxs7("div", { className: "ss-w-bi-row", children: [
754
+ /* @__PURE__ */ jsx10("span", { className: "ss-w-bi-row-lbl", children: s.label }),
755
+ /* @__PURE__ */ jsx10("span", { className: "ss-w-bi-row-num", children: s.value })
717
756
  ] }, `${s.label}:${s.value}`)) });
718
757
  }
719
758
  function ScopedStats({ scopes }) {
720
759
  const [active, setActive] = useState3(0);
721
760
  const current = scopes[active] ?? scopes[0] ?? { key: "", label: "", stats: [] };
722
- return /* @__PURE__ */ jsxs6("div", { className: "ss-w-bi-scoped", children: [
723
- /* @__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)) }),
724
- /* @__PURE__ */ jsx9(StatRows2, { stats: current.stats }, current.key)
761
+ return /* @__PURE__ */ jsxs7("div", { className: "ss-w-bi-scoped", children: [
762
+ /* @__PURE__ */ jsx10("div", { className: "ss-w-bi-scopes", role: "group", children: scopes.map((s, i) => /* @__PURE__ */ jsx10("button", { type: "button", className: "ss-w-bi-chip", "aria-pressed": i === active, onClick: () => setActive(i), children: s.label }, s.key)) }),
763
+ /* @__PURE__ */ jsx10(StatRows2, { stats: current.stats }, current.key)
725
764
  ] });
726
765
  }
727
766
  function StatsDisclosure({ it, ui }) {
@@ -729,8 +768,8 @@ function StatsDisclosure({ it, ui }) {
729
768
  const hasScopes = !!it.scopes && it.scopes.length >= 2;
730
769
  const hasStats = hasScopes || it.stats.length > 0;
731
770
  if (!hasStats) return null;
732
- return /* @__PURE__ */ jsxs6("div", { className: "ss-w-bi-stats", children: [
733
- /* @__PURE__ */ jsxs6(
771
+ return /* @__PURE__ */ jsxs7("div", { className: "ss-w-bi-stats", children: [
772
+ /* @__PURE__ */ jsxs7(
734
773
  "button",
735
774
  {
736
775
  type: "button",
@@ -738,121 +777,123 @@ function StatsDisclosure({ it, ui }) {
738
777
  "aria-expanded": open,
739
778
  onClick: () => setOpen((v) => !v),
740
779
  children: [
741
- /* @__PURE__ */ jsx9("span", { children: open ? ui?.hideStats ?? "Hide stats" : ui?.showStats ?? "Show stats" }),
742
- /* @__PURE__ */ jsx9("span", { className: `ss-w-bi-chev${open ? " ss-w-bi-chev-up" : ""}`, "aria-hidden": "true" })
780
+ /* @__PURE__ */ jsx10("span", { children: open ? ui?.hideStats ?? "Hide stats" : ui?.showStats ?? "Show stats" }),
781
+ /* @__PURE__ */ jsx10("span", { className: `ss-w-bi-chev${open ? " ss-w-bi-chev-up" : ""}`, "aria-hidden": "true" })
743
782
  ]
744
783
  }
745
784
  ),
746
- open ? hasScopes ? /* @__PURE__ */ jsx9(ScopedStats, { scopes: it.scopes }) : /* @__PURE__ */ jsx9(StatRows2, { stats: it.stats }) : null
785
+ open ? hasScopes ? /* @__PURE__ */ jsx10(ScopedStats, { scopes: it.scopes }) : /* @__PURE__ */ jsx10(StatRows2, { stats: it.stats }) : null
747
786
  ] });
748
787
  }
749
788
  function InsightCard({ it, ui, slip }) {
750
789
  const live = it.isLive || it.phase === "live";
751
- return /* @__PURE__ */ jsxs6("div", { className: "ss-w-binsight", children: [
752
- live ? /* @__PURE__ */ jsx9("div", { className: "ss-w-bi-status", children: /* @__PURE__ */ jsxs6("span", { className: "ss-w-bi-live", children: [
753
- /* @__PURE__ */ jsx9("span", { className: "ss-w-bi-live-dot", "aria-hidden": "true" }),
790
+ return /* @__PURE__ */ jsxs7("div", { className: "ss-w-binsight", children: [
791
+ live ? /* @__PURE__ */ jsx10("div", { className: "ss-w-bi-status", children: /* @__PURE__ */ jsxs7("span", { className: "ss-w-bi-live", children: [
792
+ /* @__PURE__ */ jsx10("span", { className: "ss-w-bi-live-dot", "aria-hidden": "true" }),
754
793
  ui?.liveLabel ?? "Live"
755
794
  ] }) }) : null,
756
- /* @__PURE__ */ jsxs6("div", { className: "ss-w-bi-head", children: [
757
- /* @__PURE__ */ jsxs6("span", { className: "ss-w-bi-mkt", children: [
758
- /* @__PURE__ */ jsx9("span", { className: "ss-w-bi-market", children: it.market }),
759
- it.selection ? /* @__PURE__ */ jsx9("span", { className: "ss-w-bi-sel", children: it.selection }) : null,
760
- it.fixture || it.competition ? /* @__PURE__ */ jsxs6("span", { className: "ss-w-bi-match", children: [
761
- it.fixture ? /* @__PURE__ */ jsx9("span", { className: "ss-w-bi-fixture", children: it.fixture }) : null,
762
- it.competition ? /* @__PURE__ */ jsx9("span", { className: "ss-w-bi-comp", children: it.competition }) : null
795
+ /* @__PURE__ */ jsxs7("div", { className: "ss-w-bi-head", children: [
796
+ /* @__PURE__ */ jsxs7("span", { className: "ss-w-bi-mkt", children: [
797
+ /* @__PURE__ */ jsx10("span", { className: "ss-w-bi-market", children: it.market }),
798
+ it.selection ? /* @__PURE__ */ jsx10("span", { className: "ss-w-bi-sel", children: it.selection }) : null,
799
+ it.fixture || it.competition ? /* @__PURE__ */ jsxs7("span", { className: "ss-w-bi-match", children: [
800
+ it.fixture ? /* @__PURE__ */ jsx10("span", { className: "ss-w-bi-fixture", children: it.fixture }) : null,
801
+ it.competition ? /* @__PURE__ */ jsx10("span", { className: "ss-w-bi-comp", children: it.competition }) : null
763
802
  ] }) : null
764
803
  ] }),
765
- /* @__PURE__ */ jsxs6("div", { className: "ss-w-bi-action", children: [
766
- it.odds ? /* @__PURE__ */ jsxs6("span", { className: "ss-w-bi-odds", children: [
767
- /* @__PURE__ */ jsx9("span", { className: "ss-w-bi-odds-cap", children: ui?.oddsLabel ?? "Odds" }),
768
- /* @__PURE__ */ jsx9("b", { children: it.odds }),
769
- it.bookmaker ? /* @__PURE__ */ jsx9("span", { className: "ss-w-bi-book", children: it.bookmaker }) : null
804
+ /* @__PURE__ */ jsxs7("div", { className: "ss-w-bi-action", children: [
805
+ it.odds ? /* @__PURE__ */ jsxs7("span", { className: "ss-w-bi-odds", children: [
806
+ /* @__PURE__ */ jsx10("span", { className: "ss-w-bi-odds-cap", children: ui?.oddsLabel ?? "Odds" }),
807
+ /* @__PURE__ */ jsx10("b", { children: it.odds }),
808
+ /* @__PURE__ */ jsx10(OddsMovementBadge, { previous: it.previousOdds, current: it.odds, direction: it.movementDirection, ui }),
809
+ it.bookmaker ? /* @__PURE__ */ jsx10("span", { className: "ss-w-bi-book", children: it.bookmaker }) : null
770
810
  ] }) : null,
771
- /* @__PURE__ */ jsx9(AddToSlipButton, { it, slip, ui })
811
+ /* @__PURE__ */ jsx10(AddToSlipButton, { it, slip, ui })
772
812
  ] })
773
813
  ] }),
774
- it.text ? /* @__PURE__ */ jsx9("p", { className: "ss-w-bi-text", children: it.text }) : null,
775
- /* @__PURE__ */ jsx9(StatsDisclosure, { it, ui }),
776
- it.disclaimer ? /* @__PURE__ */ jsx9("p", { className: "ss-w-bi-disc", children: it.disclaimer }) : null
814
+ it.text ? /* @__PURE__ */ jsx10("p", { className: "ss-w-bi-text", children: it.text }) : null,
815
+ /* @__PURE__ */ jsx10(StatsDisclosure, { it, ui }),
816
+ it.disclaimer ? /* @__PURE__ */ jsx10("p", { className: "ss-w-bi-disc", children: it.disclaimer }) : null
777
817
  ] });
778
818
  }
779
819
  function BettingInsightList({ insights, ui, slip }) {
780
820
  if (!insights || insights.length === 0) return null;
781
- return /* @__PURE__ */ jsx9("div", { className: "ss-w-binsights", children: insights.map((it) => /* @__PURE__ */ jsx9(InsightCard, { it, ui, slip }, it.id)) });
821
+ return /* @__PURE__ */ jsx10("div", { className: "ss-w-binsights", children: insights.map((it) => /* @__PURE__ */ jsx10(InsightCard, { it, ui, slip }, it.id)) });
782
822
  }
783
823
 
784
824
  // src/components/MarketOddsCardList.tsx
785
825
  import { useState as useState4 } from "react";
786
- import { jsx as jsx10, jsxs as jsxs7 } from "react/jsx-runtime";
826
+ import { jsx as jsx11, jsxs as jsxs8 } from "react/jsx-runtime";
787
827
  function OddsRow({ vm, row, ui, slip }) {
788
828
  const selection = vm.addToSlipEnabled ? marketOddsRowToSlipSelection(vm, row, slip.conversationId) : null;
789
- return /* @__PURE__ */ jsxs7("div", { className: "ss-w-mo-row", children: [
790
- /* @__PURE__ */ jsx10("span", { className: "ss-w-mo-sel", children: row.selection }),
791
- /* @__PURE__ */ jsx10("span", { className: "ss-w-mo-price", children: row.odds }),
792
- /* @__PURE__ */ jsx10(SlipButton, { selection, shownOdds: row.odds, marketName: vm.market, slip, ui })
829
+ return /* @__PURE__ */ jsxs8("div", { className: "ss-w-mo-row", children: [
830
+ /* @__PURE__ */ jsx11("span", { className: "ss-w-mo-sel", children: row.selection }),
831
+ /* @__PURE__ */ jsx11(OddsMovementBadge, { previous: row.previousOdds, current: row.odds, direction: row.movementDirection, ui }),
832
+ /* @__PURE__ */ jsx11("span", { className: "ss-w-mo-price", children: row.odds }),
833
+ /* @__PURE__ */ jsx11(SlipButton, { selection, shownOdds: row.odds, marketName: vm.market, slip, ui })
793
834
  ] });
794
835
  }
795
836
  function MarketOddsCard({ vm, ui, slip }) {
796
837
  const [open, setOpen] = useState4(false);
797
- return /* @__PURE__ */ jsxs7("div", { className: "ss-w-mocard", children: [
798
- /* @__PURE__ */ jsxs7("div", { className: "ss-w-mo-head", children: [
799
- /* @__PURE__ */ jsx10("span", { className: "ss-w-mo-market", children: vm.market }),
800
- /* @__PURE__ */ jsx10("span", { className: "ss-w-mo-tag", children: ui?.marketPrices ?? "Market prices" })
838
+ return /* @__PURE__ */ jsxs8("div", { className: "ss-w-mocard", children: [
839
+ /* @__PURE__ */ jsxs8("div", { className: "ss-w-mo-head", children: [
840
+ /* @__PURE__ */ jsx11("span", { className: "ss-w-mo-market", children: vm.market }),
841
+ /* @__PURE__ */ jsx11("span", { className: "ss-w-mo-tag", children: ui?.marketPrices ?? "Market prices" })
801
842
  ] }),
802
- vm.fixture || vm.competition ? /* @__PURE__ */ jsxs7("div", { className: "ss-w-mo-fixture", children: [
803
- vm.fixture ? /* @__PURE__ */ jsx10("span", { children: vm.fixture }) : null,
804
- vm.competition ? /* @__PURE__ */ jsx10("span", { className: "ss-w-mo-comp", children: vm.competition }) : null
843
+ vm.fixture || vm.competition ? /* @__PURE__ */ jsxs8("div", { className: "ss-w-mo-fixture", children: [
844
+ vm.fixture ? /* @__PURE__ */ jsx11("span", { children: vm.fixture }) : null,
845
+ vm.competition ? /* @__PURE__ */ jsx11("span", { className: "ss-w-mo-comp", children: vm.competition }) : null
805
846
  ] }) : null,
806
- /* @__PURE__ */ jsxs7("div", { className: "ss-w-mo-rows", children: [
807
- vm.mainRows.map((row) => /* @__PURE__ */ jsx10(OddsRow, { vm, row, ui, slip }, row.key)),
808
- open ? vm.moreRows.map((row) => /* @__PURE__ */ jsx10(OddsRow, { vm, row, ui, slip }, row.key)) : null
847
+ /* @__PURE__ */ jsxs8("div", { className: "ss-w-mo-rows", children: [
848
+ vm.mainRows.map((row) => /* @__PURE__ */ jsx11(OddsRow, { vm, row, ui, slip }, row.key)),
849
+ open ? vm.moreRows.map((row) => /* @__PURE__ */ jsx11(OddsRow, { vm, row, ui, slip }, row.key)) : null
809
850
  ] }),
810
- vm.moreRows.length > 0 ? /* @__PURE__ */ jsxs7("button", { type: "button", className: "ss-w-bi-toggle", "aria-expanded": open, onClick: () => setOpen((v) => !v), children: [
811
- /* @__PURE__ */ jsx10("span", { children: open ? ui?.hideLines ?? "Hide lines" : `${ui?.moreLines ?? "More lines"} (${vm.moreRows.length})` }),
812
- /* @__PURE__ */ jsx10("span", { className: `ss-w-bi-chev${open ? " ss-w-bi-chev-up" : ""}`, "aria-hidden": "true" })
851
+ vm.moreRows.length > 0 ? /* @__PURE__ */ jsxs8("button", { type: "button", className: "ss-w-bi-toggle", "aria-expanded": open, onClick: () => setOpen((v) => !v), children: [
852
+ /* @__PURE__ */ jsx11("span", { children: open ? ui?.hideLines ?? "Hide lines" : `${ui?.moreLines ?? "More lines"} (${vm.moreRows.length})` }),
853
+ /* @__PURE__ */ jsx11("span", { className: `ss-w-bi-chev${open ? " ss-w-bi-chev-up" : ""}`, "aria-hidden": "true" })
813
854
  ] }) : null,
814
- vm.bookmaker ? /* @__PURE__ */ jsx10("div", { className: "ss-w-mo-foot", children: /* @__PURE__ */ jsx10("span", { children: vm.bookmaker }) }) : null,
815
- vm.disclaimer ? /* @__PURE__ */ jsx10("p", { className: "ss-w-bi-disc", children: vm.disclaimer }) : null
855
+ vm.bookmaker ? /* @__PURE__ */ jsx11("div", { className: "ss-w-mo-foot", children: /* @__PURE__ */ jsx11("span", { children: vm.bookmaker }) }) : null,
856
+ vm.disclaimer ? /* @__PURE__ */ jsx11("p", { className: "ss-w-bi-disc", children: vm.disclaimer }) : null
816
857
  ] });
817
858
  }
818
859
  function MarketOddsCardList({ cards, ui, slip }) {
819
860
  if (!cards || cards.length === 0) return null;
820
- return /* @__PURE__ */ jsx10("div", { className: "ss-w-mocards", children: cards.map((vm) => /* @__PURE__ */ jsx10(MarketOddsCard, { vm, ui, slip }, vm.id)) });
861
+ return /* @__PURE__ */ jsx11("div", { className: "ss-w-mocards", children: cards.map((vm) => /* @__PURE__ */ jsx11(MarketOddsCard, { vm, ui, slip }, vm.id)) });
821
862
  }
822
863
 
823
864
  // src/components/ActionButtons.tsx
824
- import { Fragment, jsx as jsx11, jsxs as jsxs8 } from "react/jsx-runtime";
865
+ import { Fragment, jsx as jsx12, jsxs as jsxs9 } from "react/jsx-runtime";
825
866
  function ActionButtons({ actions, loading, disabled, onAct }) {
826
867
  if (actions.length === 0 && !loading) return null;
827
- return /* @__PURE__ */ jsxs8("div", { className: "ss-w-actions", children: [
828
- actions.map((a, i) => /* @__PURE__ */ jsx11("button", { type: "button", className: "ss-w-abtn", disabled, onClick: () => onAct(a), children: a.label }, `${a.label}-${i}`)),
829
- actions.length === 0 && loading ? /* @__PURE__ */ jsxs8(Fragment, { children: [
830
- /* @__PURE__ */ jsx11("span", { className: "ss-w-abtn-shimmer" }),
831
- /* @__PURE__ */ jsx11("span", { className: "ss-w-abtn-shimmer" })
868
+ return /* @__PURE__ */ jsxs9("div", { className: "ss-w-actions", children: [
869
+ actions.map((a, i) => /* @__PURE__ */ jsx12("button", { type: "button", className: "ss-w-abtn", disabled, onClick: () => onAct(a), children: a.label }, `${a.label}-${i}`)),
870
+ actions.length === 0 && loading ? /* @__PURE__ */ jsxs9(Fragment, { children: [
871
+ /* @__PURE__ */ jsx12("span", { className: "ss-w-abtn-shimmer" }),
872
+ /* @__PURE__ */ jsx12("span", { className: "ss-w-abtn-shimmer" })
832
873
  ] }) : null
833
874
  ] });
834
875
  }
835
876
 
836
877
  // src/components/ChatMessage.tsx
837
- import { jsx as jsx12, jsxs as jsxs9 } from "react/jsx-runtime";
878
+ import { jsx as jsx13, jsxs as jsxs10 } from "react/jsx-runtime";
838
879
  function ChatMessage({ message, disabled, onAct, ui, slip }) {
839
880
  if (message.role === "user") {
840
- return /* @__PURE__ */ jsx12("div", { className: "ss-w-msg ss-w-user", children: /* @__PURE__ */ jsx12("div", { className: "ss-w-bubble", children: message.text }) });
881
+ return /* @__PURE__ */ jsx13("div", { className: "ss-w-msg ss-w-user", children: /* @__PURE__ */ jsx13("div", { className: "ss-w-bubble", children: message.text }) });
841
882
  }
842
- return /* @__PURE__ */ jsxs9("div", { className: "ss-w-msg ss-w-bot", children: [
843
- message.text ? /* @__PURE__ */ jsx12(Markdown, { text: message.text }) : null,
844
- /* @__PURE__ */ jsx12(EntityCardList, { cards: message.cards, disabled, showLessLabel: ui?.showLess, showMoreLabel: ui?.showMore }),
845
- /* @__PURE__ */ jsx12(BettingInsightList, { insights: message.insights, ui, slip }),
846
- /* @__PURE__ */ jsx12(MarketOddsCardList, { cards: message.marketOdds, ui, slip }),
847
- /* @__PURE__ */ jsx12(ActionButtons, { actions: message.actions, loading: false, disabled, onAct })
883
+ return /* @__PURE__ */ jsxs10("div", { className: "ss-w-msg ss-w-bot", children: [
884
+ message.text ? /* @__PURE__ */ jsx13(Markdown, { text: message.text }) : null,
885
+ /* @__PURE__ */ jsx13(EntityCardList, { cards: message.cards, disabled, showLessLabel: ui?.showLess, showMoreLabel: ui?.showMore }),
886
+ /* @__PURE__ */ jsx13(BettingInsightList, { insights: message.insights, ui, slip }),
887
+ /* @__PURE__ */ jsx13(MarketOddsCardList, { cards: message.marketOdds, ui, slip }),
888
+ /* @__PURE__ */ jsx13(ActionButtons, { actions: message.actions, loading: false, disabled, onAct })
848
889
  ] });
849
890
  }
850
891
 
851
892
  // src/components/IntentChips.tsx
852
- import { jsx as jsx13 } from "react/jsx-runtime";
893
+ import { jsx as jsx14 } from "react/jsx-runtime";
853
894
  function IntentChips({ chips, onPick }) {
854
895
  if (chips.length === 0) return null;
855
- 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}`)) });
896
+ return /* @__PURE__ */ jsx14("div", { className: "ss-w-chips", children: chips.map((c, i) => /* @__PURE__ */ jsx14("button", { type: "button", className: "ss-w-chip", onClick: () => onPick(c.text), children: c.text }, `${c.text}-${i}`)) });
856
897
  }
857
898
 
858
899
  // src/scroll.ts
@@ -862,7 +903,7 @@ function isPinnedToBottom(el, threshold = PIN_THRESHOLD) {
862
903
  }
863
904
 
864
905
  // src/components/MessageList.tsx
865
- import { jsx as jsx14, jsxs as jsxs10 } from "react/jsx-runtime";
906
+ import { jsx as jsx15, jsxs as jsxs11 } from "react/jsx-runtime";
866
907
  function MessageList({ controller, ui, slip }) {
867
908
  const { state, send } = controller;
868
909
  const listRef = useRef2(null);
@@ -872,7 +913,7 @@ function MessageList({ controller, ui, slip }) {
872
913
  if (pinnedRef.current) endRef.current?.scrollIntoView({ block: "end" });
873
914
  }, [state.messages, state.status]);
874
915
  const empty = state.messages.length === 0;
875
- return /* @__PURE__ */ jsxs10(
916
+ return /* @__PURE__ */ jsxs11(
876
917
  "div",
877
918
  {
878
919
  className: "ss-w-list",
@@ -882,7 +923,7 @@ function MessageList({ controller, ui, slip }) {
882
923
  if (listRef.current) pinnedRef.current = isPinnedToBottom(listRef.current);
883
924
  },
884
925
  children: [
885
- state.messages.map((m) => /* @__PURE__ */ jsx14(
926
+ state.messages.map((m) => /* @__PURE__ */ jsx15(
886
927
  ChatMessage,
887
928
  {
888
929
  message: m,
@@ -893,15 +934,15 @@ function MessageList({ controller, ui, slip }) {
893
934
  },
894
935
  m.id
895
936
  )),
896
- state.isStreaming && state.actionsLoading ? /* @__PURE__ */ jsx14(ActionButtons, { actions: [], loading: true, onAct: () => {
937
+ state.isStreaming && state.actionsLoading ? /* @__PURE__ */ jsx15(ActionButtons, { actions: [], loading: true, onAct: () => {
897
938
  } }) : null,
898
- state.status ? /* @__PURE__ */ jsx14("div", { className: "ss-w-status", children: state.status }) : null,
899
- state.error ? /* @__PURE__ */ jsxs10("div", { className: "ss-w-error", role: "alert", children: [
900
- /* @__PURE__ */ jsx14("span", { children: "message" in state.error ? state.error.message : ui.somethingWentWrong }),
901
- /* @__PURE__ */ jsx14("button", { type: "button", className: "ss-w-retry", onClick: () => controller.retry(), children: ui.retry })
939
+ state.status ? /* @__PURE__ */ jsx15("div", { className: "ss-w-status", children: state.status }) : null,
940
+ state.error ? /* @__PURE__ */ jsxs11("div", { className: "ss-w-error", role: "alert", children: [
941
+ /* @__PURE__ */ jsx15("span", { children: "message" in state.error ? state.error.message : ui.somethingWentWrong }),
942
+ /* @__PURE__ */ jsx15("button", { type: "button", className: "ss-w-retry", onClick: () => controller.retry(), children: ui.retry })
902
943
  ] }) : null,
903
- empty ? /* @__PURE__ */ jsx14(IntentChips, { chips: state.starters, onPick: (t) => send(t) }) : null,
904
- /* @__PURE__ */ jsx14("div", { ref: endRef })
944
+ empty ? /* @__PURE__ */ jsx15(IntentChips, { chips: state.starters, onPick: (t) => send(t) }) : null,
945
+ /* @__PURE__ */ jsx15("div", { ref: endRef })
905
946
  ]
906
947
  }
907
948
  );
@@ -909,7 +950,7 @@ function MessageList({ controller, ui, slip }) {
909
950
 
910
951
  // src/components/ChatInput.tsx
911
952
  import { useState as useState5 } from "react";
912
- import { jsx as jsx15, jsxs as jsxs11 } from "react/jsx-runtime";
953
+ import { jsx as jsx16, jsxs as jsxs12 } from "react/jsx-runtime";
913
954
  function ChatInput({ placeholder, streaming, onSend, onStop, stopLabel = "Stop response", sendLabel = "Send message" }) {
914
955
  const [value, setValue] = useState5("");
915
956
  const submit = () => {
@@ -924,14 +965,14 @@ function ChatInput({ placeholder, streaming, onSend, onStop, stopLabel = "Stop r
924
965
  submit();
925
966
  }
926
967
  };
927
- return /* @__PURE__ */ jsxs11("div", { className: "ss-w-input", children: [
928
- /* @__PURE__ */ jsx15("textarea", { className: "ss-w-textarea", rows: 1, placeholder, value, disabled: streaming, onChange: (e) => setValue(e.target.value), onKeyDown }),
929
- 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, {}) })
968
+ return /* @__PURE__ */ jsxs12("div", { className: "ss-w-input", children: [
969
+ /* @__PURE__ */ jsx16("textarea", { className: "ss-w-textarea", rows: 1, placeholder, value, disabled: streaming, onChange: (e) => setValue(e.target.value), onKeyDown }),
970
+ streaming ? /* @__PURE__ */ jsx16("button", { type: "button", className: "ss-w-send ss-w-stop", "aria-label": stopLabel, onClick: onStop, children: /* @__PURE__ */ jsx16("span", { className: "ss-w-stop-glyph" }) }) : /* @__PURE__ */ jsx16("button", { type: "button", className: "ss-w-send", "aria-label": sendLabel, onClick: submit, children: /* @__PURE__ */ jsx16(SendIcon, {}) })
930
971
  ] });
931
972
  }
932
973
 
933
974
  // src/components/ChatPanel.tsx
934
- import { jsx as jsx16, jsxs as jsxs12 } from "react/jsx-runtime";
975
+ import { jsx as jsx17, jsxs as jsxs13 } from "react/jsx-runtime";
935
976
  var FULLSCREEN_QUERY = "(max-width: 639px)";
936
977
  function useIsModal() {
937
978
  const [modal, setModal] = useState6(false);
@@ -955,10 +996,10 @@ function ChatPanel({ controller, ui, slip }) {
955
996
  window.addEventListener("keydown", onKey);
956
997
  return () => window.removeEventListener("keydown", onKey);
957
998
  }, [close]);
958
- return /* @__PURE__ */ jsxs12("div", { className: "ss-w-panel", role: "dialog", "aria-modal": isModal, "aria-label": ui.chatDialogLabel, children: [
959
- /* @__PURE__ */ jsx16(ChatHeader, { onClose: close, closeLabel: ui.closeChat }),
960
- /* @__PURE__ */ jsx16(MessageList, { controller, ui, slip }),
961
- /* @__PURE__ */ jsx16(ChatInput, { placeholder: ui.placeholder, streaming: state.isStreaming, onSend: (t) => send(t), onStop: stop, stopLabel: ui.stopResponse, sendLabel: ui.sendMessage })
999
+ return /* @__PURE__ */ jsxs13("div", { className: "ss-w-panel", role: "dialog", "aria-modal": isModal, "aria-label": ui.chatDialogLabel, children: [
1000
+ /* @__PURE__ */ jsx17(ChatHeader, { onClose: close, closeLabel: ui.closeChat }),
1001
+ /* @__PURE__ */ jsx17(MessageList, { controller, ui, slip }),
1002
+ /* @__PURE__ */ jsx17(ChatInput, { placeholder: ui.placeholder, streaming: state.isStreaming, onSend: (t) => send(t), onStop: stop, stopLabel: ui.stopResponse, sendLabel: ui.sendMessage })
962
1003
  ] });
963
1004
  }
964
1005
 
@@ -1145,6 +1186,15 @@ var WIDGET_CSS = `.ss-w-root { all: revert; }
1145
1186
  .ss-w-mo-row:last-child { border-bottom: none; }
1146
1187
  .ss-w-mo-sel { font-size: 12.5px; font-weight: 600; color: var(--ss-w-ink); }
1147
1188
  .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); }
1189
+ /* Movement: BOTH directions share --ss-w-faint on purpose. Green-good/red-bad would frame a
1190
+ shortening price as a discount, which inverts what it actually means for the bettor. */
1191
+ .ss-w-move { display: inline-flex; align-items: baseline; gap: 4px; color: var(--ss-w-faint); }
1192
+ .ss-w-move-prev { font-family: ui-monospace, monospace; font-variant-numeric: tabular-nums; font-size: 12px; text-decoration: line-through; }
1193
+ .ss-w-move-arrow { font-size: 9px; line-height: 1; }
1194
+ /* margin-left:auto is row layout, not badge styling -- it belongs to the market-odds row, which is
1195
+ a flex row, and must NOT follow the badge into the insight card's odds column. */
1196
+ .ss-w-mo-row .ss-w-move { margin-left: auto; }
1197
+ .ss-w-mo-row .ss-w-move + .ss-w-mo-price { margin-left: 0; }
1148
1198
  .ss-w-mo-row .ss-w-slip { margin: 0; }
1149
1199
  .ss-w-mo-foot { display: flex; align-items: center; gap: 8px; font-size: 10.5px; color: var(--ss-w-faint); }
1150
1200
 
@@ -1185,7 +1235,7 @@ var WIDGET_CSS = `.ss-w-root { all: revert; }
1185
1235
  `;
1186
1236
 
1187
1237
  // src/StatsWidget.tsx
1188
- import { jsx as jsx17 } from "react/jsx-runtime";
1238
+ import { jsx as jsx18 } from "react/jsx-runtime";
1189
1239
  function buildClient(props) {
1190
1240
  try {
1191
1241
  if ("client" in props && props.client) {
@@ -1234,7 +1284,7 @@ function StatsWidget(props) {
1234
1284
  }, [built, ui.injectStyles]);
1235
1285
  if (!built) return null;
1236
1286
  const attrs = themeAttrs(ui);
1237
- 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(
1287
+ return /* @__PURE__ */ jsx18("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__ */ jsx18(ChatPanel, { controller, ui, slip }) : /* @__PURE__ */ jsx18(
1238
1288
  ChatFab,
1239
1289
  {
1240
1290
  label: ui.launcherLabel,