@sensiblestats/widget-react 0.15.0 → 0.17.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
@@ -2,6 +2,18 @@
2
2
  import { useEffect as useEffect6, useMemo as useMemo2, useRef as useRef3 } from "react";
3
3
  import { StatsWidgetClient } from "@sensiblestats/widget-sdk";
4
4
 
5
+ // src/kickoff.ts
6
+ function formatBettingKickoff(kickoffUtc, ui, locale, now = /* @__PURE__ */ new Date()) {
7
+ const at = new Date(kickoffUtc);
8
+ const intlLocale = locale === "tr" ? "tr-TR" : "en-GB";
9
+ const time = new Intl.DateTimeFormat(intlLocale, { hour: "2-digit", minute: "2-digit" }).format(at);
10
+ const atLocalDate = new Date(at.getFullYear(), at.getMonth(), at.getDate());
11
+ const nowLocalDate = new Date(now.getFullYear(), now.getMonth(), now.getDate());
12
+ const dayDiff = Math.round((atLocalDate.getTime() - nowLocalDate.getTime()) / 864e5);
13
+ const dayLabel = dayDiff === 0 ? ui.today : dayDiff === 1 ? ui.tomorrow : new Intl.DateTimeFormat(intlLocale, { weekday: "short", day: "numeric", month: "short" }).format(at);
14
+ return { dayLabel, time };
15
+ }
16
+
5
17
  // src/i18n.ts
