@sensiblestats/widget-react 0.69.0 → 0.70.1

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 useCallback12, useEffect as useEffect22, useMemo as useMemo4, useRef as useRef17, useState as useState41 } from "react";
2
+ import { useCallback as useCallback14, useEffect as useEffect26, useMemo as useMemo5, useRef as useRef22, useState as useState44 } from "react";
3
3
  import { StatsWidgetClient } from "@sensiblestats/widget-sdk";
4
4
 
5
5
  // src/kickoff.ts
@@ -123,6 +123,30 @@ var STRINGS = {
123
123
  gamePlay: "Play",
124
124
  gameLocked: "Locked",
125
125
  gameVoided: "Voided",
126
+ presets: {
127
+ lang: "en",
128
+ setNumbers: "Set the numbers",
129
+ buildCombo: "Build a combo",
130
+ dialName: "Set the numbers and Build a combo",
131
+ maxPriceRow: "{count} picks, max {price}",
132
+ minPriceRow: "Picks from {price} up",
133
+ comboRow: "A combo of {legs} matches priced at about {total}",
134
+ maxPriceDisplay: (count, price) => `${count} picks, max ${price.toFixed(2)}`,
135
+ maxPriceMessage: (count, price) => `Show me ${count} picks priced at ${price.toFixed(2)} or less.`,
136
+ minPriceDisplay: (count, price) => `${count} picks from ${price.toFixed(2)} up`,
137
+ minPriceMessage: (count, price) => `Show me ${count} picks priced at ${price.toFixed(2)} or more.`,
138
+ comboDisplay: (legs, total) => `A combo of ${legs} priced at about ${total.toFixed(1)}`,
139
+ comboMessage: (legs, total) => `Build me a combo of ${legs} priced at about ${total.toFixed(1)}.`,
140
+ unavailable: "Not available today.",
141
+ send: "Send",
142
+ close: "Close",
143
+ totalLabel: "Total",
144
+ matchesLabel: "Matches",
145
+ fewerMatches: "Fewer matches",
146
+ moreMatches: "More matches",
147
+ changeNumber: (value) => `${value}, tap to change`,
148
+ changeCombo: "Change total or matches"
149
+ },
126
150
  games: {
127
151
  lang: "en",
128
152
  dailyChallenge: "Daily challenge",
@@ -429,6 +453,30 @@ var STRINGS = {
429
453
  gamePlay: "Oyna",
430
454
  gameLocked: "Kilitli",
431
455
  gameVoided: "\u0130ptal edildi",
456
+ presets: {
457
+ lang: "tr",
458
+ setNumbers: "Say\u0131lar\u0131 belirle",
459
+ buildCombo: "Kombin olu\u015Ftur",
460
+ dialName: "Say\u0131lar\u0131 belirle ve Kombin olu\u015Ftur",
461
+ maxPriceRow: "{count} se\xE7im, en fazla {price}",
462
+ minPriceRow: "{price} ve \xFCzeri se\xE7imler",
463
+ comboRow: "Yakla\u015F\u0131k {total} oranl\u0131 {legs} ma\xE7l\u0131k kombin",
464
+ maxPriceDisplay: (count, price) => `${count} se\xE7im, en fazla ${price.toFixed(2)}`,
465
+ maxPriceMessage: (count, price) => `${price.toFixed(2)} veya alt\u0131 oranl\u0131 ${count} se\xE7im g\xF6ster.`,
466
+ minPriceDisplay: (count, price) => `${count} se\xE7im, ${price.toFixed(2)} ve \xFCzeri`,
467
+ minPriceMessage: (count, price) => `${price.toFixed(2)} veya \xFCzeri oranl\u0131 ${count} se\xE7im g\xF6ster.`,
468
+ comboDisplay: (legs, total) => `${legs} ma\xE7l\u0131k kombin, yakla\u015F\u0131k ${total.toFixed(1)}`,
469
+ comboMessage: (legs, total) => `Yakla\u015F\u0131k ${total.toFixed(1)} oranl\u0131 ${legs} ma\xE7l\u0131k bir kombin haz\u0131rla.`,
470
+ unavailable: "Bug\xFCn mevcut de\u011Fil.",
471
+ send: "G\xF6nder",
472
+ close: "Kapat",
473
+ totalLabel: "Toplam",
474
+ matchesLabel: "Ma\xE7 say\u0131s\u0131",
475
+ fewerMatches: "Daha az ma\xE7",
476
+ moreMatches: "Daha fazla ma\xE7",
477
+ changeNumber: (value) => `${value}, de\u011Fi\u015Ftirmek i\xE7in dokun`,
478
+ changeCombo: "Toplam\u0131 veya ma\xE7lar\u0131 de\u011Fi\u015Ftir"
479
+ },
432
480
  games: {
433
481
  lang: "tr",
434
482
  dailyChallenge: "G\xFCn\xFCn meydan okumas\u0131",
@@ -755,6 +803,7 @@ function normalizeUi(cfg) {
755
803
  gameLocked: strings.gameLocked,
756
804
  gameVoided: strings.gameVoided,
757
805
  games: strings.games,
806
+ presets: strings.presets,
758
807
  liveCard: strings.liveCard
759
808
  };
760
809
  }
@@ -1093,7 +1142,9 @@ function toComboCardVM(card) {
1093
1142
  legs,
1094
1143
  multiplication,
1095
1144
  replacedNote: str(card.replacedNote),
1096
- addToSlipEnabled: card.addToSlipEnabled === true
1145
+ addToSlipEnabled: card.addToSlipEnabled === true,
1146
+ target: finite(card.target),
1147
+ legCount: finite(card.legCount)
1097
1148
  };
1098
1149
  }
1099
1150
  function comboLegToSlipSelection(leg, conversationId) {
@@ -1167,10 +1218,10 @@ function resultToToast(result, shownOdds, ui) {
1167
1218
  }
1168
1219
 
1169
1220
  // src/useHostEvents.ts
1170
- import { useCallback as useCallback5, useEffect as useEffect13, useMemo, useRef as useRef8, useState as useState34 } from "react";
1221
+ import { useCallback as useCallback7, useEffect as useEffect16, useMemo as useMemo2, useRef as useRef12, useState as useState36 } from "react";
1171
1222
 
1172
1223
  // src/components/ChatPanel.tsx
1173
- import { useEffect as useEffect12, useState as useState33 } from "react";
1224
+ import { useCallback as useCallback6, useEffect as useEffect15, useMemo, useRef as useRef11, useState as useState35 } from "react";
1174
1225
 
1175
1226
  // src/components/ChatHeader.tsx
1176
1227
  import { useEffect, useRef, useState as useState2 } from "react";
@@ -1199,6 +1250,20 @@ function KebabIcon() {
1199
1250
  /* @__PURE__ */ jsx("circle", { cx: "12", cy: "19", r: "1.6" })
1200
1251
  ] }) });
1201
1252
  }
1253
+ function SlidersIcon() {
1254
+ return /* @__PURE__ */ jsx("svg", { viewBox: "0 0 24 24", width: "1em", height: "1em", "aria-hidden": "true", children: /* @__PURE__ */ jsxs("g", { fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", children: [
1255
+ /* @__PURE__ */ jsx("path", { d: "M4 6h9M17 6h3M4 12h3M11 12h9M4 18h11M19 18h1" }),
1256
+ /* @__PURE__ */ jsx("circle", { cx: "15", cy: "6", r: "2" }),
1257
+ /* @__PURE__ */ jsx("circle", { cx: "9", cy: "12", r: "2" }),
1258
+ /* @__PURE__ */ jsx("circle", { cx: "17", cy: "18", r: "2" })
1259
+ ] }) });
1260
+ }
1261
+ function ComboIcon() {
1262
+ return /* @__PURE__ */ jsx("svg", { viewBox: "0 0 24 24", width: "1em", height: "1em", "aria-hidden": "true", children: /* @__PURE__ */ jsxs("g", { fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinejoin: "round", children: [
1263
+ /* @__PURE__ */ jsx("rect", { x: "3", y: "3", width: "11", height: "11", rx: "2" }),
1264
+ /* @__PURE__ */ jsx("rect", { x: "10", y: "10", width: "11", height: "11", rx: "2" })
1265
+ ] }) });
1266
+ }
1202
1267
  var DASHBOARD_EMOJI = {
1203
1268
  football: "\u26BD",
1204
1269
  "goal-net": "\u{1F945}",
@@ -1971,6 +2036,13 @@ function BettingInsightList({ insights, ui, slip, monitor }) {
1971
2036
  return /* @__PURE__ */ jsx13("div", { className: "ss-w-binsights", children: insights.map((it) => /* @__PURE__ */ jsx13(InsightCard, { it, ui, slip, monitor }, it.id)) });
1972
2037
  }
1973
2038
 
2039
+ // src/presetToolsContext.ts
2040
+ import { createContext as createContext4, useContext as useContext4 } from "react";
2041
+ var PresetToolsContext = createContext4(null);
2042
+ function usePresetTools() {
2043
+ return useContext4(PresetToolsContext);
2044
+ }
2045
+
1974
2046
  // src/components/ComboCardList.tsx
1975
2047
  import { jsx as jsx14, jsxs as jsxs9 } from "react/jsx-runtime";
1976
2048
  function ComboLegRow({ leg, addToSlipEnabled, ui, slip }) {
@@ -2013,10 +2085,13 @@ function ComboLegRow({ leg, addToSlipEnabled, ui, slip }) {
2013
2085
  ] });
2014
2086
  }
2015
2087
  function ComboCardView({ vm, ui, slip }) {
2088
+ const tools = usePresetTools();
2089
+ const { target, legCount } = vm;
2016
2090
  return /* @__PURE__ */ jsxs9("div", { className: "ss-w-combo", children: [
2017
2091
  /* @__PURE__ */ jsx14("ol", { className: "ss-w-combo-legs", role: "list", children: vm.legs.map((leg) => /* @__PURE__ */ jsx14(ComboLegRow, { leg, addToSlipEnabled: vm.addToSlipEnabled, ui, slip }, leg.key)) }),
2018
2092
  /* @__PURE__ */ jsx14("div", { className: "ss-w-combo-math", children: vm.multiplication }),
2019
- vm.replacedNote ? /* @__PURE__ */ jsx14("p", { className: "ss-w-combo-note", children: vm.replacedNote }) : null
2093
+ vm.replacedNote ? /* @__PURE__ */ jsx14("p", { className: "ss-w-combo-note", children: vm.replacedNote }) : null,
2094
+ tools && target !== void 0 && legCount !== void 0 ? /* @__PURE__ */ jsx14("button", { type: "button", className: "ss-w-combo-change", onClick: () => tools.openBuilder(target, legCount), children: tools.changeLabel }) : null
2020
2095
  ] });
2021
2096
  }
