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