@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.cjs.js CHANGED
@@ -1329,15 +1329,30 @@ const SHELL_CSS = SHELL_FONT_CSS + `
1329
1329
  /* the buy-bonus scroll area is a SIZE CONTAINER, so the cards' cqh units measure the overlay
1330
1330
  (the popout frame) and not the browser window — cards fit without any vertical scroll. */
1331
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; }
1332
1345
  /* buy-bonus uses the FULL overlay width (no 800px centre cap) so the card row isn't cropped at
1333
1346
  the sides; small horizontal padding keeps the cards off the screen edges. */
1334
- #${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); }
1335
1348
  #${SHELL_ROOT_ID} .ge-bb-grid { display:flex; gap:14px; justify-content:safe center; overflow-x:auto; overflow-y:hidden; padding-bottom:6px;
1336
1349
  scroll-snap-type:x proximity; -webkit-overflow-scrolling:touch; }
1337
1350
  /* the one knob that scales the whole card — cqh measures the overlay (popout frame), not the
1338
- 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. */
1339
1354
  #${SHELL_ROOT_ID} .ge-bb-grid .ge-bonus-card { flex:0 0 18em; scroll-snap-align:start;
1340
- font-size:clamp(7px, 3.6cqh, 12px); }
1355
+ font-size:clamp(4px, 3.4cqh, 12px); }
1341
1356
  /* mobile: vertical stack at a fixed, readable size — scroll the list, don't shrink the cards */
1342
1357
  #${SHELL_ROOT_ID}.ge-mobile .ge-bb-grid { display:flex; flex-direction:column; gap:14px; overflow:visible; }
1343
1358
  #${SHELL_ROOT_ID}.ge-mobile .ge-bb-grid .ge-bonus-card { flex:0 0 auto; font-size:12px; }
@@ -1390,6 +1405,24 @@ const SHELL_CSS = SHELL_FONT_CSS + `
1390
1405
  #${SHELL_ROOT_ID} .ge-bb-betval span { display:block; font-size:7px; font-weight:600; letter-spacing:.14em; text-transform:uppercase;
1391
1406
  color:var(--shell-plaque-label); }
1392
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); }
1393
1426
 
1394
1427
  /* ═══ base/wide plaque bar — grouped dark + glass panels (reference-style) ═══ */
1395
1428
  #${SHELL_ROOT_ID} .ge-zone-plaques { gap:0; } /* panels connect; buttons overlap */