6
18
  var STRINGS = {
7
19
  en: {
@@ -20,6 +32,7 @@ var STRINGS = {
20
32
  hideStats: "Hide stats",
21
33
  oddsLabel: "Odds",
22
34
  liveLabel: "Live",
35
+ marketFloorLabel: "Market price \xB7 not a value pick",
23
36
  addToSlip: "Add to slip",
24
37
  marketPrices: "Market prices",
25
38
  moreLines: "More lines",
@@ -55,7 +68,10 @@ var STRINGS = {
55
68
  slipPriceNotAccepted: (from, to) => `Price moved to ${to} (was ${from}). Tap to add.`,
56
69
  slipLoginRequired: "Sign in to add to your slip",
57
70
  slipSuspended: "Market suspended",
58
- slipFailed: "Couldn't add to slip. Try again."
71
+ slipFailed: "Couldn't add to slip. Try again.",
72
+ today: "Today",
73
+ tomorrow: "Tomorrow",
74
+ bettingKickoff: (kickoffUtc, now) => formatBettingKickoff(kickoffUtc, STRINGS.en, "en", now)
59
75
  },
60
76
  tr: {
61
77
  launcherLabel: "SensibleStats ile sohbet et",
@@ -73,6 +89,7 @@ var STRINGS = {
73
89
  hideStats: "\u0130statistikleri gizle",
74
90
  oddsLabel: "Oran",
75
91
  liveLabel: "Canl\u0131",
92
+ marketFloorLabel: "Piyasa oran\u0131 \xB7 de\u011Fer bahsi de\u011Fil",
76
93
  addToSlip: "Kupona ekle",
77
94
  marketPrices: "Piyasa fiyatlar\u0131",
78
95
  moreLines: "Di\u011Fer \xE7izgiler",
@@ -108,7 +125,10 @@ var STRINGS = {
108
125
  slipPriceNotAccepted: (from, to) => `Oran ${to} oldu (\xF6nceki ${from}). Eklemek i\xE7in dokunun.`,
109
126
  slipLoginRequired: "Kupona eklemek i\xE7in giri\u015F yap\u0131n",
110
127
  slipSuspended: "Bu market ask\u0131ya al\u0131nd\u0131",
111
- slipFailed: "Kupona eklenemedi. Tekrar deneyin."
128
+ slipFailed: "Kupona eklenemedi. Tekrar deneyin.",
129
+ today: "Bug\xFCn",
130
+ tomorrow: "Yar\u0131n",
131
+ bettingKickoff: (kickoffUtc, now) => formatBettingKickoff(kickoffUtc, STRINGS.tr, "tr", now)
112
132
  }
113
133
  };
114
134
  function resolveLocaleStrings(locale) {
@@ -143,6 +163,7 @@ function normalizeUi(cfg) {
143
163
  hideStats: strings.hideStats,
144
164
  oddsLabel: strings.oddsLabel,
145
165
  liveLabel: strings.liveLabel,
166
+ marketFloorLabel: strings.marketFloorLabel,
146
167
  marketPrices: strings.marketPrices,
147
168
  moreLines: strings.moreLines,
148
169
  hideLines: strings.hideLines,
@@ -174,7 +195,10 @@ function normalizeUi(cfg) {
174
195
  slipPriceNotAccepted: strings.slipPriceNotAccepted,
175
196
  slipLoginRequired: strings.slipLoginRequired,
176
197
  slipSuspended: strings.slipSuspended,
177
- slipFailed: strings.slipFailed
198
+ slipFailed: strings.slipFailed,
199
+ today: strings.today,
200
+ tomorrow: strings.tomorrow,
201
+ bettingKickoff: strings.bettingKickoff
178
202
  };
179
203
  }
180
204
  function toClientOptions(cfg) {
@@ -359,10 +383,17 @@ function toInsightVM(insight) {
359
383
  matchId: typeof insight.matchId === "number" ? insight.matchId : 0,
360
384
  matchOddsId,
361
385
  addToSlipEnabled: insight.addToSlipEnabled === true,
386
+ isMarketFloor: insight.isMarketFloor === true,
362
387
  oddsDecimal: typeof oddsValue === "number" && Number.isFinite(oddsValue) ? oddsValue : void 0,
363
388
  quotedAtUtc: str(insight.odds?.quotedAtUtc),
364
389
  previousOdds: previous !== void 0 && direction !== void 0 ? previous.toFixed(2) : void 0,
365
- movementDirection: previous !== void 0 && direction !== void 0 ? direction : void 0
390
+ movementDirection: previous !== void 0 && direction !== void 0 ? direction : void 0,
391
+ homeTeamName: home,
392
+ awayTeamName: away,
393
+ homeCrest: str(insight.homeTeamImagePath),
394
+ awayCrest: str(insight.awayTeamImagePath),
395
+ competitionCrest: str(insight.competitionImagePath),
396
+ kickoffUtc: str(insight.kickoffUtc)
366
397
  };
367
398
  }
368
399
  function toSlipSelection(vm, conversationId) {
@@ -937,7 +968,7 @@ function OddsMovementBadge({
937
968
  }
938
969
 
939
970
  // src/components/BettingInsightList.tsx
940
- import { jsx as jsx10, jsxs as jsxs7 } from "react/jsx-runtime";
971
+ import { Fragment, jsx as jsx10, jsxs as jsxs7 } from "react/jsx-runtime";
941
972
  function StatRows2({ stats }) {
942
973
  return /* @__PURE__ */ jsx10("div", { className: "ss-w-bi-rows", children: stats.map((s) => /* @__PURE__ */ jsxs7("div", { className: "ss-w-bi-row", children: [
943
974
  /* @__PURE__ */ jsx10("span", { className: "ss-w-bi-row-lbl", children: s.label }),
@@ -952,10 +983,21 @@ function ScopedStats({ scopes }) {
952
983
  /* @__PURE__ */ jsx10(StatRows2, { stats: current.stats }, current.key)
953
984
  ] });
954
985
  }
986
+ function hasStatsContent(it) {
987
+ return !!it.scopes && it.scopes.length >= 2 || it.stats.length > 0;
988
+ }
989
+ function initials2(name) {
990
+ return (name ?? "").trim().slice(0, 1).toUpperCase() || "?";
991
+ }
992
+ function Crest2({ url, name, kind }) {
993
+ const cls = kind === "comp" ? "ss-w-bi-comp-logo" : "ss-w-bi-crest";
994
+ if (url) return /* @__PURE__ */ jsx10("span", { className: cls, style: { backgroundImage: `url(${url})` }, "aria-hidden": "true" });
995
+ return /* @__PURE__ */ jsx10("span", { className: `${cls} ss-w-bi-crest-mono`, "aria-hidden": "true", children: initials2(name) });
996
+ }
955
997
  function StatsDisclosure({ it, ui }) {
956
998
  const [open, setOpen] = useState3(false);
957
999
  const hasScopes = !!it.scopes && it.scopes.length >= 2;
958
- const hasStats = hasScopes || it.stats.length > 0;
1000
+ const hasStats = hasStatsContent(it);
959
1001
  if (!hasStats) return null;
960
1002
  return /* @__PURE__ */ jsxs7("div", { className: "ss-w-bi-stats", children: [
961
1003
  /* @__PURE__ */ jsxs7(
@@ -976,21 +1018,42 @@ function StatsDisclosure({ it, ui }) {
976
1018
  }
977
1019
  function InsightCard({ it, ui, slip }) {
978
1020
  const live = it.isLive || it.phase === "live";
1021
+ const kickoff = it.kickoffUtc && ui?.bettingKickoff ? ui.bettingKickoff(it.kickoffUtc) : void 0;
1022
+ const hasNamedTeams = !!it.homeTeamName && !!it.awayTeamName;
1023
+ const showHeader = !!it.competition || live || !!kickoff;
1024
+ const showReason = !!it.text || hasStatsContent(it);
979
1025
  return /* @__PURE__ */ jsxs7("div", { className: "ss-w-binsight", children: [
980
- live ? /* @__PURE__ */ jsx10("div", { className: "ss-w-bi-status", children: /* @__PURE__ */ jsxs7("span", { className: "ss-w-bi-live", children: [
981
- /* @__PURE__ */ jsx10("span", { className: "ss-w-bi-live-dot", "aria-hidden": "true" }),
982
- ui?.liveLabel ?? "Live"
983
- ] }) }) : null,
984
- /* @__PURE__ */ jsxs7("div", { className: "ss-w-bi-head", children: [
985
- /* @__PURE__ */ jsxs7("span", { className: "ss-w-bi-mkt", children: [
986
- /* @__PURE__ */ jsx10("span", { className: "ss-w-bi-market", children: it.market }),
987
- it.selection ? /* @__PURE__ */ jsx10("span", { className: "ss-w-bi-sel", children: it.selection }) : null,
988
- it.fixture || it.competition ? /* @__PURE__ */ jsxs7("span", { className: "ss-w-bi-match", children: [
989
- it.fixture ? /* @__PURE__ */ jsx10("span", { className: "ss-w-bi-fixture", children: it.fixture }) : null,
990
- it.competition ? /* @__PURE__ */ jsx10("span", { className: "ss-w-bi-comp", children: it.competition }) : null
1026
+ showHeader ? /* @__PURE__ */ jsxs7("div", { className: "ss-w-bi-header", children: [
1027
+ /* @__PURE__ */ jsx10("span", { className: "ss-w-bi-header-left", children: it.competition ? /* @__PURE__ */ jsxs7(Fragment, { children: [
1028
+ /* @__PURE__ */ jsx10(Crest2, { kind: "comp", url: it.competitionCrest, name: it.competition }),
1029
+ /* @__PURE__ */ jsx10("span", { className: "ss-w-bi-comp", children: it.competition })
1030
+ ] }) : null }),
1031
+ /* @__PURE__ */ jsxs7("span", { className: "ss-w-bi-header-right", children: [
1032
+ live ? /* @__PURE__ */ jsxs7("span", { className: "ss-w-bi-live", children: [
1033
+ /* @__PURE__ */ jsx10("span", { className: "ss-w-bi-live-dot", "aria-hidden": "true" }),
1034
+ ui?.liveLabel ?? "Live"
1035
+ ] }) : null,
1036
+ kickoff ? /* @__PURE__ */ jsxs7("span", { className: "ss-w-bi-kick", children: [
1037
+ kickoff.dayLabel,
1038
+ " ",
1039
+ kickoff.time
991
1040
  ] }) : null
992
- ] }),
1041
+ ] })
1042
+ ] }) : null,
1043
+ /* @__PURE__ */ jsxs7("div", { className: "ss-w-bi-body", children: [
1044
+ /* @__PURE__ */ jsx10("div", { className: "ss-w-bi-fixture", children: hasNamedTeams ? /* @__PURE__ */ jsxs7(Fragment, { children: [
1045
+ /* @__PURE__ */ jsx10(Crest2, { kind: "team", url: it.homeCrest, name: it.homeTeamName }),
1046
+ /* @__PURE__ */ jsx10("span", { className: "ss-w-bi-team", children: it.homeTeamName }),
1047
+ /* @__PURE__ */ jsx10("span", { className: "ss-w-bi-vs", "aria-hidden": "true", children: "vs" }),
1048
+ /* @__PURE__ */ jsx10("span", { className: "ss-w-bi-team", children: it.awayTeamName }),
1049
+ /* @__PURE__ */ jsx10(Crest2, { kind: "team", url: it.awayCrest, name: it.awayTeamName })
1050
+ ] }) : it.fixture ? /* @__PURE__ */ jsx10("span", { className: "ss-w-bi-fixture-txt", children: it.fixture }) : null }),
993
1051
  /* @__PURE__ */ jsxs7("div", { className: "ss-w-bi-action", children: [
1052
+ /* @__PURE__ */ jsxs7("span", { className: "ss-w-bi-mkt", children: [
1053
+ /* @__PURE__ */ jsx10("span", { className: "ss-w-bi-market", children: it.market }),
1054
+ it.selection ? /* @__PURE__ */ jsx10("span", { className: "ss-w-bi-sel", children: it.selection }) : null
1055
+ ] }),
1056
+ it.isMarketFloor ? /* @__PURE__ */ jsx10("span", { className: "ss-w-bi-floor", children: ui?.marketFloorLabel ?? "Market price \xB7 not a value pick" }) : null,
994
1057
  it.odds ? /* @__PURE__ */ jsxs7("span", { className: "ss-w-bi-odds", children: [
995
1058
  /* @__PURE__ */ jsx10("span", { className: "ss-w-bi-odds-cap", children: ui?.oddsLabel ?? "Odds" }),
996
1059
  /* @__PURE__ */ jsx10("b", { children: it.odds }),
@@ -1000,8 +1063,13 @@ function InsightCard({ it, ui, slip }) {
1000
1063
  /* @__PURE__ */ jsx10(AddToSlipButton, { it, slip, ui })
1001
1064
  ] })
1002
1065
  ] }),
1003
- it.text ? /* @__PURE__ */ jsx10("p", { className: "ss-w-bi-text", children: it.text }) : null,
1004
- /* @__PURE__ */ jsx10(StatsDisclosure, { it, ui }),
1066
+ showReason ? /* @__PURE__ */ jsxs7("div", { className: "ss-w-bi-reason", children: [
1067
+ /* @__PURE__ */ jsx10("span", { className: "ss-w-bi-chart", "aria-hidden": "true" }),
1068
+ /* @__PURE__ */ jsxs7("div", { className: "ss-w-bi-reason-body", children: [
1069
+ it.text ? /* @__PURE__ */ jsx10("p", { className: "ss-w-bi-text", children: it.text }) : null,
1070
+ /* @__PURE__ */ jsx10(StatsDisclosure, { it, ui })
1071
+ ] })
1072
+ ] }) : null,
1005
1073
  it.disclaimer ? /* @__PURE__ */ jsx10("p", { className: "ss-w-bi-disc", children: it.disclaimer }) : null
1006
1074
  ] });
1007
1075
  }
@@ -1051,7 +1119,7 @@ function MarketOddsCardList({ cards, ui, slip }) {
1051
1119
  }
1052
1120
 
1053
1121
  // src/components/OddsMovementCard.tsx
1054
- import { Fragment, jsx as jsx12, jsxs as jsxs9 } from "react/jsx-runtime";
1122
+ import { Fragment as Fragment2, jsx as jsx12, jsxs as jsxs9 } from "react/jsx-runtime";
1055
1123
  function directionWord(vm, ui) {
1056
1124
  if (vm.direction === "shortened") return ui?.oddsShortened ?? "shortened";
1057
1125
  if (vm.direction === "drifted") return ui?.oddsDrifted ?? "drifted";
@@ -1087,7 +1155,7 @@ function OddsMovementCard({ vm, ui }) {
1087
1155
  ] }, g.price)),
1088
1156
  /* @__PURE__ */ jsx12("path", { className: "ss-w-om-line", d: vm.geometry.path, fill: "none" }),
1089
1157
  vm.geometry.markers.map((m, i) => /* @__PURE__ */ jsx12("circle", { className: "ss-w-om-dot", cx: m.x, cy: m.y, r: 2.5 }, i)),
1090
- ui?.chartTimeLabel ? /* @__PURE__ */ jsxs9(Fragment, { children: [
1158
+ ui?.chartTimeLabel ? /* @__PURE__ */ jsxs9(Fragment2, { children: [
1091
1159
  /* @__PURE__ */ jsx12("text", { className: "ss-w-om-xlab", x: 40, y: CHART_VIEW_H - 8, textAnchor: "start", children: ui.chartTimeLabel(vm.geometry.startMs) }),
1092
1160
  /* @__PURE__ */ jsx12("text", { className: "ss-w-om-xlab", x: CHART_VIEW_W - 8, y: CHART_VIEW_H - 8, textAnchor: "end", children: ui.chartTimeLabel(vm.geometry.endMs) })
1093
1161
  ] }) : /* @__PURE__ */ jsx12("text", { className: "ss-w-om-xlab", x: 40, y: CHART_VIEW_H - 8, children: ui?.movementWindow ? ui.movementWindow(vm.windowHours) : `Last ${vm.windowHours}h` })
@@ -1118,12 +1186,12 @@ function OddsMovementCardList({ cards, ui }) {
1118
1186
  }
1119
1187
 
1120
1188
  // src/components/ActionButtons.tsx
1121
- import { Fragment as Fragment2, jsx as jsx13, jsxs as jsxs10 } from "react/jsx-runtime";
1189
+ import { Fragment as Fragment3, jsx as jsx13, jsxs as jsxs10 } from "react/jsx-runtime";
1122
1190
  function ActionButtons({ actions, loading, disabled, onAct }) {
1123
1191
  if (actions.length === 0 && !loading) return null;
1124
1192
  return /* @__PURE__ */ jsxs10("div", { className: "ss-w-actions", children: [
1125
1193
  actions.map((a, i) => /* @__PURE__ */ jsx13("button", { type: "button", className: "ss-w-abtn", disabled, onClick: () => onAct(a), children: a.label }, `${a.label}-${i}`)),
1126
- actions.length === 0 && loading ? /* @__PURE__ */ jsxs10(Fragment2, { children: [
1194
+ actions.length === 0 && loading ? /* @__PURE__ */ jsxs10(Fragment3, { children: [
1127
1195
  /* @__PURE__ */ jsx13("span", { className: "ss-w-abtn-shimmer" }),
1128
1196
  /* @__PURE__ */ jsx13("span", { className: "ss-w-abtn-shimmer" })
1129
1197
  ] }) : null
@@ -1189,13 +1257,13 @@ function GreetingHeaderBlock({ block }) {
1189
1257
  // src/components/club-dashboard/FixtureHeroBlock.tsx
1190
1258
  import { useState as useState5 } from "react";
1191
1259
  import { jsx as jsx17, jsxs as jsxs14 } from "react/jsx-runtime";
1192
- function initials2(name) {
1260
+ function initials3(name) {
1193
1261
  return name.split(/\s+/).filter(Boolean).slice(0, 2).map((w) => w[0].toUpperCase()).join("");
1194
1262
  }
1195
- function Crest2({ url, name }) {
1263
+ function Crest3({ url, name }) {
1196
1264
  const [broken, setBroken] = useState5(false);
1197
1265
  if (!url || broken) {
1198
- return /* @__PURE__ */ jsx17("span", { className: "ss-w-cdash-crest ss-w-cdash-crest--fallback", "aria-hidden": "true", children: initials2(name) });
1266
+ return /* @__PURE__ */ jsx17("span", { className: "ss-w-cdash-crest ss-w-cdash-crest--fallback", "aria-hidden": "true", children: initials3(name) });
1199
1267
  }
1200
1268
  return /* @__PURE__ */ jsx17("img", { className: "ss-w-cdash-crest", src: url, alt: "", "aria-hidden": "true", onError: () => setBroken(true) });
1201
1269
  }
@@ -1206,11 +1274,11 @@ function FixtureHeroBlock({
1206
1274
  }) {
1207
1275
  return /* @__PURE__ */ jsxs14("div", { className: "ss-w-cdash-fixture", children: [
1208
1276
  /* @__PURE__ */ jsxs14("div", { className: "ss-w-cdash-tie", children: [
1209
- /* @__PURE__ */ jsx17(Crest2, { url: block.homeCrestUrl, name: block.homeName }),
1277
+ /* @__PURE__ */ jsx17(Crest3, { url: block.homeCrestUrl, name: block.homeName }),
1210
1278
  /* @__PURE__ */ jsx17("span", { className: "ss-w-cdash-team", children: block.homeName }),
1211
1279
  /* @__PURE__ */ jsx17("span", { className: "ss-w-cdash-vs", children: "v" }),
1212
1280
  /* @__PURE__ */ jsx17("span", { className: "ss-w-cdash-team", children: block.awayName }),
1213
- /* @__PURE__ */ jsx17(Crest2, { url: block.awayCrestUrl, name: block.awayName })
1281
+ /* @__PURE__ */ jsx17(Crest3, { url: block.awayCrestUrl, name: block.awayName })
1214
1282
  ] }),
1215
1283
  /* @__PURE__ */ jsxs14("div", { className: "ss-w-cdash-fixture-meta", children: [
1216
1284
  /* @__PURE__ */ jsx17("span", { className: "ss-w-cdash-kickoff", children: ui.clubKickoff(block.kickoffUnix * 1e3) }),
@@ -1604,25 +1672,51 @@ var WIDGET_CSS = `.ss-w-root { all: revert; }
1604
1672
 
1605
1673
  /* betting insights */
1606
1674
  .ss-w-binsights { display: flex; flex-direction: column; gap: 7px; }
1607
- .ss-w-binsight { background: var(--ss-w-panel); border: 1px solid var(--ss-w-line); border-radius: 12px; border-left: 3px solid var(--ss-w-accent); padding: 9px 11px; display: flex; flex-direction: column; gap: 6px; }
1608
- .ss-w-bi-status { display: flex; align-items: center; gap: 6px; min-width: 0; }
1609
- .ss-w-bi-fixture { font-size: 11px; color: var(--ss-w-ink); font-weight: 600; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
1610
- .ss-w-bi-comp { font-size: 10.5px; color: var(--ss-w-faint); font-weight: 500; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
1675
+ .ss-w-binsight { background: var(--ss-w-panel); border: 1px solid var(--ss-w-line); border-radius: 12px; border-left: 3px solid var(--ss-w-accent); padding: 9px 11px; display: flex; flex-direction: column; gap: 8px; }
1676
+
1677
+ /* row 1: competition logo + name on the left, live badge + kickoff on the right */
1678
+ .ss-w-bi-header { display: flex; align-items: center; justify-content: space-between; gap: 8px; min-width: 0; padding-bottom: 7px; border-bottom: 1px solid var(--ss-w-line); }
1679
+ .ss-w-bi-header-left { display: inline-flex; align-items: center; gap: 6px; min-width: 0; }
1680
+ .ss-w-bi-header-right { display: inline-flex; align-items: center; gap: 8px; flex: none; }
1681
+ .ss-w-bi-comp { font-size: 10.5px; color: var(--ss-w-faint); font-weight: 600; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
1682
+ .ss-w-bi-kick { font-size: 10.5px; font-weight: 600; color: var(--ss-w-faint); white-space: nowrap; flex: none; }
1611
1683
  .ss-w-bi-live { font-size: 8.5px; font-weight: 700; letter-spacing: .04em; text-transform: uppercase; color: #fff; background: #c0392b; border-radius: 4px; padding: 1px 5px; display: inline-flex; align-items: center; gap: 4px; flex: none; }
1612
1684
  .ss-w-bi-live-dot { width: 5px; height: 5px; border-radius: 50%; background: #fff; }
1613
- .ss-w-bi-head { display: flex; align-items: flex-start; gap: 10px; }
1614
- .ss-w-bi-mkt { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
1615
- .ss-w-bi-market { font-size: 15px; font-weight: 700; line-height: 1.25; color: var(--ss-w-ink); }
1616
- .ss-w-bi-sel { font-size: 12px; line-height: 1.3; color: var(--ss-w-faint); }
1617
- .ss-w-bi-match { display: flex; flex-direction: column; gap: 1px; min-width: 0; margin-top: 5px; }
1618
- .ss-w-bi-action { margin-left: auto; flex: none; display: flex; flex-direction: column; align-items: stretch; gap: 6px; }
1685
+ .ss-w-bi-floor { font-size: 8.5px; font-weight: 700; letter-spacing: .04em; text-transform: uppercase; color: var(--ss-w-faint); background: var(--ss-w-bubble); border: 1px solid var(--ss-w-line); border-radius: 4px; padding: 1px 5px; display: inline-flex; align-items: center; align-self: flex-start; flex: none; }
1686
+
1687
+ /* crests -- CSS background-image on a span, never <img> (matches .ss-w-ecard-crest precedent) */
1688
+ .ss-w-bi-comp-logo { width: 15px; height: 15px; flex: none; display: block; border-radius: 50%; background-size: contain; background-repeat: no-repeat; background-position: center; }
1689
+ .ss-w-bi-crest { width: 22px; height: 22px; flex: none; display: block; border-radius: 50%; background-size: contain; background-repeat: no-repeat; background-position: center; background-color: var(--ss-w-bubble); border: 1px solid var(--ss-w-line); }
1690
+ .ss-w-bi-crest-mono { display: grid; place-items: center; font-size: 9px; font-weight: 700; color: var(--ss-w-accent); background: color-mix(in srgb, var(--ss-w-accent) 16%, var(--ss-w-panel)); }
1691
+
1692
+ /* row 2: crest-flanked fixture on the left, boxed action panel on the right */
1693
+ .ss-w-bi-body { display: flex; align-items: center; gap: 10px; }
1694
+ .ss-w-bi-fixture { display: flex; align-items: center; gap: 6px; min-width: 0; flex: 1; font-size: 12px; font-weight: 650; color: var(--ss-w-ink); }
1695
+ .ss-w-bi-team { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
1696
+ .ss-w-bi-vs { font-size: 10px; font-weight: 600; color: var(--ss-w-faint); flex: none; }
1697
+ .ss-w-bi-fixture-txt { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
1698
+ .ss-w-bi-action { margin-left: auto; flex: none; display: flex; flex-direction: column; align-items: stretch; gap: 6px; background: var(--ss-w-bubble); border: 1px solid var(--ss-w-line); border-radius: 12px; padding: 8px 10px; }
1619
1699
  .ss-w-bi-action .ss-w-slip > button { flex: 1; text-align: center; }
1620
1700
  /* The action column is narrow; let its toast size to its own text and anchor to the card's right edge instead of being clipped to the button width. */
1621
1701
  .ss-w-bi-action .ss-w-toast { left: auto; right: 0; width: max-content; max-width: 210px; white-space: normal; }
1702
+ .ss-w-bi-mkt { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
1703
+ .ss-w-bi-market { font-size: 13.5px; font-weight: 700; line-height: 1.25; color: var(--ss-w-ink); }
1704
+ .ss-w-bi-sel { font-size: 11.5px; line-height: 1.3; color: var(--ss-w-faint); }
1622
1705
  .ss-w-bi-odds { flex: none; display: flex; flex-direction: column; align-items: center; justify-content: center; text-align: center; gap: 1px; background: color-mix(in srgb, var(--ss-w-accent) 12%, var(--ss-w-panel)); border: 1px solid color-mix(in srgb, var(--ss-w-accent) 30%, var(--ss-w-line)); border-radius: 10px; padding: 4px 11px; min-width: 62px; }
1623
1706
  .ss-w-bi-odds-cap { font-size: 9px; font-weight: 700; letter-spacing: .09em; text-transform: uppercase; color: var(--ss-w-faint); }
1624
1707
  .ss-w-bi-odds b { font-size: 19px; font-family: ui-monospace, monospace; font-weight: 700; font-variant-numeric: tabular-nums; color: var(--ss-w-accent); line-height: 1.1; }
1625
1708
  .ss-w-bi-book { font-size: 9.5px; color: var(--ss-w-faint); }
1709
+
1710
+ /* row 3: bar-chart glyph + reasoning prose + stats-expander chevron, in a subtle strip */
1711
+ .ss-w-bi-reason { display: flex; align-items: flex-start; gap: 8px; background: var(--ss-w-bubble); border-radius: 10px; padding: 8px 10px; }
1712
+ .ss-w-bi-chart { flex: none; width: 13px; height: 12px; margin-top: 2px; opacity: .55;
1713
+ background:
1714
+ linear-gradient(var(--ss-w-accent), var(--ss-w-accent)) left / 3px 55% no-repeat,
1715
+ linear-gradient(var(--ss-w-accent), var(--ss-w-accent)) center / 3px 100% no-repeat,
1716
+ linear-gradient(var(--ss-w-accent), var(--ss-w-accent)) right / 3px 75% no-repeat;
1717
+ background-position: left bottom, center bottom, right bottom;
1718
+ }
1719
+ .ss-w-bi-reason-body { display: flex; flex-direction: column; gap: 8px; min-width: 0; flex: 1; }
1626
1720
  .ss-w-bi-text { font-size: 12px; line-height: 1.45; color: var(--ss-w-ink); }
1627
1721
  .ss-w-bi-stats { display: flex; flex-direction: column; gap: 8px; }
1628
1722
  .ss-w-bi-toggle { align-self: flex-start; display: inline-flex; align-items: center; gap: 5px; background: none; border: none; color: var(--ss-w-accent); font: inherit; font-size: 11.5px; font-weight: 600; padding: 2px 0; cursor: pointer; }