@energy8platform/platform-core 0.21.0 → 0.23.0
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 +6 -4
- package/dist/index.cjs.js +58 -32
- package/dist/index.cjs.js.map +1 -1
- package/dist/index.d.ts +17 -6
- package/dist/index.esm.js +58 -32
- package/dist/index.esm.js.map +1 -1
- package/dist/shell.cjs.js +58 -32
- package/dist/shell.cjs.js.map +1 -1
- package/dist/shell.d.ts +17 -6
- package/dist/shell.esm.js +58 -32
- package/dist/shell.esm.js.map +1 -1
- package/package.json +1 -1
- package/src/index.ts +1 -0
- package/src/shell/GameShell.ts +5 -3
- package/src/shell/components/BottomBar.ts +20 -15
- package/src/shell/components/GameInfo.ts +1 -1
- package/src/shell/components/pickers.ts +17 -4
- package/src/shell/shell.css.ts +8 -4
- package/src/shell/state.ts +1 -1
- package/src/shell/theme.ts +3 -3
- package/src/shell/types.ts +13 -2
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
|
}
|
|
@@ -1046,9 +1046,9 @@ function buildThemeVars(theme = {}) {
|
|
|
1046
1046
|
// Plaque tokens — the grouped dark/glass panel language shared by the control bar
|
|
1047
1047
|
// AND the overlays. Scheme-independent (always dark, white-on-dark) so bar + overlays
|
|
1048
1048
|
// stay visually identical regardless of the dark/light `scheme`.
|
|
1049
|
-
`--shell-plaque-dark: rgba(6,9,15,.
|
|
1050
|
-
`--shell-plaque-glass: rgba(30,36,48,.
|
|
1051
|
-
`--shell-plaque-glass-hover: rgba(40,48,64,.
|
|
1049
|
+
`--shell-plaque-dark: rgba(6,9,15,.86)`,
|
|
1050
|
+
`--shell-plaque-glass: rgba(30,36,48,.70)`,
|
|
1051
|
+
`--shell-plaque-glass-hover: rgba(40,48,64,.86)`,
|
|
1052
1052
|
// Opaque surface for centred modals (confirm, bet/autoplay pickers) so they read solid,
|
|
1053
1053
|
// not see-through, over the frosted backdrop.
|
|
1054
1054
|
`--shell-plaque-solid: #1a2030`,
|
|
@@ -1287,8 +1287,10 @@ const SHELL_CSS = SHELL_FONT_CSS + `
|
|
|
1287
1287
|
/* the buy-bonus scroll area is a SIZE CONTAINER, so the cards' cqh units measure the overlay
|
|
1288
1288
|
(the popout frame) and not the browser window — cards fit without any vertical scroll. */
|
|
1289
1289
|
#${SHELL_ROOT_ID} [data-ge="buybonus-overlay"] .ge-ov-scroll { container-type:size; }
|
|
1290
|
-
|
|
1291
|
-
|
|
1290
|
+
/* buy-bonus uses the FULL overlay width (no 800px centre cap) so the card row isn't cropped at
|
|
1291
|
+
the sides; small horizontal padding keeps the cards off the screen edges. */
|
|
1292
|
+
#${SHELL_ROOT_ID} [data-ge="buybonus-overlay"] .ge-ov-body { max-width:none; padding:clamp(8px,3cqh,16px) clamp(12px,3vw,28px); }
|
|
1293
|
+
#${SHELL_ROOT_ID} .ge-bb-grid { display:flex; gap:14px; justify-content:safe center; overflow-x:auto; overflow-y:hidden; padding-bottom:6px;
|
|
1292
1294
|
scroll-snap-type:x proximity; -webkit-overflow-scrolling:touch; }
|
|
1293
1295
|
/* the one knob that scales the whole card — cqh measures the overlay (popout frame), not the
|
|
1294
1296
|
browser window, so cards shrink to fit the real container height. */
|
|
@@ -1351,8 +1353,10 @@ const SHELL_CSS = SHELL_FONT_CSS + `
|
|
|
1351
1353
|
border-radius:16px; padding:0 20px; gap:18px; }
|
|
1352
1354
|
#${SHELL_ROOT_ID} .ge-pl-dark { background:var(--shell-plaque-dark); }
|
|
1353
1355
|
#${SHELL_ROOT_ID} .ge-pl-glass { background:var(--shell-plaque-glass); }
|
|
1354
|
-
/* FS
|
|
1355
|
-
|
|
1356
|
+
/* FS/replay left blocks — Free Spins counter (compact) + Total Win, standalone glass plaques
|
|
1357
|
+
sitting just right of the balance pill */
|
|
1358
|
+
#${SHELL_ROOT_ID} .ge-pl-fs, #${SHELL_ROOT_ID} .ge-pl-totalwin { margin-left:8px; }
|
|
1359
|
+
#${SHELL_ROOT_ID} .ge-pl-fs { padding:0 16px; }
|
|
1356
1360
|
#${SHELL_ROOT_ID} .ge-pl .ge-rd { color:#fff; text-shadow:none; }
|
|
1357
1361
|
#${SHELL_ROOT_ID} .ge-pl .ge-rd .ge-lbl { color:var(--shell-plaque-label); }
|
|
1358
1362
|
#${SHELL_ROOT_ID} .ge-pl .ge-iconbtn { color:#fff; }
|
|
@@ -1623,10 +1627,13 @@ function renderBottomBar(shell) {
|
|
|
1623
1627
|
bar.dataset.geMode = state.mode;
|
|
1624
1628
|
// menu icon button (always)
|
|
1625
1629
|
const menu = iconBtn('menu', 'menu', () => shell.openMenu());
|
|
1626
|
-
// All three modes share the base plaque layout. FS/replay hide the controls that
|
|
1627
|
-
//
|
|
1630
|
+
// All three modes share the base plaque layout. FS/replay hide the controls that don't apply
|
|
1631
|
+
// and add Free Spins + Total Win blocks on the left; the per-spin WIN uses the base pill.
|
|
1628
1632
|
const isBase = state.mode === 'base';
|
|
1629
1633
|
const isFS = state.mode === 'freeSpins';
|
|
1634
|
+
// FS always shows the spins counter + accumulated Total Win (even €0); a replay shows them
|
|
1635
|
+
// only when it's a free-spins replay (freeSpins.total > 0).
|
|
1636
|
+
const showFsBlocks = isFS || (state.mode === 'replay' && state.freeSpins.total > 0);
|
|
1630
1637
|
const balance = readout('balance', shell.t('Balance'), fmt(state.balance));
|
|
1631
1638
|
// With a feature active (e.g. Ante) the BET readout shows the effective stake, tinted with
|
|
1632
1639
|
// the feature accent; the base state.bet is unchanged and returns once the feature is off.
|
|
@@ -1653,22 +1660,25 @@ function renderBottomBar(shell) {
|
|
|
1653
1660
|
buy = config.features.buyBonus !== false ? buyBtn(shell) : null;
|
|
1654
1661
|
}
|
|
1655
1662
|
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;
|
|
1663
|
+
// FS/replay left blocks: spins counter + accumulated Total Win (shown even at €0).
|
|
1664
|
+
const fsCounter = showFsBlocks ? readout('fs-counter', shell.t('Free spins'), `${state.freeSpins.current} / ${state.freeSpins.total}`) : null;
|
|
1665
|
+
const fsTotalWin = showFsBlocks ? readout('fs-totalwin', shell.t('Total win'), fmt(state.freeSpins.totalWin)) : null;
|
|
1660
1666
|
if (mobile) {
|
|
1661
|
-
// rows: [balance · win
|
|
1662
|
-
|
|
1663
|
-
|
|
1664
|
-
|
|
1667
|
+
// rows: [balance · win] · [menu · auto · spin · FS counter · Total Win · turbo · buy] · [− bet +]
|
|
1668
|
+
// FS counter + Total Win live in the controls row (alongside menu/turbo), not the top readouts.
|
|
1669
|
+
bar.appendChild(plaque('ge-m-top ge-pl ge-pl-glass', compact([balance, winEl])));
|
|
1670
|
+
const center = isBase ? spin : null;
|
|
1671
|
+
bar.appendChild(plaque('ge-m-controls ge-pl-dark', compact([menu, auto, center, fsCounter, fsTotalWin, turbo, buy])));
|
|
1665
1672
|
bar.appendChild(plaque('ge-m-bet ge-pl ge-pl-dark', compact([betDown, betValue, betUp])));
|
|
1666
1673
|
}
|
|
1667
1674
|
else {
|
|
1668
|
-
// LEFT: [menu] ⊐ BUY BONUS coin ⊏ [balance]
|
|
1675
|
+
// LEFT: [menu] ⊐ BUY BONUS coin ⊏ [balance] · [Free Spins] · [Total Win]
|
|
1676
|
+
// (the last two only render in FS / a free-spins replay)
|
|
1669
1677
|
const menuPlaque = plaque('ge-pl ge-pl-dark ge-pl-menu', [menu]);
|
|
1670
1678
|
const balPlaque = plaque('ge-pl ge-pl-glass ge-pl-bal', [balance]);
|
|
1671
|
-
const
|
|
1679
|
+
const fsPlaque = fsCounter ? plaque('ge-pl ge-pl-glass ge-pl-fs', [fsCounter]) : null;
|
|
1680
|
+
const totalWinPlaque = fsTotalWin ? plaque('ge-pl ge-pl-glass ge-pl-totalwin', [fsTotalWin]) : null;
|
|
1681
|
+
const left = zone('ge-zone-left ge-zone-plaques', ...compact([menuPlaque, buy, balPlaque, fsPlaque, totalWinPlaque]));
|
|
1672
1682
|
// RIGHT: [bet (+ step)] · |divider| · [auto · SPIN · turbo]
|
|
1673
1683
|
const betKids = [betValue];
|
|
1674
1684
|
if (betUp && betDown) {
|
|
@@ -1684,11 +1694,9 @@ function renderBottomBar(shell) {
|
|
|
1684
1694
|
spinWrap.className = 'ge-spinwrap ge-pl-dark';
|
|
1685
1695
|
spinWrap.append(...compact([auto, spin, turbo]));
|
|
1686
1696
|
const right = zone('ge-zone-right ge-zone-plaques', betPlaque, divider, spinWrap);
|
|
1687
|
-
// MIDDLE:
|
|
1697
|
+
// MIDDLE: per-spin WIN pill in every mode — lifts above the bar on overflow.
|
|
1688
1698
|
let middle = null;
|
|
1689
|
-
if (
|
|
1690
|
-
middle = plaque('ge-pl ge-pl-glass ge-fscount', compact([fsLastWin, fsCounter, fsTotalWin]));
|
|
1691
|
-
else if (winEl) {
|
|
1699
|
+
if (winEl) {
|
|
1692
1700
|
winEl.classList.add('ge-winpill');
|
|
1693
1701
|
middle = winEl;
|
|
1694
1702
|
}
|
|
@@ -1962,7 +1970,7 @@ function sectionControls(shell, el) {
|
|
|
1962
1970
|
{ vis: slot(icon('spin')), name: 'Spin', desc: 'Start a spin at the current bet.', on: true },
|
|
1963
1971
|
{ vis: slot(icon('plus')), name: 'Raise bet', desc: 'Increase your stake.', on: true },
|
|
1964
1972
|
{ vis: slot(icon('minus')), name: 'Lower bet', desc: 'Decrease your stake.', on: true },
|
|
1965
|
-
{ vis: slot(icon('autoplay')), name: 'Autoplay', desc: 'Spin automatically a set number of times.', on: features.autoplay },
|
|
1973
|
+
{ vis: slot(icon('autoplay')), name: 'Autoplay', desc: 'Spin automatically a set number of times.', on: features.autoplay != null },
|
|
1966
1974
|
{ vis: slot(icon('turbo1')), name: 'Turbo', desc: 'Speed up spin animations.', on: features.turbo > 0 },
|
|
1967
1975
|
{ vis: buyBadge, name: 'Buy bonus', desc: 'Pay a fixed cost to enter a bonus feature.', on: features.buyBonus !== false },
|
|
1968
1976
|
];
|
|
@@ -2376,14 +2384,29 @@ function openBetModal(shell) {
|
|
|
2376
2384
|
});
|
|
2377
2385
|
}
|
|
2378
2386
|
const AUTOPLAY_COUNTS = [10, 25, 50, 100, 250, 500, 1000, 2000, Infinity];
|
|
2379
|
-
/**
|
|
2387
|
+
/** The selectable spin counts, honouring an optional jurisdiction max. With a `maxCount`:
|
|
2388
|
+
* drop ∞, keep presets ≤ max, and append the max itself when it isn't already a preset
|
|
2389
|
+
* (so the cap is always offered). Without one: the default presets including ∞. */
|
|
2390
|
+
function autoplayCounts(maxCount) {
|
|
2391
|
+
if (maxCount == null)
|
|
2392
|
+
return AUTOPLAY_COUNTS;
|
|
2393
|
+
const capped = AUTOPLAY_COUNTS.filter((n) => Number.isFinite(n) && n <= maxCount);
|
|
2394
|
+
if (!capped.includes(maxCount))
|
|
2395
|
+
capped.push(maxCount);
|
|
2396
|
+
return capped;
|
|
2397
|
+
}
|
|
2398
|
+
/** Autoplay picker — spin counts (incl. ∞ unless a maxCount caps them); Confirm starts autoplay. */
|
|
2380
2399
|
function openAutoplayModal(shell) {
|
|
2400
|
+
const maxCount = shell.config.features.autoplay?.maxCount;
|
|
2401
|
+
const counts = autoplayCounts(maxCount);
|
|
2381
2402
|
return buildSheet({
|
|
2382
2403
|
ge: 'autoplay-modal', title: shell.t('Autoplay'), columns: 3, confirmLabel: shell.t('Start'),
|
|
2383
|
-
choices:
|
|
2384
|
-
selected: String(shell.state.autoplay.remaining ||
|
|
2404
|
+
choices: counts.map((n) => ({ id: String(n), label: Number.isFinite(n) ? String(n) : '∞' })),
|
|
2405
|
+
selected: String(shell.state.autoplay.remaining || counts[0]),
|
|
2385
2406
|
onConfirm: (id) => {
|
|
2386
|
-
|
|
2407
|
+
let remaining = Number(id); // "Infinity" → Infinity
|
|
2408
|
+
if (maxCount != null)
|
|
2409
|
+
remaining = Math.min(remaining, maxCount); // defensive cap
|
|
2387
2410
|
shell.state.autoplay = { active: true, remaining };
|
|
2388
2411
|
shell.emit('autoplayStart', { active: true, remaining });
|
|
2389
2412
|
shell.render();
|
|
@@ -2701,12 +2724,15 @@ class GameShell extends EventEmitter {
|
|
|
2701
2724
|
const s = natural > 0 && avail > 0 ? Math.min(1, avail / natural) : 1;
|
|
2702
2725
|
host.style.transform = `translateX(-50%) scale(${s.toFixed(4)})`;
|
|
2703
2726
|
}
|
|
2704
|
-
/** Spacebar starts a spin — same path as the spin disc. Ignored
|
|
2705
|
-
* while autoplay is active, outside base mode, when an
|
|
2706
|
-
* editable element is focused. `repeat` (held key) is
|
|
2727
|
+
/** Spacebar starts a spin — same path as the spin disc. Ignored when `features.spacebar` is
|
|
2728
|
+
* false, while a spin is running, while autoplay is active, outside base mode, when an
|
|
2729
|
+
* overlay/modal is open, or when an editable element is focused. `repeat` (held key) is
|
|
2730
|
+
* ignored so it can't spam. */
|
|
2707
2731
|
handleKeyDown = (e) => {
|
|
2708
2732
|
if (this.destroyed || e.code !== 'Space' || e.repeat)
|
|
2709
2733
|
return;
|
|
2734
|
+
if (this.config.features.spacebar === false)
|
|
2735
|
+
return; // shortcut disabled (e.g. jurisdiction)
|
|
2710
2736
|
const t = e.target;
|
|
2711
2737
|
if (t && (t.isContentEditable || /^(INPUT|TEXTAREA|SELECT)$/.test(t.tagName)))
|
|
2712
2738
|
return;
|