@sensiblestats/widget-react 0.61.2 → 0.62.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
@@ -1,5 +1,5 @@
1
1
  // src/StatsWidget.tsx
2
- import { useCallback as useCallback9, useEffect as useEffect18, useMemo as useMemo3, useRef as useRef14, useState as useState38 } from "react";
2
+ import { useCallback as useCallback10, useEffect as useEffect19, useMemo as useMemo3, useRef as useRef15, useState as useState38 } from "react";
3
3
  import { StatsWidgetClient } from "@sensiblestats/widget-sdk";
4
4
 
5
5
  // src/kickoff.ts
@@ -614,6 +614,10 @@ var STRINGS = {
614
614
  function resolveLocaleStrings(locale) {
615
615
  return STRINGS[locale] ?? STRINGS.en;
616
616
  }
617
+ function isEnglishLocale(locale) {
618
+ const lang = (locale ?? "").trim().toLowerCase().split(/[-_]/)[0] ?? "";
619
+ return lang === "en" || !Object.prototype.hasOwnProperty.call(STRINGS, lang);
620
+ }
617
621
 
618
622
  // src/config.ts
619
623
  function normalizeUi(cfg) {
@@ -623,6 +627,7 @@ function normalizeUi(cfg) {
623
627
  accent: cfg.accent,
624
628
  logoUrl: cfg.logoUrl,
625
629
  headerTitle: cfg.headerTitle,
630
+ rawBettingLabels: isEnglishLocale(cfg.locale),
626
631
  cta: cfg.cta,
627
632
  surface: cfg.surface,
628
633
  ink: cfg.ink,
@@ -738,6 +743,18 @@ function toUserContext(cfg) {
738
743
 
739
744
  // src/slip.ts
740
745
  var ADD_TO_SLIP_TIMEOUT_MS = 1e4;
746
+ var MAX_SELECTION = 64;
747
+ var MAX_LINE = 32;
748
+ var MAX_ODDS = 1e4;
749
+ function slipPickFields(selection, label, line) {
750
+ const fields = {};
751
+ if (Number.isSafeInteger(selection.matchId) && selection.matchId > 0) fields.matchId = selection.matchId;
752
+ if (label && label.trim() && label.length <= MAX_SELECTION) fields.selection = label;
753
+ if (line && line.trim() && line.length <= MAX_LINE) fields.line = line;
754
+ const odds = selection.oddsDecimal;
755
+ if (Number.isFinite(odds) && odds > 1 && odds <= MAX_ODDS) fields.odds = odds;
756
+ return fields;
757
+ }
741
758
  function wasAdded(result) {
742
759
  if (!result) return true;
743
760
  if (result.status === "price_changed") return result.accepted;
@@ -1024,6 +1041,8 @@ function toMarketOddsVM(card) {
1024
1041
  return {
1025
1042
  key: `${selection}:${str(r.line) ?? ""}:${i}`,
1026
1043
  selection,
1044
+ selectionLabel: str(r.selectionLabel),
1045
+ line: str(r.line),
1027
1046
  odds: price !== void 0 ? price.toFixed(2) : "",
1028
1047
  oddsDecimal: price,
1029
1048
  matchOddsId: typeof r.matchOddsId === "number" ? r.matchOddsId : void 0,
@@ -1777,6 +1796,7 @@ function useLiveMonitor(opts) {
1777
1796
  const inFlightRef = useRef3(false);
1778
1797
  const timerRef = useRef3(void 0);
1779
1798
  const disabledRef = useRef3(false);
1799
+ const mountedRef = useRef3(false);
1780
1800
  const getLiveMonitorRef = useRef3(opts.getLiveMonitor);
1781
1801
  getLiveMonitorRef.current = opts.getLiveMonitor;
1782
1802
  const tick = useCallback2(async () => {
@@ -1784,6 +1804,7 @@ function useLiveMonitor(opts) {
1784
1804
  inFlightRef.current = true;
1785
1805
  try {
1786
1806
  const snap = await getLiveMonitorRef.current(pinsRef.current);
1807
+ if (!mountedRef.current) return;
1787
1808
  if (snap === null) {
1788
1809
  disabledRef.current = true;
1789
1810
  applySnapshot(null);
@@ -1805,10 +1826,16 @@ function useLiveMonitor(opts) {
1805
1826
  });
1806
1827
  scheduleRef.current = () => {
1807
1828
  window.clearTimeout(timerRef.current);
1808
- if (disabledRef.current || pinsRef.current.length === 0 || document.visibilityState === "hidden") return;
1829
+ if (!mountedRef.current || disabledRef.current || pinsRef.current.length === 0 || document.visibilityState === "hidden") return;
1809
1830
  const hinted = (snapshotRef.current?.nextPollSeconds ?? DEFAULT_POLL_MS / 1e3) * 1e3;
1810
1831
  timerRef.current = window.setTimeout(() => void tick(), Math.max(MIN_POLL_MS, hinted));
1811
1832
  };
1833
+ useEffect3(() => {
1834
+ mountedRef.current = true;
1835
+ return () => {
1836
+ mountedRef.current = false;
1837
+ };
1838
+ }, []);
1812
1839
  const prevPinsLengthRef = useRef3(0);
1813
1840
  useEffect3(() => {
1814
1841
  const wasEmpty = prevPinsLengthRef.current === 0;
@@ -2203,8 +2230,31 @@ function useEventLog() {
2203
2230
  return useContext(EventLogContext);
2204
2231
  }
2205
2232
 
2233
+ // src/popupTelemetry.ts
2234
+ import { useCallback as useCallback6, useEffect as useEffect7, useRef as useRef7 } from "react";
2235
+ function usePopupTelemetry(log, isOpen, popupText, dismissPopup) {
2236
+ const shownRef = useRef7(/* @__PURE__ */ new Set());
2237
+ const closedTextRef = useRef7(null);
2238
+ useEffect7(() => {
2239
+ if (isOpen) {
2240
+ if (closedTextRef.current) log?.log({ name: "popup_opened" });
2241
+ closedTextRef.current = null;
2242
+ return;
2243
+ }
2244
+ closedTextRef.current = popupText;
2245
+ if (popupText && !shownRef.current.has(popupText)) {
2246
+ shownRef.current.add(popupText);
2247
+ log?.log({ name: "popup_shown" });
2248
+ }
2249
+ }, [log, isOpen, popupText]);
2250
+ return useCallback6(() => {
2251
+ log?.log({ name: "popup_dismissed" });
2252
+ dismissPopup();
2253
+ }, [log, dismissPopup]);
2254
+ }
2255
+
2206
2256
  // src/components/ChatFab.tsx
2207
- import { useEffect as useEffect7, useRef as useRef7, useState as useState7 } from "react";
2257
+ import { useEffect as useEffect8, useRef as useRef8, useState as useState7 } from "react";
2208
2258
 
2209
2259
  // src/components/BrandMark.tsx
2210
2260
  import { useState as useState6 } from "react";
@@ -2282,8 +2332,8 @@ import { jsx as jsx4, jsxs as jsxs3 } from "react/jsx-runtime";
2282
2332
  function ChatFab({ label, greeting, popupText, logoUrl, onOpen, onDismissPopup, dismissLabel = "Dismiss", introAllowed = false, onIntroStart }) {
2283
2333
  const bubble = popupText ?? greeting;
2284
2334
  const [intro, setIntro] = useState7(false);
2285
- const armedRef = useRef7(introAllowed);
2286
- useEffect7(() => {
2335
+ const armedRef = useRef8(introAllowed);
2336
+ useEffect8(() => {
2287
2337
  if (armedRef.current && bubble) {
2288
2338
  armedRef.current = false;
2289
2339
  setIntro(true);
@@ -2297,10 +2347,10 @@ function ChatFab({ label, greeting, popupText, logoUrl, onOpen, onDismissPopup,
2297
2347
  }
2298
2348
 
2299
2349
  // src/components/ChatPanel.tsx
2300
- import { useEffect as useEffect17, useState as useState37 } from "react";
2350
+ import { useEffect as useEffect18, useState as useState37 } from "react";
2301
2351
 
2302
2352
  // src/components/ChatHeader.tsx
2303
- import { useEffect as useEffect8, useRef as useRef8, useState as useState8 } from "react";
2353
+ import { useEffect as useEffect9, useRef as useRef9, useState as useState8 } from "react";
2304
2354
  import { jsx as jsx5, jsxs as jsxs4 } from "react/jsx-runtime";
2305
2355
  function ChatHeader({
2306
2356
  onClose,
@@ -2313,14 +2363,14 @@ function ChatHeader({
2313
2363
  moreOptionsLabel = "More options"
2314
2364
  }) {
2315
2365
  const [menuOpen, setMenuOpen] = useState8(false);
2316
- const wrapRef = useRef8(null);
2317
- const kebabRef = useRef8(null);
2318
- const firstItemRef = useRef8(null);
2366
+ const wrapRef = useRef9(null);
2367
+ const kebabRef = useRef9(null);
2368
+ const firstItemRef = useRef9(null);
2319
2369
  const hasMenu = !!(canReset && onNewConversation);
2320
- useEffect8(() => {
2370
+ useEffect9(() => {
2321
2371
  if (menuOpen) firstItemRef.current?.focus();
2322
2372
  }, [menuOpen]);
2323
- useEffect8(() => {
2373
+ useEffect9(() => {
2324
2374
  if (!menuOpen) return;
2325
2375
  const onPointer = (e) => {
2326
2376
  if (!wrapRef.current?.contains(e.target)) setMenuOpen(false);
@@ -2376,7 +2426,7 @@ function ChatHeader({
2376
2426
  }
2377
2427
 
2378
2428
  // src/components/MessageList.tsx
2379
- import { useEffect as useEffect12, useRef as useRef10 } from "react";
2429
+ import { useEffect as useEffect13, useRef as useRef11 } from "react";
2380
2430
 
2381
2431
  // src/components/Markdown.tsx
2382
2432
  import ReactMarkdown from "react-markdown";
@@ -2506,15 +2556,15 @@ function EntityCardList({ cards, disabled, showLessLabel = "Show less", showMore
2506
2556
  import { useState as useState13 } from "react";
2507
2557
 
2508
2558
  // src/components/AddToSlipButton.tsx
2509
- import { useCallback as useCallback6, useState as useState10 } from "react";
2559
+ import { useCallback as useCallback7, useState as useState10 } from "react";
2510
2560
  import { isAddToSlipResult } from "@sensiblestats/widget-sdk";
2511
2561
 
2512
2562
  // src/components/Toast.tsx
2513
- import { useEffect as useEffect9 } from "react";
2563
+ import { useEffect as useEffect10 } from "react";
2514
2564
  import { jsx as jsx8 } from "react/jsx-runtime";
2515
2565
  var DISMISS_MS = 4e3;
2516
2566
  function Toast({ toast, onDismiss }) {
2517
- useEffect9(() => {
2567
+ useEffect10(() => {
2518
2568
  if (!toast) return;
2519
2569
  const timer = setTimeout(onDismiss, DISMISS_MS);
2520
2570
  return () => clearTimeout(timer);
@@ -2531,18 +2581,19 @@ function withTimeout(value) {
2531
2581
  new Promise((_, reject) => setTimeout(() => reject(new Error("add-to-slip timed out")), ADD_TO_SLIP_TIMEOUT_MS))
2532
2582
  ]);
2533
2583
  }
2534
- function SlipButton({ selection, shownOdds, marketName, marketDeveloperName, slip, ui }) {
2584
+ function SlipButton({ selection, shownOdds, marketDeveloperName, selectionLabel, line, slip, ui }) {
2535
2585
  const [pending, setPending] = useState10(false);
2536
2586
  const [settled, setSettled] = useState10(false);
2537
2587
  const [toast, setToast] = useState10(null);
2538
- const dismiss = useCallback6(() => setToast(null), []);
2588
+ const dismiss = useCallback7(() => setToast(null), []);
2539
2589
  const log = useEventLog();
2540
2590
  const handler = slip.onAddToSlip;
2541
2591
  if (!ui || !handler || !selection) return null;
2542
2592
  const onClick = async () => {
2543
2593
  setPending(true);
2594
+ const pick = slipPickFields(selection, ui.rawBettingLabels ? selectionLabel : void 0, line);
2544
2595
  if (marketDeveloperName !== void 0) {
2545
- log?.log({ name: "slip_click", entityType: "market", marketDeveloperName, entityId: marketDeveloperName });
2596
+ log?.log({ name: "slip_click", entityType: "market", marketDeveloperName, entityId: marketDeveloperName, ...pick });
2546
2597
  }
2547
2598
  try {
2548
2599
  const raw = await withTimeout(Promise.resolve(handler(selection)));
@@ -2554,20 +2605,15 @@ function SlipButton({ selection, shownOdds, marketName, marketDeveloperName, sli
2554
2605
  entityType: "market",
2555
2606
  marketDeveloperName,
2556
2607
  entityId: marketDeveloperName,
2557
- outcome: slipOutcome(result)
2608
+ outcome: slipOutcome(result),
2609
+ ...pick
2558
2610
  });
2559
2611
  }
2560
2612
  setToast(resultToToast(result, shownOdds || selection.oddsDecimal.toFixed(2), ui));
2561
2613
  setSettled(isTerminal(result));
2562
- if (wasAdded(result) && slip.onMarketClicked) {
2563
- try {
2564
- slip.onMarketClicked(marketName);
2565
- } catch {
2566
- }
2567
- }
2568
2614
  } catch {
2569
2615
  if (marketDeveloperName !== void 0) {
2570
- log?.log({ name: "slip_result", entityType: "market", marketDeveloperName, entityId: marketDeveloperName, outcome: "timeout" });
2616
+ log?.log({ name: "slip_result", entityType: "market", marketDeveloperName, entityId: marketDeveloperName, outcome: "timeout", ...pick });
2571
2617
  }
2572
2618
  setToast({ kind: "error", text: ui.slipFailed });
2573
2619
  } finally {
@@ -2595,8 +2641,9 @@ function AddToSlipButton({ it, slip, ui }) {
2595
2641
  {
2596
2642
  selection,
2597
2643
  shownOdds: it.odds ?? "",
2598
- marketName: it.market,
2599
2644
  marketDeveloperName: it.marketDeveloperName,
2645
+ selectionLabel: it.selectionLabel,
2646
+ line: it.line,
2600
2647
  slip,
2601
2648
  ui
2602
2649
  }
@@ -2640,7 +2687,7 @@ function Crest2({ url, name, kind }) {
2640
2687
  }
2641
2688
 
2642
2689
  // src/components/PinToggle.tsx
2643
- import { useCallback as useCallback7, useState as useState12 } from "react";
2690
+ import { useCallback as useCallback8, useState as useState12 } from "react";
2644
2691
  import { jsx as jsx12, jsxs as jsxs8 } from "react/jsx-runtime";
2645
2692
  var PIN_GLYPHS = {
2646
2693
  pin: { viewBox: "0 0 24 24", strokeWidth: 1.4, transform: "scale(1.5)", d: "M9.5 1.5 14.5 6.5 13 8l-.5-.5-3 3 .3 2.2-1.4 1.4L5.6 11.3l-3.3 3.3-1-1 3.3-3.3L1.9 7.5l1.4-1.4 2.2.3 3-3L8 2.9z" },
@@ -2649,7 +2696,7 @@ var PIN_GLYPHS = {
2649
2696
  };
2650
2697
  function PinToggle({ pin, monitor, ui }) {
2651
2698
  const [toast, setToast] = useState12(null);
2652
- const dismiss = useCallback7(() => setToast(null), []);
2699
+ const dismiss = useCallback8(() => setToast(null), []);
2653
2700
  const log = useEventLog();
2654
2701
  if (!monitor || !monitor.enabled) return null;
2655
2702
  const key = pinKey(pin);
@@ -2853,7 +2900,7 @@ function OddsRow({ vm, row, ui, slip, inSlipIds }) {
2853
2900
  "\u2713 ",
2854
2901
  ui.inSlipLabel
2855
2902
  ] }) : null,
2856
- /* @__PURE__ */ jsx14(SlipButton, { selection, shownOdds: row.odds, marketName: vm.market, marketDeveloperName: vm.marketDeveloperName, slip, ui })
2903
+ /* @__PURE__ */ jsx14(SlipButton, { selection, shownOdds: row.odds, marketDeveloperName: vm.marketDeveloperName, selectionLabel: row.selectionLabel, line: row.line, slip, ui })
2857
2904
  ] });
2858
2905
  }
2859
2906
  function MarketOddsCard({ vm, ui, slip, monitor, inSlipIds, hideHeader, pinRef }) {
@@ -3025,7 +3072,37 @@ function ActionButtons({ actions, loading, disabled, onAct }) {
3025
3072
  // src/components/dashboard/DashboardTile.tsx
3026
3073
  import { jsx as jsx17, jsxs as jsxs13 } from "react/jsx-runtime";
3027
3074
  var MAX_CRESTS = 3;
3028
- function DashboardTile({ tile, onTap }) {
3075
+ var ODDS_TAIL = /\s*·\s*@(\d+(?:\.\d+)?)\s*$/;
3076
+ function splitOdds(subtitle) {
3077
+ const m = ODDS_TAIL.exec(subtitle);
3078
+ return m ? { text: subtitle.slice(0, m.index), odds: m[1] } : { text: subtitle };
3079
+ }
3080
+ function HeroTile({ tile, onTap, oddsLabel }) {
3081
+ const crests = (tile.imageUrls ?? []).filter(Boolean).slice(0, 2);
3082
+ const sub = tile.subtitle ? splitOdds(tile.subtitle) : null;
3083
+ return /* @__PURE__ */ jsxs13("button", { type: "button", className: "ss-w-dash-tile ss-w-dash-tile--full ss-w-dash-hero", onClick: onTap, children: [
3084
+ /* @__PURE__ */ jsxs13("span", { className: "ss-w-dash-hero-top", children: [
3085
+ tile.badgeText ? /* @__PURE__ */ jsx17("span", { className: "ss-w-dash-hero-badge", children: tile.badgeText }) : null,
3086
+ sub?.odds ? /* @__PURE__ */ jsxs13("span", { className: "ss-w-dash-hero-price", children: [
3087
+ /* @__PURE__ */ jsxs13("span", { className: "ss-g-sr", children: [
3088
+ oddsLabel,
3089
+ " "
3090
+ ] }),
3091
+ sub.odds
3092
+ ] }) : null,
3093
+ /* @__PURE__ */ jsx17("span", { className: "ss-w-dash-hero-chevron", children: /* @__PURE__ */ jsx17(ChevronIcon, {}) })
3094
+ ] }),
3095
+ /* @__PURE__ */ jsxs13("span", { className: "ss-w-dash-hero-main", children: [
3096
+ /* @__PURE__ */ jsx17("span", { className: "ss-w-dash-hero-visual", "aria-hidden": "true", children: crests.length > 0 ? crests.map((url, i) => /* @__PURE__ */ jsx17("span", { className: "ss-w-dash-hero-crest", style: { backgroundImage: `url(${url})` } }, `${url}-${i}`)) : /* @__PURE__ */ jsx17("span", { className: "ss-w-dash-hero-icon", children: /* @__PURE__ */ jsx17(DashboardIcon, { name: tile.icon }) }) }),
3097
+ /* @__PURE__ */ jsxs13("span", { className: "ss-w-dash-hero-body", children: [
3098
+ /* @__PURE__ */ jsx17("span", { className: "ss-w-dash-hero-title", children: tile.title }),
3099
+ sub?.text ? /* @__PURE__ */ jsx17("span", { className: "ss-w-dash-hero-subtitle", children: sub.text }) : null
3100
+ ] })
3101
+ ] })
3102
+ ] });
3103
+ }
3104
+ function DashboardTile({ tile, onTap, oddsLabel = "Odds" }) {
3105
+ if (tile.emphasis === "hero") return /* @__PURE__ */ jsx17(HeroTile, { tile, onTap, oddsLabel });
3029
3106
  const spanClass = tile.span === "full" ? "ss-w-dash-tile--full" : "ss-w-dash-tile--half";
3030
3107
  const crests = (tile.imageUrls ?? []).slice(0, MAX_CRESTS);
3031
3108
  const hasMeta = Boolean(tile.badgeText) || crests.length > 0;
@@ -3058,13 +3135,13 @@ var LAYOUT_CLASSES = {
3058
3135
  function layoutClass(layout) {
3059
3136
  return Object.prototype.hasOwnProperty.call(LAYOUT_CLASSES, layout) ? LAYOUT_CLASSES[layout] : LAYOUT_CLASSES.goal;
3060
3137
  }
3061
- function GreetingDashboard({ dashboard, onTap }) {
3138
+ function GreetingDashboard({ dashboard, onTap, oddsLabel }) {
3062
3139
  return /* @__PURE__ */ jsxs14("div", { className: `ss-w-dash ${layoutClass(dashboard.layout)}`, children: [
3063
3140
  /* @__PURE__ */ jsxs14("div", { className: "ss-w-dash-head", children: [
3064
3141
  /* @__PURE__ */ jsx18("span", { className: "ss-w-dash-title", children: dashboard.title }),
3065
3142
  dashboard.subtitle ? /* @__PURE__ */ jsx18("span", { className: "ss-w-dash-subtitle", children: dashboard.subtitle }) : null
3066
3143
  ] }),
3067
- /* @__PURE__ */ jsx18("div", { className: "ss-w-dash-grid", children: dashboard.tiles.map((t, i) => /* @__PURE__ */ jsx18(DashboardTile, { tile: t, onTap: () => onTap(t, i) }, `${t.title}-${i}`)) }),
3144
+ /* @__PURE__ */ jsx18("div", { className: "ss-w-dash-grid", children: dashboard.tiles.map((t, i) => /* @__PURE__ */ jsx18(DashboardTile, { tile: t, onTap: () => onTap(t, i), oddsLabel }, `${t.title}-${i}`)) }),
3068
3145
  dashboard.footerTip ? /* @__PURE__ */ jsx18("span", { className: "ss-w-dash-foot", children: dashboard.footerTip }) : null
3069
3146
  ] });
3070
3147
  }
@@ -3160,7 +3237,7 @@ function FixtureCard({ card }) {
3160
3237
  }
3161
3238
 
3162
3239
  // src/components/dashboard/CountdownCard.tsx
3163
- import { Fragment as Fragment6, useEffect as useEffect10, useState as useState15 } from "react";
3240
+ import { Fragment as Fragment6, useEffect as useEffect11, useState as useState15 } from "react";
3164
3241
  import { jsx as jsx20, jsxs as jsxs16 } from "react/jsx-runtime";
3165
3242
  var DAY_MS = 864e5;
3166
3243
  function remainingSegments(kickoffUnix, nowMs, withSeconds) {
@@ -3184,7 +3261,7 @@ function CountdownCard({ card }) {
3184
3261
  () => typeof window !== "undefined" && window.matchMedia("(prefers-reduced-motion: reduce)").matches
3185
3262
  );
3186
3263
  const withinDay = data.kickoffUnix * 1e3 - nowMs < DAY_MS;
3187
- useEffect10(() => {
3264
+ useEffect11(() => {
3188
3265
  if (reduced) return;
3189
3266
  const id = window.setInterval(() => setNowMs(Date.now()), withinDay ? 1e3 : 6e4);
3190
3267
  return () => window.clearInterval(id);
@@ -3541,10 +3618,10 @@ function RestingFormCard({ card }) {
3541
3618
  }
3542
3619
 
3543
3620
  // src/components/dashboard/LiveScoreCard.tsx
3544
- import { useRef as useRef9, useState as useState19 } from "react";
3621
+ import { useRef as useRef10, useState as useState19 } from "react";
3545
3622
 
3546
3623
  // src/useLiveClock.ts
3547
- import { useEffect as useEffect11, useState as useState18 } from "react";
3624
+ import { useEffect as useEffect12, useState as useState18 } from "react";
3548
3625
  var TICK_MS = 6e4;
3549
3626
  var MAX_LIVE_TICK_MINUTES = 15;
3550
3627
  function computeDisplayMinute(minute, clockRunning, receivedAtMs) {
@@ -3556,7 +3633,7 @@ function useLiveClock(minute, clockRunning, receivedAtMs) {
3556
3633
  const [displayMinute, setDisplayMinute] = useState18(
3557
3634
  () => computeDisplayMinute(minute, clockRunning, receivedAtMs)
3558
3635
  );
3559
- useEffect11(() => {
3636
+ useEffect12(() => {
3560
3637
  setDisplayMinute(computeDisplayMinute(minute, clockRunning, receivedAtMs));
3561
3638
  if (!clockRunning) return;
3562
3639
  const id = window.setInterval(() => {
@@ -3592,7 +3669,7 @@ function LiveScoreCard({
3592
3669
  }) {
3593
3670
  const { data } = card;
3594
3671
  const seedKey = `${data.minute}:${data.homeScore}:${data.awayScore}`;
3595
- const seedRef = useRef9(null);
3672
+ const seedRef = useRef10(null);
3596
3673
  if (!seedRef.current || seedRef.current.key !== seedKey) {
3597
3674
  seedRef.current = { key: seedKey, receivedAtMs: Date.now() };
3598
3675
  }
@@ -3881,7 +3958,7 @@ function ChatMessage({ message, disabled, onAct, onTap, ui, slip, monitor }) {
3881
3958
  };
3882
3959
  return /* @__PURE__ */ jsxs37("div", { className: "ss-w-msg ss-w-bot", "data-msg-id": message.id, children: [
3883
3960
  message.text && !message.greetingDashboard && !message.clubFanDashboard ? /* @__PURE__ */ jsx41(Markdown, { text: message.text }) : null,
3884
- message.greetingDashboard && !message.clubFanDashboard ? /* @__PURE__ */ jsx41(GreetingDashboard, { dashboard: message.greetingDashboard, onTap }) : null,
3961
+ message.greetingDashboard && !message.clubFanDashboard ? /* @__PURE__ */ jsx41(GreetingDashboard, { dashboard: message.greetingDashboard, onTap, oddsLabel: ui?.oddsLabel }) : null,
3885
3962
  message.clubFanDashboard ? /* @__PURE__ */ jsx41(ClubFanDashboard, { dashboard: message.clubFanDashboard, onTap: onFanDashboardTap, ui }) : null,
3886
3963
  /* @__PURE__ */ jsx41(EntityCardList, { cards: message.cards, disabled, showLessLabel: ui?.showLess, showMoreLabel: ui?.showMore }),
3887
3964
  /* @__PURE__ */ jsx41(BettingInsightList, { insights: message.insights, ui, slip, monitor }),
@@ -3914,11 +3991,11 @@ function turnAnchor(messages) {
3914
3991
  }
3915
3992
  function MessageList({ controller, ui, slip, onDashboardTap, monitor }) {
3916
3993
  const { state, send } = controller;
3917
- const listRef = useRef10(null);
3918
- const endRef = useRef10(null);
3919
- const pinnedRef = useRef10(true);
3920
- const anchoredTurnRef = useRef10(null);
3921
- useEffect12(() => {
3994
+ const listRef = useRef11(null);
3995
+ const endRef = useRef11(null);
3996
+ const pinnedRef = useRef11(true);
3997
+ const anchoredTurnRef = useRef11(null);
3998
+ useEffect13(() => {
3922
3999
  const anchor = turnAnchor(state.messages);
3923
4000
  const list = listRef.current;
3924
4001
  if (anchor && anchor.turnId !== anchoredTurnRef.current) {
@@ -4207,8 +4284,8 @@ function MonitorMatchCard({ match, promotedPins, monitor, ui, slip }) {
4207
4284
  {
4208
4285
  selection,
4209
4286
  shownOdds: typeof row.market.price === "number" ? row.market.price.toFixed(2) : "",
4210
- marketName: row.market.marketLabel,
4211
4287
  marketDeveloperName: row.market.marketDeveloperName,
4288
+ selectionLabel: row.market.selectionLabel,
4212
4289
  slip,
4213
4290
  ui
4214
4291
  }
@@ -4400,7 +4477,7 @@ function TodayPanel({ discovery, ui, monitor }) {
4400
4477
  }
4401
4478
 
4402
4479
  // src/components/GamesPanel.tsx
4403
- import { useEffect as useEffect16, useState as useState35 } from "react";
4480
+ import { useEffect as useEffect17, useState as useState35 } from "react";
4404
4481
 
4405
4482
  // src/components/GamesLoginWall.tsx
4406
4483
  import { jsx as jsx52 } from "react/jsx-runtime";
@@ -4409,7 +4486,7 @@ function GamesLoginWall({ ui }) {
4409
4486
  }
4410
4487
 
4411
4488
  // src/components/GamesHub.tsx
4412
- import { useEffect as useEffect13, useState as useState22 } from "react";
4489
+ import { useEffect as useEffect14, useState as useState22 } from "react";
4413
4490
 
4414
4491
  // src/components/games/ui/gameMeta.ts
4415
4492
  function prettifyGameId(gameId) {
@@ -4595,7 +4672,7 @@ function pickHeroGame(games) {
4595
4672
  var tileKey = (g) => `${g.gameId}-${g.gameDay}-${g.fixtureId}`;
4596
4673
  function GamesHub({ games, myWeeklyRow, weeklyPrize, onOpen, ui }) {
4597
4674
  const [, setTick] = useState22(0);
4598
- useEffect13(() => {
4675
+ useEffect14(() => {
4599
4676
  const id = setInterval(() => setTick((t) => t + 1), 6e4);
4600
4677
  return () => clearInterval(id);
4601
4678
  }, []);
@@ -4655,10 +4732,10 @@ function GamesHub({ games, myWeeklyRow, weeklyPrize, onOpen, ui }) {
4655
4732
  }
4656
4733
 
4657
4734
  // src/components/GameScreenHost.tsx
4658
- import { useEffect as useEffect15, useRef as useRef13, useState as useState34 } from "react";
4735
+ import { useEffect as useEffect16, useRef as useRef14, useState as useState34 } from "react";
4659
4736
 
4660
4737
  // src/useGamePlay.ts
4661
- import { useCallback as useCallback8, useRef as useRef11, useState as useState23 } from "react";
4738
+ import { useCallback as useCallback9, useRef as useRef12, useState as useState23 } from "react";
4662
4739
  function toSubmitResult(payload) {
4663
4740
  return {
4664
4741
  // 'pending' means the play is recorded but awaiting resolution (fixture games)
@@ -4673,14 +4750,14 @@ function useGamePlay(conversation, { gameId, gameDay, fixtureId, locale, refresh
4673
4750
  const [result, setResult] = useState23(null);
4674
4751
  const [submitting, setSubmitting] = useState23(false);
4675
4752
  const [rawStatus, setRawStatus] = useState23(null);
4676
- const inFlightRef = useRef11(false);
4677
- const convRef = useRef11(conversation);
4753
+ const inFlightRef = useRef12(false);
4754
+ const convRef = useRef12(conversation);
4678
4755
  convRef.current = conversation;
4679
- const optsRef = useRef11({ gameId, gameDay, fixtureId, locale, refresh });
4756
+ const optsRef = useRef12({ gameId, gameDay, fixtureId, locale, refresh });
4680
4757
  optsRef.current = { gameId, gameDay, fixtureId, locale, refresh };
4681
- const resultRef = useRef11(null);
4758
+ const resultRef = useRef12(null);
4682
4759
  resultRef.current = result;
4683
- const refetch = useCallback8(async () => {
4760
+ const refetch = useCallback9(async () => {
4684
4761
  const conv = convRef.current;
4685
4762
  if (!conv) return;
4686
4763
  const opts = optsRef.current;
@@ -4696,7 +4773,7 @@ function useGamePlay(conversation, { gameId, gameDay, fixtureId, locale, refresh
4696
4773
  } catch {
4697
4774
  }
4698
4775
  }, []);
4699
- const submit = useCallback8(async (answerJson) => {
4776
+ const submit = useCallback9(async (answerJson) => {
4700
4777
  const conv = convRef.current;
4701
4778
  if (!conv || inFlightRef.current) return;
4702
4779
  inFlightRef.current = true;
@@ -4723,7 +4800,7 @@ function useGamePlay(conversation, { gameId, gameDay, fixtureId, locale, refresh
4723
4800
  setSubmitting(false);
4724
4801
  }
4725
4802
  }, [refetch]);
4726
- const hydrate = useCallback8(() => {
4803
+ const hydrate = useCallback9(() => {
4727
4804
  void refetch();
4728
4805
  }, [refetch]);
4729
4806
  return { result, submitting, submit, hydrate, rawStatus };
@@ -5524,7 +5601,7 @@ function CareerPathGame({
5524
5601
  /* @__PURE__ */ jsx70(GameHeader, { title, eyebrow: s.eyebrow, right, onBack, backLabel: ui.back }),
5525
5602
  /* @__PURE__ */ jsxs58("div", { className: "ss-g-body", children: [
5526
5603
  /* @__PURE__ */ jsxs58("div", { className: "ss-g-cp-card", children: [
5527
- /* @__PURE__ */ jsx70(Silhouette, { size: 64, variant: isOver ? "default" : "mystery" }),
5604
+ isOver && clientState?.revealedPlayerName ? /* @__PURE__ */ jsx70(PlayerAvatar, { className: "ss-g-cp-photo", name: clientState.revealedPlayerName, imagePath: clientState.revealedPlayerImagePath }) : /* @__PURE__ */ jsx70(Silhouette, { size: 64, variant: isOver ? "default" : "mystery" }),
5528
5605
  /* @__PURE__ */ jsxs58("div", { className: "ss-g-cp-card-text", children: [
5529
5606
  /* @__PURE__ */ jsx70("span", { className: "ss-g-cp-card-title", children: isOver && clientState?.revealedPlayerName ? clientState.revealedPlayerName : s.mysteryPlayer }),
5530
5607
  clientState?.hintLine ? /* @__PURE__ */ jsx70("span", { className: "ss-g-cp-hint", children: clientState.hintLine }) : !isOver ? /* @__PURE__ */ jsxs58("span", { className: "ss-g-cp-locks", children: [
@@ -5608,7 +5685,7 @@ function CareerPathGame({
5608
5685
  }
5609
5686
 
5610
5687
  // src/components/games/BeatTheGridGame.tsx
5611
- import React, { useEffect as useEffect14, useId as useId4, useRef as useRef12, useState as useState28 } from "react";
5688
+ import React, { useEffect as useEffect15, useId as useId4, useRef as useRef13, useState as useState28 } from "react";
5612
5689
  import { Fragment as Fragment14, jsx as jsx71, jsxs as jsxs59 } from "react/jsx-runtime";
5613
5690
  function criterionIconKind(label, key) {
5614
5691
  const l = (key ?? label).toLowerCase();
@@ -5691,8 +5768,8 @@ function SearchSheet({
5691
5768
  const s = ui.games.beatTheGrid;
5692
5769
  const [query, setQuery] = useState28("");
5693
5770
  const inputId = useId4();
5694
- const inputRef = useRef12(null);
5695
- useEffect14(() => {
5771
+ const inputRef = useRef13(null);
5772
+ useEffect15(() => {
5696
5773
  inputRef.current?.focus();
5697
5774
  }, [row, col]);
5698
5775
  const filtered = playerPool.filter(
@@ -5940,7 +6017,8 @@ function toRows(positions) {
5940
6017
  bucket.push(p);
5941
6018
  byRow.set(rowKey, bucket);
5942
6019
  });
5943
- return [...byRow.keys()].sort((a, b) => b - a).map((k) => byRow.get(k));
6020
+ const col = (p) => Number(p.slot?.split(":")[1]) || 0;
6021
+ return [...byRow.keys()].sort((a, b) => b - a).map((k) => [...byRow.get(k)].sort((a, b) => col(b) - col(a)));
5944
6022
  }
5945
6023
  function formationLabel(rowsAttackFirst) {
5946
6024
  if (rowsAttackFirst.length < 2) return "";
@@ -6993,7 +7071,7 @@ function GameScreenHost({
6993
7071
  }) {
6994
7072
  const { gameId, fixtureId, gameDay = "", lockAtUtc = null } = selected;
6995
7073
  const [configJson, setConfigJson] = useState34(null);
6996
- useEffect15(() => {
7074
+ useEffect16(() => {
6997
7075
  setConfigJson(null);
6998
7076
  if (!conversation) return;
6999
7077
  let cancelled = false;
@@ -7016,12 +7094,12 @@ function GameScreenHost({
7016
7094
  fixtureId: fixtureId || void 0,
7017
7095
  locale: ui.games.lang
7018
7096
  });
7019
- useEffect15(() => {
7097
+ useEffect16(() => {
7020
7098
  if (conversation) hydrate();
7021
7099
  }, [conversation, gameId]);
7022
- const hydrateRef = useRef13(hydrate);
7100
+ const hydrateRef = useRef14(hydrate);
7023
7101
  hydrateRef.current = hydrate;
7024
- useEffect15(() => {
7102
+ useEffect16(() => {
7025
7103
  if (!FIXTURE_GAME_IDS.has(gameId)) return;
7026
7104
  if (rawStatus !== "pending") return;
7027
7105
  const timerId = setInterval(() => {
@@ -7157,7 +7235,7 @@ function GamesPanel({
7157
7235
  refreshHub
7158
7236
  }) {
7159
7237
  const [selected, setSelected] = useState35(null);
7160
- useEffect16(() => {
7238
+ useEffect17(() => {
7161
7239
  ensureGamesFonts();
7162
7240
  }, []);
7163
7241
  if (!loggedIn) return /* @__PURE__ */ jsx80(GamesLoginWall, { ui });
@@ -7357,7 +7435,7 @@ import { jsx as jsx85, jsxs as jsxs70 } from "react/jsx-runtime";
7357
7435
  var FULLSCREEN_QUERY = "(max-width: 639px)";
7358
7436
  function useIsModal() {
7359
7437
  const [modal, setModal] = useState37(false);
7360
- useEffect17(() => {
7438
+ useEffect18(() => {
7361
7439
  if (typeof window === "undefined" || typeof window.matchMedia !== "function") return;
7362
7440
  const mql = window.matchMedia(FULLSCREEN_QUERY);
7363
7441
  const sync = () => setModal(mql.matches);
@@ -7370,7 +7448,7 @@ function useIsModal() {
7370
7448
  function ChatPanel({ controller, ui, slip, onDashboardTap, monitor, discovery, gamesResult, leaderboardResult, loggedIn, conversation }) {
7371
7449
  const { state, close, stop, send, reset } = controller;
7372
7450
  const isModal = useIsModal();
7373
- useEffect17(() => {
7451
+ useEffect18(() => {
7374
7452
  const onKey = (e) => {
7375
7453
  if (e.key === "Escape") close();
7376
7454
  };
@@ -7381,7 +7459,7 @@ function ChatPanel({ controller, ui, slip, onDashboardTap, monitor, discovery, g
7381
7459
  monitor?.setActiveTab("chat");
7382
7460
  send(text);
7383
7461
  };
7384
- useEffect17(() => {
7462
+ useEffect18(() => {
7385
7463
  if (monitor && monitor.activeTab !== "chat" && state.isStreaming) monitor.markChatActivity();
7386
7464
  }, [monitor, state.isStreaming, state.messages.length]);
7387
7465
  return /* @__PURE__ */ jsxs70("div", { className: "ss-w-panel", role: "dialog", "aria-modal": isModal, "aria-label": ui.chatDialogLabel, children: [
@@ -7992,6 +8070,59 @@ var WIDGET_CSS = `.ss-w-root { all: revert; }
7992
8070
  .ss-w-dash--discovery .ss-w-dash-tile:nth-child(2) { background: color-mix(in srgb, #3a82f6 10%, var(--ss-w-panel)); }
7993
8071
  .ss-w-dash--discovery .ss-w-dash-tile:nth-child(3) { background: color-mix(in srgb, #f5a623 10%, var(--ss-w-panel)); }
7994
8072
  .ss-w-dash--discovery .ss-w-dash-tile:nth-child(4) { background: color-mix(in srgb, #a855f7 10%, var(--ss-w-panel)); }
8073
+ /* A hero tile would take the first tint position; count only regular tiles where \`of S\` parses. */
8074
+ .ss-w-dash--discovery .ss-w-dash-tile:nth-child(1 of :not(.ss-w-dash-hero)) { background: color-mix(in srgb, #34c759 10%, var(--ss-w-panel)); }
8075
+ .ss-w-dash--discovery .ss-w-dash-tile:nth-child(2 of :not(.ss-w-dash-hero)) { background: color-mix(in srgb, #3a82f6 10%, var(--ss-w-panel)); }
8076
+ .ss-w-dash--discovery .ss-w-dash-tile:nth-child(3 of :not(.ss-w-dash-hero)) { background: color-mix(in srgb, #f5a623 10%, var(--ss-w-panel)); }
8077
+ .ss-w-dash--discovery .ss-w-dash-tile:nth-child(4 of :not(.ss-w-dash-hero)) { background: color-mix(in srgb, #a855f7 10%, var(--ss-w-panel)); }
8078
+
8079
+ /* Hero: the personalized lead tile. Declared after the layout rules and at three-class
8080
+ specificity so it keeps one look in every layout -- no market rail, no goal pill, no
8081
+ discovery tint or stacking. Colours derive from accent/panel/ink so an operator surface
8082
+ and dark mode both work without their own rules. */
8083
+ .ss-w-dash .ss-w-dash-grid .ss-w-dash-hero {
8084
+ display: flex; flex-direction: column; align-items: stretch; gap: 10px;
8085
+ padding: 12px 14px 14px; border-radius: 16px;
8086
+ border: 1px solid color-mix(in srgb, var(--ss-w-accent) 38%, var(--ss-w-line));
8087
+ background:
8088
+ radial-gradient(120% 140% at 0% 0%, color-mix(in srgb, var(--ss-w-accent) 18%, transparent) 0%, transparent 60%),
8089
+ var(--ss-w-panel);
8090
+ box-shadow: 0 10px 24px -18px color-mix(in srgb, var(--ss-w-accent) 80%, transparent);
8091
+ }
8092
+ .ss-w-dash .ss-w-dash-grid .ss-w-dash-hero:hover { border-color: var(--ss-w-accent); }
8093
+ .ss-w-dash .ss-w-dash-grid .ss-w-dash-hero:focus-visible { outline: 2px solid var(--ss-w-accent); outline-offset: 2px; }
8094
+ .ss-w-dash-hero-top { display: flex; align-items: center; gap: 8px; min-height: 20px; }
8095
+ .ss-w-dash-hero-badge {
8096
+ font-size: 10.5px; font-weight: 750; letter-spacing: .06em; text-transform: uppercase;
8097
+ color: var(--ss-w-panel); background: color-mix(in srgb, var(--ss-w-accent) 45%, var(--ss-w-ink)); border-radius: 999px; padding: 3px 10px;
8098
+ white-space: nowrap;
8099
+ }
8100
+ .ss-w-dash-hero-chevron { flex: none; width: 14px; height: 14px; color: var(--ss-w-faint); }
8101
+ .ss-w-dash-hero-badge { margin-right: auto; }
8102
+ .ss-w-dash-hero-chevron:only-child { margin-left: auto; }
8103
+ .ss-w-dash-hero-main { display: flex; align-items: center; gap: 12px; min-width: 0; }
8104
+ .ss-w-dash-hero-visual { flex: none; display: flex; align-items: center; }
8105
+ .ss-w-dash-hero-crest {
8106
+ width: 40px; height: 40px; border-radius: 50%;
8107
+ background-color: color-mix(in srgb, var(--ss-w-ink) 4%, var(--ss-w-panel));
8108
+ background-size: 72%; background-position: center; background-repeat: no-repeat;
8109
+ box-shadow: 0 0 0 2px var(--ss-w-panel), 0 0 0 3px color-mix(in srgb, var(--ss-w-ink) 10%, var(--ss-w-panel));
8110
+ }
8111
+ .ss-w-dash-hero-crest + .ss-w-dash-hero-crest { margin-left: -8px; }
8112
+ .ss-w-dash-hero-icon {
8113
+ display: grid; place-items: center; width: 40px; height: 40px; border-radius: 50%; font-size: 22px;
8114
+ background: color-mix(in srgb, var(--ss-w-accent) 14%, var(--ss-w-panel));
8115
+ }
8116
+ .ss-w-dash-hero-body { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 3px; }
8117
+ .ss-w-dash-hero-title { font-size: 15.5px; font-weight: 700; line-height: 1.25; color: var(--ss-w-ink); overflow-wrap: anywhere; }
8118
+ .ss-w-dash-hero-subtitle { font-size: 12px; color: var(--ss-w-faint); overflow-wrap: anywhere; }
8119
+ .ss-w-dash-hero-price {
8120
+ flex: none; margin-left: auto;
8121
+ font-family: ui-monospace, monospace; font-variant-numeric: tabular-nums; font-size: 15px; font-weight: 750; line-height: 1;
8122
+ color: color-mix(in srgb, var(--ss-w-accent) 45%, var(--ss-w-ink)); padding: 5px 9px; border-radius: 8px;
8123
+ background: color-mix(in srgb, var(--ss-w-accent) 12%, var(--ss-w-panel));
8124
+ border: 1px solid color-mix(in srgb, var(--ss-w-accent) 30%, var(--ss-w-panel));
8125
+ }
7995
8126
 
7996
8127
  /* club fan pre-match dashboard */
7997
8128
  .ss-w-fandash { display: flex; flex-direction: column; gap: 10px; padding: 4px 0; }
@@ -8496,6 +8627,8 @@ var WIDGET_CSS = `.ss-w-root { all: revert; }
8496
8627
  .ss-g-mt-portrait .ss-g-sil { font-size: 46px; }
8497
8628
  .ss-g-mt-photo { width: 132px; height: 132px; margin-bottom: 30px; border-radius: 50%; overflow: hidden; display: grid; place-items: center; background: var(--ss-g-accent-tint); color: var(--ss-w-accent); font: 800 40px var(--ss-g-font-display); }
8498
8629
  .ss-g-mt-photo img { width: 100%; height: 100%; object-fit: cover; }
8630
+ .ss-g-cp-photo { width: 64px; height: 64px; flex: none; border-radius: 50%; overflow: hidden; display: grid; place-items: center; background: var(--ss-g-accent-tint); color: var(--ss-w-accent); font: 800 22px var(--ss-g-font-display); }
8631
+ .ss-g-cp-photo img { width: 100%; height: 100%; object-fit: cover; }
8499
8632
  .ss-g-mt-reveal-name { position: absolute; bottom: 6px; left: 12px; right: 12px; text-align: center; font: 800 20px var(--ss-g-font-display); text-transform: uppercase; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
8500
8633
  .ss-g-mt-known { display: grid; gap: 6px; padding: 12px; }
8501
8634
  .ss-g-mt-known-cell { display: flex; flex-direction: column; align-items: center; gap: 4px; min-width: 0; }
@@ -8897,9 +9030,9 @@ function StatsWidget(props) {
8897
9030
  [built?.client]
8898
9031
  );
8899
9032
  const ui = useMemo3(() => normalizeUi(props.config ?? { operatorId: "", publicKey: "" }), [props.config]);
8900
- const rootRef = useRef14(null);
8901
- const introDoneRef = useRef14(false);
8902
- const markIntroDone = useCallback9(() => {
9033
+ const rootRef = useRef15(null);
9034
+ const introDoneRef = useRef15(false);
9035
+ const markIntroDone = useCallback10(() => {
8903
9036
  introDoneRef.current = true;
8904
9037
  }, []);
8905
9038
  const convStorageKey = `ss-w-conv:${cfg?.operatorId ?? ""}:${cfg?.userContext?.operatorUserId ?? "anon"}`;
@@ -8912,11 +9045,11 @@ function StatsWidget(props) {
8912
9045
  isOpen: controller.state.isOpen,
8913
9046
  hasToday
8914
9047
  });
8915
- const getSchedule = useCallback9(
9048
+ const getSchedule = useCallback10(
8916
9049
  (localDate, utcOffsetMinutes) => built ? built.conversation.getSchedule(localDate, utcOffsetMinutes) : Promise.resolve(null),
8917
9050
  [built]
8918
9051
  );
8919
- const getStates = useCallback9(
9052
+ const getStates = useCallback10(
8920
9053
  (matchIds) => built ? built.conversation.getStates(matchIds) : Promise.resolve(null),
8921
9054
  [built]
8922
9055
  );
@@ -8932,14 +9065,14 @@ function StatsWidget(props) {
8932
9065
  pinnedMatchIds
8933
9066
  });
8934
9067
  const todayHasContent = visibleGroupsOf(discovery.groups, discovery.states).length > 0;
8935
- useEffect18(() => {
9068
+ useEffect19(() => {
8936
9069
  setHasToday(todayHasContent);
8937
9070
  }, [todayHasContent]);
8938
9071
  const gamesResult = useGames(built?.conversation ?? null, { activeTab: monitor.activeTab });
8939
9072
  const leaderboardResult = useLeaderboard(built?.conversation ?? null, { activeTab: monitor.activeTab });
8940
9073
  const markTodayActivity = monitor.markTodayActivity;
8941
- const prevLiveCountRef = useRef14(0);
8942
- useEffect18(() => {
9074
+ const prevLiveCountRef = useRef15(0);
9075
+ useEffect19(() => {
8943
9076
  const rose = prevLiveCountRef.current === 0 && discovery.liveCount > 0;
8944
9077
  prevLiveCountRef.current = discovery.liveCount;
8945
9078
  if (rose) markTodayActivity();
@@ -8956,29 +9089,24 @@ function StatsWidget(props) {
8956
9089
  if (wasAdded(result)) monitor.markInSlip(sel.matchOddsId);
8957
9090
  return result;
8958
9091
  }),
8959
- conversationId: controller.state.conversationId ?? void 0,
8960
- onMarketClicked: (marketName) => {
8961
- try {
8962
- void built?.conversation.sendSignal({ signalType: "InsightMarketClicked", entityType: "Market", entityName: marketName }).catch(() => void 0);
8963
- } catch {
8964
- }
8965
- }
9092
+ conversationId: controller.state.conversationId ?? void 0
8966
9093
  };
8967
9094
  },
8968
- [props.config?.onAddToSlip, controller.state.conversationId, built, monitor.markInSlip]
9095
+ [props.config?.onAddToSlip, controller.state.conversationId, monitor.markInSlip]
8969
9096
  );
8970
9097
  const onDashboardTap = useMemo3(
8971
9098
  () => built ? createDashboardTapSignal((input) => built.conversation.sendSignal(input), logger) : void 0,
8972
9099
  [built, logger]
8973
9100
  );
9101
+ const dismissPopup = usePopupTelemetry(logger, controller.state.isOpen, controller.state.popupText, controller.dismissPopup);
8974
9102
  const operatorUserId = cfg?.userContext?.operatorUserId;
8975
9103
  const anonymousUserId = cfg?.userContext?.anonymousUserId;
8976
9104
  const conversationId = controller.state.conversationId ?? void 0;
8977
- useEffect18(() => {
9105
+ useEffect19(() => {
8978
9106
  if (!logger) return;
8979
9107
  logger.setIdentity({ operatorUserId, anonymousUserId, conversationId });
8980
9108
  }, [logger, operatorUserId, anonymousUserId, conversationId]);
8981
- useEffect18(() => {
9109
+ useEffect19(() => {
8982
9110
  if (!logger) return;
8983
9111
  const onHide = () => {
8984
9112
  logger.flushOnHide();
@@ -8995,7 +9123,7 @@ function StatsWidget(props) {
8995
9123
  logger.dispose();
8996
9124
  };
8997
9125
  }, [logger]);
8998
- useEffect18(() => {
9126
+ useEffect19(() => {
8999
9127
  if (!built || !ui.injectStyles) return;
9000
9128
  const node = rootRef.current?.getRootNode();
9001
9129
  if (node) injectStyles(node);
@@ -9024,7 +9152,7 @@ function StatsWidget(props) {
9024
9152
  popupText: controller.state.popupText,
9025
9153
  logoUrl: ui.logoUrl,
9026
9154
  onOpen: controller.open,
9027
- onDismissPopup: controller.dismissPopup,
9155
+ onDismissPopup: dismissPopup,
9028
9156
  dismissLabel: ui.dismissGreeting,
9029
9157
  introAllowed: !introDoneRef.current,
9030
9158
  onIntroStart: markIntroDone