@energy8platform/platform-core 0.23.2 → 0.24.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.ts CHANGED
@@ -501,11 +501,12 @@ type ShellMode = 'base' | 'freeSpins' | 'replay';
501
501
  interface CurrencyConfig {
502
502
  symbol: string;
503
503
  position: 'left' | 'right';
504
- /** Maximum fraction digits (default 2). The value is rounded to this precision. */
505
- decimals?: number;
506
- /** Minimum fraction digits (defaults to `decimals`). Trailing zeros are trimmed down to
507
- * this many places, so small wins keep their significant digits (e.g. 0.0673) while round
508
- * amounts stay compact (e.g. 0.30). */
504
+ /** Maximum fraction digits (default 2). Win / total-win readouts are rounded to this precision;
505
+ * balance / bet / prices stay fixed at `minDecimals`. */
506
+ maxDecimals?: number;
507
+ /** Minimum fraction digits (defaults to `maxDecimals`). For win / total-win, trailing zeros are
508
+ * trimmed down to this many places so small wins keep their significant digits (e.g. 0.0673)
509
+ * while round amounts stay compact (e.g. 0.30). Everything else is shown at exactly this many. */
509
510
  minDecimals?: number;
510
511
  separator?: {
511
512
  thousands?: string;
@@ -552,8 +553,9 @@ interface BonusCardContext {
552
553
  interface ThemeConfig {
553
554
  /** Palette scheme: 'dark' (default) for dark games, 'light' for light backgrounds. */
554
555
  scheme?: 'dark' | 'light';
556
+ /** Brand accent — active states, the SPIN hover glow, and the BUY BONUS button.
557
+ * (Per-bonus card accents are set on each `BonusOption.accentColor`.) */
555
558
  accent?: string;
556
- buyBonusColor?: string;
557
559
  }
558
560
  /** One paytable entry: a symbol (text/image) and its win tiers, rendered "<count> x<multiplier>". */
559
561
  interface PaytableRow {
@@ -658,7 +660,9 @@ interface AutoplayOptions {
658
660
  remaining: number;
659
661
  }
660
662
  interface FreeSpinsState {
661
- current: number;
663
+ /** Spin index for the `current / total` counter. Set to `null` (or omit) to instead show just
664
+ * `total` as a single number — drive a countdown by decrementing `total` each spin. */
665
+ current?: number | null;
662
666
  total: number;
663
667
  totalWin: number;
664
668
  }
package/dist/index.esm.js CHANGED
@@ -1026,7 +1026,7 @@ const PALETTE = {
1026
1026
  },
1027
1027
  };
1028
1028
  /** CSS custom-property block for the shell root. `scheme` picks the palette;
1029
- * only accent and buyBonus are additionally game-overridable. */
1029
+ * only the accent is additionally game-overridable. */
1030
1030
  function buildThemeVars(theme = {}) {
1031
1031
  const p = PALETTE[theme.scheme === 'light' ? 'light' : 'dark'];
1032
1032
  return [
@@ -1055,8 +1055,6 @@ function buildThemeVars(theme = {}) {
1055
1055
  `--shell-plaque-line: rgba(255,255,255,.22)`,
1056
1056
  `--shell-plaque-label: rgba(255,255,255,.6)`,
1057
1057
  `--shell-accent: ${theme.accent ?? DEFAULT_ACCENT}`,
1058
- // buy-bonus tint follows the accent by default; only overridden when set explicitly
1059
- `--shell-buybonus: ${theme.buyBonusColor ?? theme.accent ?? DEFAULT_ACCENT}`,
1060
1058
  ].join('; ') + ';';
1061
1059
  }
1062
1060
 
@@ -1120,7 +1118,7 @@ const SHELL_CSS = SHELL_FONT_CSS + `
1120
1118
 
1121
1119
  /* BUY BONUS — round accent badge, 2-line label, text pulses + accent glow on hover */
1122
1120
  #${SHELL_ROOT_ID} .ge-shell-buybonus { pointer-events:auto; cursor:pointer; box-sizing:border-box;
1123
- width:80px; height:80px; border-radius:50%; border:3px solid #000; background:var(--shell-buybonus);
1121
+ width:80px; height:80px; border-radius:50%; border:3px solid #000; background:var(--shell-accent);
1124
1122
  color:#fff; font-weight:800; letter-spacing:.02em; font-size:13px; line-height:1.08; text-align:center;
1125
1123
  display:flex; align-items:center; justify-content:center; transition:transform .08s ease, box-shadow .12s ease; }
1126
1124
  #${SHELL_ROOT_ID} .ge-shell-buybonus span { display:inline-block; will-change:transform; }
@@ -1452,22 +1450,25 @@ const SHELL_CSS = SHELL_FONT_CSS + `
1452
1450
 
1453
1451
  /** The shared money formatter for every shell readout (balance, win, total win, bet, prices).
1454
1452
  *
1455
- * `decimals` is the MAXIMUM number of fraction digits; `minDecimals` (defaults to `decimals`)
1456
- * is the MINIMUM. The value is rounded to `decimals`, then trailing zeros are trimmed down to
1457
- * — but never past `minDecimals`. With `minDecimals` unset both bounds are equal, so the
1458
- * output is always exactly `decimals` places (the classic behaviour).
1453
+ * `maxDecimals` is the MAXIMUM fraction digits (default 2); `minDecimals` (defaults to
1454
+ * `maxDecimals`) is the MINIMUM. By default the value is shown at exactly `minDecimals` places.
1455
+ * With `variableDecimals` used only for win / total-win it is rounded to `maxDecimals`, then
1456
+ * trailing zeros are trimmed down to (but never past) `minDecimals`, so small wins keep their
1457
+ * significant digits. Balance / bet / prices stay fixed at `minDecimals`.
1459
1458
  *
1460
- * Example with `decimals: 4, minDecimals: 2`:
1461
- * 0.0673 → 0,0673 0.0670 → 0,067 0.0600 → 0,06
1462
- * 0.0004 → 0,0004 0.0040 → 0,004 0.3000 → 0,30 0.0000 → 0,00
1459
+ * Example with `maxDecimals: 4, minDecimals: 2`:
1460
+ * fixed → 0.0673 → 0,07 0.3 → 0,30
1461
+ * variable → 0.0673 → 0,0673 0.067 → 0,067 0.3 → 0,30 0 → 0,00
1463
1462
  */
1464
- function formatCurrency(value, currency) {
1465
- const decimals = currency.decimals ?? 2;
1466
- const minDecimals = Math.max(0, Math.min(decimals, currency.minDecimals ?? decimals));
1463
+ function formatCurrency(value, currency, variableDecimals = false) {
1464
+ const maxDecimals = currency.maxDecimals ?? 2;
1465
+ const minDecimals = Math.max(0, Math.min(maxDecimals, currency.minDecimals ?? maxDecimals));
1467
1466
  const thousands = currency.separator?.thousands ?? '.';
1468
1467
  const decimal = currency.separator?.decimal ?? ',';
1469
1468
  const safe = Number.isFinite(value) ? value : 0;
1470
- const fixed = safe.toFixed(decimals); // round at the max precision, e.g. "0.0670"
1469
+ // fixed callers round at minDecimals; variable callers round at maxDecimals then trim back down.
1470
+ const places = variableDecimals ? maxDecimals : minDecimals;
1471
+ const fixed = safe.toFixed(places);
1471
1472
  const [intPart, rawFrac = ''] = fixed.split('.');
1472
1473
  // trim trailing zeros, but keep at least `minDecimals` fraction digits
1473
1474
  let fracPart = rawFrac;
@@ -1628,6 +1629,7 @@ function iconBtn(ge, name, onClick, active = false) {
1628
1629
  function renderBottomBar(shell) {
1629
1630
  const { state, config } = shell;
1630
1631
  const fmt = (n) => formatCurrency(n, config.currency);
1632
+ const fmtWin = (n) => formatCurrency(n, config.currency, true); // win / total-win: variable decimals
1631
1633
  const mobile = shell.layout === 'mobile';
1632
1634
  const bar = document.createElement('div');
1633
1635
  bar.className = 'ge-shell-bottom';
@@ -1648,8 +1650,13 @@ function renderBottomBar(shell) {
1648
1650
  const betShown = feature ? state.bet * feature.priceMultiplier : state.bet;
1649
1651
  const betValue = readout('bet-value', shell.t('Bet'), fmt(betShown));
1650
1652
  if (feature) {
1653
+ const accent = effectiveAccent(feature);
1651
1654
  betValue.classList.add('ge-bet-feature');
1652
- betValue.style.color = effectiveAccent(feature);
1655
+ betValue.style.color = accent;
1656
+ // tint the "BET" label too (its .ge-lbl colour is set in CSS, so override inline)
1657
+ const lbl = betValue.querySelector('.ge-lbl');
1658
+ if (lbl)
1659
+ lbl.style.color = accent;
1653
1660
  }
1654
1661
  const turbo = config.features.turbo > 0
1655
1662
  ? iconBtn('turbo', turboIcon(state.turbo), () => onTurbo(shell), state.turbo > 0) : null;
@@ -1666,10 +1673,13 @@ function renderBottomBar(shell) {
1666
1673
  auto = config.features.autoplay ? autoButton(shell) : null;
1667
1674
  buy = (config.features.buyBonus !== false || config.onBonusBuy) ? buyBtn(shell) : null;
1668
1675
  }
1669
- const winEl = state.win > 0 ? readout('win', shell.t('Win'), fmt(state.win)) : null;
1676
+ const winEl = state.win > 0 ? readout('win', shell.t('Win'), fmtWin(state.win)) : null;
1670
1677
  // FS/replay left blocks: spins counter + accumulated Total Win (shown even at €0).
1671
- const fsCounter = showFsBlocks ? readout('fs-counter', shell.t('Free spins'), `${state.freeSpins.current} / ${state.freeSpins.total}`) : null;
1672
- const fsTotalWin = showFsBlocks ? readout('fs-totalwin', shell.t('Total win'), fmt(state.freeSpins.totalWin)) : null;
1678
+ // current = number "current / total"; current = null/undefined → just the (game-driven) total.
1679
+ const fs = state.freeSpins;
1680
+ const fsText = fs.current == null ? `${fs.total}` : `${fs.current} / ${fs.total}`;
1681
+ const fsCounter = showFsBlocks ? readout('fs-counter', shell.t('Free spins'), fsText) : null;
1682
+ const fsTotalWin = showFsBlocks ? readout('fs-totalwin', shell.t('Total win'), fmtWin(fs.totalWin)) : null;
1673
1683
  if (mobile) {
1674
1684
  // rows: [balance · win] · [menu · auto · spin · FS counter · Total Win · turbo · buy] · [− bet +]
1675
1685
  // FS counter + Total Win live in the controls row (alongside menu/turbo), not the top readouts.
@@ -2485,6 +2495,7 @@ function buildModal(opts) {
2485
2495
  function buildReplayModal(shell, opts) {
2486
2496
  const { bonusId, bet, payoutMultiplier } = opts;
2487
2497
  const fmt = (n) => formatCurrency(n, shell.config.currency);
2498
+ const fmtWin = (n) => formatCurrency(n, shell.config.currency, true); // total win: variable decimals
2488
2499
  const bonus = Array.isArray(shell.config.features.buyBonus)
2489
2500
  ? shell.config.features.buyBonus.find((b) => b.id === bonusId)
2490
2501
  : undefined;
@@ -2513,7 +2524,7 @@ function buildReplayModal(shell, opts) {
2513
2524
  row('Cost multiplier', `${costMultiplier}×`);
2514
2525
  row('Total cost bet', fmt(bet * costMultiplier));
2515
2526
  row('Payout multiplier', `${payoutMultiplier}×`);
2516
- row('Total win', fmt(payoutMultiplier * bet), true);
2527
+ row('Total win', fmtWin(payoutMultiplier * bet), true);
2517
2528
  ui.body.appendChild(rows);
2518
2529
  const actions = document.createElement('div');
2519
2530
  actions.className = 'ge-modal-actions';
@@ -2802,12 +2813,13 @@ class GameShell extends EventEmitter {
2802
2813
  }
2803
2814
  animateMoney() {
2804
2815
  const fmt = (n) => formatCurrency(n, this.config.currency);
2816
+ const fmtWin = (n) => formatCurrency(n, this.config.currency, true); // win: variable decimals
2805
2817
  const bal = this.barHost.querySelector('[data-ge="balance"]');
2806
2818
  const win = this.barHost.querySelector('[data-ge="win"]');
2807
2819
  if (bal && this.state.balance !== this.prevBalance)
2808
2820
  this.moneyAnims.push(animateReadout(bal, this.prevBalance, this.state.balance, fmt));
2809
2821
  if (win && this.state.win !== this.prevWin)
2810
- this.moneyAnims.push(animateReadout(win, this.prevWin, this.state.win, fmt));
2822
+ this.moneyAnims.push(animateReadout(win, this.prevWin, this.state.win, fmtWin));
2811
2823
  this.prevBalance = this.state.balance;
2812
2824
  this.prevWin = this.state.win;
2813
2825
  }