@energy8platform/platform-core 0.25.1 → 0.25.3

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
@@ -1327,15 +1327,30 @@ const SHELL_CSS = SHELL_FONT_CSS + `
1327
1327
  /* the buy-bonus scroll area is a SIZE CONTAINER, so the cards' cqh units measure the overlay
1328
1328
  (the popout frame) and not the browser window — cards fit without any vertical scroll. */
1329
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; }
1330
1343
  /* buy-bonus uses the FULL overlay width (no 800px centre cap) so the card row isn't cropped at
1331
1344
  the sides; small horizontal padding keeps the cards off the screen edges. */
1332
- #${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); }
1333
1346
  #${SHELL_ROOT_ID} .ge-bb-grid { display:flex; gap:14px; justify-content:safe center; overflow-x:auto; overflow-y:hidden; padding-bottom:6px;
1334
1347
  scroll-snap-type:x proximity; -webkit-overflow-scrolling:touch; }
1335
1348
  /* the one knob that scales the whole card — cqh measures the overlay (popout frame), not the
1336
- 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. */
1337
1352
  #${SHELL_ROOT_ID} .ge-bb-grid .ge-bonus-card { flex:0 0 18em; scroll-snap-align:start;
1338
- font-size:clamp(7px, 3.6cqh, 12px); }
1353
+ font-size:clamp(4px, 3.4cqh, 12px); }
1339
1354
  /* mobile: vertical stack at a fixed, readable size — scroll the list, don't shrink the cards */
1340
1355
  #${SHELL_ROOT_ID}.ge-mobile .ge-bb-grid { display:flex; flex-direction:column; gap:14px; overflow:visible; }
1341
1356
  #${SHELL_ROOT_ID}.ge-mobile .ge-bb-grid .ge-bonus-card { flex:0 0 auto; font-size:12px; }
@@ -1388,6 +1403,24 @@ const SHELL_CSS = SHELL_FONT_CSS + `
1388
1403
  #${SHELL_ROOT_ID} .ge-bb-betval span { display:block; font-size:7px; font-weight:600; letter-spacing:.14em; text-transform:uppercase;
1389
1404
  color:var(--shell-plaque-label); }
1390
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); }
1391
1424
 
1392
1425
  /* ═══ base/wide plaque bar — grouped dark + glass panels (reference-style) ═══ */
1393
1426
  #${SHELL_ROOT_ID} .ge-zone-plaques { gap:0; } /* panels connect; buttons overlap */
@@ -1961,7 +1994,7 @@ function openSettingsModal(shell) {
1961
1994
 
1962
1995
  // AUTO-GENERATED by scripts/gen-version.mjs — do not edit. Mirrors package.json "version".
1963
1996
  /** The @energy8platform/platform-core package version, stamped at build time. */
1964
- const PACKAGE_VERSION = '0.25.1';
1997
+ const PACKAGE_VERSION = '0.25.3';
1965
1998
 
1966
1999
  const SVG_NS = 'http://www.w3.org/2000/svg';
1967
2000
  function openGameInfoModal(shell) {
@@ -2616,7 +2649,7 @@ function buildReplayModal(shell, opts) {
2616
2649
  row('Base bet', fmt(bet));
2617
2650
  row('Cost multiplier', `${costMultiplier}×`);
2618
2651
  row('Total cost', fmt(bet * costMultiplier));
2619
- row('Payout multiplier', `${payoutMultiplier}×`);
2652
+ row('Win multiplier', `${payoutMultiplier}×`);
2620
2653
  row('Total win', fmtWin(payoutMultiplier * bet), true);
2621
2654
  ui.body.appendChild(rows);
2622
2655
  const actions = document.createElement('div');
@@ -2701,6 +2734,7 @@ const RULES = [
2701
2734
  ['pay out', 'win / won'],
2702
2735
  ['paid out', 'won'],
2703
2736
  ['pays out', 'win'],
2737
+ ['payout', 'win'], // single word; "pay out" (spaced) is handled above
2704
2738
  ['paytable', 'win table'],
2705
2739
  ['paylines', 'winlines'],
2706
2740
  ['payline', 'winline'],
@@ -2839,6 +2873,11 @@ class GameShell extends EventEmitter {
2839
2873
  host.classList.remove('ge-fit');
2840
2874
  host.style.transform = '';
2841
2875
  host.style.transformOrigin = '';
2876
+ // clear any per-zone height-scale from a prior pass
2877
+ for (const el of host.querySelectorAll('.ge-zone, .ge-winpill')) {
2878
+ el.style.transform = '';
2879
+ el.style.transformOrigin = '';
2880
+ }
2842
2881
  if (this.layout === 'mobile') {
2843
2882
  // Shrink the whole stack to fit narrow phones (mobile-s, or big balance/win/total-win
2844
2883
  // numbers in a row). The rows use space-between, so on overflow their content is
@@ -2855,20 +2894,43 @@ class GameShell extends EventEmitter {
2855
2894
  }
2856
2895
  return;
2857
2896
  }
2858
- if (bar.scrollWidth <= bar.clientWidth + 1)
2859
- return; // bar fits inline → leave it
2860
- // overflow: lift the pill onto its own row above the bar (flex column → real 8px gap)
2861
- if (pill) {
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).
2901
+ if (pill && bar.scrollWidth > bar.clientWidth + 1) {
2862
2902
  host.insertBefore(pill, bar);
2863
2903
  pill.classList.add('ge-up');
2864
2904
  }
2865
- if (bar.scrollWidth <= bar.clientWidth + 1)
2866
- return; // bar now fits full-width, pill above
2867
- // still too wide → shrink the whole stack (pill + bar) to fit, anchored bottom-centre
2868
- host.classList.add('ge-fit');
2869
- const natural = bar.offsetWidth, avail = this.root.clientWidth - 12;
2870
- const s = natural > 0 && avail > 0 ? Math.min(1, avail / natural) : 1;
2871
- host.style.transform = `translateX(-50%) scale(${s.toFixed(4)})`;
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');
2933
+ }
2872
2934
  }
2873
2935
  /** Spacebar starts a spin — same path as the spin disc. Ignored when `features.spacebar` is
2874
2936
  * false, while a spin is running, while autoplay is active, outside base mode, when an
@@ -2981,6 +3043,9 @@ class GameShell extends EventEmitter {
2981
3043
  /** Fraction of the frame a card modal may occupy; the rest is breathing-room margin. Keeps
2982
3044
  * modals from filling a small popout edge-to-edge (so even short pickers scale down there). */
2983
3045
  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;
2984
3049
  fitSheet(root) {
2985
3050
  const card = root.querySelector('.ge-modal-card');
2986
3051
  if (!card)