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