@enigma-lake/tower-play-controller-sdk 2.0.24 → 2.0.25

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.
Files changed (28) hide show
  1. package/dist/components/base/DifficultySelector/DifficultySelector.d.ts +4 -7
  2. package/dist/components/base/DifficultySelector/Selector.d.ts +1 -1
  3. package/dist/components/base/DifficultySelector/types.d.ts +3 -0
  4. package/dist/components/base/FreeRoundsIntroModal/FreeRoundsIntroModal.d.ts +14 -0
  5. package/dist/components/base/FreeRoundsIntroModal/freeRoundsCoin.d.ts +11 -0
  6. package/dist/components/base/FreeRoundsIntroModal/index.d.ts +3 -0
  7. package/dist/components/base/FreeRoundsSummaryModal/FreeRoundsSummaryModal.d.ts +9 -0
  8. package/dist/components/base/FreeRoundsSummaryModal/index.d.ts +3 -0
  9. package/dist/components/base/GroupRow/GroupRow.d.ts +2 -1
  10. package/dist/components/base/Input/Input.d.ts +3 -1
  11. package/dist/components/base/InputWithSwitch/InputWithSwitch.d.ts +2 -1
  12. package/dist/components/base/PlayController/PlayController.d.ts +2 -1
  13. package/dist/components/base/PlayValueInput/PlayValueInput.d.ts +2 -1
  14. package/dist/components/hooks/usePlayController.d.ts +8 -0
  15. package/dist/components/hooks/useSpacebarButtonTrigger.d.ts +2 -1
  16. package/dist/i18n/I18nContext.d.ts +13 -0
  17. package/dist/i18n/index.d.ts +5 -0
  18. package/dist/i18n/locales/en.d.ts +2 -0
  19. package/dist/i18n/locales/zh-CN.d.ts +2 -0
  20. package/dist/i18n/translator.d.ts +5 -0
  21. package/dist/i18n/types.d.ts +41 -0
  22. package/dist/index.d.ts +132 -11
  23. package/dist/index.mjs +594 -115
  24. package/dist/index.mjs.map +1 -1
  25. package/dist/style.css +1 -1
  26. package/dist/types/index.d.ts +2 -2
  27. package/dist/types/playController.d.ts +52 -9
  28. package/package.json +9 -2
package/dist/index.mjs CHANGED
@@ -1,8 +1,9 @@
1
1
  import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
2
- import React, { createContext, useContext, useRef, useCallback, useEffect, useState, useMemo, Fragment as Fragment$1 } from 'react';
3
- import { Currency, sendSetUserCurrencyEvent, sendSetUserCurrencyEventV2, setToggleGameWidgetsVisibility, setToggleGameWidgetsExpansion, getUserInformationEvent, EVENTS, sendOpenHighPlayModalEvent, setTogglePlayAmountView } from '@enigma-lake/zoot-platform-sdk';
2
+ import React, { createContext, useContext, useMemo, useRef, useEffect, useCallback, useState, Fragment as Fragment$1 } from 'react';
4
3
  import cx$1 from 'classnames';
5
- import { Menu, MenuButton, MenuItems, MenuItem, Switch as Switch$1 } from '@headlessui/react';
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';
6
+ import { Menu, MenuButton, MenuItems, MenuItem, Switch as Switch$1, Dialog, DialogPanel, DialogTitle } from '@headlessui/react';
6
7
 
