@energy8platform/platform-core 0.23.0 → 0.23.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/README.md +21 -0
- package/dist/index.cjs.js +60 -29
- package/dist/index.cjs.js.map +1 -1
- package/dist/index.d.ts +27 -1
- package/dist/index.esm.js +60 -29
- package/dist/index.esm.js.map +1 -1
- package/dist/shell.cjs.js +60 -29
- package/dist/shell.cjs.js.map +1 -1
- package/dist/shell.d.ts +27 -1
- package/dist/shell.esm.js +60 -29
- package/dist/shell.esm.js.map +1 -1
- package/package.json +1 -1
- package/src/index.ts +1 -0
- package/src/shell/GameShell.ts +1 -0
- package/src/shell/components/BottomBar.ts +1 -1
- package/src/shell/components/BuyBonus.ts +24 -4
- package/src/shell/components/GameInfo.ts +7 -7
- package/src/shell/i18n.ts +1 -0
- package/src/shell/shell.css.ts +25 -18
- package/src/shell/types.ts +27 -0
package/dist/index.esm.js
CHANGED
|
@@ -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);
|
|
@@ -1294,16 +1295,18 @@ const SHELL_CSS = SHELL_FONT_CSS + `
|
|
|
1294
1295
|
scroll-snap-type:x proximity; -webkit-overflow-scrolling:touch; }
|
|
1295
1296
|
/* the one knob that scales the whole card — cqh measures the overlay (popout frame), not the
|
|
1296
1297
|
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,
|
|
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); }
|
|
1299
1300
|
/* mobile: vertical stack at a fixed, readable size — scroll the list, don't shrink the cards */
|
|
1300
1301
|
#${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:
|
|
1302
|
+
#${SHELL_ROOT_ID}.ge-mobile .ge-bb-grid .ge-bonus-card { flex:0 0 auto; font-size:12px; }
|
|
1302
1303
|
#${SHELL_ROOT_ID} .ge-bonus-card { display:flex; flex-direction:column; border-radius:1.4em; overflow:hidden;
|
|
1303
1304
|
background:var(--shell-plaque-glass); border:1px solid var(--shell-plaque-line); color:#fff; text-align:center;
|
|
1304
1305
|
pointer-events:auto; cursor:pointer; transition:box-shadow .12s ease, background .12s ease; }
|
|
1305
1306
|
#${SHELL_ROOT_ID} .ge-bonus-card:hover:not(.ge-bonus-off) {
|
|
1306
1307
|
box-shadow:0 0 0 1px var(--card-acc), 0 12px 34px -12px var(--card-acc); }
|
|
1308
|
+
/* custom card (BonusOption.custom): keep grid sizing + accent vars, drop the default chrome so the game owns the UI */
|
|
1309
|
+
#${SHELL_ROOT_ID} .ge-bonus-card--custom { background:none; border:none; cursor:default; }
|
|
1307
1310
|
#${SHELL_ROOT_ID} .ge-bonus-body { display:flex; flex-direction:column; align-items:center; flex:1; padding:1.25em 1.1em .9em; }
|
|
1308
1311
|
#${SHELL_ROOT_ID} .ge-bonus-title { font-size:1.3em; font-weight:800; letter-spacing:.04em; text-transform:uppercase;
|
|
1309
1312
|
color:var(--card-acc); margin-bottom:.75em; }
|
|
@@ -1405,40 +1408,44 @@ const SHELL_CSS = SHELL_FONT_CSS + `
|
|
|
1405
1408
|
align-items:center; justify-content:center; padding:clamp(10px,4vh,24px); box-sizing:border-box;
|
|
1406
1409
|
background:rgba(12,17,28,.5); backdrop-filter:blur(var(--ge-sheet-blur,20px)) saturate(120%);
|
|
1407
1410
|
-webkit-backdrop-filter:blur(var(--ge-sheet-blur,20px)) saturate(120%); animation:ge-ov-in .16s ease-out; }
|
|
1408
|
-
/*
|
|
1409
|
-
|
|
1410
|
-
|
|
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; }
|
|
1411
1418
|
/* ✕ pinned to the overlay corner (the screen), not the card */
|
|
1412
1419
|
#${SHELL_ROOT_ID} .ge-modal-close { position:absolute; top:12px; right:12px; z-index:2; width:36px; height:36px;
|
|
1413
1420
|
border:none; border-radius:50%; cursor:pointer; pointer-events:auto; background:var(--shell-plaque-dark); color:#fff;
|
|
1414
1421
|
display:flex; align-items:center; justify-content:center; font-size:20px; transition:background .12s ease, color .12s ease; }
|
|
1415
1422
|
#${SHELL_ROOT_ID} .ge-modal-close:hover { background:var(--shell-plaque-glass); color:var(--shell-accent); }
|
|
1416
|
-
#${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; }
|
|
1417
1424
|
#${SHELL_ROOT_ID} .ge-modal-title { margin:0; text-align:center; color:var(--card-acc, var(--shell-accent));
|
|
1418
|
-
font-weight:800; letter-spacing:.04em; text-transform:uppercase; font-size:
|
|
1419
|
-
#${SHELL_ROOT_ID} .ge-modal-text { margin:0; text-align:center; color:rgba(255,255,255,.85); font-size
|
|
1420
|
-
#${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; }
|
|
1421
1428
|
#${SHELL_ROOT_ID} .ge-chip { pointer-events:auto; cursor:pointer; border:1px solid var(--shell-plaque-line);
|
|
1422
|
-
border-radius
|
|
1423
|
-
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; }
|
|
1424
1431
|
#${SHELL_ROOT_ID} .ge-chip:hover { background:var(--shell-plaque-glass-hover); }
|
|
1425
1432
|
#${SHELL_ROOT_ID} .ge-chip.ge-on { border-color:var(--shell-accent); background:var(--shell-accent); color:#fff; }
|
|
1426
1433
|
/* full-bleed footer button(s), flush to the card's bottom edge (card clips the corners) */
|
|
1427
1434
|
#${SHELL_ROOT_ID} .ge-modal-actions { display:flex; }
|
|
1428
1435
|
#${SHELL_ROOT_ID} .ge-modal-actions > * { flex:1; }
|
|
1429
|
-
#${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;
|
|
1430
1437
|
letter-spacing:.04em; text-transform:uppercase; cursor:pointer; pointer-events:auto; transition:filter .12s ease; }
|
|
1431
1438
|
#${SHELL_ROOT_ID} .ge-modal-btn:hover:not([disabled]) { filter:brightness(1.08); }
|
|
1432
1439
|
#${SHELL_ROOT_ID} .ge-modal-btn--accent { background:var(--card-acc, var(--shell-accent)); color:#fff; }
|
|
1433
1440
|
#${SHELL_ROOT_ID} .ge-modal-btn--ghost { background:var(--shell-plaque-glass-hover); color:#fff; }
|
|
1434
1441
|
/* replay summary — label/value rows, accented total-win row */
|
|
1435
1442
|
#${SHELL_ROOT_ID} .ge-replay-rows { display:flex; flex-direction:column; }
|
|
1436
|
-
#${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; }
|
|
1437
1444
|
#${SHELL_ROOT_ID} .ge-replay-row + .ge-replay-row { border-top:1px solid var(--shell-plaque-line); }
|
|
1438
|
-
#${SHELL_ROOT_ID} .ge-replay-row span { color:var(--shell-plaque-label); text-transform:uppercase; letter-spacing:.07em; font-size
|
|
1439
|
-
#${SHELL_ROOT_ID} .ge-replay-row b { color:#fff; font-weight:800; font-size:
|
|
1440
|
-
#${SHELL_ROOT_ID} .ge-replay-total span { color:#fff; font-size
|
|
1441
|
-
#${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; }
|
|
1442
1449
|
|
|
1443
1450
|
#${SHELL_ROOT_ID}.ge-shell-hidden { opacity:0; pointer-events:none; transition:opacity .25s ease; }
|
|
1444
1451
|
`;
|
|
@@ -1657,7 +1664,7 @@ function renderBottomBar(shell) {
|
|
|
1657
1664
|
shell.openBetPicker(); });
|
|
1658
1665
|
spin = spinButton(shell);
|
|
1659
1666
|
auto = config.features.autoplay ? autoButton(shell) : null;
|
|
1660
|
-
buy = config.features.buyBonus !== false ? buyBtn(shell) : null;
|
|
1667
|
+
buy = (config.features.buyBonus !== false || config.onBonusBuy) ? buyBtn(shell) : null;
|
|
1661
1668
|
}
|
|
1662
1669
|
const winEl = state.win > 0 ? readout('win', shell.t('Win'), fmt(state.win)) : null;
|
|
1663
1670
|
// FS/replay left blocks: spins counter + accumulated Total Win (shown even at €0).
|
|
@@ -1914,7 +1921,7 @@ function openGameInfoModal(shell) {
|
|
|
1914
1921
|
}
|
|
1915
1922
|
function renderSection(shell, s) {
|
|
1916
1923
|
switch (s.type) {
|
|
1917
|
-
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')));
|
|
1918
1925
|
case 'controls': return sectionControls(shell, sec('info-controls', s.title, shell.t('Controls')));
|
|
1919
1926
|
case 'paytable': return sectionPaytable(s.rows, sec('info-paytable', s.title, shell.t('Paytable')));
|
|
1920
1927
|
case 'wins': return sectionWins(s, sec('info-wins', s.title, shell.t(winFallbackTitle(s.kind))));
|
|
@@ -1935,25 +1942,25 @@ function sec(ge, title, fallback) {
|
|
|
1935
1942
|
return el;
|
|
1936
1943
|
}
|
|
1937
1944
|
// ── modes (rows — varying description lengths read better than fixed cards) ────
|
|
1938
|
-
function sectionModes(modes, el) {
|
|
1945
|
+
function sectionModes(shell, modes, el) {
|
|
1939
1946
|
const list = document.createElement('div');
|
|
1940
1947
|
list.className = 'ge-gi-modes';
|
|
1941
1948
|
for (const m of modes)
|
|
1942
|
-
list.appendChild(modeRow(m));
|
|
1949
|
+
list.appendChild(modeRow(shell, m));
|
|
1943
1950
|
el.appendChild(list);
|
|
1944
1951
|
return el;
|
|
1945
1952
|
}
|
|
1946
|
-
function modeRow(m) {
|
|
1953
|
+
function modeRow(shell, m) {
|
|
1947
1954
|
const row = document.createElement('div');
|
|
1948
1955
|
row.className = 'ge-gi-mode';
|
|
1949
1956
|
const stat = (label, val) => `<span class="ge-gi-mode-st"><span>${label}</span><b>${val}</b></span>`;
|
|
1950
1957
|
let stats = '';
|
|
1951
1958
|
if (m.price != null)
|
|
1952
|
-
stats += stat('Price', m.price);
|
|
1959
|
+
stats += stat(shell.t('Price'), m.price);
|
|
1953
1960
|
if (typeof m.rtp === 'number')
|
|
1954
|
-
stats += stat('RTP', `${m.rtp}%`);
|
|
1961
|
+
stats += stat(shell.t('RTP'), `${m.rtp}%`);
|
|
1955
1962
|
if (m.maxWin != null)
|
|
1956
|
-
stats += stat('Max win', m.maxWin);
|
|
1963
|
+
stats += stat(shell.t('Max win'), m.maxWin);
|
|
1957
1964
|
row.innerHTML =
|
|
1958
1965
|
`<div class="ge-gi-mode-top"><span class="ge-gi-mode-h">${m.title}</span>` +
|
|
1959
1966
|
(stats ? `<span class="ge-gi-mode-stats">${stats}</span>` : '') + '</div>' +
|
|
@@ -2242,20 +2249,39 @@ function buildCard(shell, bonus, overlay) {
|
|
|
2242
2249
|
card.dataset.ge = `bonus-card-${bonus.id}`;
|
|
2243
2250
|
card.style.setProperty('--card-acc', accent);
|
|
2244
2251
|
card.style.setProperty('--card-ink', contrastText(accent));
|
|
2252
|
+
const enabled = isAffordable(shell, bonus);
|
|
2253
|
+
// Stack the confirm on top of the overlay grid (cancel returns to the grid). Re-checks
|
|
2254
|
+
// affordability at click time, so it's a safe no-op when the option can't be bought.
|
|
2255
|
+
const select = () => {
|
|
2256
|
+
if (!isAffordable(shell, bonus))
|
|
2257
|
+
return;
|
|
2258
|
+
overlay.appendChild(buildConfirm(shell, bonus, overlay));
|
|
2259
|
+
shell.fitModals();
|
|
2260
|
+
};
|
|
2261
|
+
// Game-supplied card UI: the shell keeps the wrapper (grid sizing + accent vars) and runs the
|
|
2262
|
+
// buy flow when the game calls ctx.select(); the game owns everything inside.
|
|
2263
|
+
if (bonus.custom) {
|
|
2264
|
+
card.classList.add('ge-bonus-card--custom');
|
|
2265
|
+
const price = bonus.priceMultiplier * shell.state.bet;
|
|
2266
|
+
card.appendChild(bonus.custom({
|
|
2267
|
+
bonus, bet: shell.state.bet, price,
|
|
2268
|
+
priceText: formatCurrency(price, shell.config.currency),
|
|
2269
|
+
disabled: !enabled, accent, select,
|
|
2270
|
+
}));
|
|
2271
|
+
return card;
|
|
2272
|
+
}
|
|
2245
2273
|
card.appendChild(cardBody(shell, bonus));
|
|
2246
2274
|
const cta = document.createElement('button');
|
|
2247
2275
|
cta.className = 'ge-bonus-cta';
|
|
2248
2276
|
cta.dataset.ge = `bonus-cta-${bonus.id}`;
|
|
2249
2277
|
cta.textContent = shell.t(actionLabel(bonus));
|
|
2250
2278
|
card.appendChild(cta);
|
|
2251
|
-
const enabled = isAffordable(shell, bonus);
|
|
2252
2279
|
if (!enabled) {
|
|
2253
2280
|
card.classList.add('ge-bonus-off');
|
|
2254
2281
|
cta.disabled = true;
|
|
2255
2282
|
}
|
|
2256
2283
|
else {
|
|
2257
|
-
|
|
2258
|
-
card.addEventListener('click', () => { overlay.appendChild(buildConfirm(shell, bonus, overlay)); shell.fitModals(); });
|
|
2284
|
+
card.addEventListener('click', select);
|
|
2259
2285
|
}
|
|
2260
2286
|
return card;
|
|
2261
2287
|
}
|
|
@@ -2581,6 +2607,7 @@ const RULES = [
|
|
|
2581
2607
|
['paid', 'won'],
|
|
2582
2608
|
['bought', 'instantly triggered'],
|
|
2583
2609
|
['purchase', 'play'],
|
|
2610
|
+
['price', 'play'],
|
|
2584
2611
|
['deposit', 'get coins'],
|
|
2585
2612
|
['withdraw', 'redeem'],
|
|
2586
2613
|
['currency', 'token'],
|
|
@@ -2844,6 +2871,10 @@ class GameShell extends EventEmitter {
|
|
|
2844
2871
|
openSettings() { this.emit('settingsOpen'); this.showModal(openSettingsModal(this)); }
|
|
2845
2872
|
openInfo() { this.emit('infoOpen'); this.showModal(openGameInfoModal(this)); }
|
|
2846
2873
|
openBuyBonus() {
|
|
2874
|
+
if (this.config.onBonusBuy) {
|
|
2875
|
+
this.config.onBonusBuy();
|
|
2876
|
+
return;
|
|
2877
|
+
} // game handles it (own UI)
|
|
2847
2878
|
const overlay = openBuyBonusOverlay(this);
|
|
2848
2879
|
if (overlay)
|
|
2849
2880
|
this.showModal(overlay);
|