@energy8platform/platform-core 0.22.0 → 0.23.1
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/README.md +1 -1
- package/dist/index.cjs.js +60 -46
- package/dist/index.cjs.js.map +1 -1
- package/dist/index.d.ts +0 -1
- package/dist/index.esm.js +60 -46
- package/dist/index.esm.js.map +1 -1
- package/dist/shell.cjs.js +60 -46
- package/dist/shell.cjs.js.map +1 -1
- package/dist/shell.d.ts +0 -1
- package/dist/shell.esm.js +60 -46
- package/dist/shell.esm.js.map +1 -1
- package/package.json +1 -1
- package/src/shell/components/BottomBar.ts +20 -15
- package/src/shell/components/GameInfo.ts +7 -7
- package/src/shell/i18n.ts +1 -0
- package/src/shell/shell.css.ts +31 -22
- package/src/shell/state.ts +1 -1
- package/src/shell/types.ts +0 -1
package/README.md
CHANGED
|
@@ -714,7 +714,7 @@ the previous value.
|
|
|
714
714
|
shell.setBalance(n); shell.setWin(n); shell.setBet(n);
|
|
715
715
|
shell.setBusy(true); // disables controls mid-spin
|
|
716
716
|
shell.setMode('freeSpins');
|
|
717
|
-
shell.setFreeSpins({ current: 1, total: 10, totalWin: 0
|
|
717
|
+
shell.setFreeSpins({ current: 1, total: 10, totalWin: 0 }); // Free Spins + Total Win bar readout
|
|
718
718
|
shell.setAutoplay({ active: true, remaining: 25 });
|
|
719
719
|
shell.setTurbo(2);
|
|
720
720
|
shell.setBuyBonusEnabled(false); // grey out BUY BONUS (e.g. insufficient balance)
|
package/dist/index.cjs.js
CHANGED
|
@@ -963,7 +963,7 @@ function createInitialState(config) {
|
|
|
963
963
|
autoplay: { active: false, remaining: 0 },
|
|
964
964
|
turbo: 0,
|
|
965
965
|
buyBonusEnabled: true,
|
|
966
|
-
freeSpins: { current: 0, total: 0, totalWin: 0
|
|
966
|
+
freeSpins: { current: 0, total: 0, totalWin: 0 },
|
|
967
967
|
activeFeature: null,
|
|
968
968
|
};
|
|
969
969
|
}
|
|
@@ -1078,6 +1078,7 @@ const SHELL_ROOT_ID = '__ge-game-shell__';
|
|
|
1078
1078
|
const SHELL_CSS = SHELL_FONT_CSS + `
|
|
1079
1079
|
#${SHELL_ROOT_ID} {
|
|
1080
1080
|
position: absolute; inset: 0;
|
|
1081
|
+
container-type: size; /* query container → centred modals size in cq units (responsive on every screen) */
|
|
1081
1082
|
pointer-events: none; z-index: 9000;
|
|
1082
1083
|
font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
|
1083
1084
|
color: var(--shell-fg);
|
|
@@ -1289,16 +1290,18 @@ const SHELL_CSS = SHELL_FONT_CSS + `
|
|
|
1289
1290
|
/* the buy-bonus scroll area is a SIZE CONTAINER, so the cards' cqh units measure the overlay
|
|
1290
1291
|
(the popout frame) and not the browser window — cards fit without any vertical scroll. */
|
|
1291
1292
|
#${SHELL_ROOT_ID} [data-ge="buybonus-overlay"] .ge-ov-scroll { container-type:size; }
|
|
1292
|
-
|
|
1293
|
-
|
|
1293
|
+
/* buy-bonus uses the FULL overlay width (no 800px centre cap) so the card row isn't cropped at
|
|
1294
|
+
the sides; small horizontal padding keeps the cards off the screen edges. */
|
|
1295
|
+
#${SHELL_ROOT_ID} [data-ge="buybonus-overlay"] .ge-ov-body { max-width:none; padding:clamp(8px,3cqh,16px) clamp(12px,3vw,28px); }
|
|
1296
|
+
#${SHELL_ROOT_ID} .ge-bb-grid { display:flex; gap:14px; justify-content:safe center; overflow-x:auto; overflow-y:hidden; padding-bottom:6px;
|
|
1294
1297
|
scroll-snap-type:x proximity; -webkit-overflow-scrolling:touch; }
|
|
1295
1298
|
/* the one knob that scales the whole card — cqh measures the overlay (popout frame), not the
|
|
1296
1299
|
browser window, so cards shrink to fit the real container height. */
|
|
1297
|
-
#${SHELL_ROOT_ID} .ge-bb-grid .ge-bonus-card { flex:0 0
|
|
1298
|
-
font-size:clamp(7px,
|
|
1300
|
+
#${SHELL_ROOT_ID} .ge-bb-grid .ge-bonus-card { flex:0 0 18em; scroll-snap-align:start;
|
|
1301
|
+
font-size:clamp(7px, 3.6cqh, 12px); }
|
|
1299
1302
|
/* mobile: vertical stack at a fixed, readable size — scroll the list, don't shrink the cards */
|
|
1300
1303
|
#${SHELL_ROOT_ID}.ge-mobile .ge-bb-grid { display:flex; flex-direction:column; gap:14px; overflow:visible; }
|
|
1301
|
-
#${SHELL_ROOT_ID}.ge-mobile .ge-bb-grid .ge-bonus-card { flex:0 0 auto; font-size:
|
|
1304
|
+
#${SHELL_ROOT_ID}.ge-mobile .ge-bb-grid .ge-bonus-card { flex:0 0 auto; font-size:12px; }
|
|
1302
1305
|
#${SHELL_ROOT_ID} .ge-bonus-card { display:flex; flex-direction:column; border-radius:1.4em; overflow:hidden;
|
|
1303
1306
|
background:var(--shell-plaque-glass); border:1px solid var(--shell-plaque-line); color:#fff; text-align:center;
|
|
1304
1307
|
pointer-events:auto; cursor:pointer; transition:box-shadow .12s ease, background .12s ease; }
|
|
@@ -1353,8 +1356,10 @@ const SHELL_CSS = SHELL_FONT_CSS + `
|
|
|
1353
1356
|
border-radius:16px; padding:0 20px; gap:18px; }
|
|
1354
1357
|
#${SHELL_ROOT_ID} .ge-pl-dark { background:var(--shell-plaque-dark); }
|
|
1355
1358
|
#${SHELL_ROOT_ID} .ge-pl-glass { background:var(--shell-plaque-glass); }
|
|
1356
|
-
/* FS
|
|
1357
|
-
|
|
1359
|
+
/* FS/replay left blocks — Free Spins counter (compact) + Total Win, standalone glass plaques
|
|
1360
|
+
sitting just right of the balance pill */
|
|
1361
|
+
#${SHELL_ROOT_ID} .ge-pl-fs, #${SHELL_ROOT_ID} .ge-pl-totalwin { margin-left:8px; }
|
|
1362
|
+
#${SHELL_ROOT_ID} .ge-pl-fs { padding:0 16px; }
|
|
1358
1363
|
#${SHELL_ROOT_ID} .ge-pl .ge-rd { color:#fff; text-shadow:none; }
|
|
1359
1364
|
#${SHELL_ROOT_ID} .ge-pl .ge-rd .ge-lbl { color:var(--shell-plaque-label); }
|
|
1360
1365
|
#${SHELL_ROOT_ID} .ge-pl .ge-iconbtn { color:#fff; }
|
|
@@ -1403,40 +1408,44 @@ const SHELL_CSS = SHELL_FONT_CSS + `
|
|
|
1403
1408
|
align-items:center; justify-content:center; padding:clamp(10px,4vh,24px); box-sizing:border-box;
|
|
1404
1409
|
background:rgba(12,17,28,.5); backdrop-filter:blur(var(--ge-sheet-blur,20px)) saturate(120%);
|
|
1405
1410
|
-webkit-backdrop-filter:blur(var(--ge-sheet-blur,20px)) saturate(120%); animation:ge-ov-in .16s ease-out; }
|
|
1406
|
-
/*
|
|
1407
|
-
|
|
1408
|
-
|
|
1411
|
+
/* Card sizes in cq units of the shell root → responsive on EVERY screen, not just popouts. The
|
|
1412
|
+
card's font-size is the one knob (clamped for readability); everything inside is em-relative so
|
|
1413
|
+
the whole card scales as a unit. GameShell.fitModal() still transform-scales it down as a
|
|
1414
|
+
backstop for very short popouts. */
|
|
1415
|
+
#${SHELL_ROOT_ID} .ge-modal-card { font-size:clamp(11px, 2cqmin, 15px); width:100%; max-width:28em; box-sizing:border-box;
|
|
1416
|
+
overflow:hidden; transform-origin:center center; background:var(--shell-plaque-solid); border-radius:1.3em;
|
|
1417
|
+
display:flex; flex-direction:column; }
|
|
1409
1418
|
/* ✕ pinned to the overlay corner (the screen), not the card */
|
|
1410
1419
|
#${SHELL_ROOT_ID} .ge-modal-close { position:absolute; top:12px; right:12px; z-index:2; width:36px; height:36px;
|
|
1411
1420
|
border:none; border-radius:50%; cursor:pointer; pointer-events:auto; background:var(--shell-plaque-dark); color:#fff;
|
|
1412
1421
|
display:flex; align-items:center; justify-content:center; font-size:20px; transition:background .12s ease, color .12s ease; }
|
|
1413
1422
|
#${SHELL_ROOT_ID} .ge-modal-close:hover { background:var(--shell-plaque-glass); color:var(--shell-accent); }
|
|
1414
|
-
#${SHELL_ROOT_ID} .ge-modal-body { padding:
|
|
1423
|
+
#${SHELL_ROOT_ID} .ge-modal-body { padding:1.2em; display:flex; flex-direction:column; gap:1.05em; }
|
|
1415
1424
|
#${SHELL_ROOT_ID} .ge-modal-title { margin:0; text-align:center; color:var(--card-acc, var(--shell-accent));
|
|
1416
|
-
font-weight:800; letter-spacing:.04em; text-transform:uppercase; font-size:
|
|
1417
|
-
#${SHELL_ROOT_ID} .ge-modal-text { margin:0; text-align:center; color:rgba(255,255,255,.85); font-size
|
|
1418
|
-
#${SHELL_ROOT_ID} .ge-sheet-grid { display:grid; gap
|
|
1425
|
+
font-weight:800; letter-spacing:.04em; text-transform:uppercase; font-size:1.2em; }
|
|
1426
|
+
#${SHELL_ROOT_ID} .ge-modal-text { margin:0; text-align:center; color:rgba(255,255,255,.85); font-size:.93em; line-height:1.5; }
|
|
1427
|
+
#${SHELL_ROOT_ID} .ge-sheet-grid { display:grid; gap:.65em; }
|
|
1419
1428
|
#${SHELL_ROOT_ID} .ge-chip { pointer-events:auto; cursor:pointer; border:1px solid var(--shell-plaque-line);
|
|
1420
|
-
border-radius
|
|
1421
|
-
font-variant-numeric:tabular-nums; padding
|
|
1429
|
+
border-radius:.8em; background:rgba(255,255,255,.04); color:#fff; font-size:1em; font-weight:700;
|
|
1430
|
+
font-variant-numeric:tabular-nums; padding:.8em .55em; transition:background .12s ease, border-color .12s ease; }
|
|
1422
1431
|
#${SHELL_ROOT_ID} .ge-chip:hover { background:var(--shell-plaque-glass-hover); }
|
|
1423
1432
|
#${SHELL_ROOT_ID} .ge-chip.ge-on { border-color:var(--shell-accent); background:var(--shell-accent); color:#fff; }
|
|
1424
1433
|
/* full-bleed footer button(s), flush to the card's bottom edge (card clips the corners) */
|
|
1425
1434
|
#${SHELL_ROOT_ID} .ge-modal-actions { display:flex; }
|
|
1426
1435
|
#${SHELL_ROOT_ID} .ge-modal-actions > * { flex:1; }
|
|
1427
|
-
#${SHELL_ROOT_ID} .ge-modal-btn { width:100%; border:none; padding:
|
|
1436
|
+
#${SHELL_ROOT_ID} .ge-modal-btn { width:100%; border:none; padding:1.05em; font-size:1em; font-weight:800;
|
|
1428
1437
|
letter-spacing:.04em; text-transform:uppercase; cursor:pointer; pointer-events:auto; transition:filter .12s ease; }
|
|
1429
1438
|
#${SHELL_ROOT_ID} .ge-modal-btn:hover:not([disabled]) { filter:brightness(1.08); }
|
|
1430
1439
|
#${SHELL_ROOT_ID} .ge-modal-btn--accent { background:var(--card-acc, var(--shell-accent)); color:#fff; }
|
|
1431
1440
|
#${SHELL_ROOT_ID} .ge-modal-btn--ghost { background:var(--shell-plaque-glass-hover); color:#fff; }
|
|
1432
1441
|
/* replay summary — label/value rows, accented total-win row */
|
|
1433
1442
|
#${SHELL_ROOT_ID} .ge-replay-rows { display:flex; flex-direction:column; }
|
|
1434
|
-
#${SHELL_ROOT_ID} .ge-replay-row { display:flex; justify-content:space-between; align-items:baseline; gap:
|
|
1443
|
+
#${SHELL_ROOT_ID} .ge-replay-row { display:flex; justify-content:space-between; align-items:baseline; gap:1.05em; padding:.73em .13em; }
|
|
1435
1444
|
#${SHELL_ROOT_ID} .ge-replay-row + .ge-replay-row { border-top:1px solid var(--shell-plaque-line); }
|
|
1436
|
-
#${SHELL_ROOT_ID} .ge-replay-row span { color:var(--shell-plaque-label); text-transform:uppercase; letter-spacing:.07em; font-size
|
|
1437
|
-
#${SHELL_ROOT_ID} .ge-replay-row b { color:#fff; font-weight:800; font-size:
|
|
1438
|
-
#${SHELL_ROOT_ID} .ge-replay-total span { color:#fff; font-size
|
|
1439
|
-
#${SHELL_ROOT_ID} .ge-replay-total b { color:var(--shell-accent); font-size:
|
|
1445
|
+
#${SHELL_ROOT_ID} .ge-replay-row span { color:var(--shell-plaque-label); text-transform:uppercase; letter-spacing:.07em; font-size:.73em; font-weight:700; }
|
|
1446
|
+
#${SHELL_ROOT_ID} .ge-replay-row b { color:#fff; font-weight:800; font-size:1em; font-variant-numeric:tabular-nums; }
|
|
1447
|
+
#${SHELL_ROOT_ID} .ge-replay-total span { color:#fff; font-size:.8em; }
|
|
1448
|
+
#${SHELL_ROOT_ID} .ge-replay-total b { color:var(--shell-accent); font-size:1.27em; }
|
|
1440
1449
|
|
|
1441
1450
|
#${SHELL_ROOT_ID}.ge-shell-hidden { opacity:0; pointer-events:none; transition:opacity .25s ease; }
|
|
1442
1451
|
`;
|
|
@@ -1625,10 +1634,13 @@ function renderBottomBar(shell) {
|
|
|
1625
1634
|
bar.dataset.geMode = state.mode;
|
|
1626
1635
|
// menu icon button (always)
|
|
1627
1636
|
const menu = iconBtn('menu', 'menu', () => shell.openMenu());
|
|
1628
|
-
// All three modes share the base plaque layout. FS/replay hide the controls that
|
|
1629
|
-
//
|
|
1637
|
+
// All three modes share the base plaque layout. FS/replay hide the controls that don't apply
|
|
1638
|
+
// and add Free Spins + Total Win blocks on the left; the per-spin WIN uses the base pill.
|
|
1630
1639
|
const isBase = state.mode === 'base';
|
|
1631
1640
|
const isFS = state.mode === 'freeSpins';
|
|
1641
|
+
// FS always shows the spins counter + accumulated Total Win (even €0); a replay shows them
|
|
1642
|
+
// only when it's a free-spins replay (freeSpins.total > 0).
|
|
1643
|
+
const showFsBlocks = isFS || (state.mode === 'replay' && state.freeSpins.total > 0);
|
|
1632
1644
|
const balance = readout('balance', shell.t('Balance'), fmt(state.balance));
|
|
1633
1645
|
// With a feature active (e.g. Ante) the BET readout shows the effective stake, tinted with
|
|
1634
1646
|
// the feature accent; the base state.bet is unchanged and returns once the feature is off.
|
|
@@ -1655,22 +1667,25 @@ function renderBottomBar(shell) {
|
|
|
1655
1667
|
buy = config.features.buyBonus !== false ? buyBtn(shell) : null;
|
|
1656
1668
|
}
|
|
1657
1669
|
const winEl = state.win > 0 ? readout('win', shell.t('Win'), fmt(state.win)) : null;
|
|
1658
|
-
// FS
|
|
1659
|
-
const fsCounter =
|
|
1660
|
-
const fsTotalWin =
|
|
1661
|
-
const fsLastWin = isFS ? readout('fs-lastwin', shell.t('Last win'), fmt(state.freeSpins.lastWin)) : null;
|
|
1670
|
+
// FS/replay left blocks: spins counter + accumulated Total Win (shown even at €0).
|
|
1671
|
+
const fsCounter = showFsBlocks ? readout('fs-counter', shell.t('Free spins'), `${state.freeSpins.current} / ${state.freeSpins.total}`) : null;
|
|
1672
|
+
const fsTotalWin = showFsBlocks ? readout('fs-totalwin', shell.t('Total win'), fmt(state.freeSpins.totalWin)) : null;
|
|
1662
1673
|
if (mobile) {
|
|
1663
|
-
// rows: [balance · win
|
|
1664
|
-
|
|
1665
|
-
|
|
1666
|
-
|
|
1674
|
+
// rows: [balance · win] · [menu · auto · spin · FS counter · Total Win · turbo · buy] · [− bet +]
|
|
1675
|
+
// FS counter + Total Win live in the controls row (alongside menu/turbo), not the top readouts.
|
|
1676
|
+
bar.appendChild(plaque('ge-m-top ge-pl ge-pl-glass', compact([balance, winEl])));
|
|
1677
|
+
const center = isBase ? spin : null;
|
|
1678
|
+
bar.appendChild(plaque('ge-m-controls ge-pl-dark', compact([menu, auto, center, fsCounter, fsTotalWin, turbo, buy])));
|
|
1667
1679
|
bar.appendChild(plaque('ge-m-bet ge-pl ge-pl-dark', compact([betDown, betValue, betUp])));
|
|
1668
1680
|
}
|
|
1669
1681
|
else {
|
|
1670
|
-
// LEFT: [menu] ⊐ BUY BONUS coin ⊏ [balance]
|
|
1682
|
+
// LEFT: [menu] ⊐ BUY BONUS coin ⊏ [balance] · [Free Spins] · [Total Win]
|
|
1683
|
+
// (the last two only render in FS / a free-spins replay)
|
|
1671
1684
|
const menuPlaque = plaque('ge-pl ge-pl-dark ge-pl-menu', [menu]);
|
|
1672
1685
|
const balPlaque = plaque('ge-pl ge-pl-glass ge-pl-bal', [balance]);
|
|
1673
|
-
const
|
|
1686
|
+
const fsPlaque = fsCounter ? plaque('ge-pl ge-pl-glass ge-pl-fs', [fsCounter]) : null;
|
|
1687
|
+
const totalWinPlaque = fsTotalWin ? plaque('ge-pl ge-pl-glass ge-pl-totalwin', [fsTotalWin]) : null;
|
|
1688
|
+
const left = zone('ge-zone-left ge-zone-plaques', ...compact([menuPlaque, buy, balPlaque, fsPlaque, totalWinPlaque]));
|
|
1674
1689
|
// RIGHT: [bet (+ step)] · |divider| · [auto · SPIN · turbo]
|
|
1675
1690
|
const betKids = [betValue];
|
|
1676
1691
|
if (betUp && betDown) {
|
|
@@ -1686,11 +1701,9 @@ function renderBottomBar(shell) {
|
|
|
1686
1701
|
spinWrap.className = 'ge-spinwrap ge-pl-dark';
|
|
1687
1702
|
spinWrap.append(...compact([auto, spin, turbo]));
|
|
1688
1703
|
const right = zone('ge-zone-right ge-zone-plaques', betPlaque, divider, spinWrap);
|
|
1689
|
-
// MIDDLE:
|
|
1704
|
+
// MIDDLE: per-spin WIN pill in every mode — lifts above the bar on overflow.
|
|
1690
1705
|
let middle = null;
|
|
1691
|
-
if (
|
|
1692
|
-
middle = plaque('ge-pl ge-pl-glass ge-fscount', compact([fsLastWin, fsCounter, fsTotalWin]));
|
|
1693
|
-
else if (winEl) {
|
|
1706
|
+
if (winEl) {
|
|
1694
1707
|
winEl.classList.add('ge-winpill');
|
|
1695
1708
|
middle = winEl;
|
|
1696
1709
|
}
|
|
@@ -1908,7 +1921,7 @@ function openGameInfoModal(shell) {
|
|
|
1908
1921
|
}
|
|
1909
1922
|
function renderSection(shell, s) {
|
|
1910
1923
|
switch (s.type) {
|
|
1911
|
-
case 'modes': return sectionModes(s.modes, sec('info-modes', s.title, shell.t('Modes')));
|
|
1924
|
+
case 'modes': return sectionModes(shell, s.modes, sec('info-modes', s.title, shell.t('Modes')));
|
|
1912
1925
|
case 'controls': return sectionControls(shell, sec('info-controls', s.title, shell.t('Controls')));
|
|
1913
1926
|
case 'paytable': return sectionPaytable(s.rows, sec('info-paytable', s.title, shell.t('Paytable')));
|
|
1914
1927
|
case 'wins': return sectionWins(s, sec('info-wins', s.title, shell.t(winFallbackTitle(s.kind))));
|
|
@@ -1929,25 +1942,25 @@ function sec(ge, title, fallback) {
|
|
|
1929
1942
|
return el;
|
|
1930
1943
|
}
|
|
1931
1944
|
// ── modes (rows — varying description lengths read better than fixed cards) ────
|
|
1932
|
-
function sectionModes(modes, el) {
|
|
1945
|
+
function sectionModes(shell, modes, el) {
|
|
1933
1946
|
const list = document.createElement('div');
|
|
1934
1947
|
list.className = 'ge-gi-modes';
|
|
1935
1948
|
for (const m of modes)
|
|
1936
|
-
list.appendChild(modeRow(m));
|
|
1949
|
+
list.appendChild(modeRow(shell, m));
|
|
1937
1950
|
el.appendChild(list);
|
|
1938
1951
|
return el;
|
|
1939
1952
|
}
|
|
1940
|
-
function modeRow(m) {
|
|
1953
|
+
function modeRow(shell, m) {
|
|
1941
1954
|
const row = document.createElement('div');
|
|
1942
1955
|
row.className = 'ge-gi-mode';
|
|
1943
1956
|
const stat = (label, val) => `<span class="ge-gi-mode-st"><span>${label}</span><b>${val}</b></span>`;
|
|
1944
1957
|
let stats = '';
|
|
1945
1958
|
if (m.price != null)
|
|
1946
|
-
stats += stat('Price', m.price);
|
|
1959
|
+
stats += stat(shell.t('Price'), m.price);
|
|
1947
1960
|
if (typeof m.rtp === 'number')
|
|
1948
|
-
stats += stat('RTP', `${m.rtp}%`);
|
|
1961
|
+
stats += stat(shell.t('RTP'), `${m.rtp}%`);
|
|
1949
1962
|
if (m.maxWin != null)
|
|
1950
|
-
stats += stat('Max win', m.maxWin);
|
|
1963
|
+
stats += stat(shell.t('Max win'), m.maxWin);
|
|
1951
1964
|
row.innerHTML =
|
|
1952
1965
|
`<div class="ge-gi-mode-top"><span class="ge-gi-mode-h">${m.title}</span>` +
|
|
1953
1966
|
(stats ? `<span class="ge-gi-mode-stats">${stats}</span>` : '') + '</div>' +
|
|
@@ -2575,6 +2588,7 @@ const RULES = [
|
|
|
2575
2588
|
['paid', 'won'],
|
|
2576
2589
|
['bought', 'instantly triggered'],
|
|
2577
2590
|
['purchase', 'play'],
|
|
2591
|
+
['price', 'play'],
|
|
2578
2592
|
['deposit', 'get coins'],
|
|
2579
2593
|
['withdraw', 'redeem'],
|
|
2580
2594
|
['currency', 'token'],
|