7
8
  const hexToRgb = (hex) => {
8
9
  const shorthandRegex = /^#?([a-f\d])([a-f\d])([a-f\d])$/i;
@@ -102,6 +103,23 @@ const createAutoplayButtonAction = ({ state, cashoutDisabled, onStopPlay, onPlay
102
103
  };
103
104
  };
104
105
 
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
+
105
123
  const AutoManualPlayStateContext = createContext(undefined);
106
124
  const useAutoManualPlayState = () => {
107
125
  const context = useContext(AutoManualPlayStateContext);
@@ -111,6 +129,104 @@ const useAutoManualPlayState = () => {
111
129
  return context;
112
130
  };
113
131
 
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
+
114
230
  const getNextPlayAmount = ({ currentAmount, defaultBetOptions, direction, }) => {
115
231
  if (!defaultBetOptions.length) {
116
232
  return currentAmount;
@@ -141,7 +257,8 @@ function isFullMode(config) {
141
257
  return config.withoutRiskAndAutoplay !== true;
142
258
  }
143
259
  const usePlayController = () => {
144
- const { config, autoPlay: { setNumberOfPlays, numberOfPlays, setPlayedRounds, playedRounds, selection, setState, state, }, playValues: { displayPlayAmountView, setDisplayPlayAmountView, togglePlayAmountView, }, } = useAutoManualPlayState();
260
+ const { t } = useTranslation();
261
+ const { config, mode, autoPlay: { setNumberOfPlays, numberOfPlays, setPlayedRounds, playedRounds, selection, setState, state, }, playValues: { displayPlayAmountView, setDisplayPlayAmountView, togglePlayAmountView, }, } = useAutoManualPlayState();
145
262
  const { current } = config.currencyOptions;
146
263
  const { isPlaying, canCashout, disabledCashout = false, disabledController, playHook, totalBalance, } = config.playOptions;
147
264
  // --- Autoplay-related values only valid in full mode ---
@@ -152,11 +269,65 @@ const usePlayController = () => {
152
269
  const autoPlayDelay = autoplayEnabled
153
270
  ? (config.playOptions.autoPlayDelay ?? 1500)
154
271
  : 1500;
155
- const { playAmount = 0, playLimits, setPlayAmount = () => { }, } = playHook?.() ?? {};
272
+ const { playAmount: rawPlayAmount = 0, playLimits, setPlayAmount: rawSetPlayAmount = () => { }, } = playHook?.() ?? {};
156
273
  const minPlayAmount = playLimits?.[current.code]?.limits.min ?? 0;
157
274
  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.
284
+ const isManualMode = mode === GAME_MODE.MANUAL;
285
+ const freeRoundsOptions = config.freeRoundsOptions;
286
+ const supportsGrantFundedAutoplay = Boolean(freeRoundsOptions?.supportsGrantFundedAutoplay);
287
+ const isFreeRoundsEngaged = Boolean((isManualMode || supportsGrantFundedAutoplay) &&
288
+ freeRoundsOptions?.engaged &&
289
+ freeRoundsOptions.roundsRemaining > 0);
290
+ // playAmount is in display units everywhere in this SDK (e.g. 1.00), while the
291
+ // grant stake is in cents, so convert. When engaged, setPlayAmount is a no-op
292
+ // (the stake is locked to the grant).
293
+ const playAmount = isFreeRoundsEngaged
294
+ ? freeRoundsOptions.stakeCents /
295
+ 100
296
+ : rawPlayAmount;
297
+ const setPlayAmount = isFreeRoundsEngaged
298
+ ? () => {
299
+ // locked to the free-round grant stake (no-op setter)
300
+ }
301
+ : rawSetPlayAmount;
158
302
  const playIntervalRef = useRef(null);
159
303
  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
+ });
160
331
  const stopAutoplay = () => {
161
332
  isAutoplayActiveRef.current = false;
162
333
  if (playIntervalRef.current) {
@@ -176,6 +347,35 @@ const usePlayController = () => {
176
347
  if (!isAutoplayActiveRef.current || !autoplayEnabled) {
177
348
  return;
178
349
  }
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
+ }
179
379
  if (remainingPlays < 1 || numberOfPlays === 0) {
180
380
  setNumberOfPlays(AUTOPLAY_DEFAULT_PLAY_ROUNDS_COUNT);
181
381
  stopAutoplay();
@@ -183,6 +383,7 @@ const usePlayController = () => {
183
383
  }
184
384
  setPlayedRounds(currentPlayedRounds + 1);
185
385
  setNumberOfPlays((prev) => Math.max(prev - 1, 0));
386
+ session.roundsSubmitted += 1;
186
387
  // onAutoPlay exists only in full mode (guarded by autoplayEnabled)
187
388
  config.onAutoPlay(selection, () => {
188
389
  if (!isAutoplayActiveRef.current) {
@@ -200,13 +401,32 @@ const usePlayController = () => {
200
401
  if (selection.length === 0) {
201
402
  showAutoPlayToast?.({
202
403
  type: "info",
203
- message: "Please select at least one tile to start autoplay.",
404
+ message: t("selectTileToast"),
204
405
  });
205
406
  return;
206
407
  }
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
+ };
207
427
  isAutoplayActiveRef.current = true;
208
428
  setState(AUTO_PLAY_STATE.PLAYING);
209
- loopRounds(playedRounds, numberOfPlays);
429
+ loopRounds(playedRounds, effectiveNumberOfPlays);
210
430
  };
211
431
  const adjustPlayAmount = ({ direction }) => {
212
432
  if (isDisabled()) {
@@ -229,7 +449,8 @@ const usePlayController = () => {
229
449
  }
230
450
  setPlayAmount(playValue);
231
451
  };
232
- const isValidPlayAmount = playAmount >= minPlayAmount && playAmount <= maxPlayAmount;
452
+ const isValidPlayAmount = isFreeRoundsEngaged ||
453
+ (playAmount >= minPlayAmount && playAmount <= maxPlayAmount);
233
454
  return {
234
455
  // amounts
235
456
  playAmount,
@@ -263,6 +484,16 @@ const usePlayController = () => {
263
484
  setDisplayPlayAmountView,
264
485
  togglePlayAmountView,
265
486
  },
487
+ // free rounds (binary engagement via the intro modal; manual-mode only
488
+ // unless the game opts into grant-funded autoplay)
489
+ freeRounds: {
490
+ isEngaged: isFreeRoundsEngaged,
491
+ roundsRemaining: freeRoundsOptions?.roundsRemaining ?? 0,
492
+ totalRounds: freeRoundsOptions?.totalRounds ?? 0,
493
+ stakeCents: freeRoundsOptions?.stakeCents ?? 0,
494
+ coinType: freeRoundsOptions?.coinType,
495
+ currency: freeRoundsOptions?.currency,
496
+ },
266
497
  };
267
498
  };
268
499
 
@@ -280,10 +511,12 @@ const Button = ({ disabled, roleType, className = "", theme = "primary", ...prop
280
511
  };
281
512
  Button.themes = themes;
282
513
 
283
- var styles_group = {"base":"GroupRow-module__base___b241-","label":"GroupRow-module__label___Du57P","group":"GroupRow-module__group___V7xa6","groupItem":"GroupRow-module__groupItem___yNSX8","refreshButton":"GroupRow-module__refreshButton___m61F8","x2":"GroupRow-module__x2___9bLae","last":"GroupRow-module__last___ArsSn"};
514
+ var styles_group = {"base":"GroupRow-module__base___b241-","label":"GroupRow-module__label___Du57P","group":"GroupRow-module__group___V7xa6","groupItem":"GroupRow-module__groupItem___yNSX8","refreshButton":"GroupRow-module__refreshButton___m61F8","x2":"GroupRow-module__x2___9bLae","last":"GroupRow-module__last___ArsSn","isClassicGame":"GroupRow-module__isClassicGame___m9r03"};
284
515
 
285
- const GroupRow = ({ children, label, className, ...restProps }) => {
286
- return (jsxs("div", { ...restProps, className: cx$1(styles_group.base, className), children: [label && jsx("div", { className: styles_group.label, children: label }), jsx("div", { className: styles_group.group, children: children })] }));
516
+ const GroupRow = ({ children, label, className, isClassicGame = false, ...restProps }) => {
517
+ return (jsxs("div", { ...restProps, className: cx$1(styles_group.base, className, {
518
+ [styles_group.isClassicGame]: isClassicGame,
519
+ }), children: [label && jsx("div", { className: styles_group.label, children: label }), jsx("div", { className: styles_group.group, children: children })] }));
287
520
  };
288
521
 
289
522
  var style_select$1 = {"button":"SelectMenu-module__button___N-HeB","menu":"SelectMenu-module__menu___c5jvZ","menuItems":"SelectMenu-module__menuItems___BX1YQ","menuItem":"SelectMenu-module__menuItem___eXbi3","selected":"SelectMenu-module__selected___XyADw","disabledSVG":"SelectMenu-module__disabledSVG___OdVFW","disabled":"SelectMenu-module__disabled___jiTPl"};
@@ -315,6 +548,15 @@ const getIcon = (option) => {
315
548
  const SelectMenu = ({ disabled = false }) => {
316
549
  const { config } = useAutoManualPlayState();
317
550
  const { current, available } = config.currencyOptions;
551
+ useEffect(() => {
552
+ const hex = config?.classicGame?.dropdown?.bgColorHex ??
553
+ config?.dropdown?.bgColorHex ??
554
+ "#01243A";
555
+ const v = hexToRgb(hex);
556
+ if (v) {
557
+ document.documentElement.style.setProperty("--play-dropdown-bg", v);
558
+ }
559
+ }, [config?.classicGame?.dropdown?.bgColorHex, config?.dropdown?.bgColorHex]);
318
560
  const handleClick = (e) => {
319
561
  if (disabled) {
320
562
  return;
@@ -344,39 +586,117 @@ const SelectMenu = ({ disabled = false }) => {
344
586
  }) })] })) }));
345
587
  };
346
588
 
347
- var styles$4 = {"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"};
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"};
348
590
 
349
- const PlayValueInput = ({ disabled, children, className, value, onClick, }) => {
591
+ const PlayValueInput = ({ disabled, children, className, value, onClick, isClassicGame = false, }) => {
592
+ const { t } = useTranslation();
350
593
  const handleOnClick = () => {
351
594
  if (disabled) {
352
595
  return;
353
596
  }
354
597
  onClick();
355
598
  };
356
- return (jsxs("div", { className: cx$1(styles$4.base, className, {
357
- [styles$4.disabled]: disabled,
358
- }), children: [children && jsx("div", { className: cx$1(styles$4.icon), children: children }), jsxs("div", { className: cx$1(styles$4.inputContainer), onClick: (e) => {
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) => {
359
603
  e.preventDefault();
360
604
  handleOnClick();
361
- }, children: [jsx("span", { className: styles$4.label, children: "Play amount" }), jsx("div", { className: cx$1(styles$4.playValueInput, {
362
- [styles$4.playValueInputWithLabel]: true,
605
+ }, children: [jsx("span", { className: styles$6.label, children: t("playAmountLabel") }), jsx("div", { className: cx$1(styles$6.playValueInput, {
606
+ [styles$6.playValueInputWithLabel]: true,
363
607
  }), children: value })] })] }));
364
608
  };
365
609
 
366
- const PlayAmountControl = ({ isDisabled, forceRefresh, }) => {
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
+ const PlayAmountControl = ({ isDisabled, forceRefresh, lockCurrency = false, }) => {
367
669
  const { config } = useAutoManualPlayState();
368
- const { playAmount, adjustPlayAmount, playValues: { togglePlayAmountView }, autoPlay: { state }, } = usePlayController();
670
+ const { playAmount, adjustPlayAmount, playValues: { togglePlayAmountView }, autoPlay: { state }, freeRounds, } = usePlayController();
671
+ 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;
369
688
  const handleTogglePlayAmount = () => {
370
689
  setToggleGameWidgetsVisibility();
371
690
  togglePlayAmountView();
372
691
  setToggleGameWidgetsExpansion();
373
692
  };
374
- return (jsxs(GroupRow, { children: [jsx(PlayValueInput, { className: styles_group.groupItem, value: `${format(playAmount, config.currencyOptions.current.decimals)} ${config.currencyOptions.current.abbr}`, disabled: isDisabled() ||
693
+ return (jsxs(GroupRow, { isClassicGame: isClassicGame, children: [jsx(PlayValueInput, { className: styles_group.groupItem, value: `${format(playAmount, valueDecimals)} ${coinLabel}`, disabled: isDisabled() ||
375
694
  config.playOptions.disableInput ||
376
695
  config.playOptions.disabledController ||
377
- state === AUTO_PLAY_STATE.PLAYING, onClick: handleTogglePlayAmount, children: jsx(SelectMenu, { disabled: isDisabled() }) }), 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: () => {
696
+ 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: () => {
378
698
  forceRefresh();
379
- }, "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: "+" }) })] }));
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: "+" }) })] }));
380
700
  };
381
701
 
382
702
  const getDefaultButtonClassName = (currencyCode, styles) => {
@@ -398,38 +718,56 @@ const getActiveButtonClassName = (currencyCode, styles) => {
398
718
  return styles.buttonDefault__active;
399
719
  };
400
720
 
401
- var AUTOPLAY_LABEL;
402
- (function (AUTOPLAY_LABEL) {
403
- AUTOPLAY_LABEL["START"] = "START AUTOPLAY";
404
- AUTOPLAY_LABEL["STOP"] = "STOP AUTOPLAY";
405
- })(AUTOPLAY_LABEL || (AUTOPLAY_LABEL = {}));
406
-
407
- const useSpacebarButtonTrigger = ({ roleButton, activeClassName, canTrigger, }) => {
721
+ // 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.
726
+ const isOtherInteractiveControlFocused = (button) => {
727
+ const active = document.activeElement;
728
+ return Boolean(active &&
729
+ active !== button &&
730
+ typeof active.matches === "function" &&
731
+ active.matches("button, a, input, select, textarea"));
732
+ };
733
+ const useSpacebarButtonTrigger = ({ roleButton, activeClassName, canTrigger, suppressed = false, }) => {
408
734
  const handleKeyPress = useCallback((event) => {
409
735
  if (event.code !== "Space") {
410
736
  return;
411
737
  }
738
+ if (suppressed) {
739
+ return;
740
+ }
412
741
  const button = selectButton(roleButton);
413
742
  if (!button || !canTrigger()) {
414
743
  return;
415
744
  }
745
+ if (isOtherInteractiveControlFocused(button)) {
746
+ return;
747
+ }
416
748
  event.preventDefault();
417
749
  event.stopPropagation();
418
750
  addPressedClass(roleButton, activeClassName);
419
751
  button.click();
420
- }, [activeClassName, canTrigger, roleButton]);
752
+ }, [activeClassName, canTrigger, roleButton, suppressed]);
421
753
  const handleKeyUp = useCallback((event) => {
422
754
  if (event.code !== "Space") {
423
755
  return;
424
756
  }
757
+ if (suppressed) {
758
+ return;
759
+ }
425
760
  const button = selectButton(roleButton);
426
761
  if (!button) {
427
762
  return;
428
763
  }
764
+ if (isOtherInteractiveControlFocused(button)) {
765
+ return;
766
+ }
429
767
  event.preventDefault();
430
768
  event.stopPropagation();
431
769
  removePressedClass(roleButton, activeClassName);
432
- }, [activeClassName, roleButton]);
770
+ }, [activeClassName, roleButton, suppressed]);
433
771
  useEffect(() => {
434
772
  window.addEventListener("keydown", handleKeyPress, true);
435
773
  window.addEventListener("keyup", handleKeyUp, true);
@@ -442,20 +780,29 @@ const useSpacebarButtonTrigger = ({ roleButton, activeClassName, canTrigger, })
442
780
 
443
781
  const AutoPlayController = () => {
444
782
  const { config } = useAutoManualPlayState();
783
+ const { t } = useTranslation();
445
784
  const [forceRefresh, setForceRefresh] = useState(false);
446
- const { isValidPlayAmount, playValues: { displayPlayAmountView, togglePlayAmountView }, autoPlay: { isDisabled, state, onPlay, onStopPlay, cashoutDisabled }, } = usePlayController();
785
+ const { isValidPlayAmount, freeRounds, playValues: { displayPlayAmountView, togglePlayAmountView }, autoPlay: { isDisabled, state, onPlay, onStopPlay, cashoutDisabled }, } = usePlayController();
447
786
  const { current } = config.currencyOptions;
448
787
  const roleButton = GAME_MODE.AUTOPLAY;
449
- const buttonLabel = state === AUTO_PLAY_STATE.PLAYING
450
- ? AUTOPLAY_LABEL.STOP
451
- : AUTOPLAY_LABEL.START;
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
+ }
452
797
  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.
453
800
  const defaultClassName = useMemo(() => {
454
- if (buttonLabel === AUTOPLAY_LABEL.STOP) {
801
+ if (isStopState) {
455
802
  return styles_button.buttonCashout;
456
803
  }
457
804
  return getDefaultButtonClassName(current.code, styles_button);
458
- }, [buttonLabel, current.code]);
805
+ }, [isStopState, current.code]);
459
806
  useSpacebarButtonTrigger({
460
807
  roleButton,
461
808
  activeClassName,
@@ -464,6 +811,10 @@ const AutoPlayController = () => {
464
811
  cashoutDisabled,
465
812
  isDisabled: isDisabled(),
466
813
  }),
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),
467
818
  });
468
819
  const isButtonDisabled = isAutoplayButtonDisabled({
469
820
  state,
@@ -488,7 +839,7 @@ const AutoPlayController = () => {
488
839
  }, [togglePlayAmountView]);
489
840
  const renderButton = useMemo(() => {
490
841
  if (displayPlayAmountView) {
491
- return (jsx(Button, { className: styles_button.buttonPlayAmount, onClick: handleTogglePlayAmount, roleType: roleButton, children: "SELECT PLAY AMOUNT" }));
842
+ return (jsx(Button, { className: styles_button.buttonPlayAmount, onClick: handleTogglePlayAmount, roleType: roleButton, children: t("selectPlayAmount") }));
492
843
  }
493
844
  return (jsx(Button, { disabled: isButtonDisabled, className: defaultClassName, onClick: buttonAction, roleType: roleButton, children: buttonLabel }));
494
845
  }, [
@@ -499,6 +850,7 @@ const AutoPlayController = () => {
499
850
  handleTogglePlayAmount,
500
851
  isButtonDisabled,
501
852
  roleButton,
853
+ t,
502
854
  ]);
503
855
  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] }));
504
856
  };
@@ -527,9 +879,10 @@ const useIsRunningExternal = () => {
527
879
 
528
880
  const ManualPlayController = () => {
529
881
  const { config, doubleOrNothing } = useAutoManualPlayState();
882
+ const { t } = useTranslation();
530
883
  const { isRunningExternal } = useIsRunningExternal();
531
884
  const [forceRefresh, setForceRefresh] = useState(false);
532
- const { isValidPlayAmount, playValues: { displayPlayAmountView, togglePlayAmountView }, manualPlay: { isDisabled, onPlay, canCashout, cashoutDisabled }, } = usePlayController();
885
+ const { isValidPlayAmount, playValues: { displayPlayAmountView, togglePlayAmountView }, manualPlay: { isDisabled, onPlay, canCashout, cashoutDisabled }, freeRounds, } = usePlayController();
533
886
  const { current } = config.currencyOptions;
534
887
  const roleButton = GAME_MODE.MANUAL;
535
888
  const activeClassName = getActiveButtonClassName(current.code, styles_button);
@@ -538,6 +891,10 @@ const ManualPlayController = () => {
538
891
  roleButton,
539
892
  activeClassName,
540
893
  canTrigger: () => !isDisabled(),
894
+ // Suppress the global Space-to-play shortcut while the free-rounds intro
895
+ // modal is open so Space belongs to the modal's PLAY NOW / PLAY LATER
896
+ // buttons instead of firing a paid play behind it.
897
+ suppressed: Boolean(config.freeRoundsOptions?.showIntroModal),
541
898
  });
542
899
  const isButtonDisabled = isDisabled() || !isValidPlayAmount;
543
900
  const handleTogglePlayAmount = useCallback(() => {
@@ -572,7 +929,7 @@ const ManualPlayController = () => {
572
929
  }), [cashoutDisabled, config.onCashout]);
573
930
  const renderButton = useMemo(() => {
574
931
  if (displayPlayAmountView) {
575
- return (jsx(Button, { className: styles_button.buttonPlayAmount, onClick: handleTogglePlayAmount, roleType: roleButton, children: "SELECT PLAY AMOUNT" }));
932
+ return (jsx(Button, { className: styles_button.buttonPlayAmount, onClick: handleTogglePlayAmount, roleType: roleButton, children: t("selectPlayAmount") }));
576
933
  }
577
934
  if (canCashout) {
578
935
  const isCashoutDisabled = isManualCashoutDisabled({
@@ -586,16 +943,29 @@ const ManualPlayController = () => {
586
943
  doubleOrNothing.state === DOUBLE_OR_NOTHING_STATE.PROGRESS;
587
944
  return (jsxs("div", { className: styles_button.buttonRow, children: [jsxs(Button, { disabled: isCashoutDisabled, className: cx$1(styles_button.buttonCashout, {
588
945
  [styles_button.buttonRow__cashout]: showDoubleOrNothing,
589
- }), 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, {
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, {
590
947
  [styles_button.buttonDON__disabled]: isDoubleOrNothingDisabled,
591
- }), 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] }));
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;
592
955
  }
593
- return (jsx(Button, { disabled: isButtonDisabled, className: defaultClassName, onClick: onPlay, roleType: roleButton, children: "PLAY NOW" }));
956
+ // 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).
959
+ const playNowText = freeRounds.isEngaged
960
+ ? t("playFreeRound")
961
+ : t("playNow");
962
+ return (jsx(Button, { disabled: isButtonDisabled, className: defaultClassName, onClick: onPlay, roleType: roleButton, children: playNowText }));
594
963
  }, [
595
964
  canCashout,
596
965
  cashoutDisabled,
597
966
  config.doubleOrNothing?.disabled,
598
967
  config.doubleOrNothing?.display,
968
+ config.freeRoundsOptions?.autoplayActive,
599
969
  config.playOptions.disabledController,
600
970
  config.playOptions.isPlaying,
601
971
  current.abbr,
@@ -604,6 +974,7 @@ const ManualPlayController = () => {
604
974
  defaultClassName,
605
975
  displayPlayAmountView,
606
976
  doubleOrNothing.state,
977
+ freeRounds.isEngaged,
607
978
  handleDoubleOrNothingClick,
608
979
  handleCashoutClick,
609
980
  handleTogglePlayAmount,
@@ -611,18 +982,20 @@ const ManualPlayController = () => {
611
982
  isRunningExternal,
612
983
  onPlay,
613
984
  roleButton,
985
+ t,
614
986
  ]);
615
- return (jsxs(Fragment, { children: [jsx(PlayAmountControl, { isDisabled: isDisabled || displayPlayAmountView, forceRefresh: () => setForceRefresh(!forceRefresh), "data-force-refresh": `mini-play-amount-control-data-${forceRefresh.toString()}` }, `mini-play-amount-control-key-${forceRefresh.toString()}`), renderButton] }));
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] }));
616
988
  };
617
989
 
618
- var styles_ui = {"base":"UI-module__base___wThyQ","container":"UI-module__container___gMSiK","betForm":"UI-module__betForm___hQkYd","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"};
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"};
619
991
 
620
992
  var style_difficulty = {"base":"DifficultySelector-module__base___lDaQ-"};
621
993
 
622
- 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"};
994
+ 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"};
623
995
 
624
996
  const capitalize = (str) => str.charAt(0).toUpperCase() + str.slice(1).toLowerCase();
625
- const Selector = ({ currentValue, label, riskColor, values, onSelect, disabled, }) => {
997
+ const Selector = ({ currentValue, label, riskColor, values, onSelect, disabled, isClassicGame = false, formatDisplay, }) => {
998
+ const display = (value) => formatDisplay ? formatDisplay(value) : capitalize(String(value));
626
999
  const handleClick = (e) => {
627
1000
  if (disabled) {
628
1001
  return;
@@ -635,31 +1008,28 @@ const Selector = ({ currentValue, label, riskColor, values, onSelect, disabled,
635
1008
  onSelect(value);
636
1009
  };
637
1010
  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, {
638
- [style_select.disabled]: disabled,
639
- }), 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, {
1011
+ [style_select.disabled]: disabled && !isClassicGame,
1012
+ [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, {
640
1014
  [style_select.selected]: String(currentValue) === String(value),
641
- }), children: capitalize(String(value)) }, `element-${value}`))) })] })) })] }));
1015
+ }), children: display(value) }, `element-${value}`))) })] })) })] }));
642
1016
  };
