@enigma-lake/tower-play-controller-sdk 2.2.4 → 2.2.6

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.mjs CHANGED
@@ -1,8 +1,7 @@
1
1
  import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
2
- import React, { createContext, useContext, useMemo, useRef, useEffect, useCallback, useState, Fragment as Fragment$1 } from 'react';
2
+ import React, { createContext, useContext, useRef, useEffect, useCallback, useState, useMemo, Fragment as Fragment$1 } from 'react';
3
3
  import cx$1 from 'classnames';
4
- import { createTranslator as createTranslator$1, DEFAULT_LANGUAGE, resolveLanguage, Currency, sendSetUserCurrencyEvent, sendSetUserCurrencyEventV2, setToggleGameWidgetsVisibility, setToggleGameWidgetsExpansion, getUserInformationEvent, EVENTS, sendOpenHighPlayModalEvent } from '@enigma-lake/zoot-platform-sdk';
5
- export { DEFAULT_LANGUAGE, SUPPORTED_LANGUAGES, SUPPORTED_LANGUAGE_CODES, resolveLanguage } from '@enigma-lake/zoot-platform-sdk';
4
+ import { Currency, sendSetUserCurrencyEvent, sendSetUserCurrencyEventV2, setToggleGameWidgetsVisibility, setToggleGameWidgetsExpansion, getUserInformationEvent, EVENTS, sendOpenHighPlayModalEvent } from '@enigma-lake/zoot-platform-sdk';
6
5
  import { Menu, MenuButton, MenuItems, MenuItem, Switch as Switch$1, Dialog, DialogPanel, DialogTitle } from '@headlessui/react';
7
6
 