@@ -1963,7 +1996,7 @@ function openSettingsModal(shell) {
1963
1996
 
1964
1997
  // AUTO-GENERATED by scripts/gen-version.mjs — do not edit. Mirrors package.json "version".
1965
1998
  /** The @energy8platform/platform-core package version, stamped at build time. */
1966
- const PACKAGE_VERSION = '0.25.1';
1999
+ const PACKAGE_VERSION = '0.25.3';
1967
2000
 
1968
2001
  const SVG_NS = 'http://www.w3.org/2000/svg';
1969
2002
  function openGameInfoModal(shell) {
@@ -2618,7 +2651,7 @@ function buildReplayModal(shell, opts) {
2618
2651
  row('Base bet', fmt(bet));
2619
2652
  row('Cost multiplier', `${costMultiplier}×`);
2620
2653
  row('Total cost', fmt(bet * costMultiplier));
2621
- row('Payout multiplier', `${payoutMultiplier}×`);
2654
+ row('Win multiplier', `${payoutMultiplier}×`);
2622
2655
  row('Total win', fmtWin(payoutMultiplier * bet), true);
2623
2656
  ui.body.appendChild(rows);
2624
2657
  const actions = document.createElement('div');
@@ -2703,6 +2736,7 @@ const RULES = [
2703
2736
  ['pay out', 'win / won'],
2704
2737
  ['paid out', 'won'],
2705
2738
  ['pays out', 'win'],
2739
+ ['payout', 'win'], // single word; "pay out" (spaced) is handled above
2706
2740
  ['paytable', 'win table'],
2707
2741
  ['paylines', 'winlines'],
2708
2742
  ['payline', 'winline'],
@@ -2841,6 +2875,11 @@ class GameShell extends EventEmitter {
2841
2875
  host.classList.remove('ge-fit');
2842
2876
  host.style.transform = '';
2843
2877
  host.style.transformOrigin = '';
2878
+ // clear any per-zone height-scale from a prior pass
2879
+ for (const el of host.querySelectorAll('.ge-zone, .ge-winpill')) {
2880
+ el.style.transform = '';
2881
+ el.style.transformOrigin = '';
2882
+ }
2844
2883
  if (this.layout === 'mobile') {
2845
2884
  // Shrink the whole stack to fit narrow phones (mobile-s, or big balance/win/total-win
2846
2885
  // numbers in a row). The rows use space-between, so on overflow their content is
@@ -2857,20 +2896,43 @@ class GameShell extends EventEmitter {
2857
2896
  }
2858
2897
  return;
2859
2898
  }
2860
- if (bar.scrollWidth <= bar.clientWidth + 1)
2861
- return; // bar fits inline → leave it
2862
- // overflow: lift the pill onto its own row above the bar (flex column → real 8px gap)
2863
- if (pill) {
2899
+ const availW = this.root.clientWidth - 12;
2900
+ const availH = this.root.clientHeight * GameShell.BAR_MAX_FRACTION;
2901
+ // 1) If the inline row overflows the width, lift the WIN pill onto its own line above the bar
2902
+ // (keeps the controls as large as possible — base's wide row + a big WIN pill hit this).
2903
+ if (pill && bar.scrollWidth > bar.clientWidth + 1) {
2864
2904
  host.insertBefore(pill, bar);
2865
2905
  pill.classList.add('ge-up');
2866
2906
  }
2867
- if (bar.scrollWidth <= bar.clientWidth + 1)
2868
- return; // bar now fits full-width, pill above
2869
- // still too wide → shrink the whole stack (pill + bar) to fit, anchored bottom-centre
2870
- host.classList.add('ge-fit');
2871
- const natural = bar.offsetWidth, avail = this.root.clientWidth - 12;
2872
- const s = natural > 0 && avail > 0 ? Math.min(1, avail / natural) : 1;
2873
- host.style.transform = `translateX(-50%) scale(${s.toFixed(4)})`;
2907
+ // 2) Still too WIDE (the control row itself doesn't fit) → shrink-to-content + uniform scale,
2908
+ // centred. Only base's wide row reaches here.
2909
+ if (bar.scrollWidth > bar.clientWidth + 1) {
2910
+ host.classList.add('ge-fit');
2911
+ const naturalW = host.offsetWidth, naturalH = host.offsetHeight;
2912
+ const s = Math.min(1, naturalW > 0 ? availW / naturalW : 1, naturalH > 0 ? availH / naturalH : 1);
2913
+ if (s < 0.999)
2914
+ host.style.transform = `translateX(-50%) scale(${s.toFixed(4)})`;
2915
+ else
2916
+ host.classList.remove('ge-fit');
2917
+ return;
2918
+ }
2919
+ // 3) Fits the WIDTH but the stack (control row + any lifted WIN pill) is too TALL for a short
2920
+ // frame — replay/free-spins on Popout S. Shrink each piece toward its OWN edge so the bar
2921
+ // keeps its full-width space-between layout (menu hard-left, controls hard-right), just
2922
+ // lower — NOT packed into a centred cluster. Scale by frame HEIGHT only.
2923
+ const naturalH = host.offsetHeight;
2924
+ if (naturalH > availH && naturalH > 0) {
2925
+ const s = (availH / naturalH).toFixed(4);
2926
+ const scaleEdge = (el, origin) => {
2927
+ if (!el)
2928
+ return;
2929
+ el.style.transformOrigin = origin;
2930
+ el.style.transform = `scale(${s})`;
2931
+ };
2932
+ scaleEdge(bar.querySelector('.ge-zone-left'), 'left bottom');
2933
+ scaleEdge(bar.querySelector('.ge-zone-right'), 'right bottom');
2934
+ scaleEdge(host.querySelector('.ge-winpill'), 'center bottom');
2935
+ }
2874
2936
  }
2875
2937
  /** Spacebar starts a spin — same path as the spin disc. Ignored when `features.spacebar` is
2876
2938
  * false, while a spin is running, while autoplay is active, outside base mode, when an
@@ -2983,6 +3045,9 @@ class GameShell extends EventEmitter {
2983
3045
  /** Fraction of the frame a card modal may occupy; the rest is breathing-room margin. Keeps
2984
3046
  * modals from filling a small popout edge-to-edge (so even short pickers scale down there). */
2985
3047
  static MODAL_FIT = 0.86;
3048
+ /** Max fraction of the frame HEIGHT the bottom bar may occupy before it fit-scales down. Keeps the
3049
+ * bar a consistent, small slice on short popouts in EVERY mode (base ≈ this already via width). */
3050
+ static BAR_MAX_FRACTION = 0.27;
2986
3051
  fitSheet(root) {
2987
3052
  const card = root.querySelector('.ge-modal-card');
2988
3053
  if (!card)