643
1017
 
644
- var SELECTORS;
645
- (function (SELECTORS) {
646
- SELECTORS["RISK"] = "Risk";
647
- })(SELECTORS || (SELECTORS = {}));
648
- var RiskTypes;
649
- (function (RiskTypes) {
650
- RiskTypes["LOW"] = "LOW";
651
- RiskTypes["MEDIUM"] = "MEDIUM";
652
- RiskTypes["HIGH"] = "HIGH";
653
- })(RiskTypes || (RiskTypes = {}));
654
-
655
- const DifficultySelector = ({ playOptions, dropdownConfig, }) => {
1018
+ const RISK_LABEL_KEY = {
1019
+ [RiskTypes.LOW]: "riskLow",
1020
+ [RiskTypes.MEDIUM]: "riskMedium",
1021
+ [RiskTypes.HIGH]: "riskHigh",
1022
+ };
1023
+ const DifficultySelector = ({ playOptions, dropdownConfig, isClassicGame = false, }) => {
1024
+ const { t } = useTranslation();
656
1025
  const { risks, disabledMenu, currentRisk, onRiskChange } = playOptions;
657
- const riskColor = useRef(dropdownConfig.riskColorConfig[currentRisk ?? RiskTypes.LOW]);
1026
+ const riskColors = { ...DEFAULT_RISK_COLORS, ...dropdownConfig.riskColorConfig };
1027
+ const riskColor = useRef(riskColors[currentRisk ?? RiskTypes.LOW]);
658
1028
  function onRiskSelected(value) {
659
- riskColor.current = dropdownConfig.riskColorConfig[value];
1029
+ riskColor.current = riskColors[value];
660
1030
  onRiskChange(value);
661
1031
  }
662
- return (jsx("div", { className: style_difficulty.base, children: jsx(Selector, { currentValue: currentRisk, label: SELECTORS.RISK, values: risks, onSelect: onRiskSelected, disabled: disabledMenu, riskColor: riskColor.current }) }));
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]) }) }));
663
1033
  };
