@energy8platform/platform-core 0.25.0 → 0.25.2

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.cjs.js CHANGED
@@ -851,6 +851,10 @@ function createCSSPreloader(container, config) {
851
851
  50% { opacity: 1; }
852
852
  }
853
853
  `;
854
+ // The absolute overlay needs a positioned ancestor. Only override a STATIC container, and
855
+ // remember the prior inline value so removeCSSPreloader can restore it (an inline `relative`
856
+ // left behind would beat the game's `#game { position: fixed; inset: 0 }` and collapse it).
857
+ const prevPosition = container.style.position;
854
858
  container.style.position = container.style.position || 'relative';
855
859
  container.appendChild(styleEl);
856
860
  container.appendChild(overlay);
@@ -861,6 +865,7 @@ function createCSSPreloader(container, config) {
861
865
  // We still record state so removeCSSPreloader works.
862
866
  state = {
863
867
  container,
868
+ prevPosition,
864
869
  overlay,
865
870
  styleEl,
866
871
  rectEl: null,
@@ -879,6 +884,7 @@ function createCSSPreloader(container, config) {
879
884
  }
880
885
  state = {
881
886
  container,
887
+ prevPosition,
882
888
  overlay,
883
889
  styleEl,
884
890
  rectEl,
@@ -957,7 +963,7 @@ function removeCSSPreloader(_container) {
957
963
  state.tapResolve = null;
958
964
  }
959
965
  state.removed = true;
960
- const { overlay, styleEl } = state;
966
+ const { overlay, styleEl, container, prevPosition } = state;
961
967
  overlay.classList.add('ge-preloader-hidden');
962
968
  return new Promise((resolve) => {
963
969
  let settled = false;
@@ -967,6 +973,9 @@ function removeCSSPreloader(_container) {
967
973
  settled = true;
968
974
  overlay.remove();
969
975
  styleEl.remove();
976
+ // Restore the container's original inline position so the game's own layout
977
+ // (e.g. `#game { position: fixed; inset: 0 }`) is no longer defeated by our inline override.
978
+ container.style.position = prevPosition;
970
979
  state = null;
971
980
  resolve();
972
981
  };
@@ -1320,15 +1329,30 @@ const SHELL_CSS = SHELL_FONT_CSS + `
1320
1329
  /* the buy-bonus scroll area is a SIZE CONTAINER, so the cards' cqh units measure the overlay
1321
1330
  (the popout frame) and not the browser window — cards fit without any vertical scroll. */
1322
1331
  #${SHELL_ROOT_ID} [data-ge="buybonus-overlay"] .ge-ov-scroll { container-type:size; }
1332
+ /* Popout / landscape: the horizontal card strip must be the ONLY scroll axis. The cqh-sized cards
1333
+ are meant to fit the overlay height (no vertical scroll), but the old 7px font floor stopped them
1334
+ shrinking on the tiniest popouts — so they spilled past the frame and the overlay grew a SECOND,
1335
+ vertical scrollbar (scrollable in both directions). Two changes keep it single-axis:
1336
+ 1. the card font floor drops (below) so cards actually fit the frame height; and
1337
+ 2. vertical scrolling is locked off as a belt-and-braces guard, with the strip centred (the body
1338
+ fills the frame and centres the grid) so any sub-pixel slack splits evenly instead of clipping
1339
+ the price/CTA off the bottom.
1340
+ This mirrors the pixi shell, which masks the cards and drag-scrolls on the X axis alone. (Centring
1341
+ lives on the body, not the scroll box, so the grid keeps its width and its own X-scroll.) */
1342
+ #${SHELL_ROOT_ID}:not(.ge-mobile) [data-ge="buybonus-overlay"] .ge-ov-scroll { overflow-y:hidden; }
1343
+ #${SHELL_ROOT_ID}:not(.ge-mobile) [data-ge="buybonus-overlay"] .ge-ov-body {
1344
+ min-height:100%; box-sizing:border-box; display:flex; flex-direction:column; justify-content:center; }
1323
1345
  /* buy-bonus uses the FULL overlay width (no 800px centre cap) so the card row isn't cropped at
1324
1346
  the sides; small horizontal padding keeps the cards off the screen edges. */
1325
- #${SHELL_ROOT_ID} [data-ge="buybonus-overlay"] .ge-ov-body { max-width:none; padding:clamp(8px,3cqh,16px) clamp(12px,3vw,28px); }
1347
+ #${SHELL_ROOT_ID} [data-ge="buybonus-overlay"] .ge-ov-body { max-width:none; padding:clamp(6px,2.5cqh,16px) clamp(12px,3vw,28px); }
1326
1348
  #${SHELL_ROOT_ID} .ge-bb-grid { display:flex; gap:14px; justify-content:safe center; overflow-x:auto; overflow-y:hidden; padding-bottom:6px;
1327
1349
  scroll-snap-type:x proximity; -webkit-overflow-scrolling:touch; }
1328
1350
  /* the one knob that scales the whole card — cqh measures the overlay (popout frame), not the
1329
- browser window, so cards shrink to fit the real container height. */
1351
+ browser window, so cards shrink to fit the real container height. The floor is deliberately tiny:
1352
+ on a 400×225 popout the whole card (incl. the CTA) only fits below ~5px, and a fully visible,
1353
+ single-axis-scrolling card beats a bigger one whose button is clipped or needs a 2nd scrollbar. */
1330
1354
  #${SHELL_ROOT_ID} .ge-bb-grid .ge-bonus-card { flex:0 0 18em; scroll-snap-align:start;
1331
- font-size:clamp(7px, 3.6cqh, 12px); }
1355
+ font-size:clamp(4px, 3.4cqh, 12px); }
1332
1356
  /* mobile: vertical stack at a fixed, readable size — scroll the list, don't shrink the cards */
1333
1357
  #${SHELL_ROOT_ID}.ge-mobile .ge-bb-grid { display:flex; flex-direction:column; gap:14px; overflow:visible; }
1334
1358
  #${SHELL_ROOT_ID}.ge-mobile .ge-bb-grid .ge-bonus-card { flex:0 0 auto; font-size:12px; }
@@ -1381,6 +1405,24 @@ const SHELL_CSS = SHELL_FONT_CSS + `
1381
1405
  #${SHELL_ROOT_ID} .ge-bb-betval span { display:block; font-size:7px; font-weight:600; letter-spacing:.14em; text-transform:uppercase;
1382
1406
  color:var(--shell-plaque-label); }
1383
1407
  #${SHELL_ROOT_ID} .ge-bb-betval b { font-size:14px; font-weight:800; font-variant-numeric:tabular-nums; color:#fff; }
1408
+ /* Popout S (short landscape): the header (title + ✕) and the bet footer are fixed-px and dwarf the
1409
+ shrunk cards. Scale the buy-bonus chrome down with the FRAME height. Units are cqh (the overlay is a
1410
+ size container below), NOT vh — vh tracks the browser window, which only equals the frame inside the
1411
+ Stake iframe, so it wouldn't shrink in the demo's device-frame view. Coefficients hit the normal cap
1412
+ by ~450px tall (Popout L) and shrink below that, to a readable floor at Popout S (225px). */
1413
+ #${SHELL_ROOT_ID} [data-ge="buybonus-overlay"] { container:ge-bb-frame / size; }
1414
+ #${SHELL_ROOT_ID} [data-ge="buybonus-overlay"] .ge-ov-head { padding:clamp(3px,1.33cqh,6px) 10px; }
1415
+ #${SHELL_ROOT_ID} [data-ge="buybonus-overlay"] .ge-ov-title { font-size:clamp(11px,3.5cqh,16px); }
1416
+ #${SHELL_ROOT_ID} [data-ge="buybonus-overlay"] .ge-ov-spacer { width:clamp(24px,7cqh,32px); }
1417
+ #${SHELL_ROOT_ID} [data-ge="buybonus-overlay"] .ge-ov-nav { width:clamp(24px,7cqh,32px); height:clamp(24px,7cqh,32px);
1418
+ font-size:clamp(14px,4cqh,18px); border-radius:clamp(7px,2cqh,9px); }
1419
+ #${SHELL_ROOT_ID} [data-ge="buybonus-overlay"] .ge-bb-betbar { padding:clamp(2px,.9cqh,4px); }
1420
+ #${SHELL_ROOT_ID} [data-ge="buybonus-overlay"] .ge-bb-betpill { padding:clamp(2px,.67cqh,3px) clamp(4px,1.1cqh,5px); }
1421
+ #${SHELL_ROOT_ID} [data-ge="buybonus-overlay"] .ge-bb-betstep { width:clamp(24px,7cqh,32px); height:clamp(24px,7cqh,32px);
1422
+ font-size:clamp(15px,4.4cqh,20px); }
1423
+ #${SHELL_ROOT_ID} [data-ge="buybonus-overlay"] .ge-bb-betval { min-width:clamp(62px,17.5cqh,80px); }
1424
+ #${SHELL_ROOT_ID} [data-ge="buybonus-overlay"] .ge-bb-betval b { font-size:clamp(11px,3.1cqh,14px); }
1425
+ #${SHELL_ROOT_ID} [data-ge="buybonus-overlay"] .ge-bb-betval span { font-size:clamp(6px,1.55cqh,7px); }
1384
1426
 
1385
1427
  /* ═══ base/wide plaque bar — grouped dark + glass panels (reference-style) ═══ */
1386
1428
  #${SHELL_ROOT_ID} .ge-zone-plaques { gap:0; } /* panels connect; buttons overlap */
@@ -1954,7 +1996,7 @@ function openSettingsModal(shell) {
1954
1996
 
1955
1997
  // AUTO-GENERATED by scripts/gen-version.mjs — do not edit. Mirrors package.json "version".
1956
1998
  /** The @energy8platform/platform-core package version, stamped at build time. */
1957
- const PACKAGE_VERSION = '0.25.0';
1999
+ const PACKAGE_VERSION = '0.25.2';
1958
2000
 
1959
2001
  const SVG_NS = 'http://www.w3.org/2000/svg';
1960
2002
  function openGameInfoModal(shell) {
@@ -2609,7 +2651,7 @@ function buildReplayModal(shell, opts) {
2609
2651
  row('Base bet', fmt(bet));
2610
2652
  row('Cost multiplier', `${costMultiplier}×`);
2611
2653
  row('Total cost', fmt(bet * costMultiplier));
2612
- row('Payout multiplier', `${payoutMultiplier}×`);
2654
+ row('Win multiplier', `${payoutMultiplier}×`);
2613
2655
  row('Total win', fmtWin(payoutMultiplier * bet), true);
2614
2656
  ui.body.appendChild(rows);
2615
2657
  const actions = document.createElement('div');
@@ -2694,6 +2736,7 @@ const RULES = [
2694
2736
  ['pay out', 'win / won'],
2695
2737
  ['paid out', 'won'],
2696
2738
  ['pays out', 'win'],
2739
+ ['payout', 'win'], // single word; "pay out" (spaced) is handled above
2697
2740
  ['paytable', 'win table'],
2698
2741
  ['paylines', 'winlines'],
2699
2742
  ['payline', 'winline'],
@@ -2848,20 +2891,29 @@ class GameShell extends EventEmitter {
2848
2891
  }
2849
2892
  return;
2850
2893
  }
2851
- if (bar.scrollWidth <= bar.clientWidth + 1)
2852
- return; // bar fits inline leave it
2853
- // overflow: lift the pill onto its own row above the bar (flex column real 8px gap)
2854
- if (pill) {
2894
+ // Scale the bar to the SCREEN, not the mode. Two independent limits, whichever bites harder:
2895
+ // width — the row mustn't exceed the frame (base's wide row hits this); and
2896
+ // height the bar mustn't eat more than BAR_MAX_FRACTION of a SHORT frame. Replay's narrow
2897
+ // row never overflows width, so without this it stayed full-size and ate ~47% of a Popout S
2898
+ // frame, overlapping the reels — while base (wide row) shrank to ~27%. Now every mode tracks
2899
+ // the frame size the same way.
2900
+ // Lift the WIN pill onto its own line first when the full-width row overflows (controls stay big).
2901
+ if (pill && bar.scrollWidth > bar.clientWidth + 1) {
2855
2902
  host.insertBefore(pill, bar);
2856
2903
  pill.classList.add('ge-up');
2857
2904
  }
2858
- if (bar.scrollWidth <= bar.clientWidth + 1)
2859
- return; // bar now fits full-width, pill above
2860
- // still too wide → shrink the whole stack (pill + bar) to fit, anchored bottom-centre
2905
+ // Measure natural CONTENT size — `.ge-fit` makes the bar shrink-to-content (width:max-content).
2861
2906
  host.classList.add('ge-fit');
2862
- const natural = bar.offsetWidth, avail = this.root.clientWidth - 12;
2863
- const s = natural > 0 && avail > 0 ? Math.min(1, avail / natural) : 1;
2864
- host.style.transform = `translateX(-50%) scale(${s.toFixed(4)})`;
2907
+ const availW = this.root.clientWidth - 12;
2908
+ const availH = this.root.clientHeight * GameShell.BAR_MAX_FRACTION;
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
+ }
2914
+ else {
2915
+ host.classList.remove('ge-fit'); // fits full-size → restore the normal full-width bar
2916
+ }
2865
2917
  }
2866
2918
  /** Spacebar starts a spin — same path as the spin disc. Ignored when `features.spacebar` is
2867
2919
  * false, while a spin is running, while autoplay is active, outside base mode, when an
@@ -2974,6 +3026,9 @@ class GameShell extends EventEmitter {
2974
3026
  /** Fraction of the frame a card modal may occupy; the rest is breathing-room margin. Keeps
2975
3027
  * modals from filling a small popout edge-to-edge (so even short pickers scale down there). */
2976
3028
  static MODAL_FIT = 0.86;
3029
+ /** Max fraction of the frame HEIGHT the bottom bar may occupy before it fit-scales down. Keeps the
3030
+ * bar a consistent, small slice on short popouts in EVERY mode (base ≈ this already via width). */
3031
+ static BAR_MAX_FRACTION = 0.27;
2977
3032
  fitSheet(root) {
2978
3033
  const card = root.querySelector('.ge-modal-card');
2979
3034
  if (!card)