@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
|
@@ -43,10 +43,13 @@ export function renderBottomBar(shell: GameShell): HTMLElement {
|
|
|
43
43
|
// menu icon button (always)
|
|
44
44
|
const menu = iconBtn('menu', 'menu', () => shell.openMenu());
|
|
45
45
|
|
|
46
|
-
// All three modes share the base plaque layout. FS/replay hide the controls that
|
|
47
|
-
//
|
|
46
|
+
// All three modes share the base plaque layout. FS/replay hide the controls that don't apply
|
|
47
|
+
// and add Free Spins + Total Win blocks on the left; the per-spin WIN uses the base pill.
|
|
48
48
|
const isBase = state.mode === 'base';
|
|
49
49
|
const isFS = state.mode === 'freeSpins';
|
|
50
|
+
// FS always shows the spins counter + accumulated Total Win (even €0); a replay shows them
|
|
51
|
+
// only when it's a free-spins replay (freeSpins.total > 0).
|
|
52
|
+
const showFsBlocks = isFS || (state.mode === 'replay' && state.freeSpins.total > 0);
|
|
50
53
|
|
|
51
54
|
const balance = readout('balance', shell.t('Balance'), fmt(state.balance));
|
|
52
55
|
// With a feature active (e.g. Ante) the BET readout shows the effective stake, tinted with
|
|
@@ -72,22 +75,25 @@ export function renderBottomBar(shell: GameShell): HTMLElement {
|
|
|
72
75
|
}
|
|
73
76
|
|
|
74
77
|
const winEl = state.win > 0 ? readout('win', shell.t('Win'), fmt(state.win)) : null;
|
|
75
|
-
// FS
|
|
76
|
-
const fsCounter =
|
|
77
|
-
const fsTotalWin =
|
|
78
|
-
const fsLastWin = isFS ? readout('fs-lastwin', shell.t('Last win'), fmt(state.freeSpins.lastWin)) : null;
|
|
78
|
+
// FS/replay left blocks: spins counter + accumulated Total Win (shown even at €0).
|
|
79
|
+
const fsCounter = showFsBlocks ? readout('fs-counter', shell.t('Free spins'), `${state.freeSpins.current} / ${state.freeSpins.total}`) : null;
|
|
80
|
+
const fsTotalWin = showFsBlocks ? readout('fs-totalwin', shell.t('Total win'), fmt(state.freeSpins.totalWin)) : null;
|
|
79
81
|
|
|
80
82
|
if (mobile) {
|
|
81
|
-
// rows: [balance · win
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
83
|
+
// rows: [balance · win] · [menu · auto · spin · FS counter · Total Win · turbo · buy] · [− bet +]
|
|
84
|
+
// FS counter + Total Win live in the controls row (alongside menu/turbo), not the top readouts.
|
|
85
|
+
bar.appendChild(plaque('ge-m-top ge-pl ge-pl-glass', compact([balance, winEl])));
|
|
86
|
+
const center = isBase ? spin : null;
|
|
87
|
+
bar.appendChild(plaque('ge-m-controls ge-pl-dark', compact([menu, auto, center, fsCounter, fsTotalWin, turbo, buy])));
|
|
85
88
|
bar.appendChild(plaque('ge-m-bet ge-pl ge-pl-dark', compact([betDown, betValue, betUp])));
|
|
86
89
|
} else {
|
|
87
|
-
// LEFT: [menu] ⊐ BUY BONUS coin ⊏ [balance]
|
|
90
|
+
// LEFT: [menu] ⊐ BUY BONUS coin ⊏ [balance] · [Free Spins] · [Total Win]
|
|
91
|
+
// (the last two only render in FS / a free-spins replay)
|
|
88
92
|
const menuPlaque = plaque('ge-pl ge-pl-dark ge-pl-menu', [menu]);
|
|
89
93
|
const balPlaque = plaque('ge-pl ge-pl-glass ge-pl-bal', [balance]);
|
|
90
|
-
const
|
|
94
|
+
const fsPlaque = fsCounter ? plaque('ge-pl ge-pl-glass ge-pl-fs', [fsCounter]) : null;
|
|
95
|
+
const totalWinPlaque = fsTotalWin ? plaque('ge-pl ge-pl-glass ge-pl-totalwin', [fsTotalWin]) : null;
|
|
96
|
+
const left = zone('ge-zone-left ge-zone-plaques', ...compact([menuPlaque, buy, balPlaque, fsPlaque, totalWinPlaque]));
|
|
91
97
|
|
|
92
98
|
// RIGHT: [bet (+ step)] · |divider| · [auto · SPIN · turbo]
|
|
93
99
|
const betKids: HTMLElement[] = [betValue];
|
|
@@ -101,10 +107,9 @@ export function renderBottomBar(shell: GameShell): HTMLElement {
|
|
|
101
107
|
spinWrap.append(...compact([auto, spin, turbo]));
|
|
102
108
|
const right = zone('ge-zone-right ge-zone-plaques', betPlaque, divider, spinWrap);
|
|
103
109
|
|
|
104
|
-
// MIDDLE:
|
|
110
|
+
// MIDDLE: per-spin WIN pill in every mode — lifts above the bar on overflow.
|
|
105
111
|
let middle: HTMLElement | null = null;
|
|
106
|
-
if (
|
|
107
|
-
else if (winEl) { winEl.classList.add('ge-winpill'); middle = winEl; }
|
|
112
|
+
if (winEl) { winEl.classList.add('ge-winpill'); middle = winEl; }
|
|
108
113
|
bar.append(...compact([left, middle, right]));
|
|
109
114
|
}
|
|
110
115
|
|
|
@@ -81,7 +81,7 @@ function sectionControls(shell: GameShell, el: HTMLElement): HTMLElement {
|
|
|
81
81
|
{ vis: slot(icon('spin')), name: 'Spin', desc: 'Start a spin at the current bet.', on: true },
|
|
82
82
|
{ vis: slot(icon('plus')), name: 'Raise bet', desc: 'Increase your stake.', on: true },
|
|
83
83
|
{ vis: slot(icon('minus')), name: 'Lower bet', desc: 'Decrease your stake.', on: true },
|
|
84
|
-
{ vis: slot(icon('autoplay')), name: 'Autoplay', desc: 'Spin automatically a set number of times.', on: features.autoplay },
|
|
84
|
+
{ vis: slot(icon('autoplay')), name: 'Autoplay', desc: 'Spin automatically a set number of times.', on: features.autoplay != null },
|
|
85
85
|
{ vis: slot(icon('turbo1')), name: 'Turbo', desc: 'Speed up spin animations.', on: features.turbo > 0 },
|
|
86
86
|
{ vis: buyBadge, name: 'Buy bonus', desc: 'Pay a fixed cost to enter a bonus feature.', on: features.buyBonus !== false },
|
|
87
87
|
];
|
|
@@ -60,14 +60,27 @@ export function openBetModal(shell: GameShell): HTMLElement {
|
|
|
60
60
|
|
|
61
61
|
const AUTOPLAY_COUNTS = [10, 25, 50, 100, 250, 500, 1000, 2000, Infinity];
|
|
62
62
|
|
|
63
|
-
/**
|
|
63
|
+
/** The selectable spin counts, honouring an optional jurisdiction max. With a `maxCount`:
|
|
64
|
+
* drop ∞, keep presets ≤ max, and append the max itself when it isn't already a preset
|
|
65
|
+
* (so the cap is always offered). Without one: the default presets including ∞. */
|
|
66
|
+
function autoplayCounts(maxCount?: number): number[] {
|
|
67
|
+
if (maxCount == null) return AUTOPLAY_COUNTS;
|
|
68
|
+
const capped = AUTOPLAY_COUNTS.filter((n) => Number.isFinite(n) && n <= maxCount);
|
|
69
|
+
if (!capped.includes(maxCount)) capped.push(maxCount);
|
|
70
|
+
return capped;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/** Autoplay picker — spin counts (incl. ∞ unless a maxCount caps them); Confirm starts autoplay. */
|
|
64
74
|
export function openAutoplayModal(shell: GameShell): HTMLElement {
|
|
75
|
+
const maxCount = shell.config.features.autoplay?.maxCount;
|
|
76
|
+
const counts = autoplayCounts(maxCount);
|
|
65
77
|
return buildSheet({
|
|
66
78
|
ge: 'autoplay-modal', title: shell.t('Autoplay'), columns: 3, confirmLabel: shell.t('Start'),
|
|
67
|
-
choices:
|
|
68
|
-
selected: String(shell.state.autoplay.remaining ||
|
|
79
|
+
choices: counts.map((n) => ({ id: String(n), label: Number.isFinite(n) ? String(n) : '∞' })),
|
|
80
|
+
selected: String(shell.state.autoplay.remaining || counts[0]),
|
|
69
81
|
onConfirm: (id) => {
|
|
70
|
-
|
|
82
|
+
let remaining = Number(id); // "Infinity" → Infinity
|
|
83
|
+
if (maxCount != null) remaining = Math.min(remaining, maxCount); // defensive cap
|
|
71
84
|
shell.state.autoplay = { active: true, remaining };
|
|
72
85
|
shell.emit('autoplayStart', { active: true, remaining });
|
|
73
86
|
shell.render();
|
package/src/shell/shell.css.ts
CHANGED
|
@@ -218,8 +218,10 @@ export const SHELL_CSS = SHELL_FONT_CSS + `
|
|
|
218
218
|
/* the buy-bonus scroll area is a SIZE CONTAINER, so the cards' cqh units measure the overlay
|
|
219
219
|
(the popout frame) and not the browser window — cards fit without any vertical scroll. */
|
|
220
220
|
#${SHELL_ROOT_ID} [data-ge="buybonus-overlay"] .ge-ov-scroll { container-type:size; }
|
|
221
|
-
|
|
222
|
-
|
|
221
|
+
/* buy-bonus uses the FULL overlay width (no 800px centre cap) so the card row isn't cropped at
|
|
222
|
+
the sides; small horizontal padding keeps the cards off the screen edges. */
|
|
223
|
+
#${SHELL_ROOT_ID} [data-ge="buybonus-overlay"] .ge-ov-body { max-width:none; padding:clamp(8px,3cqh,16px) clamp(12px,3vw,28px); }
|
|
224
|
+
#${SHELL_ROOT_ID} .ge-bb-grid { display:flex; gap:14px; justify-content:safe center; overflow-x:auto; overflow-y:hidden; padding-bottom:6px;
|
|
223
225
|
scroll-snap-type:x proximity; -webkit-overflow-scrolling:touch; }
|
|
224
226
|
/* the one knob that scales the whole card — cqh measures the overlay (popout frame), not the
|
|
225
227
|
browser window, so cards shrink to fit the real container height. */
|
|
@@ -282,8 +284,10 @@ export const SHELL_CSS = SHELL_FONT_CSS + `
|
|
|
282
284
|
border-radius:16px; padding:0 20px; gap:18px; }
|
|
283
285
|
#${SHELL_ROOT_ID} .ge-pl-dark { background:var(--shell-plaque-dark); }
|
|
284
286
|
#${SHELL_ROOT_ID} .ge-pl-glass { background:var(--shell-plaque-glass); }
|
|
285
|
-
/* FS
|
|
286
|
-
|
|
287
|
+
/* FS/replay left blocks — Free Spins counter (compact) + Total Win, standalone glass plaques
|
|
288
|
+
sitting just right of the balance pill */
|
|
289
|
+
#${SHELL_ROOT_ID} .ge-pl-fs, #${SHELL_ROOT_ID} .ge-pl-totalwin { margin-left:8px; }
|
|
290
|
+
#${SHELL_ROOT_ID} .ge-pl-fs { padding:0 16px; }
|
|
287
291
|
#${SHELL_ROOT_ID} .ge-pl .ge-rd { color:#fff; text-shadow:none; }
|
|
288
292
|
#${SHELL_ROOT_ID} .ge-pl .ge-rd .ge-lbl { color:var(--shell-plaque-label); }
|
|
289
293
|
#${SHELL_ROOT_ID} .ge-pl .ge-iconbtn { color:#fff; }
|
package/src/shell/state.ts
CHANGED
|
@@ -11,7 +11,7 @@ export function createInitialState(config: ShellConfig): ShellState {
|
|
|
11
11
|
autoplay: { active: false, remaining: 0 },
|
|
12
12
|
turbo: 0,
|
|
13
13
|
buyBonusEnabled: true,
|
|
14
|
-
freeSpins: { current: 0, total: 0, totalWin: 0
|
|
14
|
+
freeSpins: { current: 0, total: 0, totalWin: 0 },
|
|
15
15
|
activeFeature: null,
|
|
16
16
|
};
|
|
17
17
|
}
|
package/src/shell/theme.ts
CHANGED
|
@@ -41,9 +41,9 @@ export function buildThemeVars(theme: ThemeConfig = {}): string {
|
|
|
41
41
|
// Plaque tokens — the grouped dark/glass panel language shared by the control bar
|
|
42
42
|
// AND the overlays. Scheme-independent (always dark, white-on-dark) so bar + overlays
|
|
43
43
|
// stay visually identical regardless of the dark/light `scheme`.
|
|
44
|
-
`--shell-plaque-dark: rgba(6,9,15,.
|
|
45
|
-
`--shell-plaque-glass: rgba(30,36,48,.
|
|
46
|
-
`--shell-plaque-glass-hover: rgba(40,48,64,.
|
|
44
|
+
`--shell-plaque-dark: rgba(6,9,15,.86)`,
|
|
45
|
+
`--shell-plaque-glass: rgba(30,36,48,.70)`,
|
|
46
|
+
`--shell-plaque-glass-hover: rgba(40,48,64,.86)`,
|
|
47
47
|
// Opaque surface for centred modals (confirm, bet/autoplay pickers) so they read solid,
|
|
48
48
|
// not see-through, over the frosted backdrop.
|
|
49
49
|
`--shell-plaque-solid: #1a2030`,
|
package/src/shell/types.ts
CHANGED
|
@@ -90,9 +90,21 @@ export interface GameInfoContent {
|
|
|
90
90
|
sections?: GameInfoSection[];
|
|
91
91
|
}
|
|
92
92
|
|
|
93
|
+
/** Autoplay limits. Presence of this object (vs `null`) is what enables autoplay. */
|
|
94
|
+
export interface AutoplayConfig {
|
|
95
|
+
/** Maximum selectable spin count in the autoplay picker. Caps the built-in presets and
|
|
96
|
+
* drops the unlimited (∞) choice; if it isn't already a preset it becomes the top choice.
|
|
97
|
+
* Omit for the default presets (including ∞). */
|
|
98
|
+
maxCount?: number;
|
|
99
|
+
}
|
|
100
|
+
|
|
93
101
|
export interface ShellFeatures {
|
|
94
102
|
turbo: 0 | 1 | 2 | 3;
|
|
95
|
-
|
|
103
|
+
/** Spacebar starts a spin in base mode. Defaults to `true`; set `false` to disable the
|
|
104
|
+
* keyboard shortcut (e.g. jurisdictions that forbid quick-spin keys). */
|
|
105
|
+
spacebar?: boolean;
|
|
106
|
+
/** Autoplay: `null` (or omitted) disables it; an object enables it (optionally with limits). */
|
|
107
|
+
autoplay?: AutoplayConfig | null;
|
|
96
108
|
buyBonus: BonusOption[] | false;
|
|
97
109
|
}
|
|
98
110
|
|
|
@@ -105,7 +117,6 @@ export interface FreeSpinsState {
|
|
|
105
117
|
current: number;
|
|
106
118
|
total: number;
|
|
107
119
|
totalWin: number;
|
|
108
|
-
lastWin: number;
|
|
109
120
|
}
|
|
110
121
|
|
|
111
122
|
/** One footer button of a generic modal. Clicking it runs `on` (if any), then closes the modal. */
|