@energy8platform/shell 0.5.0 → 0.6.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/html.d.ts CHANGED
@@ -21,6 +21,9 @@ declare class EventEmitter<TEvents extends {}> {
21
21
  * `bonus` pairs with `setBonus()` to show a game-supplied label + value (adventure, hold-and-spin,
22
22
  * respins — anything that isn't a plain free-spins counter). */
23
23
  type ShellMode = 'base' | 'bonus' | 'freeSpins' | 'replay';
24
+ /** The three independent volume sliders shown in the Settings overlay. */
25
+ type VolumeKey = 'master' | 'music' | 'sfx';
26
+ type VolumeLevels = Record<VolumeKey, number>;
24
27
  interface CurrencyConfig {
25
28
  symbol: string;
26
29
  position: 'left' | 'right';
@@ -282,9 +285,13 @@ interface ShellConfig {
282
285
  * opening the built-in buy-bonus overlay (e.g. the game shows its own bonus UI). The button
283
286
  * is shown whenever this OR `features.buyBonus` is set. */
284
287
  onBonusBuy?: () => void;
288
+ /** Initial Settings-overlay volume slider positions (each 0..1, defaults to 1 = 100%). The shell
289
+ * keeps them stateful across opens; read/update at runtime via `shell.getVolume()` /
290
+ * `shell.setVolume()`, and listen to `settingChange` ({ key: 'master'|'music'|'sfx' }) to apply. */
291
+ volumes?: Partial<VolumeLevels>;
285
292
  }
286
293
  /** ShellConfig after the controller applies defaults (version, isSocial, replay, theme). No mount. */
287
- type ResolvedShellConfig = Required<Pick<ShellConfig, 'language' | 'currency' | 'availableBets' | 'defaultBet' | 'balance' | 'win' | 'mode' | 'features' | 'gameInfo' | 'version' | 'isSocial' | 'replay'>> & Pick<ShellConfig, 'currentBet' | 'theme' | 'onBonusBuy'>;
294
+ type ResolvedShellConfig = Required<Pick<ShellConfig, 'language' | 'currency' | 'availableBets' | 'defaultBet' | 'balance' | 'win' | 'mode' | 'features' | 'gameInfo' | 'version' | 'isSocial' | 'replay'>> & Pick<ShellConfig, 'currentBet' | 'theme' | 'onBonusBuy' | 'volumes'>;
288
295
  interface ShellState {
289
296
  mode: ShellMode;
290
297
  /** Sticky replay marker — true for a historical-round replay, regardless of the current
@@ -309,6 +316,9 @@ interface ShellState {
309
316
  /** The currently activated `feature` option (e.g. Ante), or null. Drives the
310
317
  * effective-bet readout tint and the BUY BONUS → DISABLE toggle on the bar. */
311
318
  activeFeature: BonusOption | null;
319
+ /** Volume slider positions (0..1) surfaced in the Settings overlay. Stateful across opens so a
320
+ * reopened overlay reflects the last-set positions instead of resetting to 100%. */
321
+ volumes: VolumeLevels;
312
322
  }
313
323
  interface ShellEvents {
314
324
  spin: void;
@@ -431,6 +441,13 @@ interface ShellHost {
431
441
  setSound(on: boolean): void;
432
442
  /** An open Settings overlay registers an icon updater here (null clears it on close). */
433
443
  setSoundRefresh(fn: ((on: boolean) => void) | null): void;
444
+ /** Current volume slider position (0..1) for master/music/sfx. */
445
+ getVolume(key: VolumeKey): number;
446
+ /** Set a volume slider (0..1): clamps, stores, emits `settingChange`, and live-updates an open
447
+ * Settings overlay. Called by the slider control on drag AND by game code as the public API. */
448
+ setVolume(key: VolumeKey, value: number): void;
449
+ /** An open Settings overlay registers a slider updater here (null clears it on close). */
450
+ setVolumeRefresh(fn: ((key: VolumeKey, value: number) => void) | null): void;
434
451
  /** Logic-bearing actions invoked by renderer controls. */
435
452
  readonly actions: ShellActions;
436
453
  }
@@ -494,13 +511,14 @@ declare class ShellController extends EventEmitter<ShellEvents> implements Shell
494
511
  tokens: ShellTokens;
495
512
  layout: ShellLayoutMode;
496
513
  soundOn: boolean;
497
- readonly engineVersion = "0.5.0";
514
+ readonly engineVersion = "0.6.1";
498
515
  readonly actions: ShellActions;
499
516
  private renderer;
500
517
  private i18n;
501
518
  private kbd?;
502
519
  private overlay;
503
520
  private soundRefresh;
521
+ private volumeRefresh;
504
522
  private prevBalance;
505
523
  private prevWin;
506
524
  private destroyed;
@@ -525,11 +543,22 @@ declare class ShellController extends EventEmitter<ShellEvents> implements Shell
525
543
  closeModal(): void;
526
544
  setSound(on: boolean): void;
527
545
  setSoundRefresh(fn: ((on: boolean) => void) | null): void;
546
+ getVolume(key: VolumeKey): number;
547
+ /** Set a volume slider (0..1). Shared by the slider control (drag) and game code (public API):
548
+ * clamps, stores so a reopened Settings overlay reflects it, emits `settingChange`, and
549
+ * live-updates the slider if the overlay is currently open. */
550
+ setVolume(key: VolumeKey, value: number): void;
551
+ setVolumeRefresh(fn: ((key: VolumeKey, value: number) => void) | null): void;
528
552
  activateFeature(bonus: BonusOption): void;
529
553
  deactivateFeature(): void;
530
554
  private money;
531
555
  setBalance(n: number): void;
532
- setWin(n: number): void;
556
+ /** Set the WIN readout. Counts up/down from the previous value by default. Pass
557
+ * `{ animate: false }` to SNAP instantly (renderBar cancels any in-flight count-up) — used by the
558
+ * host to clear WIN to 0 at spin start, where an animated count-DOWN would look wrong. */
559
+ setWin(n: number, opts?: {
560
+ animate?: boolean;
561
+ }): void;
533
562
  setBet(n: number): void;
534
563
  setMode(mode: ShellMode): void;
535
564
  setBusy(busy: boolean): void;
@@ -564,7 +593,7 @@ interface I18n {
564
593
  declare function createI18n(opts: I18nOptions): I18n;
565
594
 
566
595
  /** The @energy8platform/shell package version, stamped into the game-info footer. */
567
- declare const PACKAGE_VERSION = "0.5.0";
596
+ declare const PACKAGE_VERSION = "0.6.1";
568
597
 
569
598
  /** A shell: the renderer-agnostic controller plus the surface facade (safeArea/barHeight/setVisible)
570
599
  * an embedding host reads. `createShell`, `createGameShell` and `createPixiShell` all return this. */
@@ -640,4 +669,4 @@ declare function createGameShell(config: HtmlShellConfig): ShellController;
640
669
  declare function removeGameShell(): Promise<void>;
641
670
 
642
671
  export { DEFAULT_ACCENT, ShellController as GameShell, HtmlRenderer, PACKAGE_VERSION, SCHEMES, ShellController, createGameShell, createI18n, createShell, normalizeLang, removeGameShell, resolveConfig, resolveTheme, socialize };
643
- export type { AutoplayConfig, AutoplayOptions, BonusCardContext, BonusOption, BonusReadout, CellRef, CreateShellOptions, CurrencyConfig, FreeSpinsState, GameInfoContent, GameInfoSection, GameMode, HtmlShellConfig, I18n, I18nOptions, Lang, ModalAction, ModalOptions, OverlayHandle, OverlayRequest, PaylineDef, PaytableRow, ReplayModalOptions, ResolvedShellConfig, SafeArea, ShapeDef, Shell, ShellActions, HtmlShellConfig as ShellConfig, ShellEvents, ShellFeatures, ShellHost, ShellLayoutMode, ShellMode, ShellRenderer, ShellState, ShellSurface, ShellTokens, ThemeConfig, WinSection };
672
+ export type { AutoplayConfig, AutoplayOptions, BonusCardContext, BonusOption, BonusReadout, CellRef, CreateShellOptions, CurrencyConfig, FreeSpinsState, GameInfoContent, GameInfoSection, GameMode, HtmlShellConfig, I18n, I18nOptions, Lang, ModalAction, ModalOptions, OverlayHandle, OverlayRequest, PaylineDef, PaytableRow, ReplayModalOptions, ResolvedShellConfig, SafeArea, ShapeDef, Shell, ShellActions, HtmlShellConfig as ShellConfig, ShellEvents, ShellFeatures, ShellHost, ShellLayoutMode, ShellMode, ShellRenderer, ShellState, ShellSurface, ShellTokens, ThemeConfig, VolumeKey, VolumeLevels, WinSection };
package/dist/html.esm.js CHANGED
@@ -65,8 +65,17 @@ function createInitialState(config) {
65
65
  freeSpins: { current: 0, total: 0, totalWin: 0 },
66
66
  bonus: null,
67
67
  activeFeature: null,
68
+ volumes: {
69
+ master: clampVolume(config.volumes?.master),
70
+ music: clampVolume(config.volumes?.music),
71
+ sfx: clampVolume(config.volumes?.sfx),
72
+ },
68
73
  };
69
74
  }
75
+ /** Clamp a configured volume to 0..1, defaulting to full (1) when unset/invalid. */
76
+ function clampVolume(v) {
77
+ return typeof v === 'number' && Number.isFinite(v) ? Math.max(0, Math.min(1, v)) : 1;
78
+ }
70
79
  /** Step bet up/down within availableBets, clamped at the ends. */
71
80
  function stepBet(state, direction) {
72
81
  const idx = state.availableBets.indexOf(state.bet);
@@ -1437,7 +1446,7 @@ class KeyboardController {
1437
1446
 
1438
1447
  // AUTO-GENERATED by scripts/gen-version.mjs — do not edit. Mirrors package.json "version".
1439
1448
  /** The @energy8platform/shell package version, stamped into the game-info footer. */
1440
- const PACKAGE_VERSION = '0.5.0';
1449
+ const PACKAGE_VERSION = '0.6.1';
1441
1450
 
1442
1451
  /** Apply defaults to the raw config (the mount target lives on the renderer, not here). */
1443
1452
  function resolveConfig(config) {
@@ -1454,6 +1463,7 @@ function resolveConfig(config) {
1454
1463
  features: config.features,
1455
1464
  theme: config.theme,
1456
1465
  onBonusBuy: config.onBonusBuy,
1466
+ volumes: config.volumes,
1457
1467
  version: config.version ?? '1.0.0',
1458
1468
  isSocial: config.isSocial ?? false,
1459
1469
  replay: config.replay ?? config.mode === 'replay',
@@ -1475,6 +1485,7 @@ class ShellController extends EventEmitter {
1475
1485
  kbd;
1476
1486
  overlay = null;
1477
1487
  soundRefresh = null;
1488
+ volumeRefresh = null;
1478
1489
  prevBalance;
1479
1490
  prevWin;
1480
1491
  destroyed = false;
@@ -1658,6 +1669,7 @@ class ShellController extends EventEmitter {
1658
1669
  return;
1659
1670
  this.overlay = null;
1660
1671
  this.soundRefresh = null;
1672
+ this.volumeRefresh = null;
1661
1673
  this.renderer.closeOverlay();
1662
1674
  }
1663
1675
  // ── sound ──────────────────────────────────────────────────────────────────
@@ -1670,6 +1682,22 @@ class ShellController extends EventEmitter {
1670
1682
  setSoundRefresh(fn) {
1671
1683
  this.soundRefresh = fn;
1672
1684
  }
1685
+ // ── volume ─────────────────────────────────────────────────────────────────
1686
+ getVolume(key) {
1687
+ return this.state.volumes[key];
1688
+ }
1689
+ /** Set a volume slider (0..1). Shared by the slider control (drag) and game code (public API):
1690
+ * clamps, stores so a reopened Settings overlay reflects it, emits `settingChange`, and
1691
+ * live-updates the slider if the overlay is currently open. */
1692
+ setVolume(key, value) {
1693
+ const v = Math.max(0, Math.min(1, value));
1694
+ this.state.volumes[key] = v;
1695
+ this.emit('settingChange', { key, value: v });
1696
+ this.volumeRefresh?.(key, v);
1697
+ }
1698
+ setVolumeRefresh(fn) {
1699
+ this.volumeRefresh = fn;
1700
+ }
1673
1701
  // ── features ─────────────────────────────────────────────────────────────────
1674
1702
  activateFeature(bonus) {
1675
1703
  this.state.activeFeature = bonus;
@@ -1696,10 +1724,17 @@ class ShellController extends EventEmitter {
1696
1724
  this.prevBalance = n;
1697
1725
  this.money('balance', from, n);
1698
1726
  }
1699
- setWin(n) {
1727
+ /** Set the WIN readout. Counts up/down from the previous value by default. Pass
1728
+ * `{ animate: false }` to SNAP instantly (renderBar cancels any in-flight count-up) — used by the
1729
+ * host to clear WIN to 0 at spin start, where an animated count-DOWN would look wrong. */
1730
+ setWin(n, opts) {
1700
1731
  const from = this.prevWin;
1701
1732
  this.state.win = n;
1702
1733
  this.prevWin = n;
1734
+ if (opts?.animate === false) {
1735
+ this.renderer.renderBar(); // instant repaint from state; cancels running money anims
1736
+ return;
1737
+ }
1703
1738
  this.money('win', from, n);
1704
1739
  }
1705
1740
  setBet(n) {
@@ -2827,7 +2862,10 @@ function openSettingsModal(host) {
2827
2862
  return row;
2828
2863
  })();
2829
2864
  body.appendChild(sound);
2830
- // Volume sliders — full-width column rows with a live value readout
2865
+ // Volume sliders — full-width column rows with a live value readout. Positions are read from the
2866
+ // shell's stored volumes (not hardcoded to 100%), so reopening the overlay reflects the last set
2867
+ // value, and `host.setVolume()` from game code updates them live via the registered refreshers.
2868
+ const updaters = {};
2831
2869
  const slider = (key, label) => {
2832
2870
  const row = document.createElement('div');
2833
2871
  row.className = 'ge-ov-row ge-col';
@@ -2835,7 +2873,6 @@ function openSettingsModal(host) {
2835
2873
  head.className = 'ge-row-head';
2836
2874
  const val = document.createElement('span');
2837
2875
  val.className = 'ge-val';
2838
- val.textContent = '100%';
2839
2876
  head.innerHTML = `<span>${label}</span>`;
2840
2877
  head.appendChild(val);
2841
2878
  const input = document.createElement('input');
@@ -2843,19 +2880,23 @@ function openSettingsModal(host) {
2843
2880
  input.min = '0';
2844
2881
  input.max = '1';
2845
2882
  input.step = '0.05';
2846
- input.value = '1';
2847
2883
  input.className = 'ge-slider';
2848
2884
  input.dataset.ge = `setting-${key}`;
2885
+ const paint = (v) => { input.value = String(v); val.textContent = `${Math.round(v * 100)}%`; };
2886
+ paint(host.getVolume(key));
2849
2887
  input.addEventListener('input', () => {
2850
2888
  val.textContent = `${Math.round(Number(input.value) * 100)}%`;
2851
- host.emit('settingChange', { key, value: Number(input.value) });
2889
+ host.setVolume(key, Number(input.value));
2852
2890
  });
2891
+ updaters[key] = paint;
2853
2892
  row.append(head, input);
2854
2893
  return row;
2855
2894
  };
2856
2895
  body.appendChild(slider('master', host.t('Master volume')));
2857
2896
  body.appendChild(slider('music', host.t('Music')));
2858
2897
  body.appendChild(slider('sfx', host.t('SFX')));
2898
+ // Live-update sliders when volume changes via host.setVolume (shell clears on close).
2899
+ host.setVolumeRefresh((key, v) => updaters[key]?.(v));
2859
2900
  // Game info — full-width row button that opens its own overlay
2860
2901
  const gameInfo = document.createElement('button');
2861
2902
  gameInfo.className = 'ge-ov-row';