664
1034
 
665
1035
  const ALLOWED_SEPARATORS = [",", "."];
@@ -682,9 +1052,9 @@ function cleanInputNumber(value) {
682
1052
  return `${integerPart}.${decimalPart}`;
683
1053
  }
684
1054
 
685
- var styles$3 = {"base":"Input-module__base___IifiA","disabled":"Input-module__disabled___WqyKa"};
1055
+ var styles$5 = {"base":"Input-module__base___IifiA","disabled":"Input-module__disabled___WqyKa","disabledClassic":"Input-module__disabledClassic___hzsCt"};
686
1056
 
687
- const Input = ({ onChange, disabled, className, max, ...restProps }) => {
1057
+ const Input = ({ onChange, disabled, className, max, isClassicGame = false, ...restProps }) => {
688
1058
  const handleChange = React.useCallback((event) => {
689
1059
  if (disabled) {
690
1060
  return;
@@ -697,27 +1067,32 @@ const Input = ({ onChange, disabled, className, max, ...restProps }) => {
697
1067
  event.target.value = clamped;
698
1068
  onChange?.(event);
699
1069
  }, [disabled, max, onChange]);
700
- return (jsx("input", { ...restProps, type: "text", max: max, inputMode: "numeric", pattern: "\\d*", maxLength: 2, disabled: disabled, onChange: handleChange, className: cx$1(styles$3.base, className, { [styles$3.disabled]: disabled }) }));
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,
1073
+ }) }));
701
1074
  };
