@energy8platform/shell 0.5.0 → 0.6.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/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.0';
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;
@@ -2829,7 +2857,10 @@ function openSettingsModal(host) {
2829
2857
  return row;
2830
2858
  })();
2831
2859
  body.appendChild(sound);
2832
- // Volume sliders — full-width column rows with a live value readout
2860
+ // Volume sliders — full-width column rows with a live value readout. Positions are read from the
2861
+ // shell's stored volumes (not hardcoded to 100%), so reopening the overlay reflects the last set
2862
+ // value, and `host.setVolume()` from game code updates them live via the registered refreshers.
2863
+ const updaters = {};
2833
2864
  const slider = (key, label) => {
2834
2865
  const row = document.createElement('div');
2835
2866
  row.className = 'ge-ov-row ge-col';
@@ -2837,7 +2868,6 @@ function openSettingsModal(host) {
2837
2868
  head.className = 'ge-row-head';
2838
2869
  const val = document.createElement('span');
2839
2870
  val.className = 'ge-val';
2840
- val.textContent = '100%';
2841
2871
  head.innerHTML = `<span>${label}</span>`;
2842
2872
  head.appendChild(val);
2843
2873
  const input = document.createElement('input');
@@ -2845,19 +2875,23 @@ function openSettingsModal(host) {
2845
2875
  input.min = '0';
2846
2876
  input.max = '1';
2847
2877
  input.step = '0.05';
2848
- input.value = '1';
2849
2878
  input.className = 'ge-slider';
2850
2879
  input.dataset.ge = `setting-${key}`;
2880
+ const paint = (v) => { input.value = String(v); val.textContent = `${Math.round(v * 100)}%`; };
2881
+ paint(host.getVolume(key));
2851
2882
  input.addEventListener('input', () => {
2852
2883
  val.textContent = `${Math.round(Number(input.value) * 100)}%`;
2853
- host.emit('settingChange', { key, value: Number(input.value) });
2884
+ host.setVolume(key, Number(input.value));
2854
2885
  });
2886
+ updaters[key] = paint;
2855
2887
  row.append(head, input);
2856
2888
  return row;
2857
2889
  };
2858
2890
  body.appendChild(slider('master', host.t('Master volume')));
2859
2891
  body.appendChild(slider('music', host.t('Music')));
2860
2892
  body.appendChild(slider('sfx', host.t('SFX')));
2893
+ // Live-update sliders when volume changes via host.setVolume (shell clears on close).
2894
+ host.setVolumeRefresh((key, v) => updaters[key]?.(v));
2861
2895
  // Game info — full-width row button that opens its own overlay
2862
2896
  const gameInfo = document.createElement('button');
2863
2897
  gameInfo.className = 'ge-ov-row';