8
7
  const hexToRgb = (hex) => {
@@ -103,23 +102,6 @@ const createAutoplayButtonAction = ({ state, cashoutDisabled, onStopPlay, onPlay
103
102
  };
104
103
  };
105
104
 
106
- var SELECTORS;
107
- (function (SELECTORS) {
108
- SELECTORS["RISK"] = "Risk";
109
- })(SELECTORS || (SELECTORS = {}));
110
- var RiskTypes;
111
- (function (RiskTypes) {
112
- RiskTypes["LOW"] = "LOW";
113
- RiskTypes["MEDIUM"] = "MEDIUM";
114
- RiskTypes["HIGH"] = "HIGH";
115
- })(RiskTypes || (RiskTypes = {}));
116
- // standard controller palette (matches multi/plinko play-controller SDKs)
117
- const DEFAULT_RISK_COLORS = {
118
- [RiskTypes.LOW]: "#1AE380",
119
- [RiskTypes.MEDIUM]: "#FFC058",
120
- [RiskTypes.HIGH]: "#FF5646",
121
- };
122
-
123
105
  const AutoManualPlayStateContext = createContext(undefined);
124
106
  const useAutoManualPlayState = () => {
125
107
  const context = useContext(AutoManualPlayStateContext);
@@ -129,104 +111,6 @@ const useAutoManualPlayState = () => {
129
111
  return context;
130
112
  };
131
113
 
132
- const en = {
133
- playNow: "PLAY NOW",
134
- playFreeRound: "PLAY FREE ROUND",
135
- selectPlayAmount: "SELECT PLAY AMOUNT",
136
- cashout: "CASHOUT",
137
- collect: "COLLECT",
138
- doubleTitle: "DOUBLE",
139
- doubleSubtitle: "OR NOTHING",
140
- startAutoplay: "START AUTOPLAY",
141
- stopAutoplay: "STOP AUTOPLAY",
142
- numberOfPlays: "Number of Plays",
143
- selectPlayAmountAria: "Select play amount",
144
- playAmountLabel: "Play amount",
145
- autoLabel: "Auto",
146
- selectTileToast: "Please select at least one tile to start autoplay.",
147
- riskLabel: "Risk",
148
- riskLow: "Low",
149
- riskMedium: "Medium",
150
- riskHigh: "High",
151
- freeRoundLeftOne: "FREE ROUND LEFT",
152
- freeRoundLeftOther: "FREE ROUNDS LEFT",
153
- freeRoundsModalTitle: "Free Game Rounds",
154
- freeRoundsHave: "You have",
155
- freeRoundsUnitOne: "FREE GAME ROUND",
156
- freeRoundsUnitOther: "FREE GAME ROUNDS",
157
- freeRoundsPerRound: "{amount} per round",
158
- freeRoundsExpiresIn: "Expires in {days} {unit}",
159
- dayUnitOne: "day",
160
- dayUnitOther: "days",
161
- freeRoundsModalPlayNow: "Play now",
162
- freeRoundsModalPlayLater: "Maybe later",
163
- freeRoundsSummaryWon: "You won",
164
- freeRoundsSummaryIn: "in",
165
- freeRoundsSummaryUnitOne: "free game round",
166
- freeRoundsSummaryUnitOther: "free game rounds",
167
- freeRoundsSummaryCredited: "Winnings have been credited to your bonus balance.",
168
- freeRoundsSummaryClose: "Close",
169
- };
170
-
171
- const zhCN = {
172
- playNow: "立即游戏",
173
- playFreeRound: "免费游戏",
174
- selectPlayAmount: "选择下注金额",
175
- cashout: "提现",
176
- collect: "收取",
177
- doubleTitle: "双倍",
178
- doubleSubtitle: "或全无",
179
- startAutoplay: "开始自动游戏",
180
- stopAutoplay: "停止自动游戏",
181
- numberOfPlays: "游戏次数",
182
- selectPlayAmountAria: "选择下注金额",
183
- playAmountLabel: "下注金额",
184
- autoLabel: "自动",
185
- selectTileToast: "请至少选择一个方块以开始自动游戏。",
186
- riskLabel: "风险",
187
- riskLow: "低",
188
- riskMedium: "中",
189
- riskHigh: "高",
190
- freeRoundLeftOne: "剩余免费回合",
191
- freeRoundLeftOther: "剩余免费回合",
192
- freeRoundsModalTitle: "免费游戏回合",
193
- freeRoundsHave: "您有",
194
- freeRoundsUnitOne: "个免费游戏回合",
195
- freeRoundsUnitOther: "个免费游戏回合",
196
- freeRoundsPerRound: "每回合 {amount}",
197
- freeRoundsExpiresIn: "{days} {unit}后到期",
198
- dayUnitOne: "天",
199
- dayUnitOther: "天",
200
- freeRoundsModalPlayNow: "立即游戏",
201
- freeRoundsModalPlayLater: "稍后再玩",
202
- freeRoundsSummaryWon: "您赢得了",
203
- freeRoundsSummaryIn: ",共",
204
- freeRoundsSummaryUnitOne: "个免费游戏回合",
205
- freeRoundsSummaryUnitOther: "个免费游戏回合",
206
- freeRoundsSummaryCredited: "奖金已存入您的奖金余额。",
207
- freeRoundsSummaryClose: "关闭",
208
- };
209
-
210
- const dictionaries = {
211
- en,
212
- "zh-CN": zhCN,
213
- };
214
- const createTranslator = (language) => createTranslator$1(dictionaries, language);
215
-
216
- const I18nContext = createContext(undefined);
217
- const I18nProvider = ({ language, children, }) => {
218
- const value = useMemo(() => {
219
- const resolved = resolveLanguage(language);
220
- return { language: resolved, t: createTranslator(resolved) };
221
- }, [language]);
222
- return jsx(I18nContext.Provider, { value: value, children: children });
223
- };
224
- const DEFAULT_VALUE = {
225
- language: DEFAULT_LANGUAGE,
226
- t: createTranslator(DEFAULT_LANGUAGE),
227
- };
228
- const useTranslation = () => useContext(I18nContext) ?? DEFAULT_VALUE;
229
-
230
114
  const getNextPlayAmount = ({ currentAmount, defaultBetOptions, direction, }) => {
231
115
  if (!defaultBetOptions.length) {
232
116
  return currentAmount;
@@ -257,7 +141,6 @@ function isFullMode(config) {
257
141
  return config.withoutRiskAndAutoplay !== true;
258
142
  }
259
143
  const usePlayController = () => {
260
- const { t } = useTranslation();
261
144
  const { config, mode, autoPlay: { setNumberOfPlays, numberOfPlays, setPlayedRounds, playedRounds, selection, setState, state, }, playValues: { displayPlayAmountView, setDisplayPlayAmountView, togglePlayAmountView, }, } = useAutoManualPlayState();
262
145
  const { current } = config.currencyOptions;
263
146
  const { isPlaying, canCashout, disabledCashout = false, disabledController, playHook, totalBalance, } = config.playOptions;
@@ -272,19 +155,17 @@ const usePlayController = () => {
272
155
  const { playAmount: rawPlayAmount = 0, playLimits, setPlayAmount: rawSetPlayAmount = () => { }, } = playHook?.() ?? {};
273
156
  const minPlayAmount = playLimits?.[current.code]?.limits.min ?? 0;
274
157
  const maxPlayAmount = playLimits?.[current.code]?.limits.max ?? 0;
275
- // Free rounds (opt-in): only when the player has ENGAGED free-rounds mode
276
- // (via the intro modal's PLAY NOW) are the per-round value/coin locked to
277
- // the grant. When not engaged, the controller renders exactly as if the
278
- // user had no free rounds. Engagement coexists with the cashout flow.
279
- //
280
- // By default engagement is MANUAL-mode only (free rounds are never engaged
281
- // in auto-play mode). Games whose autoplay submission path funds rounds
282
- // from engaged grants opt into engaged autoplay via
283
- // `supportsGrantFundedAutoplay`, which drops the manual-mode requirement.
158
+ // Free rounds (opt-in, MANUAL-mode only): only when the player has ENGAGED
159
+ // free-round mode are the stake/coin locked to the grant. `available` (pending
160
+ // grants exist) does not lock anything on its own it surfaces a "Use Free
161
+ // Rounds" affordance. Free rounds are never available/engaged in auto-play
162
+ // mode, and engagement coexists with the cashout flow.
284
163
  const isManualMode = mode === GAME_MODE.MANUAL;
285
164
  const freeRoundsOptions = config.freeRoundsOptions;
286
- const supportsGrantFundedAutoplay = Boolean(freeRoundsOptions?.supportsGrantFundedAutoplay);
287
- const isFreeRoundsEngaged = Boolean((isManualMode || supportsGrantFundedAutoplay) &&
165
+ const isFreeRoundsAvailable = Boolean(isManualMode &&
166
+ freeRoundsOptions?.available &&
167
+ freeRoundsOptions.roundsRemaining > 0);
168
+ const isFreeRoundsEngaged = Boolean(isManualMode &&
288
169
  freeRoundsOptions?.engaged &&
289
170
  freeRoundsOptions.roundsRemaining > 0);
290
171
  // playAmount is in display units everywhere in this SDK (e.g. 1.00), while the
@@ -301,33 +182,6 @@ const usePlayController = () => {
301
182
  : rawSetPlayAmount;
302
183
  const playIntervalRef = useRef(null);
303
184
  const isAutoplayActiveRef = useRef(false);
304
- // Each setTimeout tick invokes the `loopRounds` closure from the render that
305
- // scheduled it, so reading `config.freeRoundsOptions` there can see a stale,
306
- // pre-engagement value. Keep the latest free-rounds state in a ref
307
- // (refreshed every render) so the loop's hard-stop checks always see live
308
- // values — a grant engaged mid-loop still hard-stops paid autoplay, and an
309
- // engaged grant-funded loop sees the live remaining count.
310
- const freeRoundsLiveRef = useRef({
311
- engaged: false,
312
- remaining: 0,
313
- supportsGrantFundedAutoplay: false,
314
- });
315
- freeRoundsLiveRef.current = {
316
- engaged: Boolean(config.freeRoundsOptions?.engaged),
317
- remaining: config.freeRoundsOptions?.roundsRemaining ?? 0,
318
- supportsGrantFundedAutoplay,
319
- };
320
- // Per-session accounting for engaged (grant-funded) autoplay.
321
- // roundsRemaining is game-driven and only decrements via an async refetch
322
- // AFTER each settlement, so the live ref can still read stale-high when the
323
- // loop schedules the next round. We therefore also count submissions
324
- // locally: a session that started engaged may never submit more rounds than
325
- // the grants available when it started, regardless of refetch timing.
326
- const autoplaySessionRef = useRef({
327
- startedEngaged: false,
328
- grantsAtStart: 0,
329
- roundsSubmitted: 0,
330
- });
331
185
  const stopAutoplay = () => {
332
186
  isAutoplayActiveRef.current = false;
333
187
  if (playIntervalRef.current) {
@@ -347,35 +201,6 @@ const usePlayController = () => {
347
201
  if (!isAutoplayActiveRef.current || !autoplayEnabled) {
348
202
  return;
349
203
  }
350
- // Free-rounds gates — all live values come from refs (closures here are
351
- // stale across rounds).
352
- const live = freeRoundsLiveRef.current;
353
- const session = autoplaySessionRef.current;
354
- if (!live.supportsGrantFundedAutoplay) {
355
- // Legacy (flag absent/false): tower free rounds are MANUAL-only — there
356
- // is no free autoplay. If a grant is engaged (or becomes engaged) while
357
- // an autoplay loop is live, hard-stop instead of letting PAID autoplay
358
- // run alongside the grant.
359
- if (live.engaged) {
360
- stopAutoplay();
361
- return;
362
- }
363
- }
364
- else if (session.startedEngaged) {
365
- // Grant-funded autoplay: an autoplay session that started on free
366
- // rounds ends the moment the grants are exhausted or engagement ends
367
- // mid-session — it must never submit a paid round. Checked BEFORE each
368
- // submission so the round that consumes the last grant completes
369
- // normally, then the loop halts via the normal stop path. Uses the
370
- // local per-session submission count as well because the game's
371
- // roundsRemaining refetch can lag behind the next scheduled round.
372
- const grantsExhausted = session.roundsSubmitted >= session.grantsAtStart || live.remaining <= 0;
373
- if (grantsExhausted || !live.engaged) {
374
- setNumberOfPlays(AUTOPLAY_DEFAULT_PLAY_ROUNDS_COUNT);
375
- stopAutoplay();
376
- return;
377
- }
378
- }
379
204
  if (remainingPlays < 1 || numberOfPlays === 0) {
380
205
  setNumberOfPlays(AUTOPLAY_DEFAULT_PLAY_ROUNDS_COUNT);
381
206
  stopAutoplay();
@@ -383,7 +208,6 @@ const usePlayController = () => {
383
208
  }
384
209
  setPlayedRounds(currentPlayedRounds + 1);
385
210
  setNumberOfPlays((prev) => Math.max(prev - 1, 0));
386
- session.roundsSubmitted += 1;
387
211
  // onAutoPlay exists only in full mode (guarded by autoplayEnabled)
388
212
  config.onAutoPlay(selection, () => {
389
213
  if (!isAutoplayActiveRef.current) {
@@ -401,32 +225,13 @@ const usePlayController = () => {
401
225
  if (selection.length === 0) {
402
226
  showAutoPlayToast?.({
403
227
  type: "info",
404
- message: t("selectTileToast"),
228
+ message: "Please select at least one tile to start autoplay.",
405
229
  });
406
230
  return;
407
231
  }
408
- // Grant-funded autoplay: the effective play count is clamped to the
409
- // remaining grants so autoplay can never spill into paid rounds (and an
410
- // infinite/over-cap count stops at roundsRemaining). The game client
411
- // enforces actual per-round grant consumption; this is the UI cap.
412
- const effectiveNumberOfPlays = supportsGrantFundedAutoplay && isFreeRoundsEngaged
413
- ? Math.min(numberOfPlays, freeRoundsOptions?.roundsRemaining ?? 0)
414
- : numberOfPlays;
415
- if (effectiveNumberOfPlays !== numberOfPlays) {
416
- setNumberOfPlays(effectiveNumberOfPlays);
417
- }
418
- // Snapshot the free-rounds state for this session. Uses the RAW engaged
419
- // flag (not isFreeRoundsEngaged, which also requires remaining > 0) so a
420
- // session started while engaged with 0 grants fails closed on the first
421
- // loop iteration instead of silently becoming a paid session.
422
- autoplaySessionRef.current = {
423
- startedEngaged: Boolean(freeRoundsOptions?.engaged),
424
- grantsAtStart: freeRoundsOptions?.roundsRemaining ?? 0,
425
- roundsSubmitted: 0,
426
- };
427
232
  isAutoplayActiveRef.current = true;
428
233
  setState(AUTO_PLAY_STATE.PLAYING);
429
- loopRounds(playedRounds, effectiveNumberOfPlays);
234
+ loopRounds(playedRounds, numberOfPlays);
430
235
  };
431
236
  const adjustPlayAmount = ({ direction }) => {
432
237
  if (isDisabled()) {
@@ -484,15 +289,16 @@ const usePlayController = () => {
484
289
  setDisplayPlayAmountView,
485
290
  togglePlayAmountView,
486
291
  },
487
- // free rounds (binary engagement via the intro modal; manual-mode only
488
- // unless the game opts into grant-funded autoplay)
292
+ // free rounds (manual-mode only)
489
293
  freeRounds: {
294
+ isAvailable: isFreeRoundsAvailable,
490
295
  isEngaged: isFreeRoundsEngaged,
491
296
  roundsRemaining: freeRoundsOptions?.roundsRemaining ?? 0,
492
297
  totalRounds: freeRoundsOptions?.totalRounds ?? 0,
493
298
  stakeCents: freeRoundsOptions?.stakeCents ?? 0,
494
299
  coinType: freeRoundsOptions?.coinType,
495
300
  currency: freeRoundsOptions?.currency,
301
+ onUseFreeRounds: freeRoundsOptions?.onUseFreeRounds,
496
302
  },
497
303
  };
498
304
  };
@@ -586,117 +392,42 @@ const SelectMenu = ({ disabled = false }) => {
586
392
  }) })] })) }));
587
393
  };
588
394
 
589
- var styles$6 = {"base":"PlayValueInput-module__base___sTNW2","icon":"PlayValueInput-module__icon___jZj7O","playValueInput":"PlayValueInput-module__playValueInput___7pJtn","playValueInputWithLabel":"PlayValueInput-module__playValueInputWithLabel___S55Cr","label":"PlayValueInput-module__label___1ZSAV","inputContainer":"PlayValueInput-module__inputContainer___RKr1l","playValueWithLabel":"PlayValueInput-module__playValueWithLabel___K4-pr","isClassicGame":"PlayValueInput-module__isClassicGame___lQzVE"};
395
+ var styles$5 = {"base":"PlayValueInput-module__base___sTNW2","icon":"PlayValueInput-module__icon___jZj7O","playValueInput":"PlayValueInput-module__playValueInput___7pJtn","playValueInputWithLabel":"PlayValueInput-module__playValueInputWithLabel___S55Cr","label":"PlayValueInput-module__label___1ZSAV","inputContainer":"PlayValueInput-module__inputContainer___RKr1l","playValueWithLabel":"PlayValueInput-module__playValueWithLabel___K4-pr","isClassicGame":"PlayValueInput-module__isClassicGame___lQzVE"};
590
396
 
591
397
  const PlayValueInput = ({ disabled, children, className, value, onClick, isClassicGame = false, }) => {
592
- const { t } = useTranslation();
593
398
  const handleOnClick = () => {
594
399
  if (disabled) {
595
400
  return;
596
401
  }
597
402
  onClick();
598
403
  };
599
- return (jsxs("div", { className: cx$1(styles$6.base, className, {
600
- [styles$6.disabled]: disabled,
601
- [styles$6.isClassicGame]: isClassicGame,
602
- }), children: [children && jsx("div", { className: cx$1(styles$6.icon), children: children }), jsxs("div", { className: cx$1(styles$6.inputContainer), onClick: (e) => {
404
+ return (jsxs("div", { className: cx$1(styles$5.base, className, {
405
+ [styles$5.disabled]: disabled,
406
+ [styles$5.isClassicGame]: isClassicGame,
407
+ }), children: [children && jsx("div", { className: cx$1(styles$5.icon), children: children }), jsxs("div", { className: cx$1(styles$5.inputContainer), onClick: (e) => {
603
408
  e.preventDefault();
604
409
  handleOnClick();
605
- }, children: [jsx("span", { className: styles$6.label, children: t("playAmountLabel") }), jsx("div", { className: cx$1(styles$6.playValueInput, {
606
- [styles$6.playValueInputWithLabel]: true,
410
+ }, children: [jsx("span", { className: styles$5.label, children: "Play amount" }), jsx("div", { className: cx$1(styles$5.playValueInput, {
411
+ [styles$5.playValueInputWithLabel]: true,
607
412
  }), children: value })] })] }));
608
413
  };
609
414
 
610
- const COIN_TYPE_SWEEPS = 0;
611
- const COIN_TYPE_GOLD = 1;
612
- // B2C coin decimals (platform convention): Sweeps shows 2 dp, Gold 0 dp.
613
- const COIN_DECIMALS = {
614
- [COIN_TYPE_SWEEPS]: 2,
615
- [COIN_TYPE_GOLD]: 0,
616
- };
617
- const resolveFreeRoundsCoinLabel = ({ coinType, currency, }) => {
618
- if (currency) {
619
- return currency;
620
- }
621
- if (coinType === COIN_TYPE_SWEEPS) {
622
- return "SC";
623
- }
624
- if (coinType === COIN_TYPE_GOLD) {
625
- return "GC";
626
- }
627
- return "";
628
- };
629
- // The per-round value belongs to the GRANT's coin, which can differ from the
630
- // player's active currency (e.g. a Sweeps grant surfaced while the player is
631
- // on Gold). Use the grant coin's decimals so a 0.20 SC value doesn't render as
632
- // "0" under Gold's 0-dp formatting. B2B grants (operator `currency`) fall back
633
- // to the operator currency decimals passed in via `fallback`.
634
- const resolveFreeRoundsDecimals = ({ coinType, currency, fallback, }) => {
635
- if (!currency && coinType !== undefined && coinType in COIN_DECIMALS) {
636
- return COIN_DECIMALS[coinType];
637
- }
638
- return fallback;
639
- };
640
- // Resolves display decimals from a coin/currency LABEL (cross-SDK standard):
641
- // SC/SWEEPS = 2, GC/GOLD = 0, B2B operator currency strings = 2.
642
- const resolveCoinLabelDecimals = (coin) => {
643
- const normalized = coin.trim().toUpperCase();
644
- if (normalized === "GC" || normalized === "GOLD") {
645
- return 0;
646
- }
647
- return 2;
648
- };
649
- const MS_PER_DAY = 86_400_000;
650
- // Computes the whole-day countdown for "*Expires in {N} days". Rounds UP so a
651
- // grant expiring in 36 hours reads "2 days" (it is still claimable on day 2).
652
- // Returns null when the input is missing/invalid or already expired.
653
- const computeExpiresInDays = (expiresAt, now = Date.now()) => {
654
- if (expiresAt === null || expiresAt === undefined) {
655
- return null;
656
- }
657
- const expiryMs = new Date(expiresAt).getTime();
658
- if (Number.isNaN(expiryMs)) {
659
- return null;
660
- }
661
- const remainingMs = expiryMs - now;
662
- if (remainingMs <= 0) {
663
- return null;
664
- }
665
- return Math.ceil(remainingMs / MS_PER_DAY);
666
- };
667
-
668
415
  const PlayAmountControl = ({ isDisabled, forceRefresh, lockCurrency = false, }) => {
669
416
  const { config } = useAutoManualPlayState();
670
- const { playAmount, adjustPlayAmount, playValues: { togglePlayAmountView }, autoPlay: { state }, freeRounds, } = usePlayController();
417
+ const { playAmount, adjustPlayAmount, playValues: { togglePlayAmountView }, autoPlay: { state }, } = usePlayController();
671
418
  const isClassicGame = Boolean(config.isClassicGame || config.classicGame?.forceLeftStyle);
672
- // While a free-rounds grant is engaged, the input is locked to the GRANT's
673
- // per-round value and coin (which can differ from the active currency), so
674
- // the locked amount is always truthful.
675
- const { current } = config.currencyOptions;
676
- const grantCoinLabel = resolveFreeRoundsCoinLabel({
677
- coinType: freeRounds.coinType,
678
- currency: freeRounds.currency,
679
- });
680
- const valueDecimals = freeRounds.isEngaged
681
- ? resolveFreeRoundsDecimals({
682
- coinType: freeRounds.coinType,
683
- currency: freeRounds.currency,
684
- fallback: current.decimals,
685
- })
686
- : current.decimals;
687
- const coinLabel = freeRounds.isEngaged && grantCoinLabel ? grantCoinLabel : current.abbr;
688
419
  const handleTogglePlayAmount = () => {
689
420
  setToggleGameWidgetsVisibility();
690
421
  togglePlayAmountView();
691
422
  setToggleGameWidgetsExpansion();
692
423
  };
693
- return (jsxs(GroupRow, { isClassicGame: isClassicGame, children: [jsx(PlayValueInput, { className: styles_group.groupItem, value: `${format(playAmount, valueDecimals)} ${coinLabel}`, disabled: isDisabled() ||
424
+ return (jsxs(GroupRow, { isClassicGame: isClassicGame, children: [jsx(PlayValueInput, { className: styles_group.groupItem, value: `${format(playAmount, config.currencyOptions.current.decimals)} ${config.currencyOptions.current.abbr}`, disabled: isDisabled() ||
694
425
  config.playOptions.disableInput ||
695
426
  config.playOptions.disabledController ||
696
427
  state === AUTO_PLAY_STATE.PLAYING ||
697
- lockCurrency, onClick: handleTogglePlayAmount, isClassicGame: isClassicGame, children: jsx(SelectMenu, { disabled: isDisabled() || lockCurrency }) }), jsx(Button, { className: styles_group.groupItem, onClick: () => adjustPlayAmount({ direction: -1 }), theme: "ghost", disabled: isDisabled() || lockCurrency, children: jsx("span", { className: styles_group.x2, children: "-" }) }), jsx(Button, { className: styles_group.refreshButton, onClick: () => {
428
+ lockCurrency, onClick: handleTogglePlayAmount, isClassicGame: isClassicGame, children: jsx(SelectMenu, { disabled: isDisabled() || lockCurrency }) }), jsx(Button, { className: styles_group.groupItem, onClick: () => adjustPlayAmount({ direction: -1 }), theme: "ghost", disabled: isDisabled(), children: jsx("span", { className: styles_group.x2, children: "-" }) }), jsx(Button, { className: styles_group.refreshButton, onClick: () => {
698
429
  forceRefresh();
699
- }, "data-button-refresh": true, children: jsx("span", { className: cx$1(styles_group.x2, styles_group.first) }) }), jsx(Button, { className: styles_group.groupItem, onClick: () => adjustPlayAmount({ direction: 1 }), theme: "ghost", disabled: isDisabled() || lockCurrency, children: jsx("span", { className: cx$1(styles_group.x2, styles_group.last), children: "+" }) })] }));
430
+ }, "data-button-refresh": true, children: jsx("span", { className: cx$1(styles_group.x2, styles_group.first) }) }), jsx(Button, { className: styles_group.groupItem, onClick: () => adjustPlayAmount({ direction: 1 }), theme: "ghost", disabled: isDisabled(), children: jsx("span", { className: cx$1(styles_group.x2, styles_group.last), children: "+" }) })] }));
700
431
  };
701
432
 
702
433
  const getDefaultButtonClassName = (currencyCode, styles) => {
@@ -718,11 +449,17 @@ const getActiveButtonClassName = (currencyCode, styles) => {
718
449
  return styles.buttonDefault__active;
719
450
  };
720
451
 
452
+ var AUTOPLAY_LABEL;
453
+ (function (AUTOPLAY_LABEL) {
454
+ AUTOPLAY_LABEL["START"] = "START AUTOPLAY";
455
+ AUTOPLAY_LABEL["STOP"] = "STOP AUTOPLAY";
456
+ })(AUTOPLAY_LABEL || (AUTOPLAY_LABEL = {}));
457
+
721
458
  // Space activates buttons/links natively. If the user has keyboard focus on a
722
- // different interactive control (the intro modal's PLAY NOW / PLAY LATER
723
- // buttons, etc.), let Space activate THAT control instead of hijacking it to
724
- // click the play button — otherwise a paid play can fire while the user is
725
- // trying to engage free rounds.
459
+ // different interactive control (the "Use Free Rounds" affordance, the intro
460
+ // modal's PLAY NOW / PLAY LATER buttons, etc.), let Space activate THAT control
461
+ // instead of hijacking it to click the play button — otherwise a paid play can
462
+ // fire while the user is trying to engage free rounds.
726
463
  const isOtherInteractiveControlFocused = (button) => {
727
464
  const active = document.activeElement;
728
465
  return Boolean(active &&
@@ -780,29 +517,20 @@ const useSpacebarButtonTrigger = ({ roleButton, activeClassName, canTrigger, sup
780
517
 
781
518
  const AutoPlayController = () => {
782
519
  const { config } = useAutoManualPlayState();
783
- const { t } = useTranslation();
784
520
  const [forceRefresh, setForceRefresh] = useState(false);
785
- const { isValidPlayAmount, freeRounds, playValues: { displayPlayAmountView, togglePlayAmountView }, autoPlay: { isDisabled, state, onPlay, onStopPlay, cashoutDisabled }, } = usePlayController();
521
+ const { isValidPlayAmount, playValues: { displayPlayAmountView, togglePlayAmountView }, autoPlay: { isDisabled, state, onPlay, onStopPlay, cashoutDisabled }, } = usePlayController();
786
522
  const { current } = config.currencyOptions;
787
523
  const roleButton = GAME_MODE.AUTOPLAY;
788
- const isStopState = state === AUTO_PLAY_STATE.PLAYING;
789
- // Free-rounds engaged (grant-funded autoplay): the STOP control shows the
790
- // live remaining grant count so the player sees the rounds tick down.
791
- let buttonLabel = isStopState
792
- ? t("stopAutoplay")
793
- : t("startAutoplay");
794
- if (isStopState && freeRounds.isEngaged) {
795
- buttonLabel = `${t("stopAutoplay")} (${freeRounds.roundsRemaining})`;
796
- }
524
+ const buttonLabel = state === AUTO_PLAY_STATE.PLAYING
525
+ ? AUTOPLAY_LABEL.STOP
526
+ : AUTOPLAY_LABEL.START;
797
527
  const activeClassName = getActiveButtonClassName(current.code, styles_button);
798
- // Cashout styling keys off the PLAYING state (not the label text) so the
799
- // engaged "(N)" label suffix can never break the style comparison.
800
528
  const defaultClassName = useMemo(() => {
801
- if (isStopState) {
529
+ if (buttonLabel === AUTOPLAY_LABEL.STOP) {
802
530
  return styles_button.buttonCashout;
803
531
  }
804
532
  return getDefaultButtonClassName(current.code, styles_button);
805
- }, [isStopState, current.code]);
533
+ }, [buttonLabel, current.code]);
806
534
  useSpacebarButtonTrigger({
807
535
  roleButton,
808
536
  activeClassName,
@@ -811,10 +539,6 @@ const AutoPlayController = () => {
811
539
  cashoutDisabled,
812
540
  isDisabled: isDisabled(),
813
541
  }),
814
- // Suppress the global Space shortcut while the free-rounds intro modal is
815
- // open so Space belongs to the modal's PLAY NOW / PLAY LATER buttons
816
- // instead of starting/stopping the autoplay mounted behind it.
817
- suppressed: Boolean(config.freeRoundsOptions?.showIntroModal),
818
542
  });
819
543
  const isButtonDisabled = isAutoplayButtonDisabled({
820
544
  state,
@@ -839,7 +563,7 @@ const AutoPlayController = () => {
839
563
  }, [togglePlayAmountView]);
840
564
  const renderButton = useMemo(() => {
841
565
  if (displayPlayAmountView) {
842
- return (jsx(Button, { className: styles_button.buttonPlayAmount, onClick: handleTogglePlayAmount, roleType: roleButton, children: t("selectPlayAmount") }));
566
+ return (jsx(Button, { className: styles_button.buttonPlayAmount, onClick: handleTogglePlayAmount, roleType: roleButton, children: "SELECT PLAY AMOUNT" }));
843
567
  }
844
568
  return (jsx(Button, { disabled: isButtonDisabled, className: defaultClassName, onClick: buttonAction, roleType: roleButton, children: buttonLabel }));
845
569
  }, [
@@ -850,11 +574,27 @@ const AutoPlayController = () => {
850
574
  handleTogglePlayAmount,
851
575
  isButtonDisabled,
852
576
  roleButton,
853
- t,
854
577
  ]);
855
578
  return (jsxs(Fragment, { children: [jsx(PlayAmountControl, { isDisabled: isDisabled, forceRefresh: () => setForceRefresh(!forceRefresh), "data-force-refresh": `mini-play-amount-control-data-${forceRefresh.toString()}` }, `mini-play-amount-control-key-${forceRefresh.toString()}`), renderButton] }));
856
579
  };
857
580
 
581
+ var freeRoundsStyles = {"affordance":"FreeRoundsAffordance-module__affordance___wBGcv","row":"FreeRoundsAffordance-module__row___KwByQ","playSlot":"FreeRoundsAffordance-module__playSlot___hCXTv","freeSlot":"FreeRoundsAffordance-module__freeSlot___-mQ8T","chip":"FreeRoundsAffordance-module__chip___OajJb","chipDisabled":"FreeRoundsAffordance-module__chipDisabled___W9hSU"};
582
+
583
+ // The "Use Free Rounds (N)" chip.
584
+ // - available && !engaged → renders the chip (engages via onUseFreeRounds).
585
+ // It is laid out beside the Play Now button (35/65 row) by
586
+ // ManualPlayController; this component only renders the chip itself.
587
+ // - engaged → renders nothing. There is intentionally NO "exit free rounds"
588
+ // affordance: once engaged the player cannot leave free-round mode from the
589
+ // controller. The engaged play button label is owned by ManualPlayController.
590
+ // Purely renders + fires the callback; the game owns the engaged state.
591
+ const FreeRoundsAffordance = ({ isAvailable, isEngaged, roundsRemaining, onUseFreeRounds, disabled = false, }) => {
592
+ if (isEngaged || !isAvailable || !onUseFreeRounds) {
593
+ return null;
594
+ }
595
+ return (jsxs("button", { type: "button", disabled: disabled, className: cx$1(freeRoundsStyles.chip, { [freeRoundsStyles.chipDisabled]: disabled }), onClick: disabled ? undefined : onUseFreeRounds, children: ["USE FREE ROUNDS (", roundsRemaining, ")"] }));
596
+ };
597
+
858
598
  const useIsRunningExternal = () => {
859
599
  const [isRunningExternal, setIsRunningExternal] = useState(false);
860
600
  useEffect(() => {
@@ -879,7 +619,6 @@ const useIsRunningExternal = () => {
879
619
 
880
620
  const ManualPlayController = () => {
881
621
  const { config, doubleOrNothing } = useAutoManualPlayState();
882
- const { t } = useTranslation();
883
622
  const { isRunningExternal } = useIsRunningExternal();
884
623
  const [forceRefresh, setForceRefresh] = useState(false);
885
624
  const { isValidPlayAmount, playValues: { displayPlayAmountView, togglePlayAmountView }, manualPlay: { isDisabled, onPlay, canCashout, cashoutDisabled }, freeRounds, } = usePlayController();
@@ -929,7 +668,7 @@ const ManualPlayController = () => {
929
668
  }), [cashoutDisabled, config.onCashout]);
930
669
  const renderButton = useMemo(() => {
931
670
  if (displayPlayAmountView) {
932
- return (jsx(Button, { className: styles_button.buttonPlayAmount, onClick: handleTogglePlayAmount, roleType: roleButton, children: t("selectPlayAmount") }));
671
+ return (jsx(Button, { className: styles_button.buttonPlayAmount, onClick: handleTogglePlayAmount, roleType: roleButton, children: "SELECT PLAY AMOUNT" }));
933
672
  }
934
673
  if (canCashout) {
935
674
  const isCashoutDisabled = isManualCashoutDisabled({
@@ -943,29 +682,21 @@ const ManualPlayController = () => {
943
682
  doubleOrNothing.state === DOUBLE_OR_NOTHING_STATE.PROGRESS;
944
683
  return (jsxs("div", { className: styles_button.buttonRow, children: [jsxs(Button, { disabled: isCashoutDisabled, className: cx$1(styles_button.buttonCashout, {
945
684
  [styles_button.buttonRow__cashout]: showDoubleOrNothing,
946
- }), onClick: handleCashoutClick, roleType: roleButton, children: [isRunningExternal ? t("collect") : t("cashout"), " ", format(current.possibleWin ?? 0, current.decimals), " ", current.abbr] }), showDoubleOrNothing ? (jsx(Button, { disabled: isDoubleOrNothingDisabled, className: cx$1(styles_button.buttonDON, styles_button.buttonRow__don, {
685
+ }), onClick: handleCashoutClick, roleType: roleButton, children: [isRunningExternal ? "COLLECT" : "CASHOUT", " ", format(current.possibleWin ?? 0, current.decimals), " ", current.abbr] }), showDoubleOrNothing ? (jsx(Button, { disabled: isDoubleOrNothingDisabled, className: cx$1(styles_button.buttonDON, styles_button.buttonRow__don, {
947
686
  [styles_button.buttonDON__disabled]: isDoubleOrNothingDisabled,
948
- }), onClick: handleDoubleOrNothingClick, roleType: roleButton, children: jsxs("div", { className: styles_button.buttonDON__content, children: [jsx("p", { className: styles_button.buttonDON__title, children: t("doubleTitle") }), jsx("p", { className: styles_button.buttonDON__subtitle, children: t("doubleSubtitle") })] }) })) : null] }));
949
- }
950
- // While a game-driven autoplay loop is running, the game's stop-autoplay
951
- // control is the only action — don't stack the manual play CTA behind it.
952
- // The play amount, widgets and free-rounds footer stay as-is.
953
- if (config.freeRoundsOptions?.autoplayActive) {
954
- return null;
687
+ }), onClick: handleDoubleOrNothingClick, roleType: roleButton, children: jsxs("div", { className: styles_button.buttonDON__content, children: [jsx("p", { className: styles_button.buttonDON__title, children: "DOUBLE" }), jsx("p", { className: styles_button.buttonDON__subtitle, children: "OR NOTHING" })] }) })) : null] }));
955
688
  }
956
689
  // Free rounds reuse the play button label while ENGAGED — it becomes
957
- // "PLAY FREE ROUND" (no count; the remaining count lives ONLY in the
958
- // "{N} FREE ROUNDS LEFT" footer).
690
+ // "PLAY FREE ROUND (N)". Denominator removed to simplify ("7/10" "7").
959
691
  const playNowText = freeRounds.isEngaged
960
- ? t("playFreeRound")
961
- : t("playNow");
692
+ ? `PLAY FREE ROUND (${freeRounds.roundsRemaining})`
693
+ : "PLAY NOW";
962
694
  return (jsx(Button, { disabled: isButtonDisabled, className: defaultClassName, onClick: onPlay, roleType: roleButton, children: playNowText }));
963
695
  }, [
964
696
  canCashout,
965
697
  cashoutDisabled,
966
698
  config.doubleOrNothing?.disabled,
967
699
  config.doubleOrNothing?.display,
968
- config.freeRoundsOptions?.autoplayActive,
969
700
  config.playOptions.disabledController,
970
701
  config.playOptions.isPlaying,
971
702
  current.abbr,
@@ -975,6 +706,7 @@ const ManualPlayController = () => {
975
706
  displayPlayAmountView,
976
707
  doubleOrNothing.state,
977
708
  freeRounds.isEngaged,
709
+ freeRounds.roundsRemaining,
978
710
  handleDoubleOrNothingClick,
979
711
  handleCashoutClick,
980
712
  handleTogglePlayAmount,
@@ -982,20 +714,33 @@ const ManualPlayController = () => {
982
714
  isRunningExternal,
983
715
  onPlay,
984
716
  roleButton,
985
- t,
986
717
  ]);
987
- return (jsxs(Fragment, { children: [jsx(PlayAmountControl, { isDisabled: isDisabled || displayPlayAmountView, lockCurrency: freeRounds.isEngaged, forceRefresh: () => setForceRefresh(!forceRefresh), "data-force-refresh": `mini-play-amount-control-data-${forceRefresh.toString()}` }, `mini-play-amount-control-key-${forceRefresh.toString()}`), renderButton] }));
718
+ // Grants exist but the player hasn't engaged show the side-by-side row
719
+ // (Play Now at 35%, "Use Free Rounds" chip at 65%) instead of a full-width
720
+ // play button. Only when not cashing out and not in the play-amount view.
721
+ const showFreeRoundsRow = !displayPlayAmountView &&
722
+ !canCashout &&
723
+ freeRounds.isAvailable &&
724
+ !freeRounds.isEngaged;
725
+ // Engaging free rounds must be impossible while the controller is disabled or
726
+ // a (non-cashout) round is in progress — same signals that disable the play
727
+ // button. We deliberately exclude `!isValidPlayAmount` (the chip engages a
728
+ // grant, not the paid stake) and rely on `isDisabled()` /
729
+ // `disabledController` / `isPlaying` already in scope.
730
+ const isFreeRoundsAffordanceDisabled = isDisabled() ||
731
+ config.playOptions.disabledController ||
732
+ config.playOptions.isPlaying;
733
+ return (jsxs(Fragment, { children: [jsx(PlayAmountControl, { isDisabled: isDisabled || displayPlayAmountView, lockCurrency: freeRounds.isEngaged, forceRefresh: () => setForceRefresh(!forceRefresh), "data-force-refresh": `mini-play-amount-control-data-${forceRefresh.toString()}` }, `mini-play-amount-control-key-${forceRefresh.toString()}`), showFreeRoundsRow ? (jsxs("div", { className: freeRoundsStyles.row, children: [jsx("div", { className: freeRoundsStyles.playSlot, children: renderButton }), jsx("div", { className: freeRoundsStyles.freeSlot, children: jsx(FreeRoundsAffordance, { isAvailable: freeRounds.isAvailable, isEngaged: freeRounds.isEngaged, roundsRemaining: freeRounds.roundsRemaining, totalRounds: freeRounds.totalRounds, onUseFreeRounds: freeRounds.onUseFreeRounds, disabled: isFreeRoundsAffordanceDisabled }) })] })) : (renderButton)] }));
988
734
  };
989
735
 
990
- var styles_ui = {"base":"UI-module__base___wThyQ","container":"UI-module__container___gMSiK","betForm":"UI-module__betForm___hQkYd","isClassicGame":"UI-module__isClassicGame___zV1we","baseForceLeft":"UI-module__baseForceLeft___xfc-h","containerSpread":"UI-module__containerSpread___Axb7e","disabled":"UI-module__disabled___dnZJX","auto":"UI-module__auto___5peb8","freeRoundsFooter":"UI-module__freeRoundsFooter___AOprS","freeRoundsFooterCount":"UI-module__freeRoundsFooterCount___eaTrs","controllerWidgets":"UI-module__controllerWidgets___x2lBy","sideWidget":"UI-module__sideWidget___WMGRy","centerWidget":"UI-module__centerWidget___sLLUi","left":"UI-module__left___1wtUp","center":"UI-module__center___E0ILH","right":"UI-module__right___yLHvk","controls":"UI-module__controls___Z6L-V","inActiveRound":"UI-module__inActiveRound___HYAx2"};
736
+ var styles_ui = {"base":"UI-module__base___wThyQ","container":"UI-module__container___gMSiK","betForm":"UI-module__betForm___hQkYd","isClassicGame":"UI-module__isClassicGame___zV1we","baseForceLeft":"UI-module__baseForceLeft___xfc-h","containerSpread":"UI-module__containerSpread___Axb7e","disabled":"UI-module__disabled___dnZJX","auto":"UI-module__auto___5peb8","controllerWidgets":"UI-module__controllerWidgets___x2lBy","sideWidget":"UI-module__sideWidget___WMGRy","centerWidget":"UI-module__centerWidget___sLLUi","left":"UI-module__left___1wtUp","center":"UI-module__center___E0ILH","right":"UI-module__right___yLHvk","controls":"UI-module__controls___Z6L-V","inActiveRound":"UI-module__inActiveRound___HYAx2"};
991
737
 
992
738
  var style_difficulty = {"base":"DifficultySelector-module__base___lDaQ-"};
993
739
 
994
740
  var style_select = {"base":"Selector-module__base___-q0yf","label":"Selector-module__label___JJdbd","button":"Selector-module__button___MeRoC","menu":"Selector-module__menu___P0ROg","menuItems":"Selector-module__menuItems___chFk2","menuItem":"Selector-module__menuItem___isa4c","selected":"Selector-module__selected___nUJtR","disabled":"Selector-module__disabled___f1qGJ","disabledClassic":"Selector-module__disabledClassic___baiGO"};
995
741
 
996
742
  const capitalize = (str) => str.charAt(0).toUpperCase() + str.slice(1).toLowerCase();
997
- const Selector = ({ currentValue, label, riskColor, values, onSelect, disabled, isClassicGame = false, formatDisplay, }) => {
998
- const display = (value) => formatDisplay ? formatDisplay(value) : capitalize(String(value));
743
+ const Selector = ({ currentValue, label, riskColor, values, onSelect, disabled, isClassicGame = false, }) => {
999
744
  const handleClick = (e) => {
1000
745
  if (disabled) {
1001
746
  return;
@@ -1010,26 +755,26 @@ const Selector = ({ currentValue, label, riskColor, values, onSelect, disabled,
1010
755
  return (jsxs("div", { className: cx$1(style_select.base), children: [label && jsx("span", { className: cx$1(style_select.label), children: label }), jsx(Menu, { as: "div", className: cx$1(style_select.menu), children: ({ open }) => (jsxs(Fragment, { children: [jsxs(MenuButton, { className: cx$1(style_select.button, {
1011
756
  [style_select.disabled]: disabled && !isClassicGame,
1012
757
  [style_select.disabledClassic]: disabled && isClassicGame,
1013
- }), style: { color: riskColor }, disabled: disabled, "data-difficulty-selector-button": true, children: [display(currentValue), jsx(ChevronIcon, { open: open, disabled: disabled })] }), jsx(MenuItems, { anchor: { to: "top start" }, className: cx$1(style_select.menuItems), children: values.map((value) => (jsx(MenuItem, { as: "div", "data-value": value, onClick: handleClick, className: cx$1(style_select.menuItem, {
758
+ }), style: { color: riskColor }, disabled: disabled, "data-difficulty-selector-button": true, children: [capitalize(String(currentValue)), jsx(ChevronIcon, { open: open, disabled: disabled })] }), jsx(MenuItems, { anchor: { to: "top start" }, className: cx$1(style_select.menuItems), children: values.map((value) => (jsx(MenuItem, { as: "div", "data-value": value, onClick: handleClick, className: cx$1(style_select.menuItem, {
1014
759
  [style_select.selected]: String(currentValue) === String(value),
1015
- }), children: display(value) }, `element-${value}`))) })] })) })] }));
760
+ }), children: capitalize(String(value)) }, `element-${value}`))) })] })) })] }));
1016
761
  };
1017
762
 
1018
- const RISK_LABEL_KEY = {
1019
- [RiskTypes.LOW]: "riskLow",
1020
- [RiskTypes.MEDIUM]: "riskMedium",
1021
- [RiskTypes.HIGH]: "riskHigh",
1022
- };
763
+ var SELECTORS;
764
+ (function (SELECTORS) {
765
+ SELECTORS["RISK"] = "Risk";
766
+ })(SELECTORS || (SELECTORS = {}));
767
+ var RiskTypes;
768
+ (function (RiskTypes) {
769
+ RiskTypes["LOW"] = "LOW";
770
+ RiskTypes["MEDIUM"] = "MEDIUM";
771
+ RiskTypes["HIGH"] = "HIGH";
772
+ })(RiskTypes || (RiskTypes = {}));
773
+
1023
774
  const DifficultySelector = ({ playOptions, dropdownConfig, isClassicGame = false, }) => {
1024
- const { t } = useTranslation();
1025
775
  const { risks, disabledMenu, currentRisk, onRiskChange } = playOptions;
1026
- const riskColors = { ...DEFAULT_RISK_COLORS, ...dropdownConfig.riskColorConfig };
1027
- const riskColor = useRef(riskColors[currentRisk ?? RiskTypes.LOW]);
1028
- function onRiskSelected(value) {
1029
- riskColor.current = riskColors[value];
1030
- onRiskChange(value);
1031
- }
1032
- return (jsx("div", { className: style_difficulty.base, children: jsx(Selector, { currentValue: currentRisk, label: t("riskLabel"), values: risks, onSelect: onRiskSelected, disabled: disabledMenu, riskColor: riskColor.current, isClassicGame: isClassicGame, formatDisplay: (risk) => t(RISK_LABEL_KEY[risk]) }) }));
776
+ const riskColor = dropdownConfig.riskColorConfig[currentRisk ?? RiskTypes.LOW];
777
+ return (jsx("div", { className: style_difficulty.base, children: jsx(Selector, { currentValue: currentRisk, label: SELECTORS.RISK, values: risks, onSelect: onRiskChange, disabled: disabledMenu, riskColor: riskColor, isClassicGame: isClassicGame }) }));
1033
778
  };
1034
779
 
1035
780
  const ALLOWED_SEPARATORS = [",", "."];
@@ -1052,7 +797,7 @@ function cleanInputNumber(value) {
1052
797
  return `${integerPart}.${decimalPart}`;
1053
798
  }
1054
799
 
1055
- var styles$5 = {"base":"Input-module__base___IifiA","disabled":"Input-module__disabled___WqyKa","disabledClassic":"Input-module__disabledClassic___hzsCt"};
800
+ var styles$4 = {"base":"Input-module__base___IifiA","disabled":"Input-module__disabled___WqyKa","disabledClassic":"Input-module__disabledClassic___hzsCt"};
1056
801
 
1057
802
  const Input = ({ onChange, disabled, className, max, isClassicGame = false, ...restProps }) => {
1058
803
  const handleChange = React.useCallback((event) => {
@@ -1067,32 +812,31 @@ const Input = ({ onChange, disabled, className, max, isClassicGame = false, ...r
1067
812
  event.target.value = clamped;
1068
813
  onChange?.(event);
1069
814
  }, [disabled, max, onChange]);
1070
- return (jsx("input", { ...restProps, type: "text", max: max, inputMode: "numeric", pattern: "\\d*", maxLength: 2, disabled: disabled, onChange: handleChange, className: cx$1(styles$5.base, className, {
1071
- [styles$5.disabled]: disabled && !isClassicGame,
1072
- [styles$5.disabledClassic]: disabled && isClassicGame,
815
+ return (jsx("input", { ...restProps, type: "text", max: max, inputMode: "numeric", pattern: "\\d*", maxLength: 2, disabled: disabled, onChange: handleChange, className: cx$1(styles$4.base, className, {
816
+ [styles$4.disabled]: disabled && !isClassicGame,
817
+ [styles$4.disabledClassic]: disabled && isClassicGame,
1073
818
  }) }));
1074
819
  };
1075
820
 
1076
- var styles$4 = {"base":"Switch-module__base___gj2ey","isClassicGame":"Switch-module__isClassicGame___H2Ffo","switcher":"Switch-module__switcher___gHXIx","gold":"Switch-module__gold___oewnb","sweeps":"Switch-module__sweeps___yS-IY","default":"Switch-module__default___TmHoU","unchecked":"Switch-module__unchecked___ooSS2","disabled":"Switch-module__disabled___lMRv0","thumb":"Switch-module__thumb___1wJ9D","move-right":"Switch-module__move-right___ca-6D","label":"Switch-module__label___pG2uz"};
821
+ var styles$3 = {"base":"Switch-module__base___gj2ey","isClassicGame":"Switch-module__isClassicGame___H2Ffo","switcher":"Switch-module__switcher___gHXIx","gold":"Switch-module__gold___oewnb","sweeps":"Switch-module__sweeps___yS-IY","default":"Switch-module__default___TmHoU","unchecked":"Switch-module__unchecked___ooSS2","disabled":"Switch-module__disabled___lMRv0","thumb":"Switch-module__thumb___1wJ9D","move-right":"Switch-module__move-right___ca-6D","label":"Switch-module__label___pG2uz"};
1077
822
 
1078
823
  const Switch = ({ enabled, onSwitch, disabled, isPlaying, }) => {
1079
824
  const { config } = useAutoManualPlayState();
1080
- const { t } = useTranslation();
1081
825
  const { current } = config.currencyOptions;
1082
826
  const isClassicGame = Boolean(config?.isClassicGame || config?.classicGame?.forceLeftStyle);
1083
827
  const defaultClassName = useMemo(() => {
1084
828
  if (current.code === Currency.GOLD) {
1085
- return styles$4.gold;
829
+ return styles$3.gold;
1086
830
  }
1087
831
  if (current.code === Currency.SWEEPS) {
1088
- return styles$4.sweeps;
832
+ return styles$3.sweeps;
1089
833
  }
1090
- return styles$4.default;
834
+ return styles$3.default;
1091
835
  }, [current.code]);
1092
- const switcherClassName = useMemo(() => cx$1(styles$4.switcher, defaultClassName, {
1093
- [styles$4.checked]: enabled,
1094
- [styles$4.unchecked]: !enabled,
1095
- [styles$4.disabled]: disabled,
836
+ const switcherClassName = useMemo(() => cx$1(styles$3.switcher, defaultClassName, {
837
+ [styles$3.checked]: enabled,
838
+ [styles$3.unchecked]: !enabled,
839
+ [styles$3.disabled]: disabled,
1096
840
  }), [defaultClassName, enabled, disabled]);
1097
841
  useEffect(() => {
1098
842
  const handleGlobalKeyDown = (event) => {
@@ -1113,18 +857,18 @@ const Switch = ({ enabled, onSwitch, disabled, isPlaying, }) => {
1113
857
  };
1114
858
  }, []);
1115
859
  return (jsx(Switch$1, { checked: enabled, onChange: onSwitch, as: Fragment$1, disabled: isPlaying, children: jsxs("div", { className: cx$1({
1116
- [styles$4.isClassicGame]: isClassicGame,
1117
- [styles$4.base]: !isClassicGame,
1118
- }), children: [jsx("span", { className: styles$4.label, children: t("autoLabel") }), jsx("div", { className: switcherClassName, children: jsx("span", { className: cx$1(styles$4.thumb, { [styles$4["move-right"]]: enabled }) }) })] }) }));
860
+ [styles$3.isClassicGame]: isClassicGame,
861
+ [styles$3.base]: !isClassicGame,
862
+ }), children: [jsx("span", { className: styles$3.label, children: "Auto" }), jsx("div", { className: switcherClassName, children: jsx("span", { className: cx$1(styles$3.thumb, { [styles$3["move-right"]]: enabled }) }) })] }) }));
1119
863
  };
1120
864
 
1121
- var styles$3 = {"base":"InputWithSwitch-module__base___vjLoh","icon":"InputWithSwitch-module__icon___upeAL","input":"InputWithSwitch-module__input___K6RCD","label":"InputWithSwitch-module__label___tEjpj","classic":"InputWithSwitch-module__classic___3AzII"};
865
+ var styles$2 = {"base":"InputWithSwitch-module__base___vjLoh","icon":"InputWithSwitch-module__icon___upeAL","input":"InputWithSwitch-module__input___K6RCD","label":"InputWithSwitch-module__label___tEjpj","classic":"InputWithSwitch-module__classic___3AzII"};
1122
866
 
1123
867
  const InputWithSwitch = ({ children, switcherConfig, disabled, className, isClassicGame = false, ...restProps }) => {
1124
- return (jsxs("div", { className: cx$1(styles$3.base, className, {
1125
- [styles$3.disabled]: switcherConfig.disabled,
1126
- [styles$3.classic]: isClassicGame,
1127
- }), children: [jsx(Switch, { ...switcherConfig }), jsx(Input, { ...restProps, className: cx$1(styles$3.input), disabled: disabled, isClassicGame: isClassicGame }), jsx("div", { className: cx$1(styles$3.icon), children: children })] }));
868
+ return (jsxs("div", { className: cx$1(styles$2.base, className, {
869
+ [styles$2.disabled]: switcherConfig.disabled,
870
+ [styles$2.classic]: isClassicGame,
871
+ }), children: [jsx(Switch, { ...switcherConfig }), jsx(Input, { ...restProps, className: cx$1(styles$2.input), disabled: disabled, isClassicGame: isClassicGame }), jsx("div", { className: cx$1(styles$2.icon), children: children })] }));
1128
872
  };
1129
873
 
1130
874
  function getDefaultExportFromCjs (x) {
@@ -1228,18 +972,14 @@ const WidgetContainer = ({ state, widgets, displayPlayAmountView, }) => {
1228
972
  return (jsxs("div", { className: cx("controllerWidgets"), children: [renderZone(left, "left"), renderZone(center, "center"), renderZone(right, "right")] }));
1229
973
  };
1230
974
 
1231
- var styles$2 = {"base":"PlayValueList-module__base___5duc6","playValuesWrapper":"PlayValueList-module__playValuesWrapper___-zfj2","locked":"PlayValueList-module__locked___JNPHM","playValue":"PlayValueList-module__playValue___TJQhI","selectedPlayValue":"PlayValueList-module__selectedPlayValue___3DhDC","disabledPlayValue":"PlayValueList-module__disabledPlayValue___U9Bkj","isClassicGame":"PlayValueList-module__isClassicGame___AjnGE"};
975
+ var styles$1 = {"base":"PlayValueList-module__base___5duc6","playValuesWrapper":"PlayValueList-module__playValuesWrapper___-zfj2","playValue":"PlayValueList-module__playValue___TJQhI","selectedPlayValue":"PlayValueList-module__selectedPlayValue___3DhDC","disabledPlayValue":"PlayValueList-module__disabledPlayValue___U9Bkj","isClassicGame":"PlayValueList-module__isClassicGame___AjnGE"};
1232
976
 
1233
977
  const PlayValueList = () => {
1234
978
  const [forceRefresh, setForceRefresh] = useState(false);
1235
979
  const { config } = useAutoManualPlayState();
1236
- const { t } = useTranslation();
1237
980
  const isClassicGame = Boolean(config?.isClassicGame || config?.classicGame?.forceLeftStyle);
1238
- const { onChangeAmount, playAmount, totalBalance, autoPlay: { state: autoplayState }, playValues: { displayPlayAmountView, togglePlayAmountView }, } = usePlayController();
981
+ const { onChangeAmount, playAmount, totalBalance, playValues: { displayPlayAmountView, togglePlayAmountView }, } = usePlayController();
1239
982
  const { playLimits } = config.playOptions.playHook();
1240
- // While autoplay is running the stake is locked for the whole session, so
1241
- // the play-amount table must not accept changes until it stops.
1242
- const isAutoplayPlaying = autoplayState === AUTO_PLAY_STATE.PLAYING;
1243
983
  if (!displayPlayAmountView) {
1244
984
  return null;
1245
985
  }
@@ -1270,9 +1010,6 @@ const PlayValueList = () => {
1270
1010
  setForceRefresh(!forceRefresh);
1271
1011
  };
1272
1012
  const handleClick = (e) => {
1273
- if (isAutoplayPlaying) {
1274
- return;
1275
- }
1276
1013
  const el = e.target?.closest("[data-value]");
1277
1014
  if (!el) {
1278
1015
  return;
@@ -1286,43 +1023,60 @@ const PlayValueList = () => {
1286
1023
  applyValue(value);
1287
1024
  }
1288
1025
  };
1289
- return (jsx("div", { className: cx$1(styles$2.base, { [styles$2.isClassicGame]: isClassicGame }), "data-force-refresh": forceRefresh, children: jsx("div", { className: cx$1(styles$2.playValuesWrapper, {
1290
- [styles$2.locked]: isAutoplayPlaying,
1291
- }), onClick: handleClick, role: "listbox", "aria-label": t("selectPlayAmountAria"), "aria-disabled": isAutoplayPlaying, "data-testid": "play-value-list", children: playLimits?.[config.currencyOptions.current.code].defaultBetOptions.map((playValue) => (jsx("div", { className: cx$1(styles$2.playValue, {
1292
- [styles$2.selectedPlayValue]: playValue === playAmount,
1293
- [styles$2.disabledPlayValue]: playValue > totalBalance,
1026
+ return (jsx("div", { className: cx$1(styles$1.base, { [styles$1.isClassicGame]: isClassicGame }), "data-force-refresh": forceRefresh, children: jsx("div", { className: cx$1(styles$1.playValuesWrapper), onClick: handleClick, role: "listbox", "aria-label": "Select play amount", "data-testid": "play-value-list", children: playLimits?.[config.currencyOptions.current.code].defaultBetOptions.map((playValue) => (jsx("div", { className: cx$1(styles$1.playValue, {
1027
+ [styles$1.selectedPlayValue]: playValue === playAmount,
1028
+ [styles$1.disabledPlayValue]: playValue > totalBalance,
1294
1029
  }), "data-value": playValue, role: "option", tabIndex: 0, "aria-selected": false, children: format(playValue, config.currencyOptions.current.decimals) }, `${config.currencyOptions.current.code}-${playValue}`))) }) }));
1295
1030
  };
1296
1031
 
1297
- var styles$1 = {"backdrop":"FreeRoundsIntroModal-module__backdrop___qIgNx","column":"FreeRoundsIntroModal-module__column___YG71X","panel":"FreeRoundsIntroModal-module__panel___-zQpe","header":"FreeRoundsIntroModal-module__header___K5jmV","summary":"FreeRoundsIntroModal-module__summary___qpAbF","accent":"FreeRoundsIntroModal-module__accent___m-2s6","perRound":"FreeRoundsIntroModal-module__perRound___5Sx-v","expiry":"FreeRoundsIntroModal-module__expiry___n-fve","actions":"FreeRoundsIntroModal-module__actions___toCBq","button":"FreeRoundsIntroModal-module__button___bSTmX","playNow":"FreeRoundsIntroModal-module__playNow___iVhdt","playLater":"FreeRoundsIntroModal-module__playLater___l9031"};
1032
+ var styles = {"backdrop":"FreeRoundsIntroModal-module__backdrop___qIgNx","panel":"FreeRoundsIntroModal-module__panel___-zQpe","title":"FreeRoundsIntroModal-module__title___1csj0","details":"FreeRoundsIntroModal-module__details___qXMR-","count":"FreeRoundsIntroModal-module__count___lVep8","stake":"FreeRoundsIntroModal-module__stake___vCcPI","actions":"FreeRoundsIntroModal-module__actions___toCBq","button":"FreeRoundsIntroModal-module__button___bSTmX","playNow":"FreeRoundsIntroModal-module__playNow___iVhdt","playLater":"FreeRoundsIntroModal-module__playLater___l9031"};
1298
1033
 
1299
- // Centered START intro pop-up — binary engagement decision. Renders + fires
1300
- // callbacks only; the SDK does no fetching. `onPlayNow` should engage
1301
- // free-rounds mode + close, `onPlayLater` should close and render zero
1302
- // free-rounds UI for the rest of the session.
1303
- const FreeRoundsIntroModal = ({ open, roundsRemaining, stakeCents, coinType, currency, decimals = 2, expiresAt, onPlayNow, onPlayLater, }) => {
1304
- const { t } = useTranslation();
1034
+ const COIN_TYPE_SWEEPS = 0;
1035
+ const COIN_TYPE_GOLD = 1;
1036
+ // B2C coin decimals (platform convention): Sweeps shows 2 dp, Gold 0 dp.
1037
+ const COIN_DECIMALS = {
1038
+ [COIN_TYPE_SWEEPS]: 2,
1039
+ [COIN_TYPE_GOLD]: 0,
1040
+ };
1041
+ const resolveCurrencyLabel = ({ coinType, currency, }) => {
1042
+ if (currency) {
1043
+ return currency;
1044
+ }
1045
+ if (coinType === COIN_TYPE_SWEEPS) {
1046
+ return "SC";
1047
+ }
1048
+ if (coinType === COIN_TYPE_GOLD) {
1049
+ return "GC";
1050
+ }
1051
+ return "";
1052
+ };
1053
+ // The stake belongs to the GRANT's coin, which can differ from the player's
1054
+ // active currency (e.g. a Sweeps grant surfaced while the player is on Gold).
1055
+ // Use the grant coin's decimals so a 0.20 SC stake doesn't render as "0" under
1056
+ // Gold's 0-dp formatting. B2B grants (operator `currency`) fall back to the
1057
+ // operator currency decimals passed in via `decimals`.
1058
+ const resolveDecimals = ({ coinType, currency, fallback, }) => {
1059
+ if (!currency && coinType != null && coinType in COIN_DECIMALS) {
1060
+ return COIN_DECIMALS[coinType];
1061
+ }
1062
+ return fallback;
1063
+ };
1064
+ // Centered "Play Now / Play Later" intro modal. Renders + fires callbacks only;
1065
+ // the SDK does no fetching. `onPlayNow` should engage free-round mode + close,
1066
+ // `onPlayLater` should close and stay on paid play.
1067
+ const FreeRoundsIntroModal = ({ open, roundsRemaining, stakeCents, coinType, currency, decimals = 2, onPlayNow, onPlayLater, }) => {
1305
1068
  const handleClose = () => onPlayLater?.();
1306
- const coinLabel = resolveFreeRoundsCoinLabel({ coinType, currency });
1307
- const valueDecimals = resolveFreeRoundsDecimals({
1069
+ const currencyLabel = resolveCurrencyLabel({ coinType, currency });
1070
+ const stakeDecimals = resolveDecimals({
1308
1071
  coinType,
1309
1072
  currency,
1310
1073
  fallback: decimals,
1311
1074
  });
1312
- const perRoundDisplay = format(stakeCents / 100, valueDecimals);
1313
- const expiresInDays = computeExpiresInDays(expiresAt);
1314
- return (jsx(Dialog, { open: open, onClose: handleClose, className: styles$1.backdrop, children: jsx("div", { className: styles$1.column, children: jsxs(DialogPanel, { className: styles$1.panel, children: [jsx(DialogTitle, { className: styles$1.header, children: t("freeRoundsModalTitle") }), jsxs("div", { className: styles$1.summary, children: [t("freeRoundsHave"), " ", jsx("span", { className: styles$1.accent, children: roundsRemaining }), " ", roundsRemaining === 1
1315
- ? t("freeRoundsUnitOne")
1316
- : t("freeRoundsUnitOther")] }), jsx("div", { className: styles$1.perRound, children: t("freeRoundsPerRound", {
1317
- amount: `${perRoundDisplay}${coinLabel ? ` ${coinLabel}` : ""}`,
1318
- }) }), expiresInDays !== null && (jsx("div", { className: styles$1.expiry, children: t("freeRoundsExpiresIn", {
1319
- days: expiresInDays,
1320
- unit: expiresInDays === 1 ? t("dayUnitOne") : t("dayUnitOther"),
1321
- }) })), jsxs("div", { className: styles$1.actions, children: [jsx("button", { type: "button", className: `${styles$1.button} ${styles$1.playNow}`, onClick: onPlayNow, children: t("freeRoundsModalPlayNow") }), jsx("button", { type: "button", className: `${styles$1.button} ${styles$1.playLater}`, onClick: onPlayLater, children: t("freeRoundsModalPlayLater") })] })] }) }) }));
1075
+ const stakeDisplay = format(stakeCents / 100, stakeDecimals);
1076
+ return (jsx(Dialog, { open: open, onClose: handleClose, className: styles.backdrop, children: jsxs(DialogPanel, { className: styles.panel, children: [jsx(DialogTitle, { className: styles.title, children: "You have free rounds!" }), jsx("div", { className: styles.count, children: roundsRemaining }), jsxs("div", { className: styles.details, children: [roundsRemaining === 1 ? "free round" : "free rounds", " ready to play"] }), jsxs("div", { className: styles.stake, children: [stakeDisplay, currencyLabel ? ` ${currencyLabel}` : "", " per round"] }), jsxs("div", { className: styles.actions, children: [jsx("button", { type: "button", className: `${styles.button} ${styles.playNow}`, onClick: onPlayNow, children: "PLAY NOW" }), jsx("button", { type: "button", className: `${styles.button} ${styles.playLater}`, onClick: onPlayLater, children: "PLAY LATER" })] })] }) }));
1322
1077
  };
1323
1078
 
1324
1079
  const AutoManualPlayProvider = ({ children, config, }) => {
1325
- const t = useMemo(() => createTranslator(resolveLanguage(config.language)), [config.language]);
1326
1080
  const [mode, setMode] = useState(GAME_MODE.MANUAL);
1327
1081
  const [autoplayState, setAutoplayState] = useState(AUTO_PLAY_STATE.IDLE);
1328
1082
  const [doubleOrNothingState, setDoubleOrNothingState] = useState(DOUBLE_OR_NOTHING_STATE.IDLE);
@@ -1334,31 +1088,17 @@ const AutoManualPlayProvider = ({ children, config, }) => {
1334
1088
  const isControllerDisabled = config.playOptions.disabledController;
1335
1089
  const isGamePlaying = config.playOptions.isPlaying;
1336
1090
  const isAutoplayPlaying = autoplayState === AUTO_PLAY_STATE.PLAYING;
1337
- // Free rounds are MANUAL-only by default. While a grant is engaged the
1338
- // player must not be able to flip into autoplay (which would silently drop
1339
- // the manual-gated engaged state and let PAID autoplay start). This is
1340
- // independent of `mode` so it stays true even if a switch attempt slipped
1341
- // through. Games whose autoplay submission path funds rounds from engaged
1342
- // grants opt out of the lock via `supportsGrantFundedAutoplay`.
1343
- const freeRoundsRemaining = config.freeRoundsOptions?.roundsRemaining ?? 0;
1344
- const isFreeRoundsEngaged = Boolean(config.freeRoundsOptions?.engaged && freeRoundsRemaining > 0);
1345
- const supportsGrantFundedAutoplay = Boolean(config.freeRoundsOptions?.supportsGrantFundedAutoplay);
1346
- const isModeLockedByFreeRounds = isFreeRoundsEngaged && !supportsGrantFundedAutoplay;
1091
+ // Free rounds are MANUAL-only. While a grant is engaged the player must not
1092
+ // be able to flip into autoplay (which would silently drop the manual-gated
1093
+ // engaged state and let PAID autoplay start). This is independent of `mode`
1094
+ // so it stays true even if a switch attempt slipped through.
1095
+ const isFreeRoundsEngaged = Boolean(config.freeRoundsOptions?.engaged &&
1096
+ (config.freeRoundsOptions?.roundsRemaining ?? 0) > 0);
1347
1097
  const isSimplified = config.withoutRiskAndAutoplay === true;
1348
1098
  const isClassicGame = Boolean(config?.isClassicGame || config?.classicGame?.forceLeftStyle);
1349
1099
  const togglePlayAmountView = useCallback(() => {
1350
1100
  setDisplayPlayAmountView((v) => !v);
1351
1101
  }, []);
1352
- // Grant-funded autoplay: while free rounds are engaged the selectable
1353
- // autoplay count is hard-capped to the remaining grants (covers the default
1354
- // count and the ∞ option — Infinity clamps down to roundsRemaining, so ∞ is
1355
- // effectively disabled while engaged). No paid spillover.
1356
- useEffect(() => {
1357
- if (!supportsGrantFundedAutoplay || !isFreeRoundsEngaged) {
1358
- return;
1359
- }
1360
- setNumberOfPlays((prev) => prev > freeRoundsRemaining ? freeRoundsRemaining : prev);
1361
- }, [supportsGrantFundedAutoplay, isFreeRoundsEngaged, freeRoundsRemaining]);
1362
1102
  const startAutoplay = useCallback((numPlays) => {
1363
1103
  setMode(GAME_MODE.AUTOPLAY);
1364
1104
  setAutoplayState(AUTO_PLAY_STATE.SELECTING);
@@ -1398,15 +1138,12 @@ const AutoManualPlayProvider = ({ children, config, }) => {
1398
1138
  setSelection([]);
1399
1139
  }, []);
1400
1140
  const toggleMode = useCallback(() => {
1401
- // No-op while a free-round grant is engaged WITHOUT the game's
1402
- // grant-funded-autoplay opt-in: manual-only, no-exit. The player cannot
1403
- // switch to autoplay until the grant is consumed/released. When the game
1404
- // sets `supportsGrantFundedAutoplay`, engaged switching is allowed (still
1405
- // blocked while a round is in flight via the gates above).
1141
+ // No-op while a free-round grant is engaged: manual-only, no-exit. The
1142
+ // player cannot switch to autoplay until the grant is consumed/released.
1406
1143
  if (isGamePlaying ||
1407
1144
  isControllerDisabled ||
1408
1145
  isAutoplayPlaying ||
1409
- isModeLockedByFreeRounds) {
1146
+ isFreeRoundsEngaged) {
1410
1147
  return;
1411
1148
  }
1412
1149
  setNumberOfPlays(AUTOPLAY_DEFAULT_PLAY_ROUNDS_COUNT);
@@ -1415,7 +1152,7 @@ const AutoManualPlayProvider = ({ children, config, }) => {
1415
1152
  isAutoplayPlaying,
1416
1153
  isControllerDisabled,
1417
1154
  isGamePlaying,
1418
- isModeLockedByFreeRounds,
1155
+ isFreeRoundsEngaged,
1419
1156
  ]);
1420
1157
  const updateSelection = useCallback((values) => {
1421
1158
  if (!isAutoPlaying) {
@@ -1475,60 +1212,42 @@ const AutoManualPlayProvider = ({ children, config, }) => {
1475
1212
  displayPlayAmountView,
1476
1213
  togglePlayAmountView,
1477
1214
  ]);
1478
- return (jsx(I18nProvider, { language: config.language, children: jsxs(AutoManualPlayStateContext.Provider, { value: contextValue, children: [typeof children === "function" ? children(contextValue) : children, mode === GAME_MODE.MANUAL &&
1479
- config.freeRoundsOptions?.showIntroModal && (jsx("div", { style: {
1480
- "--play-panel-bg": hexToRgb(config.panel.bgColorHex ?? "#221062"),
1481
- }, children: jsx(FreeRoundsIntroModal, { open: config.freeRoundsOptions.showIntroModal, roundsRemaining: config.freeRoundsOptions.roundsRemaining, totalRounds: config.freeRoundsOptions.totalRounds, stakeCents: config.freeRoundsOptions.stakeCents, coinType: config.freeRoundsOptions.coinType, currency: config.freeRoundsOptions.currency, decimals: config.currencyOptions.current.decimals, expiresAt: config.freeRoundsOptions.expiresAt, onPlayNow: config.freeRoundsOptions.onPlayNow, onPlayLater: config.freeRoundsOptions.onPlayLater }) })), config.playOptions.displayController && (jsx("div", { className: cx$1(styles_ui.base, styles_ui.betForm, {
1482
- [styles_ui.baseForceLeft]: config.classicGame?.forceLeftStyle,
1483
- [styles_ui.isClassicGame]: config.isClassicGame,
1484
- [styles_ui.inActiveRound]: mode === GAME_MODE.MANUAL && isGamePlaying,
1485
- }), style: {
1486
- "--play-bottom": config.panel.bottom ?? 0,
1487
- "--play-panel-bg": hexToRgb(config.panel.bgColorHex ?? "#221062"),
1488
- "--play-dropdown-bg": hexToRgb(config.classicGame?.dropdown?.bgColorHex ??
1489
- config.dropdown.bgColorHex ??
1490
- "#150C47"),
1491
- "--play-panel-bg-opacity": config.panel.bgColorOpacity ?? 0.5,
1492
- }, children: jsxs("div", { className: cx$1(styles_ui.container, {
1493
- [styles_ui.containerSpread]: config.classicGame?.forceLeftStyle,
1494
- }), children: [jsxs("div", { className: cx$1(styles_ui.controls), children: [jsx(PlayValueList, {}), !isSimplified ? (jsxs("div", { className: cx$1(styles_ui.auto), children: [jsx(DifficultySelector, { playOptions: {
1495
- ...config.playOptions,
1496
- disabledMenu: isControllerDisabled ||
1497
- config.playOptions.disabledMenu,
1498
- }, dropdownConfig: config.dropdown, isClassicGame: isClassicGame }), jsx(InputWithSwitch, { value: numberOfPlays === Infinity ? 0 : numberOfPlays, type: "number", onChange: (e) => setNumberOfPlays(Number(e.currentTarget.value)), placeholder: t("numberOfPlays"), min: 0, max: 99, disabled: isControllerDisabled || mode === GAME_MODE.MANUAL, isClassicGame: isClassicGame, switcherConfig: {
1499
- onSwitch: toggleMode,
1500
- isPlaying: isAutoPlaying || isGamePlaying,
1501
- enabled: mode !== GAME_MODE.MANUAL,
1502
- disabled: isControllerDisabled ||
1503
- isAutoplayPlaying ||
1504
- isModeLockedByFreeRounds,
1505
- }, children: !isClassicGame && (jsx("span", { className: cx$1({
1506
- [styles_ui.disabled]: mode !== GAME_MODE.AUTOPLAY ||
1507
- numberOfPlays !== Infinity ||
1508
- isAutoplayPlaying,
1509
- }), children: "\u221E" })) })] })) : null, mode === GAME_MODE.MANUAL ? (jsx(ManualPlayController, {})) : (jsx(AutoPlayController, {}))] }), isFreeRoundsEngaged && (jsxs("div", { className: styles_ui.freeRoundsFooter, children: [jsx("span", { className: styles_ui.freeRoundsFooterCount, children: config.freeRoundsOptions?.roundsRemaining }), " ", config.freeRoundsOptions?.roundsRemaining === 1
1510
- ? t("freeRoundLeftOne")
1511
- : t("freeRoundLeftOther")] })), jsx(WidgetContainer, { state: autoplayState, displayPlayAmountView: displayPlayAmountView, widgets: {
1512
- left: config.leftWidgets,
1513
- center: config.centerWidgets,
1514
- right: config.rightWidgets,
1515
- } })] }) }))] }) }));
1516
- };
1517
-
1518
- var styles = {"backdrop":"FreeRoundsSummaryModal-module__backdrop___tNi3I","panel":"FreeRoundsSummaryModal-module__panel___2MIEp","header":"FreeRoundsSummaryModal-module__header___-BkpP","summary":"FreeRoundsSummaryModal-module__summary___YExX9","accent":"FreeRoundsSummaryModal-module__accent___z9aBF","hint":"FreeRoundsSummaryModal-module__hint___CYWqA","close":"FreeRoundsSummaryModal-module__close___4-irR"};
1519
-
1520
- // END pop-up — shown by the game after the last free round settles. The game
1521
- // owns visibility: it mounts open when the play-through completes, unmount
1522
- // onClose.
1523
- const FreeRoundsSummaryModal = ({ totalWinAmount, coin, roundsPlayed, decimals, onClose, }) => {
1524
- const { t } = useTranslation();
1525
- // Shared monetary formatter — truncates (never rounds up) so the summary
1526
- // can't display more winnings than were actually credited.
1527
- const winDisplay = format(totalWinAmount, decimals ?? resolveCoinLabelDecimals(coin));
1528
- return (jsx(Dialog, { open: true, onClose: onClose, className: styles.backdrop, children: jsxs(DialogPanel, { className: styles.panel, children: [jsx(DialogTitle, { className: styles.header, children: t("freeRoundsModalTitle") }), jsxs("div", { className: styles.summary, children: [t("freeRoundsSummaryWon"), " ", jsxs("span", { className: styles.accent, children: [winDisplay, " ", coin] }), " ", t("freeRoundsSummaryIn"), " ", roundsPlayed, " ", roundsPlayed === 1
1529
- ? t("freeRoundsSummaryUnitOne")
1530
- : t("freeRoundsSummaryUnitOther")] }), jsx("div", { className: styles.hint, children: t("freeRoundsSummaryCredited") }), jsx("button", { type: "button", className: styles.close, onClick: onClose, children: t("freeRoundsSummaryClose") })] }) }));
1215
+ return (jsxs(AutoManualPlayStateContext.Provider, { value: contextValue, children: [typeof children === "function" ? children(contextValue) : children, mode === GAME_MODE.MANUAL &&
1216
+ config.freeRoundsOptions?.showIntroModal && (jsx("div", { style: {
1217
+ "--play-panel-bg": hexToRgb(config.panel.bgColorHex ?? "#01243A"),
1218
+ }, children: jsx(FreeRoundsIntroModal, { open: config.freeRoundsOptions.showIntroModal, roundsRemaining: config.freeRoundsOptions.roundsRemaining, totalRounds: config.freeRoundsOptions.totalRounds, stakeCents: config.freeRoundsOptions.stakeCents, coinType: config.freeRoundsOptions.coinType, currency: config.freeRoundsOptions.currency, decimals: config.currencyOptions.current.decimals, onPlayNow: config.freeRoundsOptions.onPlayNow, onPlayLater: config.freeRoundsOptions.onPlayLater }) })), config.playOptions.displayController && (jsx("div", { className: cx$1(styles_ui.base, styles_ui.betForm, {
1219
+ [styles_ui.baseForceLeft]: config.classicGame?.forceLeftStyle,
1220
+ [styles_ui.isClassicGame]: config.isClassicGame,
1221
+ [styles_ui.inActiveRound]: mode === GAME_MODE.MANUAL && isGamePlaying,
1222
+ }), style: {
1223
+ "--play-bottom": config.panel.bottom ?? 0,
1224
+ "--play-panel-bg": hexToRgb(config.panel.bgColorHex ?? "#01243A"),
1225
+ "--play-dropdown-bg": hexToRgb(config.classicGame?.dropdown?.bgColorHex ??
1226
+ config.dropdown.bgColorHex ??
1227
+ "#01243A"),
1228
+ "--play-panel-bg-opacity": config.panel.bgColorOpacity ?? 0.5,
1229
+ }, children: jsxs("div", { className: cx$1(styles_ui.container, {
1230
+ [styles_ui.containerSpread]: config.classicGame?.forceLeftStyle,
1231
+ }), children: [jsxs("div", { className: cx$1(styles_ui.controls), children: [jsx(PlayValueList, {}), !isSimplified ? (jsxs("div", { className: cx$1(styles_ui.auto), children: [jsx(DifficultySelector, { playOptions: {
1232
+ ...config.playOptions,
1233
+ disabledMenu: isControllerDisabled || config.playOptions.disabledMenu,
1234
+ }, dropdownConfig: config.dropdown, isClassicGame: isClassicGame }), jsx(InputWithSwitch, { value: numberOfPlays === Infinity ? 0 : numberOfPlays, type: "number", onChange: (e) => setNumberOfPlays(Number(e.currentTarget.value)), placeholder: "Number of Plays", min: 0, max: 99, disabled: isControllerDisabled || mode === GAME_MODE.MANUAL, isClassicGame: isClassicGame, switcherConfig: {
1235
+ onSwitch: toggleMode,
1236
+ isPlaying: isAutoPlaying || isGamePlaying,
1237
+ enabled: mode !== GAME_MODE.MANUAL,
1238
+ disabled: isControllerDisabled ||
1239
+ isAutoplayPlaying ||
1240
+ isFreeRoundsEngaged,
1241
+ }, children: !isClassicGame && (jsx("span", { className: cx$1({
1242
+ [styles_ui.disabled]: mode !== GAME_MODE.AUTOPLAY ||
1243
+ numberOfPlays !== Infinity ||
1244
+ isAutoplayPlaying,
1245
+ }), children: "\u221E" })) })] })) : null, mode === GAME_MODE.MANUAL ? (jsx(ManualPlayController, {})) : (jsx(AutoPlayController, {}))] }), jsx(WidgetContainer, { state: autoplayState, displayPlayAmountView: displayPlayAmountView, widgets: {
1246
+ left: config.leftWidgets,
1247
+ center: config.centerWidgets,
1248
+ right: config.rightWidgets,
1249
+ } })] }) }))] }));
1531
1250
  };
1532
1251
 
1533
- export { AUTO_PLAY_STATE, AutoManualPlayProvider, DEFAULT_RISK_COLORS, DOUBLE_OR_NOTHING_STATE, FreeRoundsIntroModal, FreeRoundsSummaryModal, GAME_MODE, I18nProvider, WIDGET, canAutoplaySpaceTrigger, createAutoplayButtonAction, createManualCashoutClickHandler, createTranslator, format, isAutoplayButtonDisabled, isManualCashoutDisabled, useTranslation };
1252
+ export { AUTO_PLAY_STATE, AutoManualPlayProvider, DOUBLE_OR_NOTHING_STATE, FreeRoundsIntroModal, GAME_MODE, WIDGET, canAutoplaySpaceTrigger, createAutoplayButtonAction, createManualCashoutClickHandler, format, isAutoplayButtonDisabled, isManualCashoutDisabled };
1534
1253
  //# sourceMappingURL=index.mjs.map