@energy8platform/platform-core 0.25.3 → 0.25.4

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
@@ -866,9 +866,12 @@ declare class GameShell extends EventEmitter<ShellEvents> {
866
866
  /** Fraction of the frame a card modal may occupy; the rest is breathing-room margin. Keeps
867
867
  * modals from filling a small popout edge-to-edge (so even short pickers scale down there). */
868
868
  private static readonly MODAL_FIT;
869
- /** Max fraction of the frame HEIGHT the bottom bar may occupy before it fit-scales down. Keeps the
870
- * bar a consistent, small slice on short popouts in EVERY mode (base this already via width). */
871
- private static readonly BAR_MAX_FRACTION;
869
+ /** The bar's design width (px). When the frame is narrower, the bar fit-scales DOWN with the
870
+ * screen the SAME factor in every mode, so replay/free-spins shrink like base instead of
871
+ * staying full-size on a popout. */
872
+ private static readonly BAR_REF_WIDTH;
873
+ /** Lower bound on the bar fit-scale (guards a degenerate near-zero frame). */
874
+ private static readonly BAR_MIN_SCALE;
872
875
  private fitSheet;
873
876
  /** Activate a `feature` option (e.g. Ante): the bar shows the effective bet, tinted with
874
877
  * the feature accent, and BUY BONUS becomes DISABLE. */
package/dist/index.esm.js CHANGED
@@ -1164,11 +1164,11 @@ const SHELL_CSS = SHELL_FONT_CSS + `
1164
1164
 
1165
1165
  /* host = bottom-anchored flex column: [win pill (on overflow)] above [the bar] */
1166
1166
  #${SHELL_ROOT_ID} .ge-shell-barhost { position:absolute; left:0; right:0; bottom:0; pointer-events:none;
1167
- display:flex; flex-direction:column; align-items:center; justify-content:flex-end; gap:8px;
1167
+ display:flex; flex-direction:column; align-items:center; justify-content:flex-end; gap:4px;
1168
1168
  transform-origin:bottom center; }
1169
1169
  /* bottom bar: transparent, two zones (wide default) */
1170
1170
  #${SHELL_ROOT_ID} .ge-shell-bottom { width:100%; box-sizing:border-box; pointer-events:none;
1171
- display:flex; align-items:center; justify-content:space-between; padding:0 18px 14px; gap:14px; }
1171
+ display:flex; align-items:center; justify-content:space-between; padding:0 18px 6px; gap:14px; }
1172
1172
  #${SHELL_ROOT_ID} .ge-zone { display:flex; align-items:center; gap:14px; pointer-events:none; }
1173
1173
  #${SHELL_ROOT_ID} .ge-zone > * { pointer-events:auto; }
1174
1174
  #${SHELL_ROOT_ID} .ge-betstep { display:flex; flex-direction:column; gap:2px; }
@@ -1437,7 +1437,7 @@ const SHELL_CSS = SHELL_FONT_CSS + `
1437
1437
  #${SHELL_ROOT_ID} .ge-pl .ge-iconbtn { color:#fff; }
1438
1438
  /* LEFT: [menu] ⊐ coin ⊏ [balance] — coin overlaps both; balance fixed-wide so it doesn't jiggle */
1439
1439
  #${SHELL_ROOT_ID} .ge-pl-menu { border-radius:16px 0 0 16px; padding-right:20px; }
1440
- #${SHELL_ROOT_ID} .ge-pl-bal { border-radius:0 16px 16px 0; padding-left:24px; min-width:240px; }
1440
+ #${SHELL_ROOT_ID} .ge-pl-bal { border-radius:0 16px 16px 0; padding-left:24px; min-width:200px; }
1441
1441
  #${SHELL_ROOT_ID} .ge-zone-plaques .ge-shell-buybonus { margin:0 -16px; position:relative; z-index:3; }
1442
1442
  /* RIGHT: [bet] · |divider| · [auto · SPIN · turbo] */
1443
1443
  #${SHELL_ROOT_ID} .ge-pl-bet { border-radius:16px 0 0 16px; justify-content:space-between;
@@ -1994,7 +1994,7 @@ function openSettingsModal(shell) {
1994
1994
 
1995
1995
  // AUTO-GENERATED by scripts/gen-version.mjs — do not edit. Mirrors package.json "version".
1996
1996
  /** The @energy8platform/platform-core package version, stamped at build time. */
1997
- const PACKAGE_VERSION = '0.25.3';
1997
+ const PACKAGE_VERSION = '0.25.4';
1998
1998
 
1999
1999
  const SVG_NS = 'http://www.w3.org/2000/svg';
2000
2000
  function openGameInfoModal(shell) {
@@ -2873,10 +2873,11 @@ class GameShell extends EventEmitter {
2873
2873
  host.classList.remove('ge-fit');
2874
2874
  host.style.transform = '';
2875
2875
  host.style.transformOrigin = '';
2876
- // clear any per-zone height-scale from a prior pass
2876
+ // clear any per-zone scale/zoom from a prior pass
2877
2877
  for (const el of host.querySelectorAll('.ge-zone, .ge-winpill')) {
2878
2878
  el.style.transform = '';
2879
2879
  el.style.transformOrigin = '';
2880
+ el.style.removeProperty('zoom');
2880
2881
  }
2881
2882
  if (this.layout === 'mobile') {
2882
2883
  // Shrink the whole stack to fit narrow phones (mobile-s, or big balance/win/total-win
@@ -2894,42 +2895,35 @@ class GameShell extends EventEmitter {
2894
2895
  }
2895
2896
  return;
2896
2897
  }
2897
- const availW = this.root.clientWidth - 12;
2898
- const availH = this.root.clientHeight * GameShell.BAR_MAX_FRACTION;
2899
- // 1) If the inline row overflows the width, lift the WIN pill onto its own line above the bar
2900
- // (keeps the controls as large as possible — base's wide row + a big WIN pill hit this).
2898
+ // ONE fit-scale, from the SCREEN SIZE, applied identically in EVERY mode — switching base⇄replay
2899
+ // must not resize the bar. The factor is the frame WIDTH vs the bar's design width, never the
2900
+ // current mode's content width.
2901
+ //
2902
+ // It's applied with `zoom` (not `transform`): zoom shrinks the LAYOUT, so the zones genuinely
2903
+ // take less room and still sit edge-to-edge (menu hard-left, controls hard-right) even when base's
2904
+ // wide row would overflow a merely-visually-scaled bar — so there is no per-mode centred cluster
2905
+ // and no width/mode branching. A wide WIN pill is still lifted above the row first so it can't
2906
+ // shove the controls off-screen. (Mobile, above, keeps its own stacked fit.)
2901
2907
  if (pill && bar.scrollWidth > bar.clientWidth + 1) {
2902
2908
  host.insertBefore(pill, bar);
2903
2909
  pill.classList.add('ge-up');
2904
2910
  }
2905
- // 2) Still too WIDE (the control row itself doesn't fit) shrink-to-content + uniform scale,
2906
- // centred. Only base's wide row reaches here.
2907
- if (bar.scrollWidth > bar.clientWidth + 1) {
2908
- host.classList.add('ge-fit');
2909
- const naturalW = host.offsetWidth, naturalH = host.offsetHeight;
2910
- const s = Math.min(1, naturalW > 0 ? availW / naturalW : 1, naturalH > 0 ? availH / naturalH : 1);
2911
- if (s < 0.999)
2912
- host.style.transform = `translateX(-50%) scale(${s.toFixed(4)})`;
2913
- else
2914
- host.classList.remove('ge-fit');
2915
- return;
2916
- }
2917
- // 3) Fits the WIDTH but the stack (control row + any lifted WIN pill) is too TALL for a short
2918
- // frame replay/free-spins on Popout S. Shrink each piece toward its OWN edge so the bar
2919
- // keeps its full-width space-between layout (menu hard-left, controls hard-right), just
2920
- // lower NOT packed into a centred cluster. Scale by frame HEIGHT only.
2921
- const naturalH = host.offsetHeight;
2922
- if (naturalH > availH && naturalH > 0) {
2923
- const s = (availH / naturalH).toFixed(4);
2924
- const scaleEdge = (el, origin) => {
2925
- if (!el)
2926
- return;
2927
- el.style.transformOrigin = origin;
2928
- el.style.transform = `scale(${s})`;
2929
- };
2930
- scaleEdge(bar.querySelector('.ge-zone-left'), 'left bottom');
2931
- scaleEdge(bar.querySelector('.ge-zone-right'), 'right bottom');
2932
- scaleEdge(host.querySelector('.ge-winpill'), 'center bottom');
2911
+ const zoomBar = (z) => {
2912
+ const v = z < 0.999 ? z.toFixed(4) : '';
2913
+ for (const el of host.querySelectorAll('.ge-zone, .ge-winpill')) {
2914
+ if (v)
2915
+ el.style.setProperty('zoom', v);
2916
+ else
2917
+ el.style.removeProperty('zoom');
2918
+ }
2919
+ };
2920
+ const s = Math.max(GameShell.BAR_MIN_SCALE, Math.min(1, this.root.clientWidth / GameShell.BAR_REF_WIDTH));
2921
+ zoomBar(s);
2922
+ // Safety: a pathologically long balance/win can still overflow the frame at the screen zoom —
2923
+ // nudge the zoom down just enough that the far control (turbo) isn't clipped. Normal content
2924
+ // never triggers this, so base and replay keep the SAME zoom (no size change on mode switch).
2925
+ if (bar.scrollWidth > bar.clientWidth + 1 && bar.scrollWidth > 0) {
2926
+ zoomBar(s * (bar.clientWidth / bar.scrollWidth));
2933
2927
  }
2934
2928
  }
2935
2929
  /** Spacebar starts a spin — same path as the spin disc. Ignored when `features.spacebar` is
@@ -3043,9 +3037,12 @@ class GameShell extends EventEmitter {
3043
3037
  /** Fraction of the frame a card modal may occupy; the rest is breathing-room margin. Keeps
3044
3038
  * modals from filling a small popout edge-to-edge (so even short pickers scale down there). */
3045
3039
  static MODAL_FIT = 0.86;
3046
- /** Max fraction of the frame HEIGHT the bottom bar may occupy before it fit-scales down. Keeps the
3047
- * bar a consistent, small slice on short popouts in EVERY mode (base this already via width). */
3048
- static BAR_MAX_FRACTION = 0.27;
3040
+ /** The bar's design width (px). When the frame is narrower, the bar fit-scales DOWN with the
3041
+ * screen the SAME factor in every mode, so replay/free-spins shrink like base instead of
3042
+ * staying full-size on a popout. */
3043
+ static BAR_REF_WIDTH = 840;
3044
+ /** Lower bound on the bar fit-scale (guards a degenerate near-zero frame). */
3045
+ static BAR_MIN_SCALE = 0.5;
3049
3046
  fitSheet(root) {
3050
3047
  const card = root.querySelector('.ge-modal-card');
3051
3048
  if (!card)