702
1075
 
703
- var styles$2 = {"base":"Switch-module__base___gj2ey","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"};
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"};
704
1077
 
705
1078
  const Switch = ({ enabled, onSwitch, disabled, isPlaying, }) => {
706
1079
  const { config } = useAutoManualPlayState();
1080
+ const { t } = useTranslation();
707
1081
  const { current } = config.currencyOptions;
1082
+ const isClassicGame = Boolean(config?.isClassicGame || config?.classicGame?.forceLeftStyle);
708
1083
  const defaultClassName = useMemo(() => {
709
1084
  if (current.code === Currency.GOLD) {
710
- return styles$2.gold;
1085
+ return styles$4.gold;
711
1086
  }
712
1087
  if (current.code === Currency.SWEEPS) {
713
- return styles$2.sweeps;
1088
+ return styles$4.sweeps;
714
1089
  }
715
- return styles$2.default;
1090
+ return styles$4.default;
716
1091
  }, [current.code]);
717
- const switcherClassName = useMemo(() => cx$1(styles$2.switcher, defaultClassName, {
718
- [styles$2.checked]: enabled,
719
- [styles$2.unchecked]: !enabled,
720
- [styles$2.disabled]: disabled,
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,
721
1096
  }), [defaultClassName, enabled, disabled]);
722
1097
  useEffect(() => {
723
1098
  const handleGlobalKeyDown = (event) => {
@@ -737,15 +1112,19 @@ const Switch = ({ enabled, onSwitch, disabled, isPlaying, }) => {
737
1112
  window.removeEventListener("keydown", handleGlobalKeyDown, true);
738
1113
  };
739
1114
  }, []);
740
- return (jsx(Switch$1, { checked: enabled, onChange: onSwitch, as: Fragment$1, disabled: isPlaying, children: jsxs("div", { className: styles$2.base, children: [jsx("span", { className: styles$2.label, children: "Auto" }), jsx("div", { className: switcherClassName, children: jsx("span", { className: cx$1(styles$2.thumb, { [styles$2["move-right"]]: enabled }) }) })] }) }));
1115
+ 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 }) }) })] }) }));
741
1119
  };