2022
2097
  function ComboCardList({ cards, ui, slip }) {
@@ -3375,11 +3450,15 @@ function MessageList({ controller, ui, slip, onDashboardTap, monitor }) {
3375
3450
  }
3376
3451
 
3377
3452
  // src/components/ChatInput.tsx
3378
- import { useState as useState16 } from "react";
3453
+ import { forwardRef, useEffect as useEffect8, useImperativeHandle, useRef as useRef5, useState as useState16 } from "react";
3379
3454
  import { jsx as jsx46, jsxs as jsxs40 } from "react/jsx-runtime";
3380
- function ChatInput({ placeholder, streaming, onSend, onStop, stopLabel = "Stop response", sendLabel = "Send message" }) {
3455
+ var ChatInput = forwardRef(function ChatInput2({ placeholder, streaming, onSend, onStop, stopLabel = "Stop response", sendLabel = "Send message", disabled = false, onType }, ref) {
3381
3456
  const [value, setValue] = useState16("");
3457
+ const textareaRef = useRef5(null);
3458
+ const stopRef = useRef5(null);
3459
+ const textBoxWantsFocusRef = useRef5(false);
3382
3460
  const submit = () => {
3461
+ if (disabled) return;
3383
3462
  const t = value.trim();
3384
3463
  if (!t) return;
3385
3464
  onSend(t);
@@ -3391,11 +3470,48 @@ function ChatInput({ placeholder, streaming, onSend, onStop, stopLabel = "Stop r
3391
3470
  submit();
3392
3471
  }
3393
3472
  };
3473
+ useImperativeHandle(ref, () => ({
3474
+ focus() {
3475
+ const box = textareaRef.current;
3476
+ if (box && !box.disabled) {
3477
+ textBoxWantsFocusRef.current = false;
3478
+ box.focus();
3479
+ return;
3480
+ }
3481
+ textBoxWantsFocusRef.current = true;
3482
+ stopRef.current?.focus();
3483
+ }
3484
+ }), []);
3485
+ useEffect8(() => {
3486
+ if (streaming || disabled || !textBoxWantsFocusRef.current) return;
3487
+ textBoxWantsFocusRef.current = false;
3488
+ const box = textareaRef.current;
3489
+ if (!box) return;
3490
+ const active = box.getRootNode().activeElement;
3491
+ const input = box.closest(".ss-w-input");
3492
+ const isWithinComponent = input?.contains(active);
3493
+ if (!active || active === box.ownerDocument.body || isWithinComponent) box.focus();
3494
+ }, [streaming, disabled]);
3394
3495
  return /* @__PURE__ */ jsxs40("div", { className: "ss-w-input", children: [
3395
- /* @__PURE__ */ jsx46("textarea", { className: "ss-w-textarea", rows: 1, placeholder, value, disabled: streaming, onChange: (e) => setValue(e.target.value), onKeyDown }),
3396
- streaming ? /* @__PURE__ */ jsx46("button", { type: "button", className: "ss-w-send ss-w-stop", "aria-label": stopLabel, onClick: onStop, children: /* @__PURE__ */ jsx46("span", { className: "ss-w-stop-glyph" }) }) : /* @__PURE__ */ jsx46("button", { type: "button", className: "ss-w-send", "aria-label": sendLabel, onClick: submit, children: /* @__PURE__ */ jsx46(SendIcon, {}) })
3496
+ /* @__PURE__ */ jsx46(
3497
+ "textarea",
3498
+ {
3499
+ ref: textareaRef,
3500
+ className: "ss-w-textarea",
3501
+ rows: 1,
3502
+ placeholder,
3503
+ value,
3504
+ disabled: streaming || disabled,
3505
+ onChange: (e) => {
3506
+ setValue(e.target.value);
3507
+ onType?.();
3508
+ },
3509
+ onKeyDown
3510
+ }
3511
+ ),
3512
+ streaming ? /* @__PURE__ */ jsx46("button", { ref: stopRef, type: "button", className: "ss-w-send ss-w-stop", "aria-label": stopLabel, disabled, onClick: onStop, children: /* @__PURE__ */ jsx46("span", { className: "ss-w-stop-glyph" }) }) : /* @__PURE__ */ jsx46("button", { type: "button", className: "ss-w-send", "aria-label": sendLabel, disabled, onClick: submit, children: /* @__PURE__ */ jsx46(SendIcon, {}) })
3397
3513
  ] });
3398
- }
3514
+ });
3399
3515
 
3400
3516
  // src/components/TabBar.tsx
3401
3517
  import { jsx as jsx47, jsxs as jsxs41 } from "react/jsx-runtime";
@@ -3878,7 +3994,7 @@ function TodayPanel({ discovery, ui, monitor }) {
3878
3994
  }
3879
3995
 
3880
3996
  // src/components/GamesPanel.tsx
3881
- import { useEffect as useEffect11, useState as useState31 } from "react";
3997
+ import { useEffect as useEffect12, useState as useState31 } from "react";
3882
3998
 
3883
3999
  // src/components/GamesLoginWall.tsx
3884
4000
  import { jsx as jsx55 } from "react/jsx-runtime";
@@ -3887,7 +4003,7 @@ function GamesLoginWall({ ui }) {
3887
4003
  }
3888
4004
 
3889
4005
  // src/components/GamesHub.tsx
3890
- import { useEffect as useEffect8, useState as useState18 } from "react";
4006
+ import { useEffect as useEffect9, useState as useState18 } from "react";
3891
4007
 
3892
4008
  // src/components/games/ui/gameMeta.ts
3893
4009
  function prettifyGameId(gameId) {
@@ -4073,7 +4189,7 @@ function pickHeroGame(games) {
4073
4189
  var tileKey = (g) => `${g.gameId}-${g.gameDay}-${g.fixtureId}`;
4074
4190
  function GamesHub({ games, myWeeklyRow, weeklyPrize, onOpen, ui }) {
4075
4191
  const [, setTick] = useState18(0);
4076
- useEffect8(() => {
4192
+ useEffect9(() => {
4077
4193
  const id = setInterval(() => setTick((t) => t + 1), 6e4);
4078
4194
  return () => clearInterval(id);
4079
4195
  }, []);
@@ -4133,10 +4249,10 @@ function GamesHub({ games, myWeeklyRow, weeklyPrize, onOpen, ui }) {
4133
4249
  }
4134
4250
 
4135
4251
  // src/components/GameScreenHost.tsx
4136
- import { useEffect as useEffect10, useRef as useRef7, useState as useState30 } from "react";
4252
+ import { useEffect as useEffect11, useRef as useRef8, useState as useState30 } from "react";
4137
4253
 
4138
4254
  // src/useGamePlay.ts
4139
- import { useCallback as useCallback4, useRef as useRef5, useState as useState19 } from "react";
4255
+ import { useCallback as useCallback4, useRef as useRef6, useState as useState19 } from "react";
4140
4256
  function toSubmitResult(payload) {
4141
4257
  return {
4142
4258
  // 'pending' means the play is recorded but awaiting resolution (fixture games)
@@ -4151,12 +4267,12 @@ function useGamePlay(conversation, { gameId, gameDay, fixtureId, locale, refresh
4151
4267
  const [result, setResult] = useState19(null);
4152
4268
  const [submitting, setSubmitting] = useState19(false);
4153
4269
  const [rawStatus, setRawStatus] = useState19(null);
4154
- const inFlightRef = useRef5(false);
4155
- const convRef = useRef5(conversation);
4270
+ const inFlightRef = useRef6(false);
4271
+ const convRef = useRef6(conversation);
4156
4272
  convRef.current = conversation;
4157
- const optsRef = useRef5({ gameId, gameDay, fixtureId, locale, refresh });
4273
+ const optsRef = useRef6({ gameId, gameDay, fixtureId, locale, refresh });
4158
4274
  optsRef.current = { gameId, gameDay, fixtureId, locale, refresh };
4159
- const resultRef = useRef5(null);
4275
+ const resultRef = useRef6(null);
4160
4276
  resultRef.current = result;
4161
4277
  const refetch = useCallback4(async () => {
4162
4278
  const conv = convRef.current;
@@ -5086,7 +5202,7 @@ function CareerPathGame({
5086
5202
  }
5087
5203
 
5088
5204
  // src/components/games/BeatTheGridGame.tsx
5089
- import React, { useEffect as useEffect9, useId as useId4, useRef as useRef6, useState as useState24 } from "react";
5205
+ import React, { useEffect as useEffect10, useId as useId4, useRef as useRef7, useState as useState24 } from "react";
5090
5206
  import { Fragment as Fragment14, jsx as jsx74, jsxs as jsxs61 } from "react/jsx-runtime";
5091
5207
  function criterionIconKind(label, key) {
5092
5208
  const l = (key ?? label).toLowerCase();
@@ -5169,8 +5285,8 @@ function SearchSheet({
5169
5285
  const s = ui.games.beatTheGrid;
5170
5286
  const [query, setQuery] = useState24("");
5171
5287
  const inputId = useId4();
5172
- const inputRef = useRef6(null);
5173
- useEffect9(() => {
5288
+ const inputRef = useRef7(null);
5289
+ useEffect10(() => {
5174
5290
  inputRef.current?.focus();
5175
5291
  }, [row, col]);
5176
5292
  const filtered = playerPool.filter(
@@ -6472,7 +6588,7 @@ function GameScreenHost({
6472
6588
  }) {
6473
6589
  const { gameId, fixtureId, gameDay = "", lockAtUtc = null } = selected;
6474
6590
  const [configJson, setConfigJson] = useState30(null);
6475
- useEffect10(() => {
6591
+ useEffect11(() => {
6476
6592
  setConfigJson(null);
6477
6593
  if (!conversation) return;
6478
6594
  let cancelled = false;
@@ -6495,12 +6611,12 @@ function GameScreenHost({
6495
6611
  fixtureId: fixtureId || void 0,
6496
6612
  locale: ui.games.lang
6497
6613
  });
6498
- useEffect10(() => {
6614
+ useEffect11(() => {
6499
6615
  if (conversation) hydrate();
6500
6616
  }, [conversation, gameId]);
6501
- const hydrateRef = useRef7(hydrate);
6617
+ const hydrateRef = useRef8(hydrate);
6502
6618
  hydrateRef.current = hydrate;
6503
- useEffect10(() => {
6619
+ useEffect11(() => {
6504
6620
  if (!FIXTURE_GAME_IDS.has(gameId)) return;
6505
6621
  if (rawStatus !== "pending") return;
6506
6622
  const timerId = setInterval(() => {
@@ -6636,7 +6752,7 @@ function GamesPanel({
6636
6752
  refreshHub
6637
6753
  }) {
6638
6754
  const [selected, setSelected] = useState31(null);
6639
- useEffect11(() => {
6755
+ useEffect12(() => {
6640
6756
  ensureGamesFonts();
6641
6757
  }, []);
6642
6758
  if (!loggedIn) return /* @__PURE__ */ jsx83(GamesLoginWall, { ui });
@@ -6831,12 +6947,440 @@ function LeaderboardPanel({ loggedIn, data, loading, onPlay, ui }) {
6831
6947
  return /* @__PURE__ */ jsx87("div", { className: "ss-w-mon-list", children: /* @__PURE__ */ jsx87(Leaderboard, { data: data ?? EMPTY_PAYLOAD, ui, onPlay }) });
6832
6948
  }
6833
6949
 
6834
- // src/components/ChatPanel.tsx
6950
+ // src/components/PresetSpeedDial.tsx
6951
+ import { useId as useId9 } from "react";
6835
6952
  import { jsx as jsx88, jsxs as jsxs72 } from "react/jsx-runtime";
6953
+ function PresetSpeedDial({ expanded, showDot, strings, onToggle, onOpen, rootRef, buttonRef }) {
6954
+ const pillsId = useId9();
6955
+ const tabIndex = expanded ? 0 : -1;
6956
+ return /* @__PURE__ */ jsxs72("div", { ref: rootRef, className: "ss-w-dial", children: [
6957
+ /* @__PURE__ */ jsxs72(
6958
+ "button",
6959
+ {
6960
+ ref: buttonRef,
6961
+ type: "button",
6962
+ className: "ss-w-dial-btn",
6963
+ "aria-expanded": expanded,
6964
+ "aria-controls": pillsId,
6965
+ "aria-label": strings.dialName,
6966
+ onClick: onToggle,
6967
+ children: [
6968
+ /* @__PURE__ */ jsx88("span", { className: "ss-w-dial-icon ss-w-dial-icon--open", children: /* @__PURE__ */ jsx88(SlidersIcon, {}) }),
6969
+ /* @__PURE__ */ jsx88("span", { className: "ss-w-dial-icon ss-w-dial-icon--close", children: /* @__PURE__ */ jsx88(CloseIcon, {}) }),
6970
+ showDot && !expanded ? /* @__PURE__ */ jsx88("span", { className: "ss-w-dial-dot", "aria-hidden": "true" }) : null
6971
+ ]
6972
+ }
6973
+ ),
6974
+ /* @__PURE__ */ jsxs72("div", { id: pillsId, className: "ss-w-dial-pills", "data-open": expanded ? "" : void 0, children: [
6975
+ /* @__PURE__ */ jsxs72("button", { type: "button", className: "ss-w-dial-pill", tabIndex, onClick: () => onOpen("numbers"), children: [
6976
+ /* @__PURE__ */ jsx88(SlidersIcon, {}),
6977
+ /* @__PURE__ */ jsx88("span", { className: "ss-w-dial-pill-label", children: strings.setNumbers })
6978
+ ] }),
6979
+ /* @__PURE__ */ jsxs72("button", { type: "button", className: "ss-w-dial-pill", tabIndex, onClick: () => onOpen("combo"), children: [
6980
+ /* @__PURE__ */ jsx88(ComboIcon, {}),
6981
+ /* @__PURE__ */ jsx88("span", { className: "ss-w-dial-pill-label", children: strings.buildCombo })
6982
+ ] })
6983
+ ] })
6984
+ ] });
6985
+ }
6986
+
6987
+ // src/components/PresetTools.tsx
6988
+ import { Fragment as Fragment20, useEffect as useEffect13, useId as useId10, useRef as useRef9, useState as useState33 } from "react";
6989
+
6990
+ // src/presetGrid.ts
6991
+ var DEFAULT_SINGLES_LIMITS = [1.3, 1.5, 1.7, 2];
6992
+ var DEFAULT_SINGLES_FLOORS = [1.5, 2, 2.5, 3];
6993
+ var DEFAULT_COMBO_TOTALS = [3, 5, 10, 20, 50];
6994
+ var FIXED_SINGLES_COUNT = 3;
6995
+ var MIN_COMBO_LEGS = 2;
6996
+ var MAX_COMBO_LEGS = 6;
6997
+ var START = { maxPrice: 1.5, minPrice: 2, comboTotal: 5, comboLegs: 3 };
6998
+ var finite2 = (v) => typeof v === "number" && Number.isFinite(v);
6999
+ var cents = (n) => Math.round(n * 100);
7000
+ function samePrice(a, b) {
7001
+ return cents(a) === cents(b);
7002
+ }
7003
+ function isPresetGrid(v) {
7004
+ if (!v || typeof v !== "object") return false;
7005
+ const g = v;
7006
+ return Array.isArray(g.singles) && Array.isArray(g.singlesFrom) && Array.isArray(g.combos) && finite2(g.maxLegs);
7007
+ }
7008
+ function drawable(p) {
7009
+ return !!p && finite2(p.price) && finite2(p.matchOddsId) && finite2(p.matchId) && typeof p.homeTeamName === "string" && typeof p.awayTeamName === "string";
7010
+ }
7011
+ function choices(defaults, present) {
7012
+ const out = [];
7013
+ for (const n of [...defaults, ...present]) {
7014
+ if (finite2(n) && n > 0 && !out.some((m) => samePrice(m, n))) out.push(n);
7015
+ }
7016
+ return out.sort((a, b) => a - b);
7017
+ }
7018
+ function singlesLimits(grid) {
7019
+ return choices(DEFAULT_SINGLES_LIMITS, grid.singles.map((c) => c?.price));
7020
+ }
7021
+ function singlesFloors(grid) {
7022
+ return choices(DEFAULT_SINGLES_FLOORS, grid.singlesFrom.map((c) => c?.price));
7023
+ }
7024
+ function comboTotals(grid) {
7025
+ return choices(DEFAULT_COMBO_TOTALS, grid.combos.map((c) => c?.total));
7026
+ }
7027
+ function comboLegChoices(grid) {
7028
+ const max = Math.min(MAX_COMBO_LEGS, Math.floor(grid.maxLegs));
7029
+ const out = [];
7030
+ for (let n = MIN_COMBO_LEGS; n <= max; n++) out.push(n);
7031
+ return out;
7032
+ }
7033
+ function cycle(values, current) {
7034
+ if (values.length === 0) return current;
7035
+ const i = values.findIndex((v) => samePrice(v, current));
7036
+ return values[(i + 1) % values.length] ?? current;
7037
+ }
7038
+ function startValue(values, preferred) {
7039
+ return values.find((v) => samePrice(v, preferred)) ?? values[0] ?? preferred;
7040
+ }
7041
+ function nearest(values, target) {
7042
+ const first = values[0];
7043
+ if (first === void 0) return target;
7044
+ if (!(target > 0)) return first;
7045
+ const distance = (v) => Math.abs(Math.log(v / target));
7046
+ return values.reduce((best, v) => distance(v) < distance(best) ? v : best, first);
7047
+ }
7048
+ function clampLegs(values, legs) {
7049
+ const lo = values[0];
7050
+ const hi = values[values.length - 1];
7051
+ if (lo === void 0 || hi === void 0) return legs;
7052
+ return Math.min(Math.max(Math.round(legs), lo), hi);
7053
+ }
7054
+ function findSingles(cells, price, count) {
7055
+ const cell = cells.find((c) => !!c && finite2(c.price) && samePrice(c.price, price) && c.count === count);
7056
+ return cell && Array.isArray(cell.picks) && cell.picks.length === count && cell.picks.every(drawable) ? cell : null;
7057
+ }
7058
+ function findCombo(grid, total, legs) {
7059
+ const cell = grid.combos.find((c) => !!c && finite2(c.total) && samePrice(c.total, total) && c.legs === legs);
7060
+ const combo = cell?.combo;
7061
+ const whole = !!combo && typeof combo.multiplication === "string" && combo.multiplication !== "" && Array.isArray(combo.legs) && combo.legs.length === legs && combo.legs.every(drawable);
7062
+ return cell && whole ? cell : null;
7063
+ }
7064
+ function idsOf(picks) {
7065
+ return { matchOddsIds: picks.map((p) => p.matchOddsId), matchIds: picks.map((p) => p.matchId) };
7066
+ }
7067
+ function maxPriceAsk(cell, s) {
7068
+ return {
7069
+ message: s.maxPriceMessage(cell.count, cell.price),
7070
+ displayText: s.maxPriceDisplay(cell.count, cell.price),
7071
+ preset: { kind: "max_price", price: cell.price, count: cell.count, ...idsOf(cell.picks) }
7072
+ };
7073
+ }
7074
+ function minPriceAsk(cell, s) {
7075
+ return {
7076
+ message: s.minPriceMessage(cell.count, cell.price),
7077
+ displayText: s.minPriceDisplay(cell.count, cell.price),
7078
+ preset: { kind: "min_price", price: cell.price, count: cell.count, ...idsOf(cell.picks) }
7079
+ };
7080
+ }
7081
+ function comboAsk(cell, s) {
7082
+ return {
7083
+ message: s.comboMessage(cell.legs, cell.total),
7084
+ displayText: s.comboDisplay(cell.legs, cell.total),
7085
+ preset: { kind: "combo", total: cell.total, legs: cell.legs, ...idsOf(cell.combo.legs) }
7086
+ };
7087
+ }
7088
+
7089
+ // src/components/PresetTools.tsx
7090
+ import { Fragment as Fragment21, jsx as jsx89, jsxs as jsxs73 } from "react/jsx-runtime";
7091
+ var FOCUSABLE = 'button:not([disabled]), [href], input:not([disabled]), textarea:not([disabled]), [tabindex]:not([tabindex="-1"])';
7092
+ function PresetToolModal({ title, closeLabel, onClose, children, footer }) {
7093
+ const dialogRef = useRef9(null);
7094
+ const titleId = useId10();
7095
+ useEffect13(() => {
7096
+ dialogRef.current?.focus();
7097
+ }, []);
7098
+ useEffect13(() => {
7099
+ const onKey = (e) => {
7100
+ if (e.key !== "Escape") return;
7101
+ e.stopPropagation();
7102
+ e.preventDefault();
7103
+ onClose();
7104
+ };
7105
+ window.addEventListener("keydown", onKey, true);
7106
+ return () => window.removeEventListener("keydown", onKey, true);
7107
+ }, [onClose]);
7108
+ const trapTab = (e) => {
7109
+ if (e.key !== "Tab") return;
7110
+ const dialog = dialogRef.current;
7111
+ if (!dialog) return;
7112
+ const items = Array.from(dialog.querySelectorAll(FOCUSABLE));
7113
+ const first = items[0];
7114
+ const last = items[items.length - 1];
7115
+ if (!first || !last) {
7116
+ e.preventDefault();
7117
+ return;
7118
+ }
7119
+ const active = dialog.getRootNode().activeElement;
7120
+ if (e.shiftKey && (active === first || active === dialog)) {
7121
+ e.preventDefault();
7122
+ last.focus();
7123
+ } else if (!e.shiftKey && active === last) {
7124
+ e.preventDefault();
7125
+ first.focus();
7126
+ }
7127
+ };
7128
+ return /* @__PURE__ */ jsx89("div", { className: "ss-w-tool-backdrop", children: /* @__PURE__ */ jsxs73("div", { ref: dialogRef, className: "ss-w-tool", role: "dialog", "aria-modal": "true", "aria-labelledby": titleId, tabIndex: -1, onKeyDown: trapTab, children: [
7129
+ /* @__PURE__ */ jsxs73("div", { className: "ss-w-tool-head", children: [
7130
+ /* @__PURE__ */ jsx89("h2", { id: titleId, className: "ss-w-tool-title", children: title }),
7131
+ /* @__PURE__ */ jsx89("button", { type: "button", className: "ss-w-tool-close", "aria-label": closeLabel, onClick: onClose, children: /* @__PURE__ */ jsx89(CloseIcon, {}) })
7132
+ ] }),
7133
+ /* @__PURE__ */ jsx89("div", { className: "ss-w-tool-body", children }),
7134
+ footer ? /* @__PURE__ */ jsx89("div", { className: "ss-w-tool-foot", children: footer }) : null
7135
+ ] }) });
7136
+ }
7137
+ function fillTemplate(template, slots) {
7138
+ return template.split(/(\{[a-z]+\})/).filter((part) => part !== "").map((part, i) => {
7139
+ const key = /^\{([a-z]+)\}$/.exec(part)?.[1];
7140
+ return /* @__PURE__ */ jsx89(Fragment20, { children: key !== void 0 && key in slots ? slots[key] : part }, i);
7141
+ });
7142
+ }
7143
+ function NumberButton({ value, label, onCycle }) {
7144
+ return /* @__PURE__ */ jsx89("button", { type: "button", className: "ss-w-tool-num", "aria-label": label, onClick: onCycle, children: value });
7145
+ }
7146
+ function SendButton({ label, display, disabled, onClick }) {
7147
+ return /* @__PURE__ */ jsx89("button", { type: "button", className: "ss-w-tool-send", "aria-label": `${label}: ${display}`, disabled, onClick, children: label });
7148
+ }
7149
+ function PickList({ picks, formatKickoff }) {
7150
+ return /* @__PURE__ */ jsx89("ul", { className: "ss-w-tool-picks", role: "list", children: picks.map((p) => /* @__PURE__ */ jsxs73("li", { className: "ss-w-tool-pick", children: [
7151
+ /* @__PURE__ */ jsxs73("span", { className: "ss-w-tool-pick-match", children: [
7152
+ p.homeTeamName,
7153
+ " \u2013 ",
7154
+ p.awayTeamName
7155
+ ] }),
7156
+ /* @__PURE__ */ jsxs73("span", { className: "ss-w-tool-pick-sel", children: [
7157
+ p.marketLabel,
7158
+ ": ",
7159
+ p.selectionLabel,
7160
+ p.line ? ` ${p.line}` : ""
7161
+ ] }),
7162
+ /* @__PURE__ */ jsx89("span", { className: "ss-w-tool-pick-kick", children: formatKickoff(p.kickoffUtc) }),
7163
+ /* @__PURE__ */ jsx89("span", { className: "ss-w-tool-pick-price", children: p.price.toFixed(2) })
7164
+ ] }, p.matchOddsId)) });
7165
+ }
7166
+ function ComboPreview({ cell, formatKickoff }) {
7167
+ return /* @__PURE__ */ jsxs73(Fragment21, { children: [
7168
+ /* @__PURE__ */ jsx89(PickList, { picks: cell.combo.legs, formatKickoff }),
7169
+ /* @__PURE__ */ jsx89("div", { className: "ss-w-tool-math", children: cell.combo.multiplication })
7170
+ ] });
7171
+ }
7172
+ function Unavailable({ text }) {
7173
+ return /* @__PURE__ */ jsx89("p", { className: "ss-w-tool-unavailable", children: text });
7174
+ }
7175
+ function SetNumbersTool({ grid, strings, streaming, formatKickoff, onSend, onClose }) {
7176
+ const limits = singlesLimits(grid);
7177
+ const floors = singlesFloors(grid);
7178
+ const totals = comboTotals(grid);
7179
+ const legChoices = comboLegChoices(grid);
7180
+ const count = FIXED_SINGLES_COUNT;
7181
+ const [maxPrice, setMaxPrice] = useState33(() => startValue(limits, START.maxPrice));
7182
+ const [minPrice, setMinPrice] = useState33(() => startValue(floors, START.minPrice));
7183
+ const [total, setTotal] = useState33(() => startValue(totals, START.comboTotal));
7184
+ const [legs, setLegs] = useState33(() => startValue(legChoices, START.comboLegs));
7185
+ const [announcement, setAnnouncement] = useState33("");
7186
+ const maxCell = findSingles(grid.singles, maxPrice, count);
7187
+ const minCell = findSingles(grid.singlesFrom, minPrice, count);
7188
+ const comboCell = findCombo(grid, total, legs);
7189
+ const announce = (display, available) => setAnnouncement(available ? display : `${display}. ${strings.unavailable}`);
7190
+ const nextMax = () => {
7191
+ const v = cycle(limits, maxPrice);
7192
+ setMaxPrice(v);
7193
+ announce(strings.maxPriceDisplay(count, v), !!findSingles(grid.singles, v, count));
7194
+ };
7195
+ const nextMin = () => {
7196
+ const v = cycle(floors, minPrice);
7197
+ setMinPrice(v);
7198
+ announce(strings.minPriceDisplay(count, v), !!findSingles(grid.singlesFrom, v, count));
7199
+ };
7200
+ const nextLegs = () => {
7201
+ const v = cycle(legChoices, legs);
7202
+ setLegs(v);
7203
+ announce(strings.comboDisplay(v, total), !!findCombo(grid, total, v));
7204
+ };
7205
+ const nextTotal = () => {
7206
+ const v = cycle(totals, total);
7207
+ setTotal(v);
7208
+ announce(strings.comboDisplay(legs, v), !!findCombo(grid, v, legs));
7209
+ };
7210
+ return /* @__PURE__ */ jsxs73(PresetToolModal, { title: strings.setNumbers, closeLabel: strings.close, onClose, children: [
7211
+ /* @__PURE__ */ jsxs73("section", { className: "ss-w-tool-row", children: [
7212
+ /* @__PURE__ */ jsx89("p", { className: "ss-w-tool-ask", children: fillTemplate(strings.maxPriceRow, {
7213
+ count: String(count),
7214
+ price: /* @__PURE__ */ jsx89(NumberButton, { value: maxPrice.toFixed(2), label: strings.changeNumber(maxPrice.toFixed(2)), onCycle: nextMax })
7215
+ }) }),
7216
+ maxCell ? /* @__PURE__ */ jsx89(PickList, { picks: maxCell.picks, formatKickoff }) : /* @__PURE__ */ jsx89(Unavailable, { text: strings.unavailable }),
7217
+ /* @__PURE__ */ jsx89(SendButton, { label: strings.send, display: strings.maxPriceDisplay(count, maxPrice), disabled: !maxCell || streaming, onClick: () => {
7218
+ if (maxCell) onSend(maxPriceAsk(maxCell, strings));
7219
+ } })
7220
+ ] }),
7221
+ /* @__PURE__ */ jsxs73("section", { className: "ss-w-tool-row", children: [
7222
+ /* @__PURE__ */ jsx89("p", { className: "ss-w-tool-ask", children: fillTemplate(strings.minPriceRow, {
7223
+ count: String(count),
7224
+ price: /* @__PURE__ */ jsx89(NumberButton, { value: minPrice.toFixed(2), label: strings.changeNumber(minPrice.toFixed(2)), onCycle: nextMin })
7225
+ }) }),
7226
+ minCell ? /* @__PURE__ */ jsx89(PickList, { picks: minCell.picks, formatKickoff }) : /* @__PURE__ */ jsx89(Unavailable, { text: strings.unavailable }),
7227
+ /* @__PURE__ */ jsx89(SendButton, { label: strings.send, display: strings.minPriceDisplay(count, minPrice), disabled: !minCell || streaming, onClick: () => {
7228
+ if (minCell) onSend(minPriceAsk(minCell, strings));
7229
+ } })
7230
+ ] }),
7231
+ /* @__PURE__ */ jsxs73("section", { className: "ss-w-tool-row", children: [
7232
+ /* @__PURE__ */ jsx89("p", { className: "ss-w-tool-ask", children: fillTemplate(strings.comboRow, {
7233
+ legs: /* @__PURE__ */ jsx89(NumberButton, { value: String(legs), label: strings.changeNumber(String(legs)), onCycle: nextLegs }),
7234
+ total: /* @__PURE__ */ jsx89(NumberButton, { value: total.toFixed(1), label: strings.changeNumber(total.toFixed(1)), onCycle: nextTotal })
7235
+ }) }),
7236
+ comboCell ? /* @__PURE__ */ jsx89(ComboPreview, { cell: comboCell, formatKickoff }) : /* @__PURE__ */ jsx89(Unavailable, { text: strings.unavailable }),
7237
+ /* @__PURE__ */ jsx89(SendButton, { label: strings.send, display: strings.comboDisplay(legs, total), disabled: !comboCell || streaming, onClick: () => {
7238
+ if (comboCell) onSend(comboAsk(comboCell, strings));
7239
+ } })
7240
+ ] }),
7241
+ /* @__PURE__ */ jsx89("p", { className: "ss-w-vh", "aria-live": "polite", children: announcement })
7242
+ ] });
7243
+ }
7244
+ function BuildComboTool({ grid, strings, streaming, formatKickoff, initialTotal, initialLegs, onSend, onClose }) {
7245
+ const totals = comboTotals(grid);
7246
+ const legChoices = comboLegChoices(grid);
7247
+ const [total, setTotal] = useState33(() => initialTotal !== void 0 ? nearest(totals, initialTotal) : startValue(totals, START.comboTotal));
7248
+ const [legs, setLegs] = useState33(() => initialLegs !== void 0 ? clampLegs(legChoices, initialLegs) : startValue(legChoices, START.comboLegs));
7249
+ const [announcement, setAnnouncement] = useState33("");
7250
+ const cell = findCombo(grid, total, legs);
7251
+ const minLegs = legChoices[0] ?? legs;
7252
+ const maxLegs = legChoices[legChoices.length - 1] ?? legs;
7253
+ const display = strings.comboDisplay(legs, total);
7254
+ const choose = (nextTotal, nextLegs) => {
7255
+ setTotal(nextTotal);
7256
+ setLegs(nextLegs);
7257
+ const d = strings.comboDisplay(nextLegs, nextTotal);
7258
+ setAnnouncement(findCombo(grid, nextTotal, nextLegs) ? d : `${d}. ${strings.unavailable}`);
7259
+ };
7260
+ const footer = /* @__PURE__ */ jsx89(SendButton, { label: strings.send, display, disabled: !cell || streaming, onClick: () => {
7261
+ if (cell) onSend(comboAsk(cell, strings));
7262
+ } });
7263
+ return /* @__PURE__ */ jsxs73(PresetToolModal, { title: strings.buildCombo, closeLabel: strings.close, onClose, footer, children: [
7264
+ /* @__PURE__ */ jsxs73("div", { className: "ss-w-tool-section", role: "group", "aria-label": strings.totalLabel, children: [
7265
+ /* @__PURE__ */ jsx89("span", { className: "ss-w-tool-label", "aria-hidden": "true", children: strings.totalLabel }),
7266
+ /* @__PURE__ */ jsx89("div", { className: "ss-w-tool-chips", children: totals.map((t) => /* @__PURE__ */ jsx89("button", { type: "button", className: "ss-w-tool-chip", "aria-pressed": samePrice(t, total), onClick: () => choose(t, legs), children: t.toFixed(1) }, t)) })
7267
+ ] }),
7268
+ /* @__PURE__ */ jsxs73("div", { className: "ss-w-tool-section", role: "group", "aria-label": strings.matchesLabel, children: [
7269
+ /* @__PURE__ */ jsx89("span", { className: "ss-w-tool-label", "aria-hidden": "true", children: strings.matchesLabel }),
7270
+ /* @__PURE__ */ jsxs73("div", { className: "ss-w-tool-stepper", children: [
7271
+ /* @__PURE__ */ jsx89("button", { type: "button", className: "ss-w-tool-step", "aria-label": strings.fewerMatches, disabled: legs <= minLegs, onClick: () => choose(total, legs - 1), children: "\u2212" }),
7272
+ /* @__PURE__ */ jsx89("span", { className: "ss-w-tool-count", children: legs }),
7273
+ /* @__PURE__ */ jsx89("button", { type: "button", className: "ss-w-tool-step", "aria-label": strings.moreMatches, disabled: legs >= maxLegs, onClick: () => choose(total, legs + 1), children: "+" })
7274
+ ] })
7275
+ ] }),
7276
+ cell ? /* @__PURE__ */ jsx89(ComboPreview, { cell, formatKickoff }) : /* @__PURE__ */ jsx89(Unavailable, { text: strings.unavailable }),
7277
+ /* @__PURE__ */ jsx89("p", { className: "ss-w-vh", "aria-live": "polite", children: announcement })
7278
+ ] });
7279
+ }
7280
+
7281
+ // src/usePresetDial.ts
7282
+ import { useCallback as useCallback5, useEffect as useEffect14, useRef as useRef10, useState as useState34 } from "react";
7283
+ function sendInFlight(messages, isStreaming) {
7284
+ const last = messages[messages.length - 1];
7285
+ return isStreaming && !!last && last.role === "assistant" && !last.done && !last.isGreeting;
7286
+ }
7287
+ function lastUserId(messages) {
7288
+ for (let i = messages.length - 1; i >= 0; i--) {
7289
+ const m = messages[i];
7290
+ if (m && m.role === "user") return m.id;
7291
+ }
7292
+ return null;
7293
+ }
7294
+ var SCROLL_KEYS = /* @__PURE__ */ new Set(["ArrowUp", "ArrowDown", "PageUp", "PageDown", "Home", "End", " "]);
7295
+ function usePresetDial({ layerRef, dialRef, dialButtonRef, messages, isStreaming, activeTab }) {
7296
+ const [expanded, setExpanded] = useState34(() => !sendInFlight(messages, isStreaming));
7297
+ const fold = useCallback5(() => setExpanded(false), []);
7298
+ const toggle = useCallback5(() => setExpanded((e) => !e), []);
7299
+ const currentUserId = lastUserId(messages);
7300
+ const userIdRef = useRef10(currentUserId);
7301
+ useEffect14(() => {
7302
+ if (currentUserId === userIdRef.current) return;
7303
+ userIdRef.current = currentUserId;
7304
+ setExpanded(false);
7305
+ }, [currentUserId]);
7306
+ const tabRef = useRef10(activeTab);
7307
+ useEffect14(() => {
7308
+ if (activeTab === tabRef.current) return;
7309
+ tabRef.current = activeTab;
7310
+ setExpanded(false);
7311
+ }, [activeTab]);
7312
+ const expandedRef = useRef10(expanded);
7313
+ expandedRef.current = expanded;
7314
+ useEffect14(() => {
7315
+ const onEscape = (e) => {
7316
+ const dial = dialRef.current;
7317
+ if (e.key !== "Escape" || !dial || !e.composedPath().includes(dial)) return;
7318
+ e.stopPropagation();
7319
+ e.preventDefault();
7320
+ if (!expandedRef.current) return;
7321
+ setExpanded(false);
7322
+ dialButtonRef.current?.focus();
7323
+ };
7324
+ window.addEventListener("keydown", onEscape, true);
7325
+ return () => window.removeEventListener("keydown", onEscape, true);
7326
+ }, [dialRef, dialButtonRef]);
7327
+ useEffect14(() => {
7328
+ if (!expanded) return;
7329
+ const layer = layerRef.current;
7330
+ const inDial = (e) => {
7331
+ const dial = dialRef.current;
7332
+ return !!dial && e.composedPath().includes(dial);
7333
+ };
7334
+ const inList = (e) => e.composedPath().some((n) => n instanceof Element && n.classList.contains("ss-w-list"));
7335
+ const onPress = (e) => {
7336
+ if (!inDial(e)) setExpanded(false);
7337
+ };
7338
+ const onScrollGesture = (e) => {
7339
+ if (inList(e) && !inDial(e)) setExpanded(false);
7340
+ };
7341
+ const onKey = (e) => {
7342
+ if (SCROLL_KEYS.has(e.key) && inList(e)) setExpanded(false);
7343
+ };
7344
+ const doc = layer?.ownerDocument ?? document;
7345
+ doc.addEventListener("pointerdown", onPress, true);
7346
+ layer?.addEventListener("wheel", onScrollGesture, { passive: true });
7347
+ layer?.addEventListener("touchmove", onScrollGesture, { passive: true });
7348
+ window.addEventListener("keydown", onKey, true);
7349
+ return () => {
7350
+ doc.removeEventListener("pointerdown", onPress, true);
7351
+ layer?.removeEventListener("wheel", onScrollGesture);
7352
+ layer?.removeEventListener("touchmove", onScrollGesture);
7353
+ window.removeEventListener("keydown", onKey, true);
7354
+ };
7355
+ }, [expanded, layerRef, dialRef]);
7356
+ return { expanded, toggle, fold };
7357
+ }
7358
+
7359
+ // src/presetSeen.ts
7360
+ var KEY = "ss-w-presets-tool-opened";
7361
+ var openedThisPage = false;
7362
+ function readToolSeen() {
7363
+ if (openedThisPage) return true;
7364
+ try {
7365
+ return window.localStorage.getItem(KEY) === "1";
7366
+ } catch {
7367
+ return false;
7368
+ }
7369
+ }
7370
+ function markToolSeen() {
7371
+ openedThisPage = true;
7372
+ try {
7373
+ window.localStorage.setItem(KEY, "1");
7374
+ } catch {
7375
+ }
7376
+ }
7377
+
7378
+ // src/components/ChatPanel.tsx
7379
+ import { jsx as jsx90, jsxs as jsxs74 } from "react/jsx-runtime";
6836
7380
  var FULLSCREEN_QUERY = "(max-width: 639px)";
6837
7381
  function useIsModal() {
6838
- const [modal, setModal] = useState33(false);
6839
- useEffect12(() => {
7382
+ const [modal, setModal] = useState35(false);
7383
+ useEffect15(() => {
6840
7384
  if (typeof window === "undefined" || typeof window.matchMedia !== "function") return;
6841
7385
  const mql = window.matchMedia(FULLSCREEN_QUERY);
6842
7386
  const sync = () => setModal(mql.matches);
@@ -6846,10 +7390,11 @@ function useIsModal() {
6846
7390
  }, []);
6847
7391
  return modal;
6848
7392
  }
6849
- function ChatPanel({ controller, ui, slip, onDashboardTap, monitor, discovery, gamesResult, leaderboardResult, loggedIn, conversation }) {
7393
+ var HIDDEN_LAYER_STYLE = { position: "absolute", inset: 0, visibility: "hidden", display: "flex", flexDirection: "column" };
7394
+ function ChatPanel({ controller, ui, slip, onDashboardTap, monitor, discovery, gamesResult, leaderboardResult, loggedIn, conversation, presetGrid }) {
6850
7395
  const { state, close, stop, send, reset } = controller;
6851
7396
  const isModal = useIsModal();
6852
- useEffect12(() => {
7397
+ useEffect15(() => {
6853
7398
  const onKey = (e) => {
6854
7399
  if (e.key === "Escape") close();
6855
7400
  };
@@ -6864,11 +7409,66 @@ function ChatPanel({ controller, ui, slip, onDashboardTap, monitor, discovery, g
6864
7409
  monitor?.setActiveTab("chat");
6865
7410
  send(text, { grounding });
6866
7411
  };
6867
- useEffect12(() => {
7412
+ useEffect15(() => {
6868
7413
  if (monitor && monitor.activeTab !== "chat" && state.isStreaming) monitor.markChatActivity();
6869
7414
  }, [monitor, state.isStreaming, state.messages.length]);
6870
- return /* @__PURE__ */ jsxs72("div", { className: "ss-w-panel", role: "dialog", "aria-modal": isModal, "aria-label": ui.chatDialogLabel, children: [
6871
- /* @__PURE__ */ jsx88(
7415
+ const onChatTab = !monitor || monitor.activeTab === "chat";
7416
+ const layerRef = useRef11(null);
7417
+ const dialRef = useRef11(null);
7418
+ const dialButtonRef = useRef11(null);
7419
+ const inputRef = useRef11(null);
7420
+ const focusAfterCloseRef = useRef11(null);
7421
+ const [tool, setTool] = useState35(null);
7422
+ const [toolSeen, setToolSeen] = useState35(readToolSeen);
7423
+ const dial = usePresetDial({
7424
+ layerRef,
7425
+ dialRef,
7426
+ dialButtonRef,
7427
+ messages: state.messages,
7428
+ isStreaming: state.isStreaming,
7429
+ activeTab: monitor?.activeTab ?? "chat"
7430
+ });
7431
+ const { fold } = dial;
7432
+ const showDial = !!presetGrid && onChatTab && !tool;
7433
+ const openTool = useCallback6((next) => {
7434
+ fold();
7435
+ markToolSeen();
7436
+ setToolSeen(true);
7437
+ setTool(next);
7438
+ }, [fold]);
7439
+ const onPill = useCallback6((kind) => openTool(kind === "numbers" ? { kind: "numbers" } : { kind: "combo" }), [openTool]);
7440
+ const toolsValue = useMemo(
7441
+ () => presetGrid ? { openBuilder: (total, legs) => openTool({ kind: "combo", total, legs }), changeLabel: ui.presets.changeCombo } : null,
7442
+ [presetGrid, openTool, ui.presets.changeCombo]
7443
+ );
7444
+ const closeTool = useCallback6(() => {
7445
+ focusAfterCloseRef.current = "dial";
7446
+ setTool(null);
7447
+ }, []);
7448
+ const sendAsk = useCallback6((ask) => {
7449
+ focusAfterCloseRef.current = "composer";
7450
+ setTool(null);
7451
+ monitor?.setActiveTab("chat");
7452
+ send(ask.message, { displayText: ask.displayText, preset: ask.preset });
7453
+ }, [monitor, send]);
7454
+ const formatKickoff = useCallback6((kickoffUtc) => {
7455
+ const k = ui.bettingKickoff(kickoffUtc);
7456
+ return `${k.dayLabel} ${k.time}`;
7457
+ }, [ui]);
7458
+ useEffect15(() => {
7459
+ if (tool) return;
7460
+ const target = focusAfterCloseRef.current;
7461
+ focusAfterCloseRef.current = null;
7462
+ if (target === "dial") dialButtonRef.current?.focus();
7463
+ else if (target === "composer") inputRef.current?.focus();
7464
+ }, [tool]);
7465
+ useEffect15(() => {
7466
+ if (!tool || onChatTab && presetGrid) return;
7467
+ focusAfterCloseRef.current = "composer";
7468
+ setTool(null);
7469
+ }, [tool, onChatTab, presetGrid]);
7470
+ return /* @__PURE__ */ jsxs74("div", { className: "ss-w-panel", role: "dialog", "aria-modal": isModal, "aria-label": ui.chatDialogLabel, children: [
7471
+ /* @__PURE__ */ jsx90(
6872
7472
  ChatHeader,
6873
7473
  {
6874
7474
  onClose: close,
@@ -6881,7 +7481,7 @@ function ChatPanel({ controller, ui, slip, onDashboardTap, monitor, discovery, g
6881
7481
  moreOptionsLabel: ui.moreOptions
6882
7482
  }
6883
7483
  ),
6884
- monitor && /* @__PURE__ */ jsx88(
7484
+ monitor && /* @__PURE__ */ jsx90(
6885
7485
  TabBar,
6886
7486
  {
6887
7487
  monitor,
@@ -6893,17 +7493,26 @@ function ChatPanel({ controller, ui, slip, onDashboardTap, monitor, discovery, g
6893
7493
  gamesEnabled: gamesResult?.enabled ?? false
6894
7494
  }
6895
7495
  ),
6896
- /* @__PURE__ */ jsx88(
6897
- "div",
6898
- {
6899
- style: !monitor || monitor.activeTab === "chat" ? { display: "contents" } : { position: "absolute", inset: 0, visibility: "hidden", display: "flex", flexDirection: "column" },
6900
- children: /* @__PURE__ */ jsx88(MessageList, { controller, ui, slip, onDashboardTap, monitor })
6901
- }
6902
- ),
6903
- monitor && discovery && monitor.activeTab === "today" && /* @__PURE__ */ jsx88(TodayPanel, { discovery, ui, monitor }),
6904
- monitor && monitor.activeTab === "following" && /* @__PURE__ */ jsx88(MatchesPanel, { monitor, ui, slip, onAsk: handleAsk }),
6905
- monitor && monitor.activeTab === "odds" && /* @__PURE__ */ jsx88(OddsPanel, { monitor, ui, slip }),
6906
- monitor && monitor.activeTab === "games" && gamesResult && /* @__PURE__ */ jsx88(
7496
+ /* @__PURE__ */ jsxs74("div", { ref: layerRef, className: "ss-w-chat-layer", "data-dial": presetGrid ? "" : void 0, style: onChatTab ? void 0 : HIDDEN_LAYER_STYLE, children: [
7497
+ /* @__PURE__ */ jsx90(PresetToolsContext.Provider, { value: toolsValue, children: /* @__PURE__ */ jsx90(MessageList, { controller, ui, slip, onDashboardTap, monitor }) }),
7498
+ showDial ? /* @__PURE__ */ jsx90(
7499
+ PresetSpeedDial,
7500
+ {
7501
+ expanded: dial.expanded,
7502
+ showDot: !toolSeen,
7503
+ strings: ui.presets,
7504
+ onToggle: dial.toggle,
7505
+ onOpen: onPill,
7506
+ rootRef: dialRef,
7507
+ buttonRef: dialButtonRef
7508
+ }
7509
+ ) : null,
7510
+ tool && presetGrid && onChatTab ? tool.kind === "numbers" ? /* @__PURE__ */ jsx90(SetNumbersTool, { grid: presetGrid, strings: ui.presets, streaming: state.isStreaming, formatKickoff, onSend: sendAsk, onClose: closeTool }) : /* @__PURE__ */ jsx90(BuildComboTool, { grid: presetGrid, strings: ui.presets, streaming: state.isStreaming, formatKickoff, initialTotal: tool.total, initialLegs: tool.legs, onSend: sendAsk, onClose: closeTool }) : null
7511
+ ] }),
7512
+ monitor && discovery && monitor.activeTab === "today" && /* @__PURE__ */ jsx90(TodayPanel, { discovery, ui, monitor }),
7513
+ monitor && monitor.activeTab === "following" && /* @__PURE__ */ jsx90(MatchesPanel, { monitor, ui, slip, onAsk: handleAsk }),
7514
+ monitor && monitor.activeTab === "odds" && /* @__PURE__ */ jsx90(OddsPanel, { monitor, ui, slip }),
7515
+ monitor && monitor.activeTab === "games" && gamesResult && /* @__PURE__ */ jsx90(
6907
7516
  GamesPanel,
6908
7517
  {
6909
7518
  loggedIn: !!loggedIn,
@@ -6917,7 +7526,7 @@ function ChatPanel({ controller, ui, slip, onDashboardTap, monitor, discovery, g
6917
7526
  }
6918
7527
  }
6919
7528
  ),
6920
- monitor && monitor.activeTab === "leaderboard" && leaderboardResult && /* @__PURE__ */ jsx88(
7529
+ monitor && monitor.activeTab === "leaderboard" && leaderboardResult && /* @__PURE__ */ jsx90(
6921
7530
  LeaderboardPanel,
6922
7531
  {
6923
7532
  loggedIn: !!loggedIn,
@@ -6927,7 +7536,20 @@ function ChatPanel({ controller, ui, slip, onDashboardTap, monitor, discovery, g
6927
7536
  ui
6928
7537
  }
6929
7538
  ),
6930
- (!monitor || monitor.activeTab !== "games" && monitor.activeTab !== "leaderboard") && /* @__PURE__ */ jsx88(ChatInput, { placeholder: ui.placeholder, streaming: state.isStreaming, onSend: handleSend, onStop: stop, stopLabel: ui.stopResponse, sendLabel: ui.sendMessage })
7539
+ (!monitor || monitor.activeTab !== "games" && monitor.activeTab !== "leaderboard") && /* @__PURE__ */ jsx90(
7540
+ ChatInput,
7541
+ {
7542
+ ref: inputRef,
7543
+ placeholder: ui.placeholder,
7544
+ streaming: state.isStreaming,
7545
+ onSend: handleSend,
7546
+ onStop: stop,
7547
+ stopLabel: ui.stopResponse,
7548
+ sendLabel: ui.sendMessage,
7549
+ disabled: !!tool,
7550
+ onType: fold
7551
+ }
7552
+ )
6931
7553
  ] });
6932
7554
  }
6933
7555
 
@@ -7136,15 +7758,15 @@ function toSlipEvent(name, entry) {
7136
7758
  // src/useHostEvents.ts
7137
7759
  function useHostEvents(opts) {
7138
7760
  const { host, operatorId, currentUser, logger, isOpen, closePanel, onOpenMatch, openMatchLabel } = opts;
7139
- const [added, setAdded] = useState34({});
7140
- const [settled, setSettled] = useState34({});
7141
- const markAdded = useCallback5((matchOddsId) => {
7761
+ const [added, setAdded] = useState36({});
7762
+ const [settled, setSettled] = useState36({});
7763
+ const markAdded = useCallback7((matchOddsId) => {
7142
7764
  setAdded((prev) => prev[matchOddsId] ? prev : { ...prev, [matchOddsId]: true });
7143
7765
  }, []);
7144
- const settle = useCallback5((matchOddsId) => {
7766
+ const settle = useCallback7((matchOddsId) => {
7145
7767
  setSettled((prev) => prev[matchOddsId] ? prev : { ...prev, [matchOddsId]: true });
7146
7768
  }, []);
7147
- const clearMarks = useCallback5((ids) => {
7769
+ const clearMarks = useCallback7((ids) => {
7148
7770
  const drop = (prev) => {
7149
7771
  if (!ids.some((id) => prev[id])) return prev;
7150
7772
  const next = { ...prev };
@@ -7155,26 +7777,26 @@ function useHostEvents(opts) {
7155
7777
  setSettled(drop);
7156
7778
  }, []);
7157
7779
  const hasHost = host !== void 0;
7158
- const ledger = useMemo(
7780
+ const ledger = useMemo2(
7159
7781
  () => hasHost ? createSlipLedger(`ss-w-slip:${operatorId}`) : null,
7160
7782
  [hasHost, operatorId]
7161
7783
  );
7162
- const currentUserRef = useRef8(currentUser);
7784
+ const currentUserRef = useRef12(currentUser);
7163
7785
  currentUserRef.current = currentUser;
7164
- const recordAdd = useCallback5((add) => {
7786
+ const recordAdd = useCallback7((add) => {
7165
7787
  ledger?.record({ id: add.selection.matchOddsId, market: add.market, ...add.pick, user: currentUserRef.current });
7166
7788
  }, [ledger]);
7167
- const slipState = useMemo(
7789
+ const slipState = useMemo2(
7168
7790
  () => ({ added, settled, settle, recordAdd }),
7169
7791
  [added, settled, settle, recordAdd]
7170
7792
  );
7171
7793
  const hasOpenMatch = typeof onOpenMatch === "function";
7172
7794
  const tracksViews = hasHost || hasOpenMatch;
7173
- const views = useMemo(
7795
+ const views = useMemo2(
7174
7796
  () => tracksViews ? createMatchViews(`ss-w-views:${operatorId}`, { user: currentUser }) : null,
7175
7797
  [tracksViews, operatorId, currentUser]
7176
7798
  );
7177
- const sinkRef = useRef8(() => {
7799
+ const sinkRef = useRef12(() => {
7178
7800
  });
7179
7801
  sinkRef.current = (command) => {
7180
7802
  if (command.kind === "matchOpened") {
@@ -7195,11 +7817,11 @@ function useHostEvents(opts) {
7195
7817
  logger?.log(toSlipEvent(name, entry));
7196
7818
  }
7197
7819
  };
7198
- useEffect13(() => {
7820
+ useEffect16(() => {
7199
7821
  if (!host) return;
7200
7822
  return attachHost(host, (command) => sinkRef.current(command));
7201
7823
  }, [host]);
7202
- useEffect13(() => {
7824
+ useEffect16(() => {
7203
7825
  if (!isOpen || !views) return;
7204
7826
  const held = views.markOpened();
7205
7827
  if (held.length === 0) return;
@@ -7208,7 +7830,7 @@ function useHostEvents(opts) {
7208
7830
  }
7209
7831
  void logger?.flushNow();
7210
7832
  }, [isOpen, views, logger]);
7211
- const openMatchRef = useRef8(() => {
7833
+ const openMatchRef = useRef12(() => {
7212
7834
  });
7213
7835
  openMatchRef.current = (matchId) => {
7214
7836
  if (views?.view(matchId) === "log") {
@@ -7222,7 +7844,7 @@ function useHostEvents(opts) {
7222
7844
  }
7223
7845
  if (typeof window.matchMedia === "function" && window.matchMedia(FULLSCREEN_QUERY).matches) closePanel();
7224
7846
  };
7225
- const matchLink = useMemo(
7847
+ const matchLink = useMemo2(
7226
7848
  () => hasOpenMatch ? { open: (matchId) => openMatchRef.current(matchId), label: openMatchLabel } : null,
7227
7849
  [hasOpenMatch, openMatchLabel]
7228
7850
  );
@@ -7290,7 +7912,7 @@ function themeAttrs(ui) {
7290
7912
  }
7291
7913
 
7292
7914
  // src/useChatStream.ts
7293
- import { useCallback as useCallback6, useEffect as useEffect15, useMemo as useMemo2, useReducer as useReducer2, useRef as useRef10 } from "react";
7915
+ import { useCallback as useCallback8, useEffect as useEffect18, useMemo as useMemo3, useReducer as useReducer2, useRef as useRef14 } from "react";
7294
7916
  import { WidgetSdkError as WidgetSdkError2 } from "@sensiblestats/widget-sdk";
7295
7917
 
7296
7918
  // src/reducer.ts
@@ -7450,7 +8072,7 @@ function reducer(state, action) {
7450
8072
  }
7451
8073
 
7452
8074
  // src/useLiveDashboardPoll.ts
7453
- import { useEffect as useEffect14, useReducer, useRef as useRef9 } from "react";
8075
+ import { useEffect as useEffect17, useReducer, useRef as useRef13 } from "react";
7454
8076
  var POLL_INTERVAL_MS = 6e4;
7455
8077
  var LEAD_MS = 12e4;
7456
8078
  var GIVE_UP_MS = 30 * 6e4;
@@ -7494,11 +8116,11 @@ function findEarliestWakeupMs(messages) {
7494
8116
  function useLiveDashboardPoll({ messages, dispatch, getLiveDashboard }) {
7495
8117
  const [, forceUpdate] = useReducer((x) => x + 1, 0);
7496
8118
  const anchor = findAnchor(messages);
7497
- const anchorRef = useRef9(anchor);
8119
+ const anchorRef = useRef13(anchor);
7498
8120
  anchorRef.current = anchor;
7499
- const inFlightRef = useRef9(false);
8121
+ const inFlightRef = useRef13(false);
7500
8122
  const anchorKey = anchor ? `${anchor.messageId}:${anchor.kind}` : null;
7501
- useEffect14(() => {
8123
+ useEffect17(() => {
7502
8124
  if (!anchorKey || !anchorRef.current) return;
7503
8125
  const tick = async () => {
7504
8126
  if (inFlightRef.current) return;
@@ -7534,7 +8156,7 @@ function useLiveDashboardPoll({ messages, dispatch, getLiveDashboard }) {
7534
8156
  }, POLL_INTERVAL_MS);
7535
8157
  return () => window.clearInterval(id);
7536
8158
  }, [anchorKey, dispatch, getLiveDashboard]);
7537
- useEffect14(() => {
8159
+ useEffect17(() => {
7538
8160
  if (anchorKey !== null) return;
7539
8161
  const wakeAt = findEarliestWakeupMs(messages);
7540
8162
  if (wakeAt === null) return;
@@ -7599,7 +8221,7 @@ function seedIdCounter(messages) {
7599
8221
  return max + 1;
7600
8222
  }
7601
8223
  function useChatStream(conversation, starterTexts, greetingMessage, storageKey) {
7602
- const boot = useMemo2(() => {
8224
+ const boot = useMemo3(() => {
7603
8225
  const payload = storageKey ? readConversation(storageKey, Date.now()) : null;
7604
8226
  const restored = payload != null && payload !== "expired" && payload !== "corrupt";
7605
8227
  const base = initialState(starterTexts.map(chipFromText));
@@ -7609,26 +8231,26 @@ function useChatStream(conversation, starterTexts, greetingMessage, storageKey)
7609
8231
  return { state: base, restored: false, stale: payload === "expired" || payload === "corrupt" };
7610
8232
  }, []);
7611
8233
  const [state, dispatch] = useReducer2(reducer, boot.state);
7612
- const idRef = useRef10(seedIdCounter(boot.state.messages));
7613
- const handleRef = useRef10(null);
7614
- const fallbackTimerRef = useRef10(null);
7615
- const stateRef = useRef10(state);
8234
+ const idRef = useRef14(seedIdCounter(boot.state.messages));
8235
+ const handleRef = useRef14(null);
8236
+ const fallbackTimerRef = useRef14(null);
8237
+ const stateRef = useRef14(state);
7616
8238
  stateRef.current = state;
7617
- const greetedRef = useRef10(boot.restored);
7618
- const hydratedRef = useRef10(false);
7619
- const restoreSeededRef = useRef10(false);
8239
+ const greetedRef = useRef14(boot.restored);
8240
+ const hydratedRef = useRef14(false);
8241
+ const restoreSeededRef = useRef14(false);
7620
8242
  if (!restoreSeededRef.current) {
7621
8243
  restoreSeededRef.current = true;
7622
8244
  if (boot.restored && boot.state.conversationId) conversation.conversationId = boot.state.conversationId;
7623
8245
  }
7624
8246
  const nextId = () => `m${idRef.current++}`;
7625
- const clearFallbackTimer = useCallback6(() => {
8247
+ const clearFallbackTimer = useCallback8(() => {
7626
8248
  if (fallbackTimerRef.current != null) {
7627
8249
  clearTimeout(fallbackTimerRef.current);
7628
8250
  fallbackTimerRef.current = null;
7629
8251
  }
7630
8252
  }, []);
7631
- const armFallbackTimer = useCallback6((onFire) => {
8253
+ const armFallbackTimer = useCallback8((onFire) => {
7632
8254
  clearFallbackTimer();
7633
8255
  fallbackTimerRef.current = setTimeout(() => {
7634
8256
  fallbackTimerRef.current = null;
@@ -7637,7 +8259,7 @@ function useChatStream(conversation, starterTexts, greetingMessage, storageKey)
7637
8259
  onFire();
7638
8260
  }, STREAM_FALLBACK_TIMEOUT_MS);
7639
8261
  }, [clearFallbackTimer]);
7640
- const consume = useCallback6((handle, onError) => {
8262
+ const consume = useCallback8((handle, onError) => {
7641
8263
  ;
7642
8264
  (async () => {
7643
8265
  try {
@@ -7655,7 +8277,7 @@ function useChatStream(conversation, starterTexts, greetingMessage, storageKey)
7655
8277
  }
7656
8278
  })();
7657
8279
  }, [clearFallbackTimer]);
7658
- const run = useCallback6((input, displayText) => {
8280
+ const run = useCallback8((input, displayText) => {
7659
8281
  handleRef.current?.cancel();
7660
8282
  clearFallbackTimer();
7661
8283
  dispatch({ kind: "USER_SENT", input, userId: nextId(), assistantId: nextId(), displayText });
@@ -7667,7 +8289,7 @@ function useChatStream(conversation, starterTexts, greetingMessage, storageKey)
7667
8289
  });
7668
8290
  consume(handle, (err) => dispatch({ kind: "STREAM_ERROR", error: err }));
7669
8291
  }, [conversation, consume, clearFallbackTimer, armFallbackTimer]);
7670
- const send = useCallback6((text, opts) => {
8292
+ const send = useCallback8((text, opts) => {
7671
8293
  const trimmed = text.trim();
7672
8294
  if (!trimmed) return;
7673
8295
  run({
@@ -7675,14 +8297,15 @@ function useChatStream(conversation, starterTexts, greetingMessage, storageKey)
7675
8297
  actionId: opts?.actionId,
7676
8298
  actionRef: opts?.actionRef,
7677
8299
  grounding: opts?.grounding,
7678
- clientCapabilities: [...CLIENT_CAPABILITIES]
8300
+ clientCapabilities: [...CLIENT_CAPABILITIES],
8301
+ ...opts?.preset ? { preset: opts.preset } : {}
7679
8302
  }, opts?.displayText);
7680
8303
  }, [run]);
7681
- const retry = useCallback6(() => {
8304
+ const retry = useCallback8(() => {
7682
8305
  const last = stateRef.current.lastUserInput;
7683
8306
  if (last) run(last, stateRef.current.lastDisplayText ?? void 0);
7684
8307
  }, [run]);
7685
- const fireGreeting = useCallback6(() => {
8308
+ const fireGreeting = useCallback8(() => {
7686
8309
  const message = greetingMessage?.trim();
7687
8310
  if (!message) return;
7688
8311
  try {
@@ -7697,15 +8320,15 @@ function useChatStream(conversation, starterTexts, greetingMessage, storageKey)
7697
8320
  dispatch({ kind: "STOP" });
7698
8321
  }
7699
8322
  }, [greetingMessage, conversation, consume, clearFallbackTimer, armFallbackTimer]);
7700
- useEffect15(() => {
8323
+ useEffect18(() => {
7701
8324
  if (greetedRef.current) return;
7702
8325
  greetedRef.current = true;
7703
8326
  fireGreeting();
7704
8327
  }, [fireGreeting]);
7705
- useEffect15(() => {
8328
+ useEffect18(() => {
7706
8329
  if (boot.stale && storageKey) clearConversation(storageKey);
7707
8330
  }, []);
7708
- useEffect15(() => {
8331
+ useEffect18(() => {
7709
8332
  if (!storageKey || state.isStreaming) return;
7710
8333
  if (hydratedRef.current) {
7711
8334
  hydratedRef.current = false;
@@ -7722,7 +8345,7 @@ function useChatStream(conversation, starterTexts, greetingMessage, storageKey)
7722
8345
  if (persistable.length === 0) return;
7723
8346
  writeConversation(storageKey, state.conversationId, persistable, Date.now());
7724
8347
  }, [storageKey, state.isStreaming, state.messages, state.conversationId]);
7725
- useEffect15(() => {
8348
+ useEffect18(() => {
7726
8349
  if (!storageKey) return;
7727
8350
  const onStorage = (e) => {
7728
8351
  if (e.key !== storageKey || e.storageArea !== window.localStorage) return;
@@ -7746,25 +8369,25 @@ function useChatStream(conversation, starterTexts, greetingMessage, storageKey)
7746
8369
  window.addEventListener("storage", onStorage);
7747
8370
  return () => window.removeEventListener("storage", onStorage);
7748
8371
  }, [storageKey, conversation, fireGreeting]);
7749
- const open = useCallback6(() => dispatch({ kind: "OPEN" }), []);
7750
- const close = useCallback6(() => {
8372
+ const open = useCallback8(() => dispatch({ kind: "OPEN" }), []);
8373
+ const close = useCallback8(() => {
7751
8374
  handleRef.current?.cancel();
7752
8375
  handleRef.current = null;
7753
8376
  clearFallbackTimer();
7754
8377
  dispatch({ kind: "CLOSE" });
7755
8378
  }, [clearFallbackTimer]);
7756
- const stop = useCallback6(() => {
8379
+ const stop = useCallback8(() => {
7757
8380
  handleRef.current?.cancel();
7758
8381
  handleRef.current = null;
7759
8382
  clearFallbackTimer();
7760
8383
  dispatch({ kind: "STOP" });
7761
8384
  }, [clearFallbackTimer]);
7762
- const toggle = useCallback6(() => {
8385
+ const toggle = useCallback8(() => {
7763
8386
  if (stateRef.current.isOpen) close();
7764
8387
  else open();
7765
8388
  }, [close, open]);
7766
- const dismissPopup = useCallback6(() => dispatch({ kind: "DISMISS_POPUP" }), []);
7767
- const reset = useCallback6(() => {
8389
+ const dismissPopup = useCallback8(() => dispatch({ kind: "DISMISS_POPUP" }), []);
8390
+ const reset = useCallback8(() => {
7768
8391
  handleRef.current?.cancel();
7769
8392
  handleRef.current = null;
7770
8393
  clearFallbackTimer();
@@ -7775,18 +8398,18 @@ function useChatStream(conversation, starterTexts, greetingMessage, storageKey)
7775
8398
  greetedRef.current = true;
7776
8399
  fireGreeting();
7777
8400
  }, [storageKey, conversation, clearFallbackTimer, fireGreeting]);
7778
- const getLiveDashboard = useCallback6(() => conversation.getLiveDashboard(), [conversation]);
8401
+ const getLiveDashboard = useCallback8(() => conversation.getLiveDashboard(), [conversation]);
7779
8402
  useLiveDashboardPoll({ messages: state.messages, dispatch, getLiveDashboard });
7780
8403
  return { state, open, close, stop, toggle, send, retry, dismissPopup, reset, _dispatch: dispatch };
7781
8404
  }
7782
8405
 
7783
8406
  // src/useLiveMonitor.ts
7784
- import { useCallback as useCallback7, useEffect as useEffect16, useRef as useRef11, useState as useState36 } from "react";
8407
+ import { useCallback as useCallback9, useEffect as useEffect19, useRef as useRef15, useState as useState38 } from "react";
7785
8408
 
7786
8409
  // src/useTabs.ts
7787
- import { useState as useState35 } from "react";
8410
+ import { useState as useState37 } from "react";
7788
8411
  function useTabs(initial = "chat") {
7789
- const [activeTab, setActiveTab] = useState35(initial);
8412
+ const [activeTab, setActiveTab] = useState37(initial);
7790
8413
  return { activeTab, setActiveTab };
7791
8414
  }
7792
8415
 
@@ -7811,38 +8434,38 @@ function phaseForPin(snapshot, pin) {
7811
8434
  return snapshot.markets.find((mk) => mk.key === pin.key)?.phase;
7812
8435
  }
7813
8436
  function useLiveMonitor(opts) {
7814
- const sessionIdRef = useRef11(crypto.randomUUID());
7815
- const [pins, setPins] = useState36([]);
7816
- const [store] = useState36(() => createPinStore(opts.storageKey, sessionIdRef.current, setPins));
7817
- const [enabled, setEnabled] = useState36(true);
7818
- const [snapshot, setSnapshotState] = useState36(null);
8437
+ const sessionIdRef = useRef15(crypto.randomUUID());
8438
+ const [pins, setPins] = useState38([]);
8439
+ const [store] = useState38(() => createPinStore(opts.storageKey, sessionIdRef.current, setPins));
8440
+ const [enabled, setEnabled] = useState38(true);
8441
+ const [snapshot, setSnapshotState] = useState38(null);
7819
8442
  const { activeTab, setActiveTab: setActiveTabState } = useTabs("chat");
7820
- const [chatDot, setChatDot] = useState36(false);
7821
- const [matchesDot, setMatchesDot] = useState36(false);
7822
- const [oddsDot, setOddsDot] = useState36(false);
7823
- const [todayDot, setTodayDot] = useState36(false);
7824
- const [lastViewedPrices, setLastViewedPrices] = useState36({});
7825
- const [openMarket, setOpenMarket] = useState36({});
7826
- const [marketOdds, setMarketOdds] = useState36({});
7827
- const pinsRef = useRef11(pins);
8443
+ const [chatDot, setChatDot] = useState38(false);
8444
+ const [matchesDot, setMatchesDot] = useState38(false);
8445
+ const [oddsDot, setOddsDot] = useState38(false);
8446
+ const [todayDot, setTodayDot] = useState38(false);
8447
+ const [lastViewedPrices, setLastViewedPrices] = useState38({});
8448
+ const [openMarket, setOpenMarket] = useState38({});
8449
+ const [marketOdds, setMarketOdds] = useState38({});
8450
+ const pinsRef = useRef15(pins);
7828
8451
  pinsRef.current = pins;
7829
- const watchesRef = useRef11(opts.watches ?? []);
8452
+ const watchesRef = useRef15(opts.watches ?? []);
7830
8453
  watchesRef.current = opts.watches ?? [];
7831
- const activeTabRef = useRef11(activeTab);
8454
+ const activeTabRef = useRef15(activeTab);
7832
8455
  activeTabRef.current = activeTab;
7833
- const snapshotRef = useRef11(snapshot);
8456
+ const snapshotRef = useRef15(snapshot);
7834
8457
  snapshotRef.current = snapshot;
7835
- const lastViewedPricesRef = useRef11(lastViewedPrices);
8458
+ const lastViewedPricesRef = useRef15(lastViewedPrices);
7836
8459
  lastViewedPricesRef.current = lastViewedPrices;
7837
- const openMarketRef = useRef11(openMarket);
8460
+ const openMarketRef = useRef15(openMarket);
7838
8461
  openMarketRef.current = openMarket;
7839
- const marketOddsRef = useRef11(marketOdds);
8462
+ const marketOddsRef = useRef15(marketOdds);
7840
8463
  marketOddsRef.current = marketOdds;
7841
8464
  const hasToday = opts.hasToday ?? false;
7842
8465
  const marketKey = (matchId, dev) => `${matchId}:${dev}`;
7843
- const getMarketOddsRef = useRef11(opts.getMarketOdds);
8466
+ const getMarketOddsRef = useRef15(opts.getMarketOdds);
7844
8467
  getMarketOddsRef.current = opts.getMarketOdds;
7845
- const fetchMarket = useCallback7((matchId, dev) => {
8468
+ const fetchMarket = useCallback9((matchId, dev) => {
7846
8469
  const key = marketKey(matchId, dev);
7847
8470
  const existing = marketOddsRef.current[key];
7848
8471
  if (existing?.loading) return;
@@ -7861,7 +8484,7 @@ function useLiveMonitor(opts) {
7861
8484
  setMarketOdds((prev) => ({ ...prev, [key]: entry }));
7862
8485
  });
7863
8486
  }, []);
7864
- const toggleMarket = useCallback7((matchId, dev) => {
8487
+ const toggleMarket = useCallback9((matchId, dev) => {
7865
8488
  const key = marketKey(matchId, dev);
7866
8489
  const wasOpen = openMarketRef.current[matchId] === dev;
7867
8490
  const nextValue = wasOpen ? null : dev;
@@ -7872,10 +8495,10 @@ function useLiveMonitor(opts) {
7872
8495
  fetchMarket(matchId, dev);
7873
8496
  }
7874
8497
  }, [fetchMarket]);
7875
- const retryMarket = useCallback7((matchId, dev) => {
8498
+ const retryMarket = useCallback9((matchId, dev) => {
7876
8499
  fetchMarket(matchId, dev);
7877
8500
  }, [fetchMarket]);
7878
- const applySnapshot = useCallback7((next) => {
8501
+ const applySnapshot = useCallback9((next) => {
7879
8502
  if (next === null) {
7880
8503
  setEnabled(false);
7881
8504
  return;
@@ -7938,13 +8561,13 @@ function useLiveMonitor(opts) {
7938
8561
  snapshotRef.current = next;
7939
8562
  setSnapshotState(next);
7940
8563
  }, [store]);
7941
- const inFlightRef = useRef11(false);
7942
- const timerRef = useRef11(void 0);
7943
- const disabledRef = useRef11(false);
7944
- const mountedRef = useRef11(false);
7945
- const getLiveMonitorRef = useRef11(opts.getLiveMonitor);
8564
+ const inFlightRef = useRef15(false);
8565
+ const timerRef = useRef15(void 0);
8566
+ const disabledRef = useRef15(false);
8567
+ const mountedRef = useRef15(false);
8568
+ const getLiveMonitorRef = useRef15(opts.getLiveMonitor);
7946
8569
  getLiveMonitorRef.current = opts.getLiveMonitor;
7947
- const tick = useCallback7(async () => {
8570
+ const tick = useCallback9(async () => {
7948
8571
  const allPins = [...pinsRef.current, ...watchesRef.current];
7949
8572
  if (inFlightRef.current || disabledRef.current || allPins.length === 0) return;
7950
8573
  inFlightRef.current = true;
@@ -7968,7 +8591,7 @@ function useLiveMonitor(opts) {
7968
8591
  scheduleRef.current();
7969
8592
  }
7970
8593
  }, [applySnapshot, fetchMarket]);
7971
- const scheduleRef = useRef11(() => {
8594
+ const scheduleRef = useRef15(() => {
7972
8595
  });
7973
8596
  scheduleRef.current = () => {
7974
8597
  window.clearTimeout(timerRef.current);
@@ -7978,14 +8601,14 @@ function useLiveMonitor(opts) {
7978
8601
  const hinted = snapshotRef.current ? snapshotRef.current.nextPollSeconds * 1e3 : fallbackMs;
7979
8602
  timerRef.current = window.setTimeout(() => void tick(), Math.max(MIN_POLL_MS, hinted));
7980
8603
  };
7981
- useEffect16(() => {
8604
+ useEffect19(() => {
7982
8605
  mountedRef.current = true;
7983
8606
  return () => {
7984
8607
  mountedRef.current = false;
7985
8608
  };
7986
8609
  }, []);
7987
- const prevPinsLengthRef = useRef11(0);
7988
- useEffect16(() => {
8610
+ const prevPinsLengthRef = useRef15(0);
8611
+ useEffect19(() => {
7989
8612
  const wasEmpty = prevPinsLengthRef.current === 0 && watchesRef.current.length === 0;
7990
8613
  prevPinsLengthRef.current = pins.length;
7991
8614
  if (pins.length === 0 && watchesRef.current.length === 0) {
@@ -7994,27 +8617,27 @@ function useLiveMonitor(opts) {
7994
8617
  }
7995
8618
  if (wasEmpty) void tick();
7996
8619
  }, [pins.length, tick]);
7997
- const prevWatchesLengthRef = useRef11(0);
8620
+ const prevWatchesLengthRef = useRef15(0);
7998
8621
  const watchesLength = opts.watches?.length ?? 0;
7999
- useEffect16(() => {
8622
+ useEffect19(() => {
8000
8623
  const wasEmpty = prevWatchesLengthRef.current === 0 && pinsRef.current.length === 0;
8001
8624
  prevWatchesLengthRef.current = watchesLength;
8002
8625
  if (wasEmpty && watchesLength > 0) void tick();
8003
8626
  }, [watchesLength, tick]);
8004
- useEffect16(() => {
8627
+ useEffect19(() => {
8005
8628
  const matchPins = pins.filter((p) => p.kind === "match").length;
8006
8629
  const marketPins = pins.length - matchPins;
8007
8630
  if (activeTab === "following" && matchPins === 0) setActiveTabState(hasToday ? "today" : "chat");
8008
8631
  if (activeTab === "odds" && marketPins === 0) setActiveTabState(hasToday ? "today" : "chat");
8009
8632
  if (activeTab === "today" && !hasToday) setActiveTabState("chat");
8010
8633
  }, [pins, activeTab, hasToday]);
8011
- const prevIsOpenRef = useRef11(false);
8012
- useEffect16(() => {
8634
+ const prevIsOpenRef = useRef15(false);
8635
+ useEffect19(() => {
8013
8636
  const wasClosed = !prevIsOpenRef.current;
8014
8637
  prevIsOpenRef.current = opts.isOpen;
8015
8638
  if (wasClosed && opts.isOpen) void tick();
8016
8639
  }, [opts.isOpen, tick]);
8017
- useEffect16(() => {
8640
+ useEffect19(() => {
8018
8641
  const onVisibility = () => {
8019
8642
  if (document.visibilityState === "hidden") window.clearTimeout(timerRef.current);
8020
8643
  else void tick();
@@ -8025,7 +8648,7 @@ function useLiveMonitor(opts) {
8025
8648
  window.clearTimeout(timerRef.current);
8026
8649
  };
8027
8650
  }, [tick]);
8028
- const setActiveTab = useCallback7((tab) => {
8651
+ const setActiveTab = useCallback9((tab) => {
8029
8652
  setActiveTabState(tab);
8030
8653
  if (tab === "chat") setChatDot(false);
8031
8654
  if (tab === "today") setTodayDot(false);
@@ -8046,14 +8669,14 @@ function useLiveMonitor(opts) {
8046
8669
  void tick();
8047
8670
  }
8048
8671
  }, [tick]);
8049
- const togglePin = useCallback7((pin) => {
8672
+ const togglePin = useCallback9((pin) => {
8050
8673
  const result = store.toggle(pin);
8051
8674
  pinsRef.current = store.pins;
8052
8675
  return result;
8053
8676
  }, [store]);
8054
- const isPinned = useCallback7((key) => pinsRef.current.some((p) => p.key === key), []);
8055
- const markChatActivity = useCallback7(() => setChatDot(true), []);
8056
- const markTodayActivity = useCallback7(() => {
8677
+ const isPinned = useCallback9((key) => pinsRef.current.some((p) => p.key === key), []);
8678
+ const markChatActivity = useCallback9(() => setChatDot(true), []);
8679
+ const markTodayActivity = useCallback9(() => {
8057
8680
  if (activeTabRef.current !== "today") setTodayDot(true);
8058
8681
  }, []);
8059
8682
  return {
@@ -8080,7 +8703,7 @@ function useLiveMonitor(opts) {
8080
8703
  }
8081
8704
 
8082
8705
  // src/useMatchDiscovery.ts
8083
- import { useCallback as useCallback8, useEffect as useEffect17, useMemo as useMemo3, useRef as useRef12, useState as useState37 } from "react";
8706
+ import { useCallback as useCallback10, useEffect as useEffect20, useMemo as useMemo4, useRef as useRef16, useState as useState39 } from "react";
8084
8707
 
8085
8708
  // src/scheduleCache.ts
8086
8709
  var PREFIX = "ss-w-schedule:";
@@ -8168,27 +8791,27 @@ function localDateOf(now) {
8168
8791
  return `${y}-${m}-${d}`;
8169
8792
  }
8170
8793
  function useMatchDiscovery(opts) {
8171
- const [groups, setGroups] = useState37([]);
8172
- const [states, setStates] = useState37({});
8173
- const [failures, setFailures] = useState37(0);
8174
- const [expanded, setExpanded] = useState37({});
8175
- const [touched, setTouched] = useState37({});
8176
- const groupsRef = useRef12(groups);
8794
+ const [groups, setGroups] = useState39([]);
8795
+ const [states, setStates] = useState39({});
8796
+ const [failures, setFailures] = useState39(0);
8797
+ const [expanded, setExpanded] = useState39({});
8798
+ const [touched, setTouched] = useState39({});
8799
+ const groupsRef = useRef16(groups);
8177
8800
  groupsRef.current = groups;
8178
- const statesRef = useRef12(states);
8801
+ const statesRef = useRef16(states);
8179
8802
  statesRef.current = states;
8180
- const getStatesRef = useRef12(opts.getStates);
8803
+ const getStatesRef = useRef16(opts.getStates);
8181
8804
  getStatesRef.current = opts.getStates;
8182
8805
  const pinnedKey = opts.pinnedMatchIds.join(",");
8183
- const [localDate, setLocalDate] = useState37(() => localDateOf(/* @__PURE__ */ new Date()));
8184
- useEffect17(() => {
8806
+ const [localDate, setLocalDate] = useState39(() => localDateOf(/* @__PURE__ */ new Date()));
8807
+ useEffect20(() => {
8185
8808
  const id = setInterval(() => {
8186
8809
  const next = localDateOf(/* @__PURE__ */ new Date());
8187
8810
  setLocalDate((prev) => prev === next ? prev : next);
8188
8811
  }, ROLLOVER_CHECK_MS);
8189
8812
  return () => clearInterval(id);
8190
8813
  }, []);
8191
- useEffect17(() => {
8814
+ useEffect20(() => {
8192
8815
  let cancelled = false;
8193
8816
  const utcOffsetMinutes = -(/* @__PURE__ */ new Date()).getTimezoneOffset();
8194
8817
  const cached = readSchedule(opts.operatorId, localDate, Date.now());
@@ -8213,7 +8836,7 @@ function useMatchDiscovery(opts) {
8213
8836
  cancelled = true;
8214
8837
  };
8215
8838
  }, [opts.operatorId, localDate]);
8216
- useEffect17(() => {
8839
+ useEffect20(() => {
8217
8840
  setExpanded((prev) => {
8218
8841
  const next = {};
8219
8842
  for (const group of groups) {
@@ -8224,7 +8847,7 @@ function useMatchDiscovery(opts) {
8224
8847
  return next;
8225
8848
  });
8226
8849
  }, [groups, pinnedKey, touched]);
8227
- const tick = useCallback8(async () => {
8850
+ const tick = useCallback10(async () => {
8228
8851
  const ids = dueMatchIds(groupsRef.current, statesRef.current, Date.now());
8229
8852
  if (ids.length === 0) return;
8230
8853
  try {
@@ -8240,7 +8863,7 @@ function useMatchDiscovery(opts) {
8240
8863
  setFailures((n) => n + 1);
8241
8864
  }
8242
8865
  }, []);
8243
- useEffect17(() => {
8866
+ useEffect20(() => {
8244
8867
  if (!opts.isOpen) return;
8245
8868
  void tick();
8246
8869
  const id = setInterval(() => {
@@ -8249,11 +8872,11 @@ function useMatchDiscovery(opts) {
8249
8872
  return () => clearInterval(id);
8250
8873
  }, [opts.isOpen, tick, groups]);
8251
8874
  const stale = failures >= STALE_AFTER_FAILURES;
8252
- const liveCount = useMemo3(
8875
+ const liveCount = useMemo4(
8253
8876
  () => stale ? 0 : Object.values(states).filter((s) => s.phase === "live").length,
8254
8877
  [states, stale]
8255
8878
  );
8256
- const toggleGroup = useCallback8((competitionId) => {
8879
+ const toggleGroup = useCallback10((competitionId) => {
8257
8880
  setTouched((prev) => ({ ...prev, [competitionId]: true }));
8258
8881
  setExpanded((prev) => ({ ...prev, [competitionId]: !prev[competitionId] }));
8259
8882
  }, []);
@@ -8261,25 +8884,25 @@ function useMatchDiscovery(opts) {
8261
8884
  }
8262
8885
 
8263
8886
  // src/useGames.ts
8264
- import { useCallback as useCallback9, useEffect as useEffect18, useRef as useRef13, useState as useState38 } from "react";
8887
+ import { useCallback as useCallback11, useEffect as useEffect21, useRef as useRef17, useState as useState40 } from "react";
8265
8888
  var GAMES_POLL_MS = 6e4;
8266
8889
  var DEFAULT_TZ = "Europe/London";
8267
8890
  function computeGameDay(timezone) {
8268
8891
  return new Intl.DateTimeFormat("en-CA", { timeZone: timezone }).format(/* @__PURE__ */ new Date());
8269
8892
  }
8270
8893
  function useGames(conversation, { activeTab, operatorTimezone }) {
8271
- const [enabled, setEnabled] = useState38(false);
8272
- const [games, setGames] = useState38([]);
8273
- const [loading, setLoading] = useState38(false);
8274
- const [refreshCount, setRefreshCount] = useState38(0);
8275
- const inFlightRef = useRef13(false);
8276
- const hardOffRef = useRef13(false);
8277
- const conversationRef = useRef13(conversation);
8894
+ const [enabled, setEnabled] = useState40(false);
8895
+ const [games, setGames] = useState40([]);
8896
+ const [loading, setLoading] = useState40(false);
8897
+ const [refreshCount, setRefreshCount] = useState40(0);
8898
+ const inFlightRef = useRef17(false);
8899
+ const hardOffRef = useRef17(false);
8900
+ const conversationRef = useRef17(conversation);
8278
8901
  conversationRef.current = conversation;
8279
- const tzRef = useRef13(operatorTimezone ?? DEFAULT_TZ);
8902
+ const tzRef = useRef17(operatorTimezone ?? DEFAULT_TZ);
8280
8903
  tzRef.current = operatorTimezone ?? DEFAULT_TZ;
8281
8904
  const ready = conversation != null;
8282
- useEffect18(() => {
8905
+ useEffect21(() => {
8283
8906
  if (hardOffRef.current) return;
8284
8907
  let active = true;
8285
8908
  const doFetch = async () => {
@@ -8322,22 +8945,22 @@ function useGames(conversation, { activeTab, operatorTimezone }) {
8322
8945
  if (intervalId !== void 0) window.clearInterval(intervalId);
8323
8946
  };
8324
8947
  }, [activeTab, refreshCount, ready]);
8325
- const refresh = useCallback9(() => {
8948
+ const refresh = useCallback11(() => {
8326
8949
  setRefreshCount((c) => c + 1);
8327
8950
  }, []);
8328
8951
  return { enabled, games, loading, refresh };
8329
8952
  }
8330
8953
 
8331
8954
  // src/useLeaderboard.ts
8332
- import { useCallback as useCallback10, useEffect as useEffect19, useRef as useRef14, useState as useState39 } from "react";
8955
+ import { useCallback as useCallback12, useEffect as useEffect22, useRef as useRef18, useState as useState41 } from "react";
8333
8956
  function useLeaderboard(conversation, { activeTab }) {
8334
- const [data, setData] = useState39(null);
8335
- const [loading, setLoading] = useState39(false);
8336
- const [refreshCount, setRefreshCount] = useState39(0);
8337
- const inFlightRef = useRef14(false);
8338
- const conversationRef = useRef14(conversation);
8957
+ const [data, setData] = useState41(null);
8958
+ const [loading, setLoading] = useState41(false);
8959
+ const [refreshCount, setRefreshCount] = useState41(0);
8960
+ const inFlightRef = useRef18(false);
8961
+ const conversationRef = useRef18(conversation);
8339
8962
  conversationRef.current = conversation;
8340
- useEffect19(() => {
8963
+ useEffect22(() => {
8341
8964
  if (activeTab !== "leaderboard" && activeTab !== "games") return;
8342
8965
  let active = true;
8343
8966
  const doFetch = async () => {
@@ -8360,18 +8983,68 @@ function useLeaderboard(conversation, { activeTab }) {
8360
8983
  inFlightRef.current = false;
8361
8984
  };
8362
8985
  }, [activeTab, refreshCount]);
8363
- const refresh = useCallback10(() => {
8986
+ const refresh = useCallback12(() => {
8364
8987
  setRefreshCount((c) => c + 1);
8365
8988
  }, []);
8366
8989
  return { data, loading, refresh };
8367
8990
  }
8368
8991
 
8992
+ // src/usePresetGrid.ts
8993
+ import { useEffect as useEffect23, useRef as useRef19, useState as useState42 } from "react";
8994
+ var GRID_MAX_AGE_MS = 6e5;
8995
+ function usePresetGrid({ getPricePresets, isOpen, locale, now = Date.now }) {
8996
+ const [grid, setGrid] = useState42(null);
8997
+ const keptRef = useRef19(null);
8998
+ const localeRef = useRef19(locale);
8999
+ const inFlightRef = useRef19(false);
9000
+ const mountedRef = useRef19(true);
9001
+ const getRef = useRef19(getPricePresets);
9002
+ getRef.current = getPricePresets;
9003
+ const nowRef = useRef19(now);
9004
+ nowRef.current = now;
9005
+ useEffect23(() => {
9006
+ mountedRef.current = true;
9007
+ return () => {
9008
+ mountedRef.current = false;
9009
+ };
9010
+ }, []);
9011
+ useEffect23(() => {
9012
+ if (localeRef.current !== locale) {
9013
+ localeRef.current = locale;
9014
+ keptRef.current = null;
9015
+ setGrid(null);
9016
+ }
9017
+ if (!isOpen || inFlightRef.current) return;
9018
+ const kept = keptRef.current;
9019
+ const age = kept ? nowRef.current() - Date.parse(kept.builtAtUtc) : Number.NaN;
9020
+ if (age <= GRID_MAX_AGE_MS) return;
9021
+ inFlightRef.current = true;
9022
+ Promise.resolve().then(() => getRef.current(locale)).then((body) => {
9023
+ if (body === null || isPresetGrid(body) && !hasAnyCell(body)) {
9024
+ keptRef.current = null;
9025
+ if (mountedRef.current) setGrid(null);
9026
+ return;
9027
+ }
9028
+ if (!isPresetGrid(body)) return;
9029
+ keptRef.current = body;
9030
+ if (mountedRef.current) setGrid(body);
9031
+ }).catch(() => {
9032
+ }).finally(() => {
9033
+ inFlightRef.current = false;
9034
+ });
9035
+ }, [isOpen, locale]);
9036
+ return grid;
9037
+ }
9038
+ function hasAnyCell(grid) {
9039
+ return grid.singles.length > 0 || grid.singlesFrom.length > 0 || grid.combos.length > 0;
9040
+ }
9041
+
8369
9042
  // src/popupTelemetry.ts
8370
- import { useCallback as useCallback11, useEffect as useEffect20, useRef as useRef15 } from "react";
9043
+ import { useCallback as useCallback13, useEffect as useEffect24, useRef as useRef20 } from "react";
8371
9044
  function usePopupTelemetry(log, isOpen, popupText, dismissPopup) {
8372
- const shownRef = useRef15(/* @__PURE__ */ new Set());
8373
- const closedTextRef = useRef15(null);
8374
- useEffect20(() => {
9045
+ const shownRef = useRef20(/* @__PURE__ */ new Set());
9046
+ const closedTextRef = useRef20(null);
9047
+ useEffect24(() => {
8375
9048
  if (isOpen) {
8376
9049
  if (closedTextRef.current) log?.log({ name: "popup_opened" });
8377
9050
  closedTextRef.current = null;
@@ -8383,65 +9056,65 @@ function usePopupTelemetry(log, isOpen, popupText, dismissPopup) {
8383
9056
  log?.log({ name: "popup_shown" });
8384
9057
  }
8385
9058
  }, [log, isOpen, popupText]);
8386
- return useCallback11(() => {
9059
+ return useCallback13(() => {
8387
9060
  log?.log({ name: "popup_dismissed" });
8388
9061
  dismissPopup();
8389
9062
  }, [log, dismissPopup]);
8390
9063
  }
8391
9064
 
8392
9065
  // src/components/ChatFab.tsx
8393
- import { useEffect as useEffect21, useRef as useRef16, useState as useState40 } from "react";
9066
+ import { useEffect as useEffect25, useRef as useRef21, useState as useState43 } from "react";
8394
9067
 
8395
9068
  // src/components/GreetingBubble.tsx
8396
- import { jsx as jsx89, jsxs as jsxs73 } from "react/jsx-runtime";
9069
+ import { jsx as jsx91, jsxs as jsxs75 } from "react/jsx-runtime";
8397
9070
  function GreetingBubble({ text, onOpen, onDismiss, dismissLabel = "Dismiss", asks }) {
8398
- const body = /* @__PURE__ */ jsxs73("button", { type: "button", className: "ss-w-greeting-body", onClick: onOpen, children: [
8399
- /* @__PURE__ */ jsxs73("span", { className: "ss-w-greeting-dots", "aria-hidden": "true", children: [
8400
- /* @__PURE__ */ jsx89("i", {}),
8401
- /* @__PURE__ */ jsx89("i", {}),
8402
- /* @__PURE__ */ jsx89("i", {})
9071
+ const body = /* @__PURE__ */ jsxs75("button", { type: "button", className: "ss-w-greeting-body", onClick: onOpen, children: [
9072
+ /* @__PURE__ */ jsxs75("span", { className: "ss-w-greeting-dots", "aria-hidden": "true", children: [
9073
+ /* @__PURE__ */ jsx91("i", {}),
9074
+ /* @__PURE__ */ jsx91("i", {}),
9075
+ /* @__PURE__ */ jsx91("i", {})
8403
9076
  ] }),
8404
- /* @__PURE__ */ jsx89("span", { className: "ss-w-greeting-text", children: text })
9077
+ /* @__PURE__ */ jsx91("span", { className: "ss-w-greeting-text", children: text })
8405
9078
  ] });
8406
- const dismiss = onDismiss ? /* @__PURE__ */ jsx89("button", { type: "button", className: "ss-w-greeting-x", "aria-label": dismissLabel, onClick: onDismiss, children: "\xD7" }) : null;
9079
+ const dismiss = onDismiss ? /* @__PURE__ */ jsx91("button", { type: "button", className: "ss-w-greeting-x", "aria-label": dismissLabel, onClick: onDismiss, children: "\xD7" }) : null;
8407
9080
  if (!asks || asks.length === 0) {
8408
- return /* @__PURE__ */ jsxs73("div", { className: "ss-w-greeting", role: "note", children: [
9081
+ return /* @__PURE__ */ jsxs75("div", { className: "ss-w-greeting", role: "note", children: [
8409
9082
  body,
8410
9083
  dismiss
8411
9084
  ] });
8412
9085
  }
8413
- return /* @__PURE__ */ jsxs73("div", { className: "ss-w-greeting ss-w-greeting--asks", role: "note", children: [
8414
- /* @__PURE__ */ jsxs73("div", { className: "ss-w-greeting-top", children: [
9086
+ return /* @__PURE__ */ jsxs75("div", { className: "ss-w-greeting ss-w-greeting--asks", role: "note", children: [
9087
+ /* @__PURE__ */ jsxs75("div", { className: "ss-w-greeting-top", children: [
8415
9088
  body,
8416
9089
  dismiss
8417
9090
  ] }),
8418
- /* @__PURE__ */ jsx89("div", { className: "ss-w-greeting-asks", children: asks.map((ask, i) => /* @__PURE__ */ jsx89("button", { type: "button", className: "ss-w-greeting-ask", onClick: ask.onTap, children: ask.label }, `${i}:${ask.label}`)) })
9091
+ /* @__PURE__ */ jsx91("div", { className: "ss-w-greeting-asks", children: asks.map((ask, i) => /* @__PURE__ */ jsx91("button", { type: "button", className: "ss-w-greeting-ask", onClick: ask.onTap, children: ask.label }, `${i}:${ask.label}`)) })
8419
9092
  ] });
8420
9093
  }
8421
9094
 
8422
9095
  // src/components/ChatFab.tsx
8423
- import { jsx as jsx90, jsxs as jsxs74 } from "react/jsx-runtime";
9096
+ import { jsx as jsx92, jsxs as jsxs76 } from "react/jsx-runtime";
8424
9097
  function ChatFab({ label, greeting, popupText, logoUrl, onOpen, onDismissPopup, dismissLabel = "Dismiss", introAllowed = false, onIntroStart, asks }) {
8425
9098
  const bubble = popupText ?? greeting;
8426
- const [intro, setIntro] = useState40(false);
8427
- const armedRef = useRef16(introAllowed);
8428
- useEffect21(() => {
9099
+ const [intro, setIntro] = useState43(false);
9100
+ const armedRef = useRef21(introAllowed);
9101
+ useEffect25(() => {
8429
9102
  if (armedRef.current && bubble) {
8430
9103
  armedRef.current = false;
8431
9104
  setIntro(true);
8432
9105
  onIntroStart?.();
8433
9106
  }
8434
9107
  }, [bubble, onIntroStart]);
8435
- return /* @__PURE__ */ jsxs74("div", { className: intro ? "ss-w-fab-wrap ss-w-intro" : "ss-w-fab-wrap", children: [
8436
- bubble ? /* @__PURE__ */ jsx90(GreetingBubble, { text: bubble, onOpen, onDismiss: popupText ? onDismissPopup : void 0, dismissLabel, asks: popupText ? asks : void 0 }) : null,
8437
- /* @__PURE__ */ jsx90("button", { type: "button", className: "ss-w-fab", "aria-label": label, onClick: onOpen, children: /* @__PURE__ */ jsx90(BrandMark, { logoUrl, className: "ss-w-fab-mark" }) })
9108
+ return /* @__PURE__ */ jsxs76("div", { className: intro ? "ss-w-fab-wrap ss-w-intro" : "ss-w-fab-wrap", children: [
9109
+ bubble ? /* @__PURE__ */ jsx92(GreetingBubble, { text: bubble, onOpen, onDismiss: popupText ? onDismissPopup : void 0, dismissLabel, asks: popupText ? asks : void 0 }) : null,
9110
+ /* @__PURE__ */ jsx92("button", { type: "button", className: "ss-w-fab", "aria-label": label, onClick: onOpen, children: /* @__PURE__ */ jsx92(BrandMark, { logoUrl, className: "ss-w-fab-mark" }) })
8438
9111
  ] });
8439
9112
  }
8440
9113
 
8441
9114
  // src/components/HostEventsProvider.tsx
8442
- import { jsx as jsx91 } from "react/jsx-runtime";
9115
+ import { jsx as jsx93 } from "react/jsx-runtime";
8443
9116
  function HostEventsProvider({ value, children }) {
8444
- return /* @__PURE__ */ jsx91(SlipStateContext.Provider, { value: value.slipState, children: /* @__PURE__ */ jsx91(MatchLinkContext.Provider, { value: value.matchLink, children }) });
9117
+ return /* @__PURE__ */ jsx93(SlipStateContext.Provider, { value: value.slipState, children: /* @__PURE__ */ jsx93(MatchLinkContext.Provider, { value: value.matchLink, children }) });
8445
9118
  }
8446
9119
 
8447
9120
  // src/styles/css.generated.ts
@@ -8878,6 +9551,71 @@ var WIDGET_CSS = `.ss-w-root { all: revert; }
8878
9551
  .ss-w-combo-leg-stat { font-size: 12px; line-height: 1.45; color: var(--ss-w-ink); background: var(--ss-w-bubble); border-radius: 8px; padding: 6px 9px; }
8879
9552
  .ss-w-combo-math { font-family: ui-monospace, monospace; font-variant-numeric: tabular-nums; font-size: 14px; font-weight: 700; line-height: 1.4; color: var(--ss-w-ink); text-align: right; background: color-mix(in srgb, var(--ss-w-accent) 9%, var(--ss-w-panel)); border-radius: 8px; padding: 8px 10px; overflow-wrap: anywhere; }
8880
9553
  .ss-w-combo-note { font-size: 11px; line-height: 1.4; color: var(--ss-w-faint); }
9554
+ .ss-w-combo-change { align-self: flex-start; min-height: 36px; padding: 6px 12px; font: inherit; font-size: 12px; font-weight: 650; color: var(--ss-w-accent); background: var(--ss-w-panel); border: 1px solid var(--ss-w-accent); border-radius: 16px; cursor: pointer; }
9555
+
9556
+ /* onboarding presets stage 2: the two tools (spec Stage 2 Behaviour, boards 1c and 2c). A sheet
9557
+ over the message list, inside .ss-w-chat-layer; the body scrolls and the builder's Send sits in
9558
+ the footer, so it stays visible on a phone. Colours come from the theme tokens. */
9559
+ .ss-w-tool-backdrop { position: absolute; inset: 0; z-index: 3; display: flex; flex-direction: column; justify-content: flex-end; background: rgba(0,0,0,.35); }
9560
+ .ss-w-tool { display: flex; flex-direction: column; min-height: 0; max-height: 100%; background: var(--ss-w-panel); color: var(--ss-w-ink); border-top: 1px solid var(--ss-w-line); border-radius: 16px 16px 0 0; box-shadow: 0 -8px 24px rgba(0,0,0,.18); outline: none; }
9561
+ .ss-w-tool-head { flex: none; display: flex; align-items: center; gap: 8px; padding: 12px 14px 8px; }
9562
+ .ss-w-tool-title { flex: 1; font-size: 15px; font-weight: 700; line-height: 1.3; }
9563
+ .ss-w-tool-close { flex: none; width: 36px; height: 36px; display: grid; place-items: center; font-size: 16px; color: var(--ss-w-faint); background: var(--ss-w-bubble); border: none; border-radius: 50%; cursor: pointer; }
9564
+ .ss-w-tool-body { flex: 1 1 auto; min-height: 0; overflow-y: auto; overscroll-behavior: contain; display: flex; flex-direction: column; gap: 12px; padding: 4px 14px 14px; }
9565
+ .ss-w-tool-body > * { flex: none; }
9566
+ .ss-w-tool-foot { flex: none; padding: 10px 14px 12px; border-top: 1px solid var(--ss-w-line); }
9567
+ .ss-w-tool-row { display: flex; flex-direction: column; gap: 8px; padding: 10px 11px; border: 1px solid var(--ss-w-line); border-left: 3px solid var(--ss-w-accent); border-radius: 12px; }
9568
+ .ss-w-tool-ask { font-size: 14px; font-weight: 650; line-height: 1.7; color: var(--ss-w-ink); overflow-wrap: anywhere; }
9569
+ .ss-w-tool-num { display: inline-block; min-width: 44px; min-height: 32px; margin: 0 2px; padding: 3px 8px; font: inherit; font-variant-numeric: tabular-nums; font-weight: 800; color: var(--ss-w-accent); background: color-mix(in srgb, var(--ss-w-accent) 10%, var(--ss-w-panel)); border: 1px dashed color-mix(in srgb, var(--ss-w-accent) 55%, var(--ss-w-line)); border-radius: 8px; cursor: pointer; }
9570
+ .ss-w-tool-picks { list-style: none; display: flex; flex-direction: column; gap: 4px; }
9571
+ .ss-w-tool-pick { display: grid; grid-template-columns: 1fr auto; column-gap: 8px; padding: 5px 8px; font-size: 12px; line-height: 1.35; background: var(--ss-w-bubble); border-radius: 8px; }
9572
+ .ss-w-tool-pick-match { grid-column: 1; font-weight: 650; color: var(--ss-w-ink); overflow-wrap: anywhere; }
9573
+ .ss-w-tool-pick-sel { grid-column: 1; color: var(--ss-w-faint); overflow-wrap: anywhere; }
9574
+ .ss-w-tool-pick-kick { grid-column: 1; font-size: 11px; color: var(--ss-w-faint); }
9575
+ .ss-w-tool-pick-price { grid-column: 2; grid-row: 1 / span 3; align-self: center; font-family: ui-monospace, monospace; font-variant-numeric: tabular-nums; font-size: 14px; font-weight: 800; color: var(--ss-w-accent); }
9576
+ .ss-w-tool-math { padding: 6px 8px; font-family: ui-monospace, monospace; font-variant-numeric: tabular-nums; font-size: 13px; font-weight: 700; text-align: right; color: var(--ss-w-ink); background: color-mix(in srgb, var(--ss-w-accent) 9%, var(--ss-w-panel)); border-radius: 8px; overflow-wrap: anywhere; }
9577
+ .ss-w-tool-unavailable { padding: 6px 8px; font-size: 12px; color: var(--ss-w-faint); border: 1px dashed var(--ss-w-line); border-radius: 8px; }
9578
+ .ss-w-tool-send { align-self: flex-end; min-height: 40px; padding: 8px 18px; font: inherit; font-size: 13px; font-weight: 700; color: #fff; background: var(--ss-w-accent); border: none; border-radius: 20px; cursor: pointer; }
9579
+ .ss-w-tool-send:disabled { color: var(--ss-w-faint); background: var(--ss-w-bubble); cursor: not-allowed; }
9580
+ .ss-w-tool-foot .ss-w-tool-send { width: 100%; }
9581
+ .ss-w-tool-section { display: flex; flex-direction: column; gap: 6px; }
9582
+ .ss-w-tool-label { font-size: 11.5px; font-weight: 650; color: var(--ss-w-faint); }
9583
+ .ss-w-tool-chips { display: flex; flex-wrap: wrap; gap: 6px; }
9584
+ .ss-w-tool-chip { min-width: 52px; min-height: 36px; padding: 6px 10px; font: inherit; font-variant-numeric: tabular-nums; font-weight: 700; color: var(--ss-w-ink); background: var(--ss-w-panel); border: 1px solid var(--ss-w-line); border-radius: 18px; cursor: pointer; }
9585
+ .ss-w-tool-chip[aria-pressed="true"] { color: #fff; background: var(--ss-w-accent); border-color: var(--ss-w-accent); }
9586
+ .ss-w-tool-stepper { display: inline-flex; align-items: center; gap: 10px; }
9587
+ .ss-w-tool-step { width: 40px; height: 40px; display: grid; place-items: center; font: inherit; font-size: 18px; font-weight: 700; color: var(--ss-w-accent); background: var(--ss-w-panel); border: 1px solid var(--ss-w-line); border-radius: 50%; cursor: pointer; }
9588
+ .ss-w-tool-step:disabled { opacity: .5; cursor: not-allowed; }
9589
+ .ss-w-tool-count { min-width: 24px; text-align: center; font-size: 16px; font-weight: 800; font-variant-numeric: tabular-nums; }
9590
+
9591
+ /* onboarding presets stage 2: the chat layer and the speed dial (spec Stage 2 Behaviour). The
9592
+ layer is the message list's positioned box (P6), so the dial sits against the list's bottom
9593
+ right and rises with the composer: right 8px puts the 44px button's centre on Send's (12px
9594
+ padding + 18px), bottom 10px is above the composer's top border. While the button shows the list
9595
+ gets 54px more bottom padding so its last message can scroll clear. */
9596
+ .ss-w-chat-layer { position: relative; flex: 1; min-height: 0; display: flex; flex-direction: column; }
9597
+ .ss-w-chat-layer[data-dial] .ss-w-list { padding-bottom: 68px; }
9598
+ .ss-w-dial { position: absolute; left: 12px; right: 8px; bottom: 10px; z-index: 2; display: flex; flex-direction: column-reverse; align-items: flex-end; gap: 8px; pointer-events: none; }
9599
+ .ss-w-dial-btn, .ss-w-dial-pill { pointer-events: auto; }
9600
+ .ss-w-dial-btn { position: relative; flex: none; width: 44px; height: 44px; display: grid; place-items: center; padding: 0; font-size: 20px; color: var(--ss-w-accent); background: var(--ss-w-panel); border: 1px solid color-mix(in srgb, var(--ss-w-accent) 40%, var(--ss-w-line)); border-radius: 50%; box-shadow: 0 4px 14px rgba(0,0,0,.18); cursor: pointer; }
9601
+ .ss-w-dial-btn[aria-expanded="true"] { color: var(--ss-w-panel); background: var(--ss-w-accent); border-color: var(--ss-w-accent); }
9602
+ .ss-w-dial-btn:focus-visible, .ss-w-dial-pill:focus-visible { outline: 2px solid var(--ss-w-accent); outline-offset: 2px; }
9603
+ .ss-w-dial-icon { grid-area: 1 / 1; display: grid; place-items: center; transition: transform 200ms ease, opacity 200ms ease; }
9604
+ .ss-w-dial-icon--close { opacity: 0; transform: rotate(-90deg); }
9605
+ .ss-w-dial-btn[aria-expanded="true"] .ss-w-dial-icon--open { opacity: 0; transform: rotate(90deg); }
9606
+ .ss-w-dial-btn[aria-expanded="true"] .ss-w-dial-icon--close { opacity: 1; transform: none; }
9607
+ .ss-w-dial-dot { position: absolute; top: 3px; right: 3px; width: 10px; height: 10px; background: var(--ss-w-accent); border: 2px solid var(--ss-w-panel); border-radius: 50%; }
9608
+ .ss-w-dial-pills { max-width: 100%; display: flex; flex-direction: column; align-items: flex-end; gap: 8px; }
9609
+ .ss-w-dial-pill { max-width: 100%; min-height: 44px; display: inline-flex; align-items: center; gap: 8px; padding: 6px 16px 6px 12px; font: inherit; font-size: 12.5px; font-weight: 650; line-height: 1.25; text-align: left; white-space: normal; overflow-wrap: anywhere; color: var(--ss-w-ink); background: var(--ss-w-panel); border: 1px solid color-mix(in srgb, var(--ss-w-accent) 35%, var(--ss-w-line)); border-radius: 22px; box-shadow: 0 4px 14px rgba(0,0,0,.16); cursor: pointer; opacity: 0; visibility: hidden; transform: translateY(14px) scale(.5); transform-origin: bottom right; transition: opacity 200ms ease, transform 200ms ease, visibility 0s linear 200ms; }
9610
+ .ss-w-dial-pill svg { flex: none; font-size: 18px; color: var(--ss-w-accent); }
9611
+ .ss-w-dial-pill-label { min-width: 0; }
9612
+ .ss-w-dial-pills[data-open] .ss-w-dial-pill { opacity: 1; visibility: visible; transform: none; transition: opacity 200ms ease, transform 200ms ease, visibility 0s; }
9613
+ @media (prefers-reduced-motion: reduce) {
9614
+ .ss-w-dial-pill { transform: none; transition: opacity 200ms ease, visibility 0s linear 200ms; }
9615
+ .ss-w-dial-pills[data-open] .ss-w-dial-pill { transition: opacity 200ms ease, visibility 0s; }
9616
+ .ss-w-dial-icon { transition: opacity 200ms ease; }
9617
+ .ss-w-dial-icon--close, .ss-w-dial-btn[aria-expanded="true"] .ss-w-dial-icon--open { transform: none; }
9618
+ }
8881
9619
 
8882
9620
  /* Compact price list (D16): three or more cards of one turn, one row per fixture. Team names wrap
8883
9621
  to a second line and are never cut; the time sits above the match pin, with no column of its own. */
@@ -8964,6 +9702,7 @@ var WIDGET_CSS = `.ss-w-root { all: revert; }
8964
9702
  .ss-w-textarea { font-size: 16px; }
8965
9703
  }
8966
9704
  .ss-w-send { width: 36px; height: 36px; flex: none; border: none; border-radius: 50%; background: var(--ss-w-accent); color: #fff; cursor: pointer; display: grid; place-items: center; font-size: 16px; }
9705
+ .ss-w-send:disabled { opacity: .45; cursor: not-allowed; }
8967
9706
  .ss-w-stop-glyph { width: 11px; height: 11px; background: currentColor; border-radius: 2px; }
8968
9707
 
8969
9708
  /* add to slip */
@@ -10003,7 +10742,7 @@ var WIDGET_CSS = `.ss-w-root { all: revert; }
10003
10742
  `;
10004
10743
 
10005
10744
  // src/StatsWidget.tsx
10006
- import { jsx as jsx92 } from "react/jsx-runtime";
10745
+ import { jsx as jsx94 } from "react/jsx-runtime";
10007
10746
  function buildClient(props) {
10008
10747
  try {
10009
10748
  if ("client" in props && props.client) {
@@ -10028,24 +10767,24 @@ function injectStyles(root) {
10028
10767
  function StatsWidget(props) {
10029
10768
  const injectedClient = "client" in props ? props.client : void 0;
10030
10769
  const cfg = props.config;
10031
- const built = useMemo4(() => buildClient(props), [injectedClient, cfg?.operatorId, cfg?.publicKey, cfg?.baseUrl]);
10032
- const logger = useMemo4(
10770
+ const built = useMemo5(() => buildClient(props), [injectedClient, cfg?.operatorId, cfg?.publicKey, cfg?.baseUrl]);
10771
+ const logger = useMemo5(
10033
10772
  () => built ? built.client.createEventLogger({
10034
10773
  operatorUserId: cfg?.userContext?.operatorUserId,
10035
10774
  anonymousUserId: cfg?.userContext?.anonymousUserId
10036
10775
  }) : null,
10037
10776
  [built?.client]
10038
10777
  );
10039
- const ui = useMemo4(() => normalizeUi(props.config ?? { operatorId: "", publicKey: "" }), [props.config]);
10040
- const rootRef = useRef17(null);
10041
- const introDoneRef = useRef17(false);
10042
- const markIntroDone = useCallback12(() => {
10778
+ const ui = useMemo5(() => normalizeUi(props.config ?? { operatorId: "", publicKey: "" }), [props.config]);
10779
+ const rootRef = useRef22(null);
10780
+ const introDoneRef = useRef22(false);
10781
+ const markIntroDone = useCallback14(() => {
10043
10782
  introDoneRef.current = true;
10044
10783
  }, []);
10045
10784
  const convStorageKey = `ss-w-conv:${cfg?.operatorId ?? ""}:${cfg?.userContext?.operatorUserId ?? "anon"}`;
10046
10785
  const controller = useChatStream(built?.conversation ?? emptyConversation(), ui.starters, built ? ui.greetingMessage : void 0, convStorageKey);
10047
- const [hasToday, setHasToday] = useState41(false);
10048
- const liveCardWatches = useMemo4(() => {
10786
+ const [hasToday, setHasToday] = useState44(false);
10787
+ const liveCardWatches = useMemo5(() => {
10049
10788
  const seen = /* @__PURE__ */ new Set();
10050
10789
  const out = [];
10051
10790
  for (const msg of controller.state.messages) {
@@ -10067,15 +10806,15 @@ function StatsWidget(props) {
10067
10806
  hasToday,
10068
10807
  watches: liveCardWatches
10069
10808
  });
10070
- const getSchedule = useCallback12(
10809
+ const getSchedule = useCallback14(
10071
10810
  (localDate, utcOffsetMinutes) => built ? built.conversation.getSchedule(localDate, utcOffsetMinutes) : Promise.resolve(null),
10072
10811
  [built]
10073
10812
  );
10074
- const getStates = useCallback12(
10813
+ const getStates = useCallback14(
10075
10814
  (matchIds) => built ? built.conversation.getStates(matchIds) : Promise.resolve(null),
10076
10815
  [built]
10077
10816
  );
10078
- const pinnedMatchIds = useMemo4(
10817
+ const pinnedMatchIds = useMemo5(
10079
10818
  () => monitor.pins.filter((p) => p.kind === "match").map((p) => p.matchId),
10080
10819
  [monitor.pins]
10081
10820
  );
@@ -10087,11 +10826,11 @@ function StatsWidget(props) {
10087
10826
  pinnedMatchIds
10088
10827
  });
10089
10828
  const todayHasContent = visibleGroupsOf(discovery.groups, discovery.states).length > 0;
10090
- useEffect22(() => {
10829
+ useEffect26(() => {
10091
10830
  setHasToday(todayHasContent);
10092
10831
  }, [todayHasContent]);
10093
- const prevLiveSnapshotRef = useRef17(monitor.snapshot);
10094
- useEffect22(() => {
10832
+ const prevLiveSnapshotRef = useRef22(monitor.snapshot);
10833
+ useEffect26(() => {
10095
10834
  const snap = monitor.snapshot;
10096
10835
  if (!snap || snap === prevLiveSnapshotRef.current) return;
10097
10836
  prevLiveSnapshotRef.current = snap;
@@ -10127,9 +10866,14 @@ function StatsWidget(props) {
10127
10866
  }, [monitor.snapshot]);
10128
10867
  const gamesResult = useGames(built?.conversation ?? null, { activeTab: monitor.activeTab });
10129
10868
  const leaderboardResult = useLeaderboard(built?.conversation ?? null, { activeTab: monitor.activeTab });
10869
+ const getPricePresets = useCallback14(
10870
+ (locale2) => built ? built.conversation.getPricePresets(locale2) : Promise.resolve(null),
10871
+ [built]
10872
+ );
10873
+ const presetGrid = usePresetGrid({ getPricePresets, isOpen: controller.state.isOpen, locale: ui.presets.lang });
10130
10874
  const markTodayActivity = monitor.markTodayActivity;
10131
- const prevLiveCountRef = useRef17(0);
10132
- useEffect22(() => {
10875
+ const prevLiveCountRef = useRef22(0);
10876
+ useEffect26(() => {
10133
10877
  const rose = prevLiveCountRef.current === 0 && discovery.liveCount > 0;
10134
10878
  prevLiveCountRef.current = discovery.liveCount;
10135
10879
  if (rose) markTodayActivity();
@@ -10145,7 +10889,7 @@ function StatsWidget(props) {
10145
10889
  openMatchLabel: ui.openMatch
10146
10890
  });
10147
10891
  const markAdded = hostEvents.markAdded;
10148
- const slip = useMemo4(
10892
+ const slip = useMemo5(
10149
10893
  () => {
10150
10894
  const handler = props.config?.onAddToSlip;
10151
10895
  return {
@@ -10164,7 +10908,7 @@ function StatsWidget(props) {
10164
10908
  },
10165
10909
  [props.config?.onAddToSlip, controller.state.conversationId, markAdded]
10166
10910
  );
10167
- const onDashboardTap = useMemo4(
10911
+ const onDashboardTap = useMemo5(
10168
10912
  () => built ? createDashboardTapSignal((input) => built.conversation.sendSignal(input), logger) : void 0,
10169
10913
  [built, logger]
10170
10914
  );
@@ -10182,11 +10926,11 @@ function StatsWidget(props) {
10182
10926
  const operatorUserId = cfg?.userContext?.operatorUserId;
10183
10927
  const anonymousUserId = cfg?.userContext?.anonymousUserId;
10184
10928
  const conversationId = controller.state.conversationId ?? void 0;
10185
- useEffect22(() => {
10929
+ useEffect26(() => {
10186
10930
  if (!logger) return;
10187
10931
  logger.setIdentity({ operatorUserId, anonymousUserId, conversationId });
10188
10932
  }, [logger, operatorUserId, anonymousUserId, conversationId]);
10189
- useEffect22(() => {
10933
+ useEffect26(() => {
10190
10934
  if (!logger) return;
10191
10935
  logger.resume();
10192
10936
  const onHide = () => {
@@ -10204,14 +10948,14 @@ function StatsWidget(props) {
10204
10948
  logger.dispose();
10205
10949
  };
10206
10950
  }, [logger]);
10207
- useEffect22(() => {
10951
+ useEffect26(() => {
10208
10952
  if (!built || !ui.injectStyles) return;
10209
10953
  const node = rootRef.current?.getRootNode();
10210
10954
  if (node) injectStyles(node);
10211
10955
  }, [built, ui.injectStyles]);
10212
10956
  if (!built) return null;
10213
10957
  const attrs = themeAttrs(ui);
10214
- return /* @__PURE__ */ jsx92(EventLogContext.Provider, { value: logger, children: /* @__PURE__ */ jsx92(HostEventsProvider, { value: hostEvents, children: /* @__PURE__ */ jsx92("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__ */ jsx92(
10958
+ return /* @__PURE__ */ jsx94(EventLogContext.Provider, { value: logger, children: /* @__PURE__ */ jsx94(HostEventsProvider, { value: hostEvents, children: /* @__PURE__ */ jsx94("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__ */ jsx94(
10215
10959
  ChatPanel,
10216
10960
  {
10217
10961
  controller,
@@ -10223,9 +10967,10 @@ function StatsWidget(props) {
10223
10967
  gamesResult,
10224
10968
  leaderboardResult,
10225
10969
  loggedIn: !!operatorUserId,
10226
- conversation: built?.conversation ?? null
10970
+ conversation: built?.conversation ?? null,
10971
+ presetGrid
10227
10972
  }
10228
- ) : /* @__PURE__ */ jsx92(
10973
+ ) : /* @__PURE__ */ jsx94(
10229
10974
  ChatFab,
10230
10975
  {
10231
10976
  label: ui.launcherLabel,