@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/CHANGELOG.md CHANGED
@@ -1,5 +1,27 @@
1
1
  # @sensiblestats/widget-react
2
2
 
3
+ ## 0.17.0
4
+
5
+ ### Minor Changes
6
+
7
+ - 5fd5471: Redesign the betting insight card: crest-led three-row layout with competition logo, kickoff time and a bar-chart reasoning strip.
8
+
9
+ ### Patch Changes
10
+
11
+ - Updated dependencies [5fd5471]
12
+ - @sensiblestats/widget-sdk@0.15.0
13
+
14
+ ## 0.16.0
15
+
16
+ ### Minor Changes
17
+
18
+ - 399d6bf: Label market-floor betting insight cards with a distinct "Market price · not a value pick" pill, so a screenshot of one can never read as a recommendation. Older servers that never send `isMarketFloor` render cards exactly as before.
19
+
20
+ ### Patch Changes
21
+
22
+ - Updated dependencies [399d6bf]
23
+ - @sensiblestats/widget-sdk@0.14.0
24
+
3
25
  ## 0.15.0
4
26
 
5
27
  ### Minor Changes
package/dist/index.cjs CHANGED
@@ -39,6 +39,18 @@ module.exports = __toCommonJS(index_exports);
39
39
  var import_react12 = require("react");
40
40
  var import_widget_sdk5 = require("@sensiblestats/widget-sdk");
41
41
 
42
+ // src/kickoff.ts
43
+ function formatBettingKickoff(kickoffUtc, ui, locale, now = /* @__PURE__ */ new Date()) {
44
+ const at = new Date(kickoffUtc);
45
+ const intlLocale = locale === "tr" ? "tr-TR" : "en-GB";
46
+ const time = new Intl.DateTimeFormat(intlLocale, { hour: "2-digit", minute: "2-digit" }).format(at);
47
+ const atLocalDate = new Date(at.getFullYear(), at.getMonth(), at.getDate());
48
+ const nowLocalDate = new Date(now.getFullYear(), now.getMonth(), now.getDate());
49
+ const dayDiff = Math.round((atLocalDate.getTime() - nowLocalDate.getTime()) / 864e5);
50
+ const dayLabel = dayDiff === 0 ? ui.today : dayDiff === 1 ? ui.tomorrow : new Intl.DateTimeFormat(intlLocale, { weekday: "short", day: "numeric", month: "short" }).format(at);
51
+ return { dayLabel, time };
52
+ }
53
+
42
54
  // src/i18n.ts
