@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.cjs.js CHANGED
@@ -67,8 +67,17 @@ function createInitialState(config) {
67
67
  freeSpins: { current: 0, total: 0, totalWin: 0 },
68
68
  bonus: null,
69
69
  activeFeature: null,
70
+ volumes: {
71
+ master: clampVolume(config.volumes?.master),
72
+ music: clampVolume(config.volumes?.music),
73
+ sfx: clampVolume(config.volumes?.sfx),
74
+ },
70
75
  };
71
76
  }
77
+ /** Clamp a configured volume to 0..1, defaulting to full (1) when unset/invalid. */
78
+ function clampVolume(v) {
79
+ return typeof v === 'number' && Number.isFinite(v) ? Math.max(0, Math.min(1, v)) : 1;
80
+ }
72
81
  /** Step bet up/down within availableBets, clamped at the ends. */
73
82
  function stepBet(state, direction) {
74
83
  const idx = state.availableBets.indexOf(state.bet);
@@ -1439,7 +1448,7 @@ class KeyboardController {
1439
1448
 
1440
1449
  // AUTO-GENERATED by scripts/gen-version.mjs — do not edit. Mirrors package.json "version".
1441
1450
  /** The @energy8platform/shell package version, stamped into the game-info footer. */
1442
- const PACKAGE_VERSION = '0.5.0';
1451
+ const PACKAGE_VERSION = '0.6.1';
1443
1452
 
1444
1453
  /** Apply defaults to the raw config (the mount target lives on the renderer, not here). */
1445
1454
  function resolveConfig(config) {
@@ -1456,6 +1465,7 @@ function resolveConfig(config) {
1456
1465
  features: config.features,
1457
1466
  theme: config.theme,
1458
1467
  onBonusBuy: config.onBonusBuy,
1468
+ volumes: config.volumes,
1459
1469
  version: config.version ?? '1.0.0',
1460
1470
  isSocial: config.isSocial ?? false,
1461
1471
  replay: config.replay ?? config.mode === 'replay',
@@ -1477,6 +1487,7 @@ class ShellController extends EventEmitter {
1477
1487
  kbd;
1478
1488
  overlay = null;
1479
1489
  soundRefresh = null;
1490
+ volumeRefresh = null;
1480
1491
  prevBalance;
1481
1492
  prevWin;
1482
1493
  destroyed = false;
@@ -1660,6 +1671,7 @@ class ShellController extends EventEmitter {
1660
1671
  return;
1661
1672
  this.overlay = null;
1662
1673
  this.soundRefresh = null;
1674
+ this.volumeRefresh = null;
1663
1675
  this.renderer.closeOverlay();
1664
1676
  }
1665
1677
  // ── sound ──────────────────────────────────────────────────────────────────
@@ -1672,6 +1684,22 @@ class ShellController extends EventEmitter {
1672
1684
  setSoundRefresh(fn) {
1673
1685
  this.soundRefresh = fn;
1674
1686
  }
1687
+ // ── volume ─────────────────────────────────────────────────────────────────
1688
+ getVolume(key) {
1689
+ return this.state.volumes[key];
1690
+ }
1691
+ /** Set a volume slider (0..1). Shared by the slider control (drag) and game code (public API):
1692
+ * clamps, stores so a reopened Settings overlay reflects it, emits `settingChange`, and
1693
+ * live-updates the slider if the overlay is currently open. */
1694
+ setVolume(key, value) {
1695
+ const v = Math.max(0, Math.min(1, value));
1696
+ this.state.volumes[key] = v;
1697
+ this.emit('settingChange', { key, value: v });
1698
+ this.volumeRefresh?.(key, v);
1699
+ }
1700
+ setVolumeRefresh(fn) {
1701
+ this.volumeRefresh = fn;
1702
+ }
1675
1703
  // ── features ─────────────────────────────────────────────────────────────────
1676
1704
  activateFeature(bonus) {
1677
1705
  this.state.activeFeature = bonus;
@@ -1698,10 +1726,17 @@ class ShellController extends EventEmitter {
1698
1726
  this.prevBalance = n;
1699
1727
  this.money('balance', from, n);
1700
1728
  }
1701
- setWin(n) {
1729
+ /** Set the WIN readout. Counts up/down from the previous value by default. Pass
1730
+ * `{ animate: false }` to SNAP instantly (renderBar cancels any in-flight count-up) — used by the
1731
+ * host to clear WIN to 0 at spin start, where an animated count-DOWN would look wrong. */
1732
+ setWin(n, opts) {
1702
1733
  const from = this.prevWin;
1703
1734
  this.state.win = n;
1704
1735
  this.prevWin = n;
1736
+ if (opts?.animate === false) {
1737
+ this.renderer.renderBar(); // instant repaint from state; cancels running money anims
1738
+ return;
1739
+ }
1705
1740
  this.money('win', from, n);
1706
1741
  }
1707
1742
  setBet(n) {
@@ -2829,7 +2864,10 @@ function openSettingsModal(host) {
2829
2864
  return row;
2830
2865
  })();
2831
2866
  body.appendChild(sound);
2832
- // Volume sliders — full-width column rows with a live value readout
2867
+ // Volume sliders — full-width column rows with a live value readout. Positions are read from the
2868
+ // shell's stored volumes (not hardcoded to 100%), so reopening the overlay reflects the last set
2869
+ // value, and `host.setVolume()` from game code updates them live via the registered refreshers.
2870
+ const updaters = {};
2833
2871
  const slider = (key, label) => {
2834
2872
  const row = document.createElement('div');
2835
2873
  row.className = 'ge-ov-row ge-col';
@@ -2837,7 +2875,6 @@ function openSettingsModal(host) {
2837
2875
  head.className = 'ge-row-head';
2838
2876
  const val = document.createElement('span');
2839
2877
  val.className = 'ge-val';
2840
- val.textContent = '100%';
2841
2878
  head.innerHTML = `<span>${label}</span>`;
2842
2879
  head.appendChild(val);
2843
2880
  const input = document.createElement('input');
@@ -2845,19 +2882,23 @@ function openSettingsModal(host) {
2845
2882
  input.min = '0';
2846
2883
  input.max = '1';
2847
2884
  input.step = '0.05';
2848
- input.value = '1';
2849
2885
  input.className = 'ge-slider';
2850
2886
  input.dataset.ge = `setting-${key}`;
2887
+ const paint = (v) => { input.value = String(v); val.textContent = `${Math.round(v * 100)}%`; };
2888
+ paint(host.getVolume(key));
2851
2889
  input.addEventListener('input', () => {
2852
2890
  val.textContent = `${Math.round(Number(input.value) * 100)}%`;
2853
- host.emit('settingChange', { key, value: Number(input.value) });
2891
+ host.setVolume(key, Number(input.value));
2854
2892
  });
2893
+ updaters[key] = paint;
2855
2894
  row.append(head, input);
2856
2895
  return row;
2857
2896
  };
2858
2897
  body.appendChild(slider('master', host.t('Master volume')));
2859
2898
  body.appendChild(slider('music', host.t('Music')));
2860
2899
  body.appendChild(slider('sfx', host.t('SFX')));
2900
+ // Live-update sliders when volume changes via host.setVolume (shell clears on close).
2901
+ host.setVolumeRefresh((key, v) => updaters[key]?.(v));
2861
2902
  // Game info — full-width row button that opens its own overlay
2862
2903
  const gameInfo = document.createElement('button');
2863
2904
  gameInfo.className = 'ge-ov-row';