742
1120
 
743
- var styles$1 = {"base":"InputWithSwitch-module__base___vjLoh","icon":"InputWithSwitch-module__icon___upeAL","input":"InputWithSwitch-module__input___K6RCD","label":"InputWithSwitch-module__label___tEjpj"};
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"};
744
1122
 
745
- const InputWithSwitch = ({ children, switcherConfig, disabled, className, ...restProps }) => {
746
- return (jsxs("div", { className: cx$1(styles$1.base, className, {
747
- [styles$1.disabled]: switcherConfig.disabled,
748
- }), children: [jsx(Switch, { ...switcherConfig }), jsx(Input, { ...restProps, className: cx$1(styles$1.input), disabled: disabled }), jsx("div", { className: cx$1(styles$1.icon), children: children })] }));
1123
+ 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 })] }));
749
1128
  };
750
1129
 
751
1130
  function getDefaultExportFromCjs (x) {
@@ -849,13 +1228,18 @@ const WidgetContainer = ({ state, widgets, displayPlayAmountView, }) => {
849
1228
  return (jsxs("div", { className: cx("controllerWidgets"), children: [renderZone(left, "left"), renderZone(center, "center"), renderZone(right, "right")] }));
850
1229
  };
851
1230
 
852
- var styles = {"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"};
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"};
853
1232
 
854
1233
  const PlayValueList = () => {
855
1234
  const [forceRefresh, setForceRefresh] = useState(false);
856
1235
  const { config } = useAutoManualPlayState();
857
- const { onChangeAmount, playAmount, totalBalance, playValues: { displayPlayAmountView, togglePlayAmountView }, } = usePlayController();
1236
+ const { t } = useTranslation();
1237
+ const isClassicGame = Boolean(config?.isClassicGame || config?.classicGame?.forceLeftStyle);
1238
+ const { onChangeAmount, playAmount, totalBalance, autoPlay: { state: autoplayState }, playValues: { displayPlayAmountView, togglePlayAmountView }, } = usePlayController();
858
1239
  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;
859
1243
  if (!displayPlayAmountView) {
860
1244
  return null;
861
1245
  }
@@ -886,6 +1270,9 @@ const PlayValueList = () => {
886
1270
  setForceRefresh(!forceRefresh);
887
1271
  };
888
1272
  const handleClick = (e) => {
1273
+ if (isAutoplayPlaying) {
1274
+ return;
1275
+ }
889
1276
  const el = e.target?.closest("[data-value]");
890
1277
  if (!el) {
891
1278
  return;
@@ -899,13 +1286,43 @@ const PlayValueList = () => {
899
1286
  applyValue(value);
900
1287
  }
901
1288
  };
902
- return (jsx("div", { className: cx$1(styles.base), "data-force-refresh": forceRefresh, children: jsx("div", { className: cx$1(styles.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.playValue, {
903
- [styles.selectedPlayValue]: playValue === playAmount,
904
- [styles.disabledPlayValue]: playValue > totalBalance,
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,
905
1294
  }), "data-value": playValue, role: "option", tabIndex: 0, "aria-selected": false, children: format(playValue, config.currencyOptions.current.decimals) }, `${config.currencyOptions.current.code}-${playValue}`))) }) }));