43
55
  var STRINGS = {
44
56
  en: {
@@ -57,6 +69,7 @@ var STRINGS = {
57
69
  hideStats: "Hide stats",
58
70
  oddsLabel: "Odds",
59
71
  liveLabel: "Live",
72
+ marketFloorLabel: "Market price \xB7 not a value pick",
60
73
  addToSlip: "Add to slip",
61
74
  marketPrices: "Market prices",
62
75
  moreLines: "More lines",
@@ -92,7 +105,10 @@ var STRINGS = {
92
105
  slipPriceNotAccepted: (from, to) => `Price moved to ${to} (was ${from}). Tap to add.`,
93
106
  slipLoginRequired: "Sign in to add to your slip",
94
107
  slipSuspended: "Market suspended",
95
- slipFailed: "Couldn't add to slip. Try again."
108
+ slipFailed: "Couldn't add to slip. Try again.",
109
+ today: "Today",
110
+ tomorrow: "Tomorrow",
111
+ bettingKickoff: (kickoffUtc, now) => formatBettingKickoff(kickoffUtc, STRINGS.en, "en", now)
96
112
  },
97
113
  tr: {
98
114
  launcherLabel: "SensibleStats ile sohbet et",
@@ -110,6 +126,7 @@ var STRINGS = {
110
126
  hideStats: "\u0130statistikleri gizle",
111
127
  oddsLabel: "Oran",
112
128
  liveLabel: "Canl\u0131",
129
+ marketFloorLabel: "Piyasa oran\u0131 \xB7 de\u011Fer bahsi de\u011Fil",
113
130
  addToSlip: "Kupona ekle",
114
131
  marketPrices: "Piyasa fiyatlar\u0131",
115
132
  moreLines: "Di\u011Fer \xE7izgiler",
@@ -145,7 +162,10 @@ var STRINGS = {
145
162
  slipPriceNotAccepted: (from, to) => `Oran ${to} oldu (\xF6nceki ${from}). Eklemek i\xE7in dokunun.`,
146
163
  slipLoginRequired: "Kupona eklemek i\xE7in giri\u015F yap\u0131n",
147
164
  slipSuspended: "Bu market ask\u0131ya al\u0131nd\u0131",
148
- slipFailed: "Kupona eklenemedi. Tekrar deneyin."
165
+ slipFailed: "Kupona eklenemedi. Tekrar deneyin.",
166
+ today: "Bug\xFCn",
167
+ tomorrow: "Yar\u0131n",
168
+ bettingKickoff: (kickoffUtc, now) => formatBettingKickoff(kickoffUtc, STRINGS.tr, "tr", now)
149
169
  }
150
170
  };
151
171
  function resolveLocaleStrings(locale) {
@@ -180,6 +200,7 @@ function normalizeUi(cfg) {
180
200
  hideStats: strings.hideStats,
181
201
  oddsLabel: strings.oddsLabel,
182
202
  liveLabel: strings.liveLabel,
203
+ marketFloorLabel: strings.marketFloorLabel,
183
204
  marketPrices: strings.marketPrices,
184
205
  moreLines: strings.moreLines,
185
206
  hideLines: strings.hideLines,
@@ -211,7 +232,10 @@ function normalizeUi(cfg) {
211
232
  slipPriceNotAccepted: strings.slipPriceNotAccepted,
212
233
  slipLoginRequired: strings.slipLoginRequired,
213
234
  slipSuspended: strings.slipSuspended,
214
- slipFailed: strings.slipFailed
235
+ slipFailed: strings.slipFailed,
236
+ today: strings.today,
237
+ tomorrow: strings.tomorrow,
238
+ bettingKickoff: strings.bettingKickoff
215
239
  };
216
240
  }
217
241
  function toClientOptions(cfg) {
@@ -396,10 +420,17 @@ function toInsightVM(insight) {
396
420
  matchId: typeof insight.matchId === "number" ? insight.matchId : 0,
397
421
  matchOddsId,
398
422
  addToSlipEnabled: insight.addToSlipEnabled === true,
423
+ isMarketFloor: insight.isMarketFloor === true,
399
424
  oddsDecimal: typeof oddsValue === "number" && Number.isFinite(oddsValue) ? oddsValue : void 0,
400
425
  quotedAtUtc: str(insight.odds?.quotedAtUtc),
401
426
  previousOdds: previous !== void 0 && direction !== void 0 ? previous.toFixed(2) : void 0,
402
- movementDirection: previous !== void 0 && direction !== void 0 ? direction : void 0
427
+ movementDirection: previous !== void 0 && direction !== void 0 ? direction : void 0,
428
+ homeTeamName: home,
429
+ awayTeamName: away,
430
+ homeCrest: str(insight.homeTeamImagePath),
431
+ awayCrest: str(insight.awayTeamImagePath),
432
+ competitionCrest: str(insight.competitionImagePath),
433
+ kickoffUtc: str(insight.kickoffUtc)
403
434
  };
404
435
  }
405
436
  function toSlipSelection(vm, conversationId) {
@@ -989,10 +1020,21 @@ function ScopedStats({ scopes }) {
989
1020
  /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(StatRows2, { stats: current.stats }, current.key)
990
1021
  ] });
991
1022
  }
1023
+ function hasStatsContent(it) {
1024
+ return !!it.scopes && it.scopes.length >= 2 || it.stats.length > 0;
1025
+ }
1026
+ function initials2(name) {
1027
+ return (name ?? "").trim().slice(0, 1).toUpperCase() || "?";
1028
+ }
1029
+ function Crest2({ url, name, kind }) {
1030
+ const cls = kind === "comp" ? "ss-w-bi-comp-logo" : "ss-w-bi-crest";
1031
+ if (url) return /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("span", { className: cls, style: { backgroundImage: `url(${url})` }, "aria-hidden": "true" });
1032
+ return /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("span", { className: `${cls} ss-w-bi-crest-mono`, "aria-hidden": "true", children: initials2(name) });
1033
+ }
992
1034
  function StatsDisclosure({ it, ui }) {
993
1035
  const [open, setOpen] = (0, import_react5.useState)(false);
994
1036
  const hasScopes = !!it.scopes && it.scopes.length >= 2;
995
- const hasStats = hasScopes || it.stats.length > 0;
1037
+ const hasStats = hasStatsContent(it);
996
1038
  if (!hasStats) return null;
997
1039
  return /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { className: "ss-w-bi-stats", children: [
998
1040
  /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(
@@ -1013,21 +1055,42 @@ function StatsDisclosure({ it, ui }) {
1013
1055
  }
1014
1056
  function InsightCard({ it, ui, slip }) {
1015
1057
  const live = it.isLive || it.phase === "live";
1058
+ const kickoff = it.kickoffUtc && ui?.bettingKickoff ? ui.bettingKickoff(it.kickoffUtc) : void 0;
1059
+ const hasNamedTeams = !!it.homeTeamName && !!it.awayTeamName;
1060
+ const showHeader = !!it.competition || live || !!kickoff;
1061
+ const showReason = !!it.text || hasStatsContent(it);
1016
1062
  return /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { className: "ss-w-binsight", children: [
1017
- 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: [
1018
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("span", { className: "ss-w-bi-live-dot", "aria-hidden": "true" }),
1019
- ui?.liveLabel ?? "Live"
1020
- ] }) }) : null,
1021
- /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { className: "ss-w-bi-head", children: [
1022
- /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("span", { className: "ss-w-bi-mkt", children: [
1023
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("span", { className: "ss-w-bi-market", children: it.market }),
1024
- it.selection ? /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("span", { className: "ss-w-bi-sel", children: it.selection }) : null,
1025
- it.fixture || it.competition ? /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("span", { className: "ss-w-bi-match", children: [
1026
- it.fixture ? /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("span", { className: "ss-w-bi-fixture", children: it.fixture }) : null,
1027
- it.competition ? /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("span", { className: "ss-w-bi-comp", children: it.competition }) : null
1063
+ showHeader ? /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { className: "ss-w-bi-header", children: [
1064
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("span", { className: "ss-w-bi-header-left", children: it.competition ? /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(import_jsx_runtime10.Fragment, { children: [
1065
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(Crest2, { kind: "comp", url: it.competitionCrest, name: it.competition }),
1066
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("span", { className: "ss-w-bi-comp", children: it.competition })
1067
+ ] }) : null }),
1068
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("span", { className: "ss-w-bi-header-right", children: [
1069
+ live ? /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("span", { className: "ss-w-bi-live", children: [
1070
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("span", { className: "ss-w-bi-live-dot", "aria-hidden": "true" }),
1071
+ ui?.liveLabel ?? "Live"
1072
+ ] }) : null,
1073
+ kickoff ? /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("span", { className: "ss-w-bi-kick", children: [
1074
+ kickoff.dayLabel,
1075
+ " ",
1076
+ kickoff.time
1028
1077
  ] }) : null
1029
- ] }),
1078
+ ] })
1079
+ ] }) : null,
1080
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { className: "ss-w-bi-body", children: [
1081
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("div", { className: "ss-w-bi-fixture", children: hasNamedTeams ? /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(import_jsx_runtime10.Fragment, { children: [
1082
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(Crest2, { kind: "team", url: it.homeCrest, name: it.homeTeamName }),
1083
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("span", { className: "ss-w-bi-team", children: it.homeTeamName }),
1084
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("span", { className: "ss-w-bi-vs", "aria-hidden": "true", children: "vs" }),
1085
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("span", { className: "ss-w-bi-team", children: it.awayTeamName }),
1086
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(Crest2, { kind: "team", url: it.awayCrest, name: it.awayTeamName })
1087
+ ] }) : it.fixture ? /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("span", { className: "ss-w-bi-fixture-txt", children: it.fixture }) : null }),
1030
1088
  /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { className: "ss-w-bi-action", children: [
1089
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("span", { className: "ss-w-bi-mkt", children: [
1090
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("span", { className: "ss-w-bi-market", children: it.market }),
1091
+ it.selection ? /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("span", { className: "ss-w-bi-sel", children: it.selection }) : null
1092
+ ] }),
1093
+ it.isMarketFloor ? /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("span", { className: "ss-w-bi-floor", children: ui?.marketFloorLabel ?? "Market price \xB7 not a value pick" }) : null,
1031
1094
  it.odds ? /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("span", { className: "ss-w-bi-odds", children: [
1032
1095
  /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("span", { className: "ss-w-bi-odds-cap", children: ui?.oddsLabel ?? "Odds" }),
1033
1096
  /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("b", { children: it.odds }),
@@ -1037,8 +1100,13 @@ function InsightCard({ it, ui, slip }) {
1037
1100
  /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(AddToSlipButton, { it, slip, ui })
1038
1101
  ] })
1039
1102
  ] }),
1040
- it.text ? /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("p", { className: "ss-w-bi-text", children: it.text }) : null,
1041
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(StatsDisclosure, { it, ui }),
1103
+ showReason ? /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { className: "ss-w-bi-reason", children: [
1104
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("span", { className: "ss-w-bi-chart", "aria-hidden": "true" }),
1105
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { className: "ss-w-bi-reason-body", children: [
1106
+ it.text ? /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("p", { className: "ss-w-bi-text", children: it.text }) : null,
1107
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(StatsDisclosure, { it, ui })
1108
+ ] })
1109
+ ] }) : null,
1042
1110
  it.disclaimer ? /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("p", { className: "ss-w-bi-disc", children: it.disclaimer }) : null
1043
1111
  ] });
1044
1112
  }
@@ -1226,13 +1294,13 @@ function GreetingHeaderBlock({ block }) {
1226
1294
  // src/components/club-dashboard/FixtureHeroBlock.tsx
1227
1295
  var import_react7 = require("react");
1228
1296
  var import_jsx_runtime17 = require("react/jsx-runtime");
1229
- function initials2(name) {
1297
+ function initials3(name) {
1230
1298
  return name.split(/\s+/).filter(Boolean).slice(0, 2).map((w) => w[0].toUpperCase()).join("");
1231
1299
  }
1232
- function Crest2({ url, name }) {
1300
+ function Crest3({ url, name }) {
1233
1301
  const [broken, setBroken] = (0, import_react7.useState)(false);
1234
1302
  if (!url || broken) {
1235
- return /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("span", { className: "ss-w-cdash-crest ss-w-cdash-crest--fallback", "aria-hidden": "true", children: initials2(name) });
1303
+ return /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("span", { className: "ss-w-cdash-crest ss-w-cdash-crest--fallback", "aria-hidden": "true", children: initials3(name) });
1236
1304
  }
1237
1305
  return /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("img", { className: "ss-w-cdash-crest", src: url, alt: "", "aria-hidden": "true", onError: () => setBroken(true) });
1238
1306
  }
@@ -1243,11 +1311,11 @@ function FixtureHeroBlock({
1243
1311
  }) {
1244
1312
  return /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)("div", { className: "ss-w-cdash-fixture", children: [
1245
1313
  /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)("div", { className: "ss-w-cdash-tie", children: [
1246
- /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(Crest2, { url: block.homeCrestUrl, name: block.homeName }),
1314
+ /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(Crest3, { url: block.homeCrestUrl, name: block.homeName }),
1247
1315
  /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("span", { className: "ss-w-cdash-team", children: block.homeName }),
1248
1316
  /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("span", { className: "ss-w-cdash-vs", children: "v" }),
1249
1317
  /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("span", { className: "ss-w-cdash-team", children: block.awayName }),
1250
- /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(Crest2, { url: block.awayCrestUrl, name: block.awayName })
1318
+ /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(Crest3, { url: block.awayCrestUrl, name: block.awayName })
1251
1319
  ] }),
1252
1320
  /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)("div", { className: "ss-w-cdash-fixture-meta", children: [
1253
1321
  /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("span", { className: "ss-w-cdash-kickoff", children: ui.clubKickoff(block.kickoffUnix * 1e3) }),
@@ -1641,25 +1709,51 @@ var WIDGET_CSS = `.ss-w-root { all: revert; }
1641
1709
 
1642
1710
  /* betting insights */
1643
1711
  .ss-w-binsights { display: flex; flex-direction: column; gap: 7px; }
1644
- .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; }
1645
- .ss-w-bi-status { display: flex; align-items: center; gap: 6px; min-width: 0; }
1646
- .ss-w-bi-fixture { font-size: 11px; color: var(--ss-w-ink); font-weight: 600; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
1647
- .ss-w-bi-comp { font-size: 10.5px; color: var(--ss-w-faint); font-weight: 500; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
1712
+ .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; }
1713
+
1714
+ /* row 1: competition logo + name on the left, live badge + kickoff on the right */
1715
+ .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); }
1716
+ .ss-w-bi-header-left { display: inline-flex; align-items: center; gap: 6px; min-width: 0; }
1717
+ .ss-w-bi-header-right { display: inline-flex; align-items: center; gap: 8px; flex: none; }
1718
+ .ss-w-bi-comp { font-size: 10.5px; color: var(--ss-w-faint); font-weight: 600; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
1719
+ .ss-w-bi-kick { font-size: 10.5px; font-weight: 600; color: var(--ss-w-faint); white-space: nowrap; flex: none; }
1648
1720
  .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; }
1649
1721
  .ss-w-bi-live-dot { width: 5px; height: 5px; border-radius: 50%; background: #fff; }
1650
- .ss-w-bi-head { display: flex; align-items: flex-start; gap: 10px; }
1651
- .ss-w-bi-mkt { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
1652
- .ss-w-bi-market { font-size: 15px; font-weight: 700; line-height: 1.25; color: var(--ss-w-ink); }
1653
- .ss-w-bi-sel { font-size: 12px; line-height: 1.3; color: var(--ss-w-faint); }
1654
- .ss-w-bi-match { display: flex; flex-direction: column; gap: 1px; min-width: 0; margin-top: 5px; }
1655
- .ss-w-bi-action { margin-left: auto; flex: none; display: flex; flex-direction: column; align-items: stretch; gap: 6px; }
1722
+ .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; }
1723
+
1724
+ /* crests -- CSS background-image on a span, never <img> (matches .ss-w-ecard-crest precedent) */
1725
+ .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; }
1726
+ .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); }
1727
+ .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)); }
1728
+
1729
+ /* row 2: crest-flanked fixture on the left, boxed action panel on the right */
1730
+ .ss-w-bi-body { display: flex; align-items: center; gap: 10px; }
1731
+ .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); }
1732
+ .ss-w-bi-team { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
1733
+ .ss-w-bi-vs { font-size: 10px; font-weight: 600; color: var(--ss-w-faint); flex: none; }
1734
+ .ss-w-bi-fixture-txt { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
1735
+ .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; }
1656
1736
  .ss-w-bi-action .ss-w-slip > button { flex: 1; text-align: center; }
1657
1737
  /* 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. */
1658
1738
  .ss-w-bi-action .ss-w-toast { left: auto; right: 0; width: max-content; max-width: 210px; white-space: normal; }
1739
+ .ss-w-bi-mkt { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
1740
+ .ss-w-bi-market { font-size: 13.5px; font-weight: 700; line-height: 1.25; color: var(--ss-w-ink); }
1741
+ .ss-w-bi-sel { font-size: 11.5px; line-height: 1.3; color: var(--ss-w-faint); }
1659
1742
  .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; }
1660
1743
  .ss-w-bi-odds-cap { font-size: 9px; font-weight: 700; letter-spacing: .09em; text-transform: uppercase; color: var(--ss-w-faint); }
1661
1744
  .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; }
1662
1745
  .ss-w-bi-book { font-size: 9.5px; color: var(--ss-w-faint); }
1746
+
1747
+ /* row 3: bar-chart glyph + reasoning prose + stats-expander chevron, in a subtle strip */
1748
+ .ss-w-bi-reason { display: flex; align-items: flex-start; gap: 8px; background: var(--ss-w-bubble); border-radius: 10px; padding: 8px 10px; }
1749
+ .ss-w-bi-chart { flex: none; width: 13px; height: 12px; margin-top: 2px; opacity: .55;
1750
+ background:
1751
+ linear-gradient(var(--ss-w-accent), var(--ss-w-accent)) left / 3px 55% no-repeat,
1752
+ linear-gradient(var(--ss-w-accent), var(--ss-w-accent)) center / 3px 100% no-repeat,
1753
+ linear-gradient(var(--ss-w-accent), var(--ss-w-accent)) right / 3px 75% no-repeat;
1754
+ background-position: left bottom, center bottom, right bottom;
1755
+ }
1756
+ .ss-w-bi-reason-body { display: flex; flex-direction: column; gap: 8px; min-width: 0; flex: 1; }
1663
1757
  .ss-w-bi-text { font-size: 12px; line-height: 1.45; color: var(--ss-w-ink); }
1664
1758
  .ss-w-bi-stats { display: flex; flex-direction: column; gap: 8px; }
1665
1759
  .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; }