906
1295
  };
907
1296
 
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"};
1298
+
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();
1305
+ const handleClose = () => onPlayLater?.();
1306
+ const coinLabel = resolveFreeRoundsCoinLabel({ coinType, currency });
1307
+ const valueDecimals = resolveFreeRoundsDecimals({
1308
+ coinType,
1309
+ currency,
1310
+ fallback: decimals,
1311
+ });
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") })] })] }) }) }));
1322
+ };
1323
+
908
1324
  const AutoManualPlayProvider = ({ children, config, }) => {
1325
+ const t = useMemo(() => createTranslator(resolveLanguage(config.language)), [config.language]);
909
1326
  const [mode, setMode] = useState(GAME_MODE.MANUAL);
910
1327
  const [autoplayState, setAutoplayState] = useState(AUTO_PLAY_STATE.IDLE);
911
1328
  const [doubleOrNothingState, setDoubleOrNothingState] = useState(DOUBLE_OR_NOTHING_STATE.IDLE);
@@ -917,13 +1334,31 @@ const AutoManualPlayProvider = ({ children, config, }) => {
917
1334
  const isControllerDisabled = config.playOptions.disabledController;
918
1335
  const isGamePlaying = config.playOptions.isPlaying;
919
1336
  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;
920
1347
  const isSimplified = config.withoutRiskAndAutoplay === true;
1348
+ const isClassicGame = Boolean(config?.isClassicGame || config?.classicGame?.forceLeftStyle);
921
1349
  const togglePlayAmountView = useCallback(() => {
922
1350
  setDisplayPlayAmountView((v) => !v);
923
1351
  }, []);
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.
924
1356
  useEffect(() => {
925
- void setTogglePlayAmountView({ isOpen: displayPlayAmountView });
926
- }, [displayPlayAmountView]);
1357
+ if (!supportsGrantFundedAutoplay || !isFreeRoundsEngaged) {
1358
+ return;
1359
+ }
1360
+ setNumberOfPlays((prev) => prev > freeRoundsRemaining ? freeRoundsRemaining : prev);
1361
+ }, [supportsGrantFundedAutoplay, isFreeRoundsEngaged, freeRoundsRemaining]);
927
1362
  const startAutoplay = useCallback((numPlays) => {
928
1363
  setMode(GAME_MODE.AUTOPLAY);
929
1364
  setAutoplayState(AUTO_PLAY_STATE.SELECTING);
@@ -963,12 +1398,25 @@ const AutoManualPlayProvider = ({ children, config, }) => {
963
1398
  setSelection([]);
964
1399
  }, []);
965
1400
  const toggleMode = useCallback(() => {
966
- if (isGamePlaying || isControllerDisabled || isAutoplayPlaying) {
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).
1406
+ if (isGamePlaying ||
1407
+ isControllerDisabled ||
1408
+ isAutoplayPlaying ||
1409
+ isModeLockedByFreeRounds) {
967
1410
  return;
968
1411
  }
969
1412
  setNumberOfPlays(AUTOPLAY_DEFAULT_PLAY_ROUNDS_COUNT);
970
1413
  setMode((prevMode) => prevMode === GAME_MODE.MANUAL ? GAME_MODE.AUTOPLAY : GAME_MODE.MANUAL);
971
- }, [isAutoplayPlaying, isControllerDisabled, isGamePlaying]);
1414
+ }, [
1415
+ isAutoplayPlaying,
1416
+ isControllerDisabled,
1417
+ isGamePlaying,
1418
+ isModeLockedByFreeRounds,
1419
+ ]);
972
1420
  const updateSelection = useCallback((values) => {
973
1421
  if (!isAutoPlaying) {
974
1422
  setSelection(values);
@@ -1027,29 +1475,60 @@ const AutoManualPlayProvider = ({ children, config, }) => {
1027
1475
  displayPlayAmountView,
1028
1476
  togglePlayAmountView,
1029
1477
  ]);
1030
- return (jsxs(AutoManualPlayStateContext.Provider, { value: contextValue, children: [typeof children === "function" ? children(contextValue) : children, config.playOptions.displayController && (jsx("div", { className: cx$1(styles_ui.base, styles_ui.betForm), style: {
1031
- "--play-bottom": config.panel.bottom ?? 0,
1032
- "--play-panel-bg": hexToRgb(config.panel.bgColorHex ?? "#01243A"),
1033
- "--play-dropdown-bg": hexToRgb(config.dropdown.bgColorHex ?? "#01243A"),
1034
- "--play-panel-bg-opacity": config.panel.bgColorOpacity ?? 0.5,
1035
- }, children: jsxs("div", { className: cx$1(styles_ui.container), children: [jsx(PlayValueList, {}), !isSimplified ? (jsxs("div", { className: cx$1(styles_ui.auto), children: [jsx(DifficultySelector, { playOptions: {
1036
- ...config.playOptions,
1037
- disabledMenu: isControllerDisabled || config.playOptions.disabledMenu,
1038
- }, dropdownConfig: config.dropdown }), 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, switcherConfig: {
1039
- onSwitch: toggleMode,
1040
- isPlaying: isAutoPlaying || isGamePlaying,
1041
- enabled: mode !== GAME_MODE.MANUAL,
1042
- disabled: isControllerDisabled || isAutoplayPlaying,
1043
- }, children: jsx("span", { className: cx$1({
1044
- [styles_ui.disabled]: mode !== GAME_MODE.AUTOPLAY ||
1045
- numberOfPlays !== Infinity ||
1046
- isAutoplayPlaying,
1047
- }), children: "\u221E" }) })] })) : null, mode === GAME_MODE.MANUAL ? (jsx(ManualPlayController, {})) : (jsx(AutoPlayController, {})), jsx(WidgetContainer, { state: autoplayState, displayPlayAmountView: displayPlayAmountView, widgets: {
1048
- left: config.leftWidgets,
1049
- center: config.centerWidgets,
1050
- right: config.rightWidgets,
1051
- } })] }) }))] }));
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") })] }) }));
1052
1531
  };
1053
1532
 
1054
- export { AUTO_PLAY_STATE, AutoManualPlayProvider, DOUBLE_OR_NOTHING_STATE, GAME_MODE, WIDGET, canAutoplaySpaceTrigger, createAutoplayButtonAction, createManualCashoutClickHandler, format, isAutoplayButtonDisabled, isManualCashoutDisabled };
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 };
1055
1534
  //# sourceMappingURL=